aboutsummaryrefslogtreecommitdiff
path: root/VNLib.Plugins.Essentials.Accounts
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-01-12 17:47:40 -0500
committerLibravatar vnugent <public@vaughnnugent.com>2023-01-12 17:47:40 -0500
commit551066ed9a255bd47c1c5789ec1998fda64bd5aa (patch)
treed6caceb0e7caa44478c6611903b4b7e120964c89 /VNLib.Plugins.Essentials.Accounts
parentb6481038bc6573af30492e9ce52b36d9f64195f3 (diff)
Large project reorder and consolidation
Diffstat (limited to 'VNLib.Plugins.Essentials.Accounts')
-rw-r--r--VNLib.Plugins.Essentials.Accounts/AccountValidations.cs107
-rw-r--r--VNLib.Plugins.Essentials.Accounts/AccountsEntryPoint.cs204
-rw-r--r--VNLib.Plugins.Essentials.Accounts/Endpoints/KeepAliveEndpoint.cs64
-rw-r--r--VNLib.Plugins.Essentials.Accounts/Endpoints/LoginEndpoint.cs410
-rw-r--r--VNLib.Plugins.Essentials.Accounts/Endpoints/LogoutEndpoint.cs53
-rw-r--r--VNLib.Plugins.Essentials.Accounts/Endpoints/MFAEndpoint.cs282
-rw-r--r--VNLib.Plugins.Essentials.Accounts/Endpoints/PasswordResetEndpoint.cs140
-rw-r--r--VNLib.Plugins.Essentials.Accounts/Endpoints/ProfileEndpoint.cs132
-rw-r--r--VNLib.Plugins.Essentials.Accounts/LICENSE.txt339
-rw-r--r--VNLib.Plugins.Essentials.Accounts/MFA/FidoAuthenticatorSelection.cs40
-rw-r--r--VNLib.Plugins.Essentials.Accounts/MFA/FidoRegClientData.cs40
-rw-r--r--VNLib.Plugins.Essentials.Accounts/MFA/FidoRegistrationMessage.cs52
-rw-r--r--VNLib.Plugins.Essentials.Accounts/MFA/MFAConfig.cs103
-rw-r--r--VNLib.Plugins.Essentials.Accounts/MFA/MFAType.cs31
-rw-r--r--VNLib.Plugins.Essentials.Accounts/MFA/MFAUpgrade.cs65
-rw-r--r--VNLib.Plugins.Essentials.Accounts/MFA/UserMFAExtensions.cs384
-rw-r--r--VNLib.Plugins.Essentials.Accounts/README.md3
-rw-r--r--VNLib.Plugins.Essentials.Accounts/VNLib.Plugins.Essentials.Accounts.csproj61
-rw-r--r--VNLib.Plugins.Essentials.Accounts/Validators/LoginMessageValidation.cs70
19 files changed, 0 insertions, 2580 deletions
diff --git a/VNLib.Plugins.Essentials.Accounts/AccountValidations.cs b/VNLib.Plugins.Essentials.Accounts/AccountValidations.cs
deleted file mode 100644
index 972bd36..0000000
--- a/VNLib.Plugins.Essentials.Accounts/AccountValidations.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: AccountValidations.cs
-*
-* AccountValidations.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using FluentValidation;
-
-using VNLib.Plugins.Extensions.Validation;
-
-#nullable enable
-
-namespace VNLib.Plugins.Essentials.Accounts
-{
- public static class AccountValidations
- {
- /// <summary>
- /// Central password requirement validator
- /// </summary>
- public static IValidator<string> PasswordValidator { get; } = GetPassVal();
-
- public static IValidator<AccountData> AccountDataValidator { get; } = GetAcVal();
-
-
- static IValidator<string> GetPassVal()
- {
- InlineValidator<string> passVal = new();
-
- passVal.RuleFor(static password => password)
- .NotEmpty()
- .Length(min: 8, max: 100)
- .Password()
- .WithMessage(errorMessage: "Password does not meet minium requirements");
-
- return passVal;
- }
-
- static IValidator<AccountData> GetAcVal()
- {
- InlineValidator<AccountData> adv = new ();
-
- //Validate city
-
- adv.RuleFor(t => t.City)
- .MaximumLength(35)
- .AlphaOnly()
- .When(t => t.City?.Length > 0);
-
- adv.RuleFor(t => t.Company)
- .MaximumLength(50)
- .SpecialCharacters()
- .When(t => t.Company?.Length > 0);
-
- //Require a first and last names to be set together
- adv.When(t => t.First?.Length > 0 || t.Last?.Length > 0, () =>
- {
- adv.RuleFor(t => t.First)
- .Length(1, 35)
- .AlphaOnly();
- adv.RuleFor(t => t.Last)
- .Length(1, 35)
- .AlphaOnly();
- });
-
- adv.RuleFor(t => t.PhoneNumber)
- .PhoneNumber()
- .When(t => t.PhoneNumber?.Length > 0)
- .OverridePropertyName("Phone");
-
- //State must be 2 characters for us states if set
- adv.RuleFor(t => t.State)
- .Length(2)
- .When(t => t.State?.Length > 0);
-
- adv.RuleFor(t => t.Street)
- .AlphaNumericOnly()
- .MaximumLength(50)
- .When(t => t.Street?.Length > 0);
-
- //Allow empty zip codes, but if one is defined, is must be less than 7 characters
- adv.RuleFor(t => t.Zip)
- .NumericOnly()
- .MaximumLength(7)
- .When(t => t.Zip?.Length > 0);
-
- return adv;
- }
- }
-}
diff --git a/VNLib.Plugins.Essentials.Accounts/AccountsEntryPoint.cs b/VNLib.Plugins.Essentials.Accounts/AccountsEntryPoint.cs
deleted file mode 100644
index ed79476..0000000
--- a/VNLib.Plugins.Essentials.Accounts/AccountsEntryPoint.cs
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: AccountsEntryPoint.cs
-*
-* AccountsEntryPoint.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Linq;
-using System.Collections.Generic;
-
-using VNLib.Utils.Memory;
-using VNLib.Utils.Logging;
-using VNLib.Plugins.Essentials.Users;
-using VNLib.Plugins.Essentials.Accounts.Endpoints;
-using VNLib.Plugins.Extensions.Loading;
-using VNLib.Plugins.Extensions.Loading.Users;
-using VNLib.Plugins.Extensions.Loading.Routing;
-
-namespace VNLib.Plugins.Essentials.Accounts
-{
- public sealed class AccountsEntryPoint : PluginBase
- {
-
- public override string PluginName => "Essentials.Accounts";
-
- protected override void OnLoad()
- {
- try
- {
- //Route endpoints
- this.Route<LoginEndpoint>();
-
- this.Route<LogoutEndpoint>();
-
- this.Route<KeepAliveEndpoint>();
-
- this.Route<ProfileEndpoint>();
-
- this.Route<PasswordChangeEndpoint>();
-
- this.Route<MFAEndpoint>();
-
- //Write loaded to log
- Log.Information("Plugin loaded");
- }
- catch (KeyNotFoundException knf)
- {
- Log.Error("Missing required account configuration variables {mess}", knf.Message);
- }
- catch (UriFormatException uri)
- {
- Log.Error("Invalid endpoint URI {message}", uri.Message);
- }
- }
-
-
-
- protected override void OnUnLoad()
- {
- //Write closing messsage and dispose the log
- Log.Information("Plugin unloaded");
- }
-
- protected override async void ProcessHostCommand(string cmd)
- {
- //Only process commands if the plugin is in debug mode
- if (!this.IsDebug())
- {
- return;
- }
- try
- {
- IUserManager Users = this.GetUserManager();
- PasswordHashing Passwords = this.GetPasswords();
-
- //get args as a list
- List<string> args = cmd.Split(' ').ToList();
- if (args.Count < 3)
- {
- Log.Warn("No command specified");
- }
- switch (args[2].ToLower())
- {
- //Create new user
- case "create":
- {
- int uid = args.IndexOf("-u");
- int pwd = args.IndexOf("-p");
- if (uid < 0 || pwd < 0)
- {
- Log.Warn("You are missing required argument values. Format 'create -u <username> -p <password>'");
- return;
- }
- string username = args[uid + 1].Trim();
- string randomUserId = AccountManager.GetRandomUserId();
- //Password as privatestring DANGEROUS to refs
- using (PrivateString password = (PrivateString)args[pwd + 1].Trim()!)
- {
- //Hash the password
- using PrivateString passHash = Passwords.Hash(password);
- //Create the user
- using IUser user = await Users.CreateUserAsync(randomUserId, username, AccountManager.MINIMUM_LEVEL, passHash);
- //Set active flag
- user.Status = UserStatus.Active;
- //Set local account
- user.SetAccountOrigin(AccountManager.LOCAL_ACCOUNT_ORIGIN);
-
- await user.ReleaseAsync();
- }
- Log.Information("Successfully created user {id}", username);
-
- }
- break;
- case "reset":
- {
- int uid = args.IndexOf("-u");
- int pwd = args.IndexOf("-p");
- if (uid < 0 || pwd < 0)
- {
- Log.Warn("You are missing required argument values. Format 'reset -u <username> -p <password>'");
- return;
- }
- string username = args[uid + 1].Trim();
- //Password as privatestring DANGEROUS to refs
- using (PrivateString password = (PrivateString)args[pwd + 1].Trim()!)
- {
- //Hash the password
- using PrivateString passHash = Passwords.Hash(password);
- //Get the user
- using IUser? user = await Users.GetUserFromEmailAsync(username);
-
- if(user == null)
- {
- Log.Warn("The specified user does not exist");
- break;
- }
-
- //Set the password
- await Users.UpdatePassAsync(user, passHash);
- }
- Log.Information("Successfully reset password for {id}", username);
- }
- break;
- case "delete":
- {
- //get user-id
- string userId = args[3].Trim();
- //Get user
- using IUser? user = await Users.GetUserFromEmailAsync(userId);
-
- if (user == null)
- {
- Log.Warn("The specified user does not exist");
- break;
- }
-
- //delete user
- user.Delete();
- //Release user
- await user.ReleaseAsync();
- }
- break;
- default:
- Log.Warn("Uknown command");
- break;
- }
- }
- catch (UserExistsException)
- {
- Log.Error("User already exists");
- }
- catch(UserCreationFailedException)
- {
- Log.Error("Failed to create the new user");
- }
- catch (ArgumentOutOfRangeException)
- {
- Log.Error("You are missing required command arguments");
- }
- catch(Exception ex)
- {
- Log.Error(ex);
- }
- }
- }
-} \ No newline at end of file
diff --git a/VNLib.Plugins.Essentials.Accounts/Endpoints/KeepAliveEndpoint.cs b/VNLib.Plugins.Essentials.Accounts/Endpoints/KeepAliveEndpoint.cs
deleted file mode 100644
index fe5a65b..0000000
--- a/VNLib.Plugins.Essentials.Accounts/Endpoints/KeepAliveEndpoint.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: KeepAliveEndpoint.cs
-*
-* KeepAliveEndpoint.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Net;
-using System.Text.Json;
-using System.Collections.Generic;
-
-using VNLib.Plugins.Essentials.Endpoints;
-using VNLib.Plugins.Extensions.Loading;
-
-namespace VNLib.Plugins.Essentials.Accounts.Endpoints
-{
- [ConfigurationName("keepalive_endpoint")]
- internal sealed class KeepAliveEndpoint : ProtectedWebEndpoint
- {
- /*
- * Endpoint does not use a log, so IniPathAndLog is never called
- * and path verification happens verbosly
- */
- public KeepAliveEndpoint(PluginBase pbase, IReadOnlyDictionary<string, JsonElement> config)
- {
- string? path = config["path"].GetString();
-
- InitPathAndLog(path, pbase.Log);
- }
-
- protected override VfReturnType Get(HttpEntity entity)
- {
- //Return okay
- entity.CloseResponse(HttpStatusCode.OK);
- return VfReturnType.VirtualSkip;
- }
-
- //Allow post to update user's credentials
- protected override VfReturnType Post(HttpEntity entity)
- {
- //Return okay
- entity.CloseResponse(HttpStatusCode.OK);
- return VfReturnType.VirtualSkip;
- }
- }
-} \ No newline at end of file
diff --git a/VNLib.Plugins.Essentials.Accounts/Endpoints/LoginEndpoint.cs b/VNLib.Plugins.Essentials.Accounts/Endpoints/LoginEndpoint.cs
deleted file mode 100644
index 4100620..0000000
--- a/VNLib.Plugins.Essentials.Accounts/Endpoints/LoginEndpoint.cs
+++ /dev/null
@@ -1,410 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: LoginEndpoint.cs
-*
-* LoginEndpoint.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Net;
-using System.Text.Json;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-using System.Security.Cryptography;
-using System.Text.Json.Serialization;
-
-using VNLib.Hashing;
-using VNLib.Utils;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Logging;
-using VNLib.Utils.Extensions;
-using VNLib.Plugins.Essentials.Users;
-using VNLib.Plugins.Essentials.Endpoints;
-using VNLib.Plugins.Essentials.Extensions;
-using VNLib.Plugins.Extensions.Validation;
-using VNLib.Plugins.Essentials.Accounts.MFA;
-using VNLib.Plugins.Essentials.Accounts.Validators;
-using VNLib.Plugins.Extensions.Loading;
-using VNLib.Plugins.Extensions.Loading.Users;
-using static VNLib.Plugins.Essentials.Statics;
-using static VNLib.Plugins.Essentials.Accounts.AccountManager;
-
-
-namespace VNLib.Plugins.Essentials.Accounts.Endpoints
-{
-
- /// <summary>
- /// Provides an authentication endpoint for user-accounts
- /// </summary>
- [ConfigurationName("login_endpoint")]
- internal sealed class LoginEndpoint : UnprotectedWebEndpoint
- {
- public const string INVALID_MESSAGE = "Please check your email or password.";
- public const string LOCKED_ACCOUNT_MESSAGE = "You have been timed out, please try again later";
- public const string MFA_ERROR_MESSAGE = "Invalid or expired request.";
-
- private static readonly LoginMessageValidation LmValidator = new();
-
- private readonly PasswordHashing Passwords;
- private readonly MFAConfig? MultiFactor;
- private readonly IUserManager Users;
- private readonly uint MaxFailedLogins;
- private readonly TimeSpan FailedCountTimeout;
-
- public LoginEndpoint(PluginBase pbase, IReadOnlyDictionary<string, JsonElement> config)
- {
- string? path = config["path"].GetString();
- FailedCountTimeout = config["failed_count_timeout_sec"].GetTimeSpan(TimeParseType.Seconds);
- MaxFailedLogins = config["failed_count_max"].GetUInt32();
-
- InitPathAndLog(path, pbase.Log);
-
- Passwords = pbase.GetPasswords();
- Users = pbase.GetUserManager();
- MultiFactor = pbase.GetMfaConfig();
- }
-
- private class MfaUpgradeWebm : ValErrWebMessage
- {
- [JsonPropertyName("pwtoken")]
- public string? PasswordToken { get; set; }
-
- [JsonPropertyName("mfa")]
- public bool? MultiFactorUpgrade { get; set; } = null;
- }
-
-
- protected async override ValueTask<VfReturnType> PostAsync(HttpEntity entity)
- {
- //Conflict if user is logged in
- if (entity.LoginCookieMatches() || entity.TokenMatches())
- {
- entity.CloseResponse(HttpStatusCode.Conflict);
- return VfReturnType.VirtualSkip;
- }
-
- //If mfa is enabled, allow processing via mfa
- if (MultiFactor != null)
- {
- if (entity.QueryArgs.ContainsKey("mfa"))
- {
- return await ProcessMfaAsync(entity);
- }
- }
- return await ProccesLoginAsync(entity);
- }
-
-
- private async ValueTask<VfReturnType> ProccesLoginAsync(HttpEntity entity)
- {
- MfaUpgradeWebm webm = new();
- try
- {
- //Make sure the id is regenerated (or upgraded if successful login)
- entity.Session.RegenID();
-
- using LoginMessage? loginMessage = await entity.GetJsonFromFileAsync<LoginMessage>(SR_OPTIONS);
-
- if (webm.Assert(loginMessage != null, "Invalid request data"))
- {
- entity.CloseResponseJson(HttpStatusCode.BadRequest, webm);
- return VfReturnType.VirtualSkip;
- }
-
- //validate the message
- if (!LmValidator.Validate(loginMessage, webm))
- {
- entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
- return VfReturnType.VirtualSkip;
- }
-
- //Time to get the user
- using IUser? user = await Users.GetUserAndPassFromEmailAsync(loginMessage.UserName);
- //Make sure account exists
- if (webm.Assert(user != null, INVALID_MESSAGE))
- {
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
-
- //Make sure the account has not been locked out
- if (webm.Assert(!UserLoginLocked(user), LOCKED_ACCOUNT_MESSAGE))
- {
- goto Cleanup;
- }
-
- //Only allow local accounts
- if (user.IsLocalAccount() && !PrivateString.IsNullOrEmpty(user.PassHash))
- {
- //If login return true, the response has been set and we should return
- if (LoginUser(entity, loginMessage, user, webm))
- {
- goto Cleanup;
- }
- }
-
- //Inc failed login count
- user.FailedLoginIncrement();
- webm.Result = INVALID_MESSAGE;
-
- Cleanup:
- await user.ReleaseAsync();
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
- catch (UserUpdateException uue)
- {
- Log.Warn(uue);
- return VfReturnType.Error;
- }
- }
-
- private bool LoginUser(HttpEntity entity, LoginMessage loginMessage, IUser user, MfaUpgradeWebm webm)
- {
- //Verify password before we tell the user the status of their account for security reasons
- if (!Passwords.Verify(user.PassHash, new PrivateString(loginMessage.Password, false)))
- {
- return false;
- }
- //Reset flc for account
- user.FailedLoginCount(0);
- try
- {
- switch (user.Status)
- {
- case UserStatus.Active:
- {
- //Is the account restricted to a local network connection?
- if (user.LocalOnly && !entity.IsLocalConnection)
- {
- Log.Information("User {uid} attempted a login from a non-local network with the correct password. Access was denied", user.UserID);
- return false;
- }
- //Gen and store the pw secret
- byte[] pwSecret = entity.Session.GenPasswordChallenge(new(loginMessage.Password, false));
- //Encrypt and convert to base64
- string clientPwSecret = EncryptSecret(loginMessage.ClientPublicKey, pwSecret);
- //get the new upgrade jwt string
- Tuple<string,string>? message = user.MFAGetUpgradeIfEnabled(MultiFactor, loginMessage, clientPwSecret);
- //if message is null, mfa was not enabled or could not be prepared
- if (message != null)
- {
- //Store the base64 signature
- entity.Session.MfaUpgradeSignature(message.Item2);
- //send challenge message to client
- webm.Result = message.Item1;
- webm.Success = true;
- webm.MultiFactorUpgrade = true;
- break;
- }
- //Set password token
- webm.PasswordToken = clientPwSecret;
- //Elevate the login status of the session to reflect the user's status
- webm.Token = entity.GenerateAuthorization(loginMessage, user);
- //Send the Username (since they already have it)
- webm.Result = new AccountData()
- {
- EmailAddress = user.EmailAddress,
- };
- webm.Success = true;
- //Write to log
- Log.Verbose("Successful login for user {uid}...", user.UserID[..8]);
- }
- break;
- default:
- //This is an unhandled case, and should never happen, but just incase write a warning to the log
- Log.Warn("Account {uid} has invalid status key and a login was attempted from {ip}", user.UserID, entity.TrustedRemoteIp);
- return false;
- }
- }
- /*
- * Account auhorization may throw excetpions if the configuration does not
- * match the client, or the client sent invalid or malicous data and
- * it could not grant authorization
- */
- catch (OutOfMemoryException)
- {
- webm.Result = "Your browser sent malformatted security information";
- }
- catch (CryptographicException ce)
- {
- webm.Result = "Your browser sent malformatted security information";
- Log.Debug(ce);
- }
- return true;
- }
-
-
- private async ValueTask<VfReturnType> ProcessMfaAsync(HttpEntity entity)
- {
- MfaUpgradeWebm webm = new();
- //Recover request message
- using JsonDocument? request = await entity.GetJsonFromFileAsync();
- if (webm.Assert(request != null, "Invalid request data"))
- {
- entity.CloseResponseJson(HttpStatusCode.BadRequest, webm);
- return VfReturnType.VirtualSkip;
- }
- //Recover upgrade jwt
- string? upgradeJwt = request.RootElement.GetPropString("upgrade");
- if (webm.Assert(upgradeJwt != null, "Missing required upgrade data"))
- {
- entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
- return VfReturnType.VirtualSkip;
- }
-
- //Recover stored signature
- string? storedSig = entity.Session.MfaUpgradeSignature();
- if(webm.Assert(!string.IsNullOrWhiteSpace(storedSig), MFA_ERROR_MESSAGE))
- {
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
-
- //Recover upgrade data from upgrade message
- if (!MultiFactor!.RecoverUpgrade(upgradeJwt, storedSig, out MFAUpgrade? upgrade))
- {
- webm.Result = MFA_ERROR_MESSAGE;
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
-
- //recover user account
- using IUser? user = await Users.GetUserFromEmailAsync(upgrade.UserName!);
-
- if (webm.Assert(user != null, MFA_ERROR_MESSAGE))
- {
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
-
- bool locked = UserLoginLocked(user);
-
- //Make sure the account has not been locked out
- if (!webm.Assert(locked == false, LOCKED_ACCOUNT_MESSAGE))
- {
- //process mfa login
- LoginMfa(entity, user, request, upgrade, webm);
- }
- else
- {
- //Locked, so clear stored signature
- entity.Session.MfaUpgradeSignature(null);
- }
-
- //Update user on clean process
- await user.ReleaseAsync();
- //Close rseponse
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
-
- private void LoginMfa(HttpEntity entity, IUser user, JsonDocument request, MFAUpgrade upgrade, MfaUpgradeWebm webm)
- {
- //Recover the user's local time
- DateTimeOffset localTime = request.RootElement.GetProperty("localtime").GetDateTimeOffset();
-
- //Check mode
- switch (upgrade.Type)
- {
- case MFAType.TOTP:
- {
- //get totp code from request
- uint code = request.RootElement.GetProperty("code").GetUInt32();
- //Verify totp code
- if (!MultiFactor!.VerifyTOTP(user, code))
- {
- webm.Result = "Please check your code.";
- //Increment flc and update the user in the store
- user.FailedLoginIncrement();
- return;
- }
- //Valid, complete
- }
- break;
- case MFAType.PGP:
- { }
- break;
- default:
- {
- webm.Result = MFA_ERROR_MESSAGE;
- }
- return;
- }
-
- //Wipe session signature
- entity.Session.MfaUpgradeSignature(null);
-
- //build login message from upgrade
- LoginMessage loginMessage = new()
- {
- ClientID = upgrade.ClientID,
- ClientPublicKey = upgrade.Base64PubKey,
- LocalLanguage = upgrade.ClientLocalLanguage,
- LocalTime = localTime,
- UserName = upgrade.UserName
- };
- //Elevate the login status of the session to reflect the user's status
- webm.Token = entity.GenerateAuthorization(loginMessage, user);
- //Set the password token as the password field of the login message
- webm.PasswordToken = upgrade.PwClientData;
- //Send the Username (since they already have it)
- webm.Result = new AccountData()
- {
- EmailAddress = user.EmailAddress,
- };
- webm.Success = true;
- //Write to log
- Log.Verbose("Successful login for user {uid}...", user.UserID[..8]);
- }
-
- private static string EncryptSecret(string pubKey, byte[] secret)
- {
- //Alloc buffer for secret
- using IMemoryHandle<byte> buffer = Memory.SafeAlloc<byte>(4096);
- //Try to encrypt the data
- ERRNO count = TryEncryptClientData(pubKey, secret, buffer.Span);
- //Clear secret
- RandomHash.GetRandomBytes(secret);
- //Convert to base64 string
- return Convert.ToBase64String(buffer.Span[..(int)count]);
- }
-
- public bool UserLoginLocked(IUser user)
- {
- //Recover last counter value
- TimestampedCounter flc = user.FailedLoginCount();
- if(flc.Count < MaxFailedLogins)
- {
- //Period exceeded
- return false;
- }
- //See if the flc timeout period has expired
- if (flc.LastModified.Add(FailedCountTimeout) < DateTimeOffset.UtcNow)
- {
- //clear flc flag
- user.FailedLoginCount(0);
- return false;
- }
- //Count has been exceeded, and has not timed out yet
- return true;
- }
- }
-} \ No newline at end of file
diff --git a/VNLib.Plugins.Essentials.Accounts/Endpoints/LogoutEndpoint.cs b/VNLib.Plugins.Essentials.Accounts/Endpoints/LogoutEndpoint.cs
deleted file mode 100644
index cc36609..0000000
--- a/VNLib.Plugins.Essentials.Accounts/Endpoints/LogoutEndpoint.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: LogoutEndpoint.cs
-*
-* LogoutEndpoint.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Net;
-using System.Text.Json;
-using System.Collections.Generic;
-
-using VNLib.Plugins.Extensions.Loading;
-using VNLib.Plugins.Essentials.Endpoints;
-
-namespace VNLib.Plugins.Essentials.Accounts.Endpoints
-{
- [ConfigurationName("logout_endpoint")]
- internal class LogoutEndpoint : ProtectedWebEndpoint
- {
-
- public LogoutEndpoint(PluginBase pbase, IReadOnlyDictionary<string, JsonElement> config)
- {
- string? path = config["path"].GetString();
- InitPathAndLog(path, pbase.Log);
- }
-
-
- protected override VfReturnType Post(HttpEntity entity)
- {
- entity.InvalidateLogin();
- entity.CloseResponse(HttpStatusCode.OK);
- return VfReturnType.VirtualSkip;
- }
- }
-}
diff --git a/VNLib.Plugins.Essentials.Accounts/Endpoints/MFAEndpoint.cs b/VNLib.Plugins.Essentials.Accounts/Endpoints/MFAEndpoint.cs
deleted file mode 100644
index 6ebb024..0000000
--- a/VNLib.Plugins.Essentials.Accounts/Endpoints/MFAEndpoint.cs
+++ /dev/null
@@ -1,282 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: MFAEndpoint.cs
-*
-* MFAEndpoint.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Net;
-using System.Text.Json;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-using System.Text.Json.Serialization;
-
-using VNLib.Hashing;
-using VNLib.Utils;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Logging;
-using VNLib.Utils.Extensions;
-using VNLib.Plugins.Essentials.Users;
-using VNLib.Plugins.Essentials.Extensions;
-using VNLib.Plugins.Essentials.Accounts.MFA;
-using VNLib.Plugins.Extensions.Validation;
-using VNLib.Plugins.Essentials.Endpoints;
-using VNLib.Plugins.Extensions.Loading;
-using VNLib.Plugins.Extensions.Loading.Users;
-
-namespace VNLib.Plugins.Essentials.Accounts.Endpoints
-{
- [ConfigurationName("mfa_endpoint")]
- internal sealed class MFAEndpoint : ProtectedWebEndpoint
- {
- public const int TOTP_URL_MAX_CHARS = 1024;
-
- private readonly IUserManager Users;
- private readonly MFAConfig? MultiFactor;
-
- public MFAEndpoint(PluginBase pbase, IReadOnlyDictionary<string, JsonElement> config)
- {
- string? path = config["path"].GetString();
- InitPathAndLog(path, pbase.Log);
-
- Users = pbase.GetUserManager();
- MultiFactor = pbase.GetMfaConfig();
- }
-
- private class TOTPUpdateMessage
- {
- [JsonPropertyName("issuer")]
- public string? Issuer { get; set; }
- [JsonPropertyName("digits")]
- public int Digits { get; set; }
- [JsonPropertyName("period")]
- public int Period { get; set; }
- [JsonPropertyName("secret")]
- public string? Base64EncSecret { get; set; }
- [JsonPropertyName("algorithm")]
- public string? Algorithm { get; set; }
- }
-
- protected override async ValueTask<VfReturnType> GetAsync(HttpEntity entity)
- {
- List<string> enabledModes = new(2);
- //Load the MFA entry for the user
- using IUser? user = await Users.GetUserFromIDAsync(entity.Session.UserID);
- //Set the TOTP flag if set
- if (!string.IsNullOrWhiteSpace(user?.MFAGetTOTPSecret()))
- {
- enabledModes.Add("totp");
- }
- //TODO Set fido flag if enabled
- if (!string.IsNullOrWhiteSpace(""))
- {
- enabledModes.Add("fido");
- }
- //Return mfa modes as an array
- entity.CloseResponseJson(HttpStatusCode.OK, enabledModes);
- return VfReturnType.VirtualSkip;
- }
-
- protected override async ValueTask<VfReturnType> PutAsync(HttpEntity entity)
- {
- WebMessage webm = new();
-
- //Get the request message
- using JsonDocument? mfaRequest = await entity.GetJsonFromFileAsync();
- if (webm.Assert(mfaRequest != null, "Invalid request"))
- {
- entity.CloseResponseJson(HttpStatusCode.BadRequest, webm);
- return VfReturnType.VirtualSkip;
- }
-
- //Get the type argument
- string? mfaType = mfaRequest.RootElement.GetPropString("type");
- if (string.IsNullOrWhiteSpace(mfaType))
- {
- webm.Result = "MFA type was not specified";
- entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
- return VfReturnType.VirtualSkip;
- }
-
- //Make sure the user's account origin is a local account
- if (webm.Assert(entity.Session.HasLocalAccount(), "Your account uses external authentication and MFA cannot be enabled"))
- {
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
-
- //Make sure mfa is loaded
- if (webm.Assert(MultiFactor != null, "MFA is not enabled on this server"))
- {
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
-
- //get the user's password challenge
- using (PrivateString? password = (PrivateString?)mfaRequest.RootElement.GetPropString("challenge"))
- {
- if (PrivateString.IsNullOrEmpty(password))
- {
- webm.Result = "Please check your password";
- entity.CloseResponseJson(HttpStatusCode.Unauthorized, webm);
- return VfReturnType.VirtualSkip;
- }
- //Verify challenge
- if (!entity.Session.VerifyChallenge(password))
- {
- webm.Result = "Please check your password";
- entity.CloseResponseJson(HttpStatusCode.Unauthorized, webm);
- return VfReturnType.VirtualSkip;
- }
- }
- //Get the user entry
- using IUser? user = await Users.GetUserFromIDAsync(entity.Session.UserID);
- if (webm.Assert(user != null, "Please log-out and try again."))
- {
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
- switch (mfaType.ToLower())
- {
- //Process a Time based one time password(TOTP) creation/regeneration
- case "totp":
- {
- //generate a new secret (passing the buffer which will get copied to an array because the pw bytes can be modified during encryption)
- byte[] secretBuffer = user.MFAGenreateTOTPSecret(MultiFactor);
- //Alloc output buffer
- UnsafeMemoryHandle<byte> outputBuffer = Memory.UnsafeAlloc<byte>(4096, true);
- try
- {
- //Encrypt the secret for the client
- ERRNO count = entity.Session.TryEncryptClientData(secretBuffer, outputBuffer.Span);
- if (!count)
- {
- webm.Result = "There was an error updating your credentials";
- //If this code is running, the client should have a valid public key stored, but log it anyway
- Log.Warn("TOTP secret encryption failed, for requested user {uid}", entity.Session.UserID);
- break;
- }
- webm.Result = new TOTPUpdateMessage()
- {
- Issuer = MultiFactor.IssuerName,
- Digits = MultiFactor.TOTPDigits,
- Period = (int)MultiFactor.TOTPPeriod.TotalSeconds,
- Algorithm = MultiFactor.TOTPAlg.ToString(),
- //Convert the secret to base64 string to send to client
- Base64EncSecret = Convert.ToBase64String(outputBuffer.Span[..(int)count])
- };
- //set success flag
- webm.Success = true;
- }
- finally
- {
- //dispose the output buffer
- outputBuffer.Dispose();
- RandomHash.GetRandomBytes(secretBuffer);
- }
- //Only write changes to the db of operation was successful
- await user.ReleaseAsync();
- }
- break;
- default:
- webm.Result = "The server does not support the specified MFA type";
- break;
- }
- //Close response
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
-
- protected override async ValueTask<VfReturnType> PostAsync(HttpEntity entity)
- {
- WebMessage webm = new();
- try
- {
- //Check account type
- if (!entity.Session.HasLocalAccount())
- {
- webm.Result = "You are using external authentication. Operation failed.";
- entity.CloseResponseJson(HttpStatusCode.BadRequest, webm);
- return VfReturnType.VirtualSkip;
- }
-
- //get the request
- using JsonDocument? request = await entity.GetJsonFromFileAsync();
- if (webm.Assert(request != null, "Invalid request."))
- {
- entity.CloseResponseJson(HttpStatusCode.BadRequest, webm);
- return VfReturnType.VirtualSkip;
- }
- /*
- * An MFA upgrade requires a challenge to be verified because
- * it can break the user's ability to access their account
- */
- string? challenge = request.RootElement.GetProperty("challenge").GetString();
- string? mfaType = request.RootElement.GetProperty("type").GetString();
- if (!entity.Session.VerifyChallenge(challenge))
- {
- webm.Result = "Please check your password";
- //return unauthorized
- entity.CloseResponseJson(HttpStatusCode.Unauthorized, webm);
- return VfReturnType.VirtualSkip;
- }
- //get the user
- using IUser? user = await Users.GetUserFromIDAsync(entity.Session.UserID);
- if (user == null)
- {
- return VfReturnType.NotFound;
- }
- //Check for totp disable
- if ("totp".Equals(mfaType, StringComparison.OrdinalIgnoreCase))
- {
- //Clear the TOTP secret
- user.MFASetTOTPSecret(null);
- //write changes
- await user.ReleaseAsync();
- webm.Result = "Successfully disabled your TOTP authentication";
- webm.Success = true;
- }
- else if ("fido".Equals(mfaType, StringComparison.OrdinalIgnoreCase))
- {
- //Clear webauthn changes
-
- //write changes
- await user.ReleaseAsync();
- webm.Result = "Successfully disabled your FIDO authentication";
- webm.Success = true;
- }
- else
- {
- webm.Result = "Invalid MFA type";
- }
- //Must write response while password is in scope
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
- catch (KeyNotFoundException)
- {
- webm.Result = "The request was is missing required fields";
- entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
- return VfReturnType.BadRequest;
- }
- }
- }
-} \ No newline at end of file
diff --git a/VNLib.Plugins.Essentials.Accounts/Endpoints/PasswordResetEndpoint.cs b/VNLib.Plugins.Essentials.Accounts/Endpoints/PasswordResetEndpoint.cs
deleted file mode 100644
index 0a51eb5..0000000
--- a/VNLib.Plugins.Essentials.Accounts/Endpoints/PasswordResetEndpoint.cs
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: PasswordResetEndpoint.cs
-*
-* PasswordResetEndpoint.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Net;
-using System.Text.Json;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-
-using FluentValidation;
-
-using VNLib.Utils.Memory;
-using VNLib.Utils.Extensions;
-using VNLib.Plugins.Essentials.Users;
-using VNLib.Plugins.Essentials.Extensions;
-using VNLib.Plugins.Extensions.Validation;
-using VNLib.Plugins.Extensions.Loading;
-using VNLib.Plugins.Extensions.Loading.Users;
-using VNLib.Plugins.Essentials.Endpoints;
-
-namespace VNLib.Plugins.Essentials.Accounts.Endpoints
-{
-
- /// <summary>
- /// Password reset for user's that are logged in and know
- /// their passwords to reset their MFA methods
- /// </summary>
- [ConfigurationName("password_endpoint")]
- internal sealed class PasswordChangeEndpoint : ProtectedWebEndpoint
- {
- private readonly IUserManager Users;
- private readonly PasswordHashing Passwords;
-
- public PasswordChangeEndpoint(PluginBase pbase, IReadOnlyDictionary<string, JsonElement> config)
- {
- string? path = config["path"].GetString();
- InitPathAndLog(path, pbase.Log);
-
- Users = pbase.GetUserManager();
- Passwords = pbase.GetPasswords();
- }
-
- protected override async ValueTask<VfReturnType> PostAsync(HttpEntity entity)
- {
- ValErrWebMessage webm = new();
- //get the request body
- using JsonDocument? request = await entity.GetJsonFromFileAsync();
- if (request == null)
- {
- webm.Result = "No request specified";
- entity.CloseResponseJson(HttpStatusCode.BadRequest, webm);
- return VfReturnType.VirtualSkip;
- }
- //get the user's old password
- using PrivateString? currentPass = (PrivateString?)request.RootElement.GetPropString("current");
- //Get password as a private string
- using PrivateString? newPass = (PrivateString?)request.RootElement.GetPropString("new_password");
- if (PrivateString.IsNullOrEmpty(currentPass))
- {
- webm.Result = "You must specifiy your current password.";
- entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
- return VfReturnType.VirtualSkip;
- }
- if (PrivateString.IsNullOrEmpty(newPass))
- {
- webm.Result = "You must specifiy a new password.";
- entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
- return VfReturnType.VirtualSkip;
- }
- //Test the password against minimum
- if (!AccountValidations.PasswordValidator.Validate((string)newPass, webm))
- {
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
- if (webm.Assert(!currentPass.Equals(newPass), "Passwords cannot be the same."))
- {
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
- //get the user's entry in the table
- using IUser? user = await Users.GetUserAndPassFromIDAsync(entity.Session.UserID);
- if(webm.Assert(user != null, "An error has occured, please log-out and try again"))
- {
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
- //Make sure the account's origin is a local profile
- if (webm.Assert(user.IsLocalAccount(), "External accounts cannot be modified"))
- {
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
- //Verify the user's old password
- if (!Passwords.Verify(user.PassHash, currentPass))
- {
- webm.Result = "Please check your current password";
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
- //Hash the user's new password
- using PrivateString newPassHash = Passwords.Hash(newPass);
- //Update the user's password
- if (!await Users.UpdatePassAsync(user, newPassHash))
- {
- //error
- webm.Result = "Your password could not be updated";
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
- await user.ReleaseAsync();
- //delete the user's MFA entry so they can re-enable it
- webm.Result = "Your password has been updated";
- webm.Success = true;
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
- }
-}
diff --git a/VNLib.Plugins.Essentials.Accounts/Endpoints/ProfileEndpoint.cs b/VNLib.Plugins.Essentials.Accounts/Endpoints/ProfileEndpoint.cs
deleted file mode 100644
index 45908e7..0000000
--- a/VNLib.Plugins.Essentials.Accounts/Endpoints/ProfileEndpoint.cs
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: ProfileEndpoint.cs
-*
-* ProfileEndpoint.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Net;
-using System.Text.Json;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-
-using VNLib.Utils.Logging;
-using VNLib.Plugins.Essentials.Users;
-using VNLib.Plugins.Essentials.Endpoints;
-using VNLib.Plugins.Essentials.Extensions;
-using VNLib.Plugins.Extensions.Validation;
-using VNLib.Plugins.Extensions.Loading;
-using VNLib.Plugins.Extensions.Loading.Users;
-using static VNLib.Plugins.Essentials.Statics;
-
-
-namespace VNLib.Plugins.Essentials.Accounts.Endpoints
-{
- /// <summary>
- /// Provides an http endpoint for user account profile access
- /// </summary>
- [ConfigurationName("profile_endpoint")]
- internal sealed class ProfileEndpoint : ProtectedWebEndpoint
- {
- private readonly IUserManager Users;
-
- public ProfileEndpoint(PluginBase pbase, IReadOnlyDictionary<string, JsonElement> config)
- {
- string? path = config["path"].GetString();
-
- InitPathAndLog(path, pbase.Log);
- //Store user system
- Users = pbase.GetUserManager();
- }
-
- protected override async ValueTask<VfReturnType> GetAsync(HttpEntity entity)
- {
- //get user data from database
- using IUser? user = await Users.GetUserFromIDAsync(entity.Session.UserID);
- //Make sure the account exists
- if (user == null || user.Status != UserStatus.Active)
- {
- //Account was not found
- entity.CloseResponse(HttpStatusCode.NotFound);
- return VfReturnType.VirtualSkip;
- }
- //Get the stored profile
- AccountData? profile = user.GetProfile();
- //No profile found, so return an empty "profile"
- profile ??= new()
- {
- //set email address
- EmailAddress = user.EmailAddress,
- //created time in rfc1123 gmt time
- Created = user.Created.ToString("R")
- };
- //Serialize the profile and return to user
- entity.CloseResponseJson(HttpStatusCode.OK, profile);
- return VfReturnType.VirtualSkip;
- }
- protected override async ValueTask<VfReturnType> PostAsync(HttpEntity entity)
- {
- ValErrWebMessage webm = new();
- try
- {
- //Recover the update message form the client
- AccountData? updateMessage = await entity.GetJsonFromFileAsync<AccountData>(SR_OPTIONS);
- if (webm.Assert(updateMessage != null, "Malformatted payload"))
- {
- entity.CloseResponseJson(HttpStatusCode.BadRequest, webm);
- return VfReturnType.VirtualSkip;
- }
- //Validate the new account data
- if (!AccountValidations.AccountDataValidator.Validate(updateMessage, webm))
- {
- entity.CloseResponseJson(HttpStatusCode.UnprocessableEntity, webm);
- return VfReturnType.VirtualSkip;
- }
- //Get the user from database
- using IUser? user = await Users.GetUserFromIDAsync(entity.Session.UserID);
- //Make sure the user exists
- if (webm.Assert(user != null, "Account does not exist"))
- {
- //Should probably log the user out here
- entity.CloseResponseJson(HttpStatusCode.NotFound, webm);
- return VfReturnType.VirtualSkip;
- }
- //Overwite the current profile data (will also sanitize inputs)
- user.SetProfile(updateMessage);
- //Update the user only if successful
- await user.ReleaseAsync();
- webm.Result = "Successfully updated account";
- webm.Success = true;
- entity.CloseResponse(webm);
- return VfReturnType.VirtualSkip;
- }
- //Catch an account update exception
- catch (UserUpdateException uue)
- {
- Log.Error(uue, "An error occured while the user account is being updated");
- //Return message to client
- webm.Result = "An error occured while updating your account, try again later";
- entity.CloseResponseJson(HttpStatusCode.InternalServerError, webm);
- return VfReturnType.VirtualSkip;
- }
- }
- }
-} \ No newline at end of file
diff --git a/VNLib.Plugins.Essentials.Accounts/LICENSE.txt b/VNLib.Plugins.Essentials.Accounts/LICENSE.txt
deleted file mode 100644
index d159169..0000000
--- a/VNLib.Plugins.Essentials.Accounts/LICENSE.txt
+++ /dev/null
@@ -1,339 +0,0 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
-
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-License is intended to guarantee your freedom to share and change free
-software--to make sure the software is free for all its users. This
-General Public License applies to most of the Free Software
-Foundation's software and to any other program whose authors commit to
-using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
-your programs, too.
-
- When we speak of free software, we are referring to freedom, not
-price. Our General Public Licenses are designed to make sure that you
-have the freedom to distribute copies of free software (and charge for
-this service if you wish), that you receive source code or can get it
-if you want it, that you can change the software or use pieces of it
-in new free programs; and that you know you can do these things.
-
- To protect your rights, we need to make restrictions that forbid
-anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if you
-distribute copies of the software, or if you modify it.
-
- For example, if you distribute copies of such a program, whether
-gratis or for a fee, you must give the recipients all the rights that
-you have. You must make sure that they, too, receive or can get the
-source code. And you must show them these terms so they know their
-rights.
-
- We protect your rights with two steps: (1) copyright the software, and
-(2) offer you this license which gives you legal permission to copy,
-distribute and/or modify the software.
-
- Also, for each author's protection and ours, we want to make certain
-that everyone understands that there is no warranty for this free
-software. If the software is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original, so
-that any problems introduced by others will not reflect on the original
-authors' reputations.
-
- Finally, any free program is threatened constantly by software
-patents. We wish to avoid the danger that redistributors of a free
-program will individually obtain patent licenses, in effect making the
-program proprietary. To prevent this, we have made it clear that any
-patent must be licensed for everyone's free use or not licensed at all.
-
- The precise terms and conditions for copying, distribution and
-modification follow.
-
- GNU GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License applies to any program or other work which contains
-a notice placed by the copyright holder saying it may be distributed
-under the terms of this General Public License. The "Program", below,
-refers to any such program or work, and a "work based on the Program"
-means either the Program or any derivative work under copyright law:
-that is to say, a work containing the Program or a portion of it,
-either verbatim or with modifications and/or translated into another
-language. (Hereinafter, translation is included without limitation in
-the term "modification".) Each licensee is addressed as "you".
-
-Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running the Program is not restricted, and the output from the Program
-is covered only if its contents constitute a work based on the
-Program (independent of having been made by running the Program).
-Whether that is true depends on what the Program does.
-
- 1. You may copy and distribute verbatim copies of the Program's
-source code as you receive it, in any medium, provided that you
-conspicuously and appropriately publish on each copy an appropriate
-copyright notice and disclaimer of warranty; keep intact all the
-notices that refer to this License and to the absence of any warranty;
-and give any other recipients of the Program a copy of this License
-along with the Program.
-
-You may charge a fee for the physical act of transferring a copy, and
-you may at your option offer warranty protection in exchange for a fee.
-
- 2. You may modify your copy or copies of the Program or any portion
-of it, thus forming a work based on the Program, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) You must cause the modified files to carry prominent notices
- stating that you changed the files and the date of any change.
-
- b) You must cause any work that you distribute or publish, that in
- whole or in part contains or is derived from the Program or any
- part thereof, to be licensed as a whole at no charge to all third
- parties under the terms of this License.
-
- c) If the modified program normally reads commands interactively
- when run, you must cause it, when started running for such
- interactive use in the most ordinary way, to print or display an
- announcement including an appropriate copyright notice and a
- notice that there is no warranty (or else, saying that you provide
- a warranty) and that users may redistribute the program under
- these conditions, and telling the user how to view a copy of this
- License. (Exception: if the Program itself is interactive but
- does not normally print such an announcement, your work based on
- the Program is not required to print an announcement.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Program,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Program, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Program.
-
-In addition, mere aggregation of another work not based on the Program
-with the Program (or with a work based on the Program) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may copy and distribute the Program (or a work based on it,
-under Section 2) in object code or executable form under the terms of
-Sections 1 and 2 above provided that you also do one of the following:
-
- a) Accompany it with the complete corresponding machine-readable
- source code, which must be distributed under the terms of Sections
- 1 and 2 above on a medium customarily used for software interchange; or,
-
- b) Accompany it with a written offer, valid for at least three
- years, to give any third party, for a charge no more than your
- cost of physically performing source distribution, a complete
- machine-readable copy of the corresponding source code, to be
- distributed under the terms of Sections 1 and 2 above on a medium
- customarily used for software interchange; or,
-
- c) Accompany it with the information you received as to the offer
- to distribute corresponding source code. (This alternative is
- allowed only for noncommercial distribution and only if you
- received the program in object code or executable form with such
- an offer, in accord with Subsection b above.)
-
-The source code for a work means the preferred form of the work for
-making modifications to it. For an executable work, complete source
-code means all the source code for all modules it contains, plus any
-associated interface definition files, plus the scripts used to
-control compilation and installation of the executable. However, as a
-special exception, the source code distributed need not include
-anything that is normally distributed (in either source or binary
-form) with the major components (compiler, kernel, and so on) of the
-operating system on which the executable runs, unless that component
-itself accompanies the executable.
-
-If distribution of executable or object code is made by offering
-access to copy from a designated place, then offering equivalent
-access to copy the source code from the same place counts as
-distribution of the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 4. You may not copy, modify, sublicense, or distribute the Program
-except as expressly provided under this License. Any attempt
-otherwise to copy, modify, sublicense or distribute the Program is
-void, and will automatically terminate your rights under this License.
-However, parties who have received copies, or rights, from you under
-this License will not have their licenses terminated so long as such
-parties remain in full compliance.
-
- 5. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Program or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Program (or any work based on the
-Program), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Program or works based on it.
-
- 6. Each time you redistribute the Program (or any work based on the
-Program), the recipient automatically receives a license from the
-original licensor to copy, distribute or modify the Program subject to
-these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties to
-this License.
-
- 7. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Program at all. For example, if a patent
-license would not permit royalty-free redistribution of the Program by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Program.
-
-If any portion of this section is held invalid or unenforceable under
-any particular circumstance, the balance of the section is intended to
-apply and the section as a whole is intended to apply in other
-circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system, which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 8. If the distribution and/or use of the Program is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Program under this License
-may add an explicit geographical distribution limitation excluding
-those countries, so that distribution is permitted only in or among
-countries not thus excluded. In such case, this License incorporates
-the limitation as if written in the body of this License.
-
- 9. The Free Software Foundation may publish revised and/or new versions
-of the General Public License from time to time. Such new versions will
-be similar in spirit to the present version, but may differ in detail to
-address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Program
-specifies a version number of this License which applies to it and "any
-later version", you have the option of following the terms and conditions
-either of that version or of any later version published by the Free
-Software Foundation. If the Program does not specify a version number of
-this License, you may choose any version ever published by the Free Software
-Foundation.
-
- 10. If you wish to incorporate parts of the Program into other free
-programs whose distribution conditions are different, write to the author
-to ask for permission. For software which is copyrighted by the Free
-Software Foundation, write to the Free Software Foundation; we sometimes
-make exceptions for this. Our decision will be guided by the two goals
-of preserving the free status of all derivatives of our free software and
-of promoting the sharing and reuse of software generally.
-
- NO WARRANTY
-
- 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
-FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
-OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
-PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
-OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
-TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
-PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
-REPAIR OR CORRECTION.
-
- 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
-WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
-REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
-INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
-OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
-TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
-YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
-PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
-POSSIBILITY OF SUCH DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
-
- If you develop a new program, and you want it to be of the greatest
-possible use to the public, the best way to achieve this is to make it
-free software which everyone can redistribute and change under these terms.
-
- To do so, attach the following notices to the program. It is safest
-to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least
-the "copyright" line and a pointer to where the full notice is found.
-
- <one line to give the program's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-Also add information on how to contact you by electronic and paper mail.
-
-If the program is interactive, make it output a short notice like this
-when it starts in an interactive mode:
-
- Gnomovision version 69, Copyright (C) year name of author
- Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
- This is free software, and you are welcome to redistribute it
- under certain conditions; type `show c' for details.
-
-The hypothetical commands `show w' and `show c' should show the appropriate
-parts of the General Public License. Of course, the commands you use may
-be called something other than `show w' and `show c'; they could even be
-mouse-clicks or menu items--whatever suits your program.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the program, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the program
- `Gnomovision' (which makes passes at compilers) written by James Hacker.
-
- <signature of Ty Coon>, 1 April 1989
- Ty Coon, President of Vice
-
-This General Public License does not permit incorporating your program into
-proprietary programs. If your program is a subroutine library, you may
-consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Lesser General
-Public License instead of this License.
diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/FidoAuthenticatorSelection.cs b/VNLib.Plugins.Essentials.Accounts/MFA/FidoAuthenticatorSelection.cs
deleted file mode 100644
index 0ea6dad..0000000
--- a/VNLib.Plugins.Essentials.Accounts/MFA/FidoAuthenticatorSelection.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: FidoAuthenticatorSelection.cs
-*
-* FidoAuthenticatorSelection.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System.Text.Json.Serialization;
-
-#nullable enable
-
-namespace VNLib.Plugins.Essentials.Accounts.MFA
-{
- class FidoAuthenticatorSelection
- {
- [JsonPropertyName("requireResidentKey")]
- public bool RequireResidentKey { get; set; } = false;
- [JsonPropertyName("authenticatorAttachment")]
- public string? AuthenticatorAttachment { get; set; } = "cross-platform";
- [JsonPropertyName("userVerification")]
- public string? UserVerification { get; set; } = "required";
- }
-}
diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegClientData.cs b/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegClientData.cs
deleted file mode 100644
index 1ef7d59..0000000
--- a/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegClientData.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: FidoRegClientData.cs
-*
-* FidoRegClientData.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System.Text.Json.Serialization;
-
-#nullable enable
-
-namespace VNLib.Plugins.Essentials.Accounts.MFA
-{
- internal class FidoRegClientData
- {
- [JsonPropertyName("challenge")]
- public string? Challenge { get; set; }
- [JsonPropertyName("origin")]
- public string? Origin { get; set; }
- [JsonPropertyName("type")]
- public string? Type { get; set; }
- }
-}
diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegistrationMessage.cs b/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegistrationMessage.cs
deleted file mode 100644
index e8fbcc4..0000000
--- a/VNLib.Plugins.Essentials.Accounts/MFA/FidoRegistrationMessage.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: FidoRegistrationMessage.cs
-*
-* FidoRegistrationMessage.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System.Text.Json.Serialization;
-
-#nullable enable
-
-namespace VNLib.Plugins.Essentials.Accounts.MFA
-{
- /// <summary>
- /// Represents a fido device registration message to be sent
- /// to a currently signed in user
- /// </summary>
- class FidoRegistrationMessage
- {
- [JsonPropertyName("id")]
- public string? GuidUserId { get; set; }
- [JsonPropertyName("challenge")]
- public string? Base64Challenge { get; set; } = null;
- [JsonPropertyName("timeout")]
- public int Timeout { get; set; } = 60000;
- [JsonPropertyName("cose_alg")]
- public int CoseAlgNumber { get; set; }
- [JsonPropertyName("rp_name")]
- public string? SiteName { get; set; }
- [JsonPropertyName("attestation")]
- public string? AttestationType { get; set; } = "none";
- [JsonPropertyName("authenticatorSelection")]
- public FidoAuthenticatorSelection? AuthSelection { get; set; } = new();
- }
-}
diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/MFAConfig.cs b/VNLib.Plugins.Essentials.Accounts/MFA/MFAConfig.cs
deleted file mode 100644
index 03d5a20..0000000
--- a/VNLib.Plugins.Essentials.Accounts/MFA/MFAConfig.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: MFAConfig.cs
-*
-* MFAConfig.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Linq;
-using System.Text.Json;
-using System.Collections.Generic;
-
-using VNLib.Hashing;
-using VNLib.Utils.Extensions;
-using VNLib.Hashing.IdentityUtility;
-
-namespace VNLib.Plugins.Essentials.Accounts.MFA
-{
- internal class MFAConfig
- {
- public ReadOnlyJsonWebKey? MFASecret { get; set; }
-
- public bool TOTPEnabled { get; }
- public string? IssuerName { get; }
- public TimeSpan TOTPPeriod { get; }
- public HashAlg TOTPAlg { get; }
- public int TOTPDigits { get; }
- public int TOTPSecretBytes { get; }
- public int TOTPTimeWindowSteps { get; }
-
-
- public bool FIDOEnabled { get; }
- public int FIDOChallangeSize { get; }
- public int FIDOTimeout { get; }
- public string? FIDOSiteName { get; }
- public string? FIDOAttestationType { get; }
- public FidoAuthenticatorSelection? FIDOAuthSelection { get; }
-
- public TimeSpan UpgradeValidFor { get; }
- public int NonceLenBytes { get; }
-
- public MFAConfig(IReadOnlyDictionary<string, JsonElement> conf)
- {
- UpgradeValidFor = conf["upgrade_expires_secs"].GetTimeSpan(TimeParseType.Seconds);
- NonceLenBytes = conf["nonce_size"].GetInt32();
- string siteName = conf["site_name"].GetString() ?? throw new KeyNotFoundException("Missing required key 'site_name' in 'mfa' config");
-
- //Totp setup
- if (conf.TryGetValue("totp", out JsonElement totpEl))
- {
- IReadOnlyDictionary<string, JsonElement> totp = totpEl.EnumerateObject().ToDictionary(k => k.Name, k => k.Value);
-
- //Get totp config
- IssuerName = siteName;
- //Get alg name
- string TOTPAlgName = totp["algorithm"].GetString()?.ToUpper() ?? throw new KeyNotFoundException("Missing required key 'algorithm' in plugin 'mfa' config");
- //Parse from enum string
- TOTPAlg = Enum.Parse<HashAlg>(TOTPAlgName);
-
-
- TOTPDigits = totp["digits"].GetInt32();
- TOTPPeriod = TimeSpan.FromSeconds(totp["period_secs"].GetInt32());
- TOTPSecretBytes = totp["secret_size"].GetInt32();
- TOTPTimeWindowSteps = totp["window_size"].GetInt32();
- //Set enabled flag
- TOTPEnabled = true;
- }
- //Fido setup
- if(conf.TryGetValue("fido", out JsonElement fidoEl))
- {
- IReadOnlyDictionary<string, JsonElement> fido = fidoEl.EnumerateObject().ToDictionary(k => k.Name, k => k.Value);
- FIDOChallangeSize = fido["challenge_size"].GetInt32();
- FIDOAttestationType = fido["attestation"].GetString();
- FIDOTimeout = fido["timeout"].GetInt32();
- FIDOSiteName = siteName;
- //Deserailze a
- if(fido.TryGetValue("authenticatorSelection", out JsonElement authSel))
- {
- FIDOAuthSelection = authSel.Deserialize<FidoAuthenticatorSelection>();
- }
- //Set enabled flag
- FIDOEnabled = true;
- }
- }
- }
-}
diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/MFAType.cs b/VNLib.Plugins.Essentials.Accounts/MFA/MFAType.cs
deleted file mode 100644
index 208eea3..0000000
--- a/VNLib.Plugins.Essentials.Accounts/MFA/MFAType.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: MFAType.cs
-*
-* MFAType.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-namespace VNLib.Plugins.Essentials.Accounts.MFA
-{
- public enum MFAType
- {
- TOTP, FIDO, PGP
- }
-}
diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/MFAUpgrade.cs b/VNLib.Plugins.Essentials.Accounts/MFA/MFAUpgrade.cs
deleted file mode 100644
index 5577d51..0000000
--- a/VNLib.Plugins.Essentials.Accounts/MFA/MFAUpgrade.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: MFAUpgrade.cs
-*
-* MFAUpgrade.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System.Text.Json.Serialization;
-
-#nullable enable
-
-namespace VNLib.Plugins.Essentials.Accounts.MFA
-{
- internal class MFAUpgrade
- {
- /// <summary>
- /// The login's client id specifier
- /// </summary>
- [JsonPropertyName("cid")]
- public string? ClientID { get; set; }
- /// <summary>
- /// The id of the user that is requesting a login
- /// </summary>
- [JsonPropertyName("uname")]
- public string? UserName{ get; set; }
- /// <summary>
- /// The <see cref="MFAType"/> of the upgrade request
- /// </summary>
- [JsonPropertyName("type")]
- public MFAType Type { get; set; }
- /// <summary>
- /// The a base64 encoded string of the user's
- /// public key
- /// </summary>
- [JsonPropertyName("pubkey")]
- public string? Base64PubKey { get; set; }
- /// <summary>
- /// The user's specified language
- /// </summary>
- [JsonPropertyName("lang")]
- public string? ClientLocalLanguage { get; set; }
- /// <summary>
- /// The encrypted password token for the client
- /// </summary>
- [JsonPropertyName("cd")]
- public string? PwClientData { get; set; }
- }
-}
diff --git a/VNLib.Plugins.Essentials.Accounts/MFA/UserMFAExtensions.cs b/VNLib.Plugins.Essentials.Accounts/MFA/UserMFAExtensions.cs
deleted file mode 100644
index 1ec9953..0000000
--- a/VNLib.Plugins.Essentials.Accounts/MFA/UserMFAExtensions.cs
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: UserMFAExtensions.cs
-*
-* UserMFAExtensions.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Linq;
-using System.Text.Json;
-using System.Collections.Generic;
-using System.Security.Cryptography;
-using System.Text.Json.Serialization;
-using System.Diagnostics.CodeAnalysis;
-
-using VNLib.Hashing;
-using VNLib.Utils;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Extensions;
-using VNLib.Hashing.IdentityUtility;
-using VNLib.Plugins.Essentials.Users;
-using VNLib.Plugins.Essentials.Sessions;
-using VNLib.Plugins.Extensions.Loading;
-
-namespace VNLib.Plugins.Essentials.Accounts.MFA
-{
-
- internal static class UserMFAExtensions
- {
- public const string WEBAUTHN_KEY_ENTRY = "mfa.fido";
- public const string TOTP_KEY_ENTRY = "mfa.totp";
- public const string PGP_PUB_KEY = "mfa.pgpp";
- public const string SESSION_SIG_KEY = "mfa.sig";
-
- /// <summary>
- /// Determines if the user account has an
- /// </summary>
- /// <param name="user"></param>
- /// <returns>True if any form of MFA is enabled for the user account</returns>
- public static bool MFAEnabled(this IUser user)
- {
- return !(string.IsNullOrWhiteSpace(user[TOTP_KEY_ENTRY]) && string.IsNullOrWhiteSpace(user[WEBAUTHN_KEY_ENTRY]));
- }
-
- #region totp
-
- /// <summary>
- /// Recovers the base32 encoded TOTP secret for the current user
- /// </summary>
- /// <param name="user"></param>
- /// <returns>The base32 encoded TOTP secret, or an emtpy string (user spec) if not set</returns>
- public static string MFAGetTOTPSecret(this IUser user) => user[TOTP_KEY_ENTRY];
-
- /// <summary>
- /// Stores or removes the current user's TOTP secret, stored in base32 format
- /// </summary>
- /// <param name="user"></param>
- /// <param name="secret">The base32 encoded TOTP secret</param>
- public static void MFASetTOTPSecret(this IUser user, string? secret) => user[TOTP_KEY_ENTRY] = secret!;
-
-
- /// <summary>
- /// Generates/overwrites the current user's TOTP secret entry and returns a
- /// byte array of the generated secret bytes
- /// </summary>
- /// <param name="entry">The <see cref="MFAEntry"/> to modify the TOTP configuration of</param>
- /// <returns>The raw secret that was encrypted and stored in the <see cref="MFAEntry"/>, to send to the client</returns>
- /// <exception cref="OutOfMemoryException"></exception>
- public static byte[] MFAGenreateTOTPSecret(this IUser user, MFAConfig config)
- {
- //Generate a random key
- byte[] newSecret = RandomHash.GetRandomBytes(config.TOTPSecretBytes);
- //Store secret in user storage
- user.MFASetTOTPSecret(VnEncoding.ToBase32String(newSecret, false));
- //return the raw secret bytes
- return newSecret;
- }
-
- /// <summary>
- /// Verfies the supplied TOTP code against the current user's totp codes
- /// This method should not be used for verifying TOTP codes for authentication
- /// </summary>
- /// <param name="user">The user account to verify the TOTP code against</param>
- /// <param name="code">The code to verify</param>
- /// <param name="config">A readonly referrence to the MFA configuration structure</param>
- /// <returns>True if the user has TOTP configured and code matches against its TOTP secret entry, false otherwise</returns>
- /// <exception cref="FormatException"></exception>
- /// <exception cref="OutOfMemoryException"></exception>
- public static bool VerifyTOTP(this MFAConfig config, IUser user, uint code)
- {
- //Get the base32 TOTP secret for the user and make sure its actually set
- string base32Secret = user.MFAGetTOTPSecret();
- if (string.IsNullOrWhiteSpace(base32Secret))
- {
- return false;
- }
- //Alloc buffer with zero o
- using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(base32Secret.Length, true);
- ERRNO count = VnEncoding.TryFromBase32Chars(base32Secret, buffer);
- //Verify the TOTP using the decrypted secret
- return count && VerifyTOTP(code, buffer.AsSpan(0, count), config);
- }
-
- private static bool VerifyTOTP(uint totpCode, ReadOnlySpan<byte> userSecret, MFAConfig config)
- {
- //A basic attempt at a constant time TOTP verification, run the calculation a fixed number of times, regardless of the resutls
- bool codeMatches = false;
-
- //cache current time
- DateTimeOffset currentUtc = DateTimeOffset.UtcNow;
- //Start the current window with the minimum window
- int currenStep = -config.TOTPTimeWindowSteps;
- Span<byte> stepBuffer = stackalloc byte[sizeof(long)];
- Span<byte> hashBuffer = stackalloc byte[(int)config.TOTPAlg];
- //Run the loop at least once to allow a 0 step tight window
- do
- {
- //Calculate the window by multiplying the window by the current step, then add it to the current time offset to produce a new window
- DateTimeOffset window = currentUtc.Add(config.TOTPPeriod.Multiply(currenStep));
- //calculate the time step
- long timeStep = (long)Math.Floor(window.ToUnixTimeSeconds() / config.TOTPPeriod.TotalSeconds);
- //try to compute the hash
- _ = BitConverter.TryWriteBytes(stepBuffer, timeStep) ? 0 : throw new InternalBufferTooSmallException("Failed to format TOTP time step");
- //If platform is little endian, reverse the byte order
- if (BitConverter.IsLittleEndian)
- {
- stepBuffer.Reverse();
- }
- ERRNO result = ManagedHash.ComputeHmac(userSecret, stepBuffer, hashBuffer, config.TOTPAlg);
- //try to compute the hash of the time step
- if (result < 1)
- {
- throw new InternalBufferTooSmallException("Failed to compute TOTP time step hash because the buffer was too small");
- }
- //Hash bytes
- ReadOnlySpan<byte> hash = hashBuffer[..(int)result];
- //compute the TOTP code and compare it to the supplied, then store the result
- codeMatches |= (totpCode == CalcTOTPCode(hash, config));
- //next step
- currenStep++;
- } while (currenStep <= config.TOTPTimeWindowSteps);
-
- return codeMatches;
- }
-
- private static uint CalcTOTPCode(ReadOnlySpan<byte> hash, MFAConfig config)
- {
- //Calculate the offset, RFC defines, the lower 4 bits of the last byte in the hash output
- byte offset = (byte)(hash[^1] & 0x0Fu);
-
- uint TOTPCode;
- if (BitConverter.IsLittleEndian)
- {
- //Store the code components
- TOTPCode = (hash[offset] & 0x7Fu) << 24 | (hash[offset + 1] & 0xFFu) << 16 | (hash[offset + 2] & 0xFFu) << 8 | hash[offset + 3] & 0xFFu;
- }
- else
- {
- //Store the code components (In reverse order for big-endian machines)
- TOTPCode = (hash[offset + 3] & 0x7Fu) << 24 | (hash[offset + 2] & 0xFFu) << 16 | (hash[offset + 1] & 0xFFu) << 8 | hash[offset] & 0xFFu;
- }
- //calculate the modulus value
- TOTPCode %= (uint)Math.Pow(10, config.TOTPDigits);
- return TOTPCode;
- }
-
- #endregion
-
- #region loading
-
- const string MFA_CONFIG_KEY = "mfa";
-
- /// <summary>
- /// Gets the plugins ambient <see cref="PasswordHashing"/> if loaded, or loads it if required. This class will
- /// be unloaded when the plugin us unloaded.
- /// </summary>
- /// <param name="plugin"></param>
- /// <returns>The ambient <see cref="PasswordHashing"/></returns>
- /// <exception cref="OverflowException"></exception>
- /// <exception cref="KeyNotFoundException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public static MFAConfig? GetMfaConfig(this PluginBase plugin)
- {
- static MFAConfig? LoadMfaConfig(PluginBase pbase)
- {
- //Try to get the configuration object
- IReadOnlyDictionary<string, JsonElement>? conf = pbase.TryGetConfig(MFA_CONFIG_KEY);
-
- if (conf == null)
- {
- return null;
- }
- //Init mfa config
- MFAConfig mfa = new(conf);
-
- //Recover secret from config and dangerous 'lazy load'
- _ = pbase.DeferTask(async () =>
- {
- mfa.MFASecret = await pbase.TryGetSecretAsync("mfa_secret").ToJsonWebKey();
-
- }, 50);
-
- return mfa;
- }
-
- plugin.ThrowIfUnloaded();
- //Get/load the passwords one time only
- return LoadingExtensions.GetOrCreateSingleton(plugin, LoadMfaConfig);
- }
-
- #endregion
-
- #region pgp
-
- private class PgpMfaCred
- {
- [JsonPropertyName("p")]
- public string? SpkiPublicKey { get; set; }
-
- [JsonPropertyName("c")]
- public string? CurveFriendlyName { get; set; }
- }
-
-
- /// <summary>
- /// Gets the stored PGP public key for the user
- /// </summary>
- /// <param name="user"></param>
- /// <returns>The stored PGP signature key </returns>
- public static string MFAGetPGPPubKey(this IUser user) => user[PGP_PUB_KEY];
-
- public static void MFASetPGPPubKey(this IUser user, string? pubKey) => user[PGP_PUB_KEY] = pubKey!;
-
- public static void VerifySignedData(string data)
- {
-
- }
-
- #endregion
-
- #region webauthn
-
- #endregion
-
- /// <summary>
- /// Recovers a signed MFA upgrade JWT and verifies its authenticity, and confrims its not expired,
- /// then recovers the upgrade mssage
- /// </summary>
- /// <param name="config"></param>
- /// <param name="upgradeJwtString">The signed JWT upgrade message</param>
- /// <param name="upgrade">The recovered upgrade</param>
- /// <param name="base64sessionSig">The stored base64 encoded signature from the session that requested an upgrade</param>
- /// <returns>True if the upgrade was verified, not expired, and was recovered from the signed message, false otherwise</returns>
- public static bool RecoverUpgrade(this MFAConfig config, ReadOnlySpan<char> upgradeJwtString, ReadOnlySpan<char> base64sessionSig, [NotNullWhen(true)] out MFAUpgrade? upgrade)
- {
- //Verifies a jwt stored signature against the actual signature
- static bool VerifyStoredSig(ReadOnlySpan<char> base64string, ReadOnlySpan<byte> signature)
- {
- using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(base64string.Length, true);
- //Recover base64
- ERRNO count = VnEncoding.TryFromBase64Chars(base64string, buffer.Span);
- //Compare
- return CryptographicOperations.FixedTimeEquals(signature, buffer.Span[..(int)count]);
- }
-
- //Verify config secret
- _ = config.MFASecret ?? throw new InvalidOperationException("MFA config is missing required upgrade signing key");
-
- upgrade = null;
-
- //Parse jwt
- using JsonWebToken jwt = JsonWebToken.Parse(upgradeJwtString);
-
- if (!jwt.VerifyFromJwk(config.MFASecret))
- {
- return false;
- }
-
- if(!VerifyStoredSig(base64sessionSig, jwt.SignatureData))
- {
- return false;
- }
-
- //get request body
- using JsonDocument doc = jwt.GetPayload();
- //Recover issued at time
- DateTimeOffset iat = DateTimeOffset.FromUnixTimeMilliseconds(doc.RootElement.GetProperty("iat").GetInt64());
- //Verify its not timed out
- if (iat.Add(config.UpgradeValidFor) < DateTimeOffset.UtcNow)
- {
- //expired
- return false;
- }
-
- //Recover the upgrade message
- upgrade = doc.RootElement.GetProperty("upgrade").Deserialize<MFAUpgrade>();
- return upgrade != null;
- }
-
-
- /// <summary>
- /// Generates an upgrade for the requested user, using the highest prirotiy method
- /// </summary>
- /// <param name="login">The message from the user requesting the login</param>
- /// <returns>A signed upgrade message the client will pass back to the server after the MFA verification</returns>
- /// <exception cref="InvalidOperationException"></exception>
- public static Tuple<string, string>? MFAGetUpgradeIfEnabled(this IUser user, MFAConfig? conf, LoginMessage login, string pwClientData)
- {
- //Webauthn config
-
-
- //Search for totp secret entry
- string base32Secret = user.MFAGetTOTPSecret();
-
- //Check totp entry
- if (!string.IsNullOrWhiteSpace(base32Secret))
- {
- //Verify config secret
- _ = conf?.MFASecret ?? throw new InvalidOperationException("MFA config is missing required upgrade signing key");
-
- //setup the upgrade
- MFAUpgrade upgrade = new()
- {
- //Set totp upgrade type
- Type = MFAType.TOTP,
- //Store login message details
- UserName = login.UserName,
- ClientID = login.ClientID,
- Base64PubKey = login.ClientPublicKey,
- ClientLocalLanguage = login.LocalLanguage,
- PwClientData = pwClientData
- };
-
- //Init jwt for upgrade
- return GetUpgradeMessage(upgrade, conf.MFASecret, conf.UpgradeValidFor);
- }
- return null;
- }
-
- private static Tuple<string, string> GetUpgradeMessage(MFAUpgrade upgrade, ReadOnlyJsonWebKey secret, TimeSpan expires)
- {
- //Add some random entropy to the upgrade message, to help prevent forgery
- string entropy = RandomHash.GetRandomBase32(16);
- //Init jwt
- using JsonWebToken upgradeJwt = new();
- upgradeJwt.WriteHeader(secret.JwtHeader);
- //Write claims
- upgradeJwt.InitPayloadClaim()
- .AddClaim("iat", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())
- .AddClaim("upgrade", upgrade)
- .AddClaim("type", upgrade.Type.ToString().ToLower())
- .AddClaim("expires", expires.TotalSeconds)
- .AddClaim("a", entropy)
- .CommitClaims();
-
- //Sign with jwk
- upgradeJwt.SignFromJwk(secret);
-
- //compile and return jwt upgrade
- return new(upgradeJwt.Compile(), Convert.ToBase64String(upgradeJwt.SignatureData));
- }
-
- public static void MfaUpgradeSignature(this in SessionInfo session, string? base64Signature) => session[SESSION_SIG_KEY] = base64Signature!;
-
- public static string? MfaUpgradeSignature(this in SessionInfo session) => session[SESSION_SIG_KEY];
- }
-}
diff --git a/VNLib.Plugins.Essentials.Accounts/README.md b/VNLib.Plugins.Essentials.Accounts/README.md
deleted file mode 100644
index fd1098c..0000000
--- a/VNLib.Plugins.Essentials.Accounts/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# VNLib.Plugins.Essentials.Accounts
-
-An essentials web plugin that provides endpoints for authenticating, registering, resetting, local user accounts including multi-factor authentication using TOTP (for now). \ No newline at end of file
diff --git a/VNLib.Plugins.Essentials.Accounts/VNLib.Plugins.Essentials.Accounts.csproj b/VNLib.Plugins.Essentials.Accounts/VNLib.Plugins.Essentials.Accounts.csproj
deleted file mode 100644
index d710cfc..0000000
--- a/VNLib.Plugins.Essentials.Accounts/VNLib.Plugins.Essentials.Accounts.csproj
+++ /dev/null
@@ -1,61 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
- <PropertyGroup>
- <TargetFramework>net6.0</TargetFramework>
- <RootNamespace>VNLib.Plugins.Essentials.Accounts</RootNamespace>
- <Copyright>Copyright © 2022 Vaughn Nugent</Copyright>
- <Authors>Vaughn Nugent</Authors>
- <AssemblyName>Accounts</AssemblyName>
-
- <PackageId>VNLib.Plugins.Essentials.Accounts</PackageId>
- <Version>1.0.1.5</Version>
- <PackageProjectUrl>https://www.vaughnnugent.com/resources</PackageProjectUrl>
- <SignAssembly>True</SignAssembly>
- <AssemblyOriginatorKeyFile>\\vaughnnugent.com\Internal\Folder Redirection\vman\Documents\Programming\Software\StrongNameingKey.snk</AssemblyOriginatorKeyFile>
- </PropertyGroup>
-
-
- <!-- Resolve nuget dll files and store them in the output dir -->
- <PropertyGroup>
- <!--Enable dynamic loading-->
- <EnableDynamicLoading>true</EnableDynamicLoading>
- <Nullable>enable</Nullable>
- <AnalysisLevel>latest-all</AnalysisLevel>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
- <Deterministic>False</Deterministic>
- </PropertyGroup>
- <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
- <Deterministic>False</Deterministic>
- </PropertyGroup>
-
- <ItemGroup>
- <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
- </PackageReference>
- <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
- </PackageReference>
- <PackageReference Include="FluentValidation" Version="11.3.0" />
- </ItemGroup>
-
- <ItemGroup>
- <ProjectReference Include="..\..\..\VNLib\Essentials\src\VNLib.Plugins.Essentials.csproj" />
- <ProjectReference Include="..\..\Extensions\VNLib.Plugins.Extensions.Loading.Sql\VNLib.Plugins.Extensions.Loading.Sql.csproj" />
- <ProjectReference Include="..\..\Extensions\VNLib.Plugins.Extensions.Validation\VNLib.Plugins.Extensions.Validation.csproj" />
- <ProjectReference Include="..\..\PluginBase\VNLib.Plugins.PluginBase.csproj" />
- </ItemGroup>
-
- <ItemGroup>
- <None Update="Accounts.json">
- <CopyToOutputDirectory>Always</CopyToOutputDirectory>
- </None>
- </ItemGroup>
-
- <Target Name="PostBuild" AfterTargets="PostBuildEvent">
- <Exec Command="start xcopy &quot;$(TargetDir)&quot; &quot;F:\Programming\Web Plugins\DevPlugins\$(TargetName)&quot; /E /Y /R" />
- </Target>
-
-</Project>
diff --git a/VNLib.Plugins.Essentials.Accounts/Validators/LoginMessageValidation.cs b/VNLib.Plugins.Essentials.Accounts/Validators/LoginMessageValidation.cs
deleted file mode 100644
index 6d96695..0000000
--- a/VNLib.Plugins.Essentials.Accounts/Validators/LoginMessageValidation.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Essentials.Accounts
-* File: LoginMessageValidation.cs
-*
-* LoginMessageValidation.cs is part of VNLib.Plugins.Essentials.Accounts which is part of the larger
-* VNLib collection of libraries and utilities.
-*
-* VNLib.Plugins.Essentials.Accounts is free software: you can redistribute it and/or modify
-* it under the terms of the GNU Affero General Public License as
-* published by the Free Software Foundation, either version 3 of the
-* License, or (at your option) any later version.
-*
-* VNLib.Plugins.Essentials.Accounts is distributed in the hope that it will be useful,
-* but WITHOUT ANY WARRANTY; without even the implied warranty of
-* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-* GNU Affero General Public License for more details.
-*
-* You should have received a copy of the GNU Affero General Public License
-* along with this program. If not, see https://www.gnu.org/licenses/.
-*/
-
-using System;
-
-using FluentValidation;
-
-using VNLib.Plugins.Extensions.Validation;
-
-namespace VNLib.Plugins.Essentials.Accounts.Validators
-{
-
- internal class LoginMessageValidation : AbstractValidator<LoginMessage>
- {
- public LoginMessageValidation()
- {
- RuleFor(static t => t.ClientID)
- .Length(min: 10, max: 100)
- .WithMessage(errorMessage: "Your browser is not sending required security information");
-
- RuleFor(static t => t.ClientPublicKey)
- .NotEmpty()
- .Length(min: 50, max: 1000)
- .WithMessage(errorMessage: "Your browser is not sending required security information");
-
- /* Rules for user-input on passwords, set max length to avoid DOS */
- RuleFor(static t => t.Password)
- .SetValidator(AccountValidations.PasswordValidator);
-
- //Username/email address
- RuleFor(static t => t.UserName)
- .Length(min: 1, max: 64)
- .WithName(overridePropertyName: "Email")
- .EmailAddress()
- .WithName(overridePropertyName: "Email")
- .IllegalCharacters()
- .WithName(overridePropertyName: "Email");
-
- RuleFor(static t => t.LocalLanguage)
- .NotEmpty()
- .IllegalCharacters()
- .WithMessage(errorMessage: "Your language is not supported");
-
- RuleFor(static t => t.LocalTime.ToUniversalTime())
- .Must(static time => time > DateTime.UtcNow.AddSeconds(-60) && time < DateTime.UtcNow.AddSeconds(60))
- .WithMessage(errorMessage: "Please check your system clock");
- }
- }
-}