diff --git a/.vscode/launch.json b/.vscode/launch.json index 8fd97968..0e2049e9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -53,7 +53,7 @@ "AYANOVA_FOLDER_USER_FILES": "c:\\temp\\RavenTestData\\userfiles", "AYANOVA_FOLDER_BACKUP_FILES": "c:\\temp\\RavenTestData\\backupfiles", "AYANOVA_FOLDER_TEMPORARY_SERVER_FILES": "c:\\temp\\RavenTestData\\tempfiles", - "AYANOVA_SERVER_TEST_MODE": "true", + "AYANOVA_SERVER_TEST_MODE": "false", "AYANOVA_SERVER_TEST_MODE_SEEDLEVEL": "small", "AYANOVA_SERVER_TEST_MODE_TZ_OFFSET": "-7", "AYANOVA_BACKUP_PG_DUMP_PATH": "C:\\data\\code\\postgres_13\\bin\\" diff --git a/server/AyaNova/AyaNova.csproj b/server/AyaNova/AyaNova.csproj index b3b35cbf..55a612e3 100644 --- a/server/AyaNova/AyaNova.csproj +++ b/server/AyaNova/AyaNova.csproj @@ -38,11 +38,13 @@ + + diff --git a/server/AyaNova/Controllers/AuthController.cs b/server/AyaNova/Controllers/AuthController.cs index 0ea82be5..c2da1f6c 100644 --- a/server/AyaNova/Controllers/AuthController.cs +++ b/server/AyaNova/Controllers/AuthController.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; +using TwoFactorAuthNet; +using QRCoder; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using AyaNova.Models; @@ -424,6 +426,46 @@ namespace AyaNova.Api.Controllers })); } + /// + /// Generate HOTP secret and return for use in auth app + /// + /// + /// From route path + /// New HOTP secret + [HttpGet("hotp")] + public async Task GenerateAndSendHOTP(ApiVersion apiVersion) + { + if (!serverState.IsOpen) + return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); + UserBiz biz = UserBiz.GetBiz(ct, HttpContext); + + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + //get user and save the secret + var UserId = UserIdFromContext.Id(HttpContext.Items); + + var u = await ct.User.FirstOrDefaultAsync(z => z.Id == UserId); + if (u == null)//should never happen but ? + return StatusCode(403, new ApiNotAuthorizedResponse()); + + var tfa = new TwoFactorAuth("AyaNova"); + u.HotpSecret = tfa.CreateSecret(160); + await ct.SaveChangesAsync(); + + + QRCodeGenerator qrGenerator = new QRCodeGenerator(); + QRCodeData qrCodeData = qrGenerator.CreateQrCode(u.HotpSecret, QRCodeGenerator.ECCLevel.Q); + Base64QRCode qrCode = new Base64QRCode(qrCodeData); + string qrCodeImageAsBase64 = qrCode.GetGraphic(20); + + return Ok(ApiOkResponse.Response(new + { + s = u.HotpSecret, + qrCode = qrCodeImageAsBase64 + })); + } + //------------------------------------------------------ public class CredentialsParam diff --git a/server/AyaNova/models/User.cs b/server/AyaNova/models/User.cs index a4dec620..c281afd7 100644 --- a/server/AyaNova/models/User.cs +++ b/server/AyaNova/models/User.cs @@ -59,6 +59,7 @@ namespace AyaNova.Models public DateTime? LastLogin { get; set; } public string Login { get; set; } public string Password { get; set; } + public bool TwoFactorEnabled {get;set;} [Required] public AuthorizationRoles Roles { get; set; } [Required] @@ -95,6 +96,8 @@ namespace AyaNova.Models public string PasswordResetCode { get; set; }//--- [JsonIgnore] public DateTime? PasswordResetCodeExpire { get; set; }//--- + [JsonIgnore] + public string HotpSecret { get; set; }//--- //========================== //relations diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index ca0ce697..c5f535df 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -22,7 +22,7 @@ namespace AyaNova.Util //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImporting WHEN NEW TABLES ADDED!!!! private const int DESIRED_SCHEMA_LEVEL = 15; - internal const long EXPECTED_COLUMN_COUNT = 788; + internal const long EXPECTED_COLUMN_COUNT = 790; internal const long EXPECTED_INDEX_COUNT = 130; internal const long EXPECTED_CHECK_CONSTRAINTS = 359; internal const long EXPECTED_FOREIGN_KEY_CONSTRAINTS = 78; @@ -441,7 +441,8 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); //Add user table await ExecQueryAsync("CREATE TABLE auser (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, active BOOL NOT NULL, name TEXT NOT NULL UNIQUE, " + "lastlogin TIMESTAMP, login TEXT NOT NULL UNIQUE, password TEXT NOT NULL, salt TEXT NOT NULL, roles INTEGER NOT NULL, currentauthtoken TEXT, " - + "dlkey TEXT, dlkeyexpire TIMESTAMP, passwordresetcode TEXT, passwordresetcodeexpire TIMESTAMP, usertype INTEGER NOT NULL, employeenumber TEXT, notes TEXT, customerid BIGINT, " + + "dlkey TEXT, dlkeyexpire TIMESTAMP, hotpsecret TEXT, twofactorenabled BOOL, passwordresetcode TEXT, passwordresetcodeexpire TIMESTAMP, usertype INTEGER NOT NULL, " + + "employeenumber TEXT, notes TEXT, customerid BIGINT, " + "headofficeid BIGINT, vendorid BIGINT, wiki TEXT, customfields TEXT, tags VARCHAR(255) ARRAY)");