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)");