From 0316fc948dd77b91b0ccf508826f66a175cb1e83 Mon Sep 17 00:00:00 2001 From: vnugent Date: Sun, 5 Nov 2023 21:22:21 -0500 Subject: user/acc updates and fix social oauth --- .../src/Endpoints/LoginEndpoint.cs | 60 ++++++++++++---------- .../src/Endpoints/MFAEndpoint.cs | 26 +++++----- .../src/Endpoints/PasswordResetEndpoint.cs | 26 ++++------ .../src/Endpoints/PkiLoginEndpoint.cs | 5 ++ 4 files changed, 59 insertions(+), 58 deletions(-) (limited to 'plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints') diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs index 66a099e..2475f36 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs @@ -25,6 +25,7 @@ using System; using System.Net; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using System.Security.Cryptography; using System.Text.Json.Serialization; @@ -32,7 +33,6 @@ using System.Text.Json.Serialization; using FluentValidation; using VNLib.Utils; -using VNLib.Utils.Memory; using VNLib.Utils.Logging; using VNLib.Utils.Extensions; using VNLib.Plugins.Essentials.Users; @@ -45,6 +45,7 @@ using VNLib.Plugins.Extensions.Loading; using VNLib.Plugins.Extensions.Loading.Users; using static VNLib.Plugins.Essentials.Statics; + /* * Password only log-ins should be immune to repeat attacks on the same backend, because sessions are * guarunteed to be mutally exclusive on the same system, therefor a successful login cannot be repeated @@ -71,8 +72,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints public const string MFA_ERROR_MESSAGE = "Invalid or expired request."; private static readonly LoginMessageValidation LmValidator = new(); - - private readonly IPasswordHashingProvider Passwords; + private readonly MFAConfig MultiFactor; private readonly IUserManager Users; private readonly FailedLoginLockout _lockout; @@ -84,8 +84,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints uint maxLogins = config["max_login_attempts"].GetUInt32(); InitPathAndLog(path, pbase.Log); - - Passwords = pbase.GetOrCreateSingleton(); + Users = pbase.GetOrCreateSingleton(); MultiFactor = pbase.GetConfigElement(); _lockout = new(maxLogins, duration); @@ -136,9 +135,8 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints { return VirtualClose(entity, webm, HttpStatusCode.UnprocessableEntity); } - - //Time to get the user - using IUser? user = await Users.GetUserAndPassFromEmailAsync(loginMessage.UserName); + + using IUser? user = await Users.GetUserFromEmailAsync(loginMessage.UserName); //Make sure account exists if (webm.Assert(user != null, INVALID_MESSAGE)) @@ -155,15 +153,25 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints } //Only allow local accounts - if (user.IsLocalAccount() && !PrivateString.IsNullOrEmpty(user.PassHash)) + if (!user.IsLocalAccount()) { - //If login return true, the response has been set and we should return - if (LoginUser(entity, loginMessage, user, webm)) - { - goto Cleanup; - } + goto Failed; + } + + //Validate password + if (await ValidatePasswordAsync(user, loginMessage, entity.EventCancellation) == false) + { + goto Failed; + } + + //If login return true, the response has been set and we should return + if (LoginUser(entity, loginMessage, user, webm)) + { + goto Cleanup; } + Failed: + //Inc failed login count _lockout.Increment(user, entity.RequestedTimeUtc); webm.Result = INVALID_MESSAGE; @@ -177,16 +185,19 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints Log.Warn(uue); return VfReturnType.Error; } - } + } + + private async Task ValidatePasswordAsync(IUser user, LoginMessage login, CancellationToken cancellation) + { + //Validate password against store + ERRNO valResult = await Users.ValidatePasswordAsync(user, login.Password!, PassValidateFlags.None, cancellation); + + //Valid results are greater than 0; + return valResult > 0; + } 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!, loginMessage.Password)) - { - return false; - } - //Only allow active users if (user.Status != UserStatus.Active) { @@ -195,13 +206,6 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints return false; } - //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; - } - //Reset flc for account, either the user will be authorized, or the mfa will be triggered, but the flc should be reset user.ClearFailedLoginCount(); diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs index d9cfd49..a156ccc 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs @@ -47,10 +47,10 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints internal sealed class MFAEndpoint : ProtectedWebEndpoint { public const int TOTP_URL_MAX_CHARS = 1024; + private const string CHECK_PASSWORD = "Please check your password"; private readonly IUserManager Users; private readonly MFAConfig? MultiFactor; - private readonly IPasswordHashingProvider Passwords; public MFAEndpoint(PluginBase pbase, IConfigScope config) { @@ -59,7 +59,6 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints Users = pbase.GetOrCreateSingleton(); MultiFactor = pbase.GetConfigElement(); - Passwords = pbase.GetOrCreateSingleton(); } protected override async ValueTask GetAsync(HttpEntity entity) @@ -124,7 +123,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints } //Get the user entry - using IUser? user = await Users.GetUserAndPassFromIDAsync(entity.Session.UserID); + using IUser? user = await Users.GetUserFromIDAsync(entity.Session.UserID); if (webm.Assert(user != null, "Please log-out and try again.")) { @@ -134,16 +133,16 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints //get the user's password challenge using (PrivateString? password = (PrivateString?)mfaRequest.RootElement.GetPropString("password")) { - if (PrivateString.IsNullOrEmpty(password)) + if (webm.Assert(!PrivateString.IsNullOrEmpty(password), CHECK_PASSWORD)) { - webm.Result = "Please check your password"; return VirtualClose(entity, webm, HttpStatusCode.Unauthorized); } //Verify password against the user - if (!user.VerifyPassword(password, Passwords)) + ERRNO result = await Users.ValidatePasswordAsync(user, password, PassValidateFlags.None, entity.EventCancellation); + + if (webm.Assert(result > 0, CHECK_PASSWORD)) { - webm.Result = "Please check your password"; return VirtualClose(entity, webm, HttpStatusCode.Unauthorized); } } @@ -192,7 +191,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints string? mfaType = request.RootElement.GetProperty("type").GetString(); //get the user - using IUser? user = await Users.GetUserAndPassFromIDAsync(entity.Session.UserID); + using IUser? user = await Users.GetUserFromIDAsync(entity.Session.UserID); if (user == null) { return VfReturnType.NotFound; @@ -204,16 +203,16 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints */ using (PrivateString? password = (PrivateString?)request.RootElement.GetPropString("password")) { - if (PrivateString.IsNullOrEmpty(password)) + if (webm.Assert(!PrivateString.IsNullOrEmpty(password), CHECK_PASSWORD)) { - webm.Result = "Please check your password"; return VirtualClose(entity, webm, HttpStatusCode.Unauthorized); } //Verify password against the user - if (!user.VerifyPassword(password, Passwords)) + ERRNO result = await Users.ValidatePasswordAsync(user, password, PassValidateFlags.None, entity.EventCancellation); + + if (webm.Assert(result > 0, CHECK_PASSWORD)) { - webm.Result = "Please check your password"; return VirtualClose(entity, webm, HttpStatusCode.Unauthorized); } } @@ -221,8 +220,9 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints //Check for totp disable if ("totp".Equals(mfaType, StringComparison.OrdinalIgnoreCase)) { - //Clear the TOTP secret + //Clear the TOTP secret to disable it user.MFASetTOTPSecret(null); + //write changes await user.ReleaseAsync(); webm.Result = "Successfully disabled your TOTP authentication"; diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PasswordResetEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PasswordResetEndpoint.cs index 6f8cb77..33c72a7 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PasswordResetEndpoint.cs +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PasswordResetEndpoint.cs @@ -29,16 +29,15 @@ using System.Text.Json.Serialization; using FluentValidation; +using VNLib.Utils; using VNLib.Utils.Memory; -using VNLib.Utils.Extensions; using VNLib.Plugins.Essentials.Users; using VNLib.Plugins.Essentials.Extensions; +using VNLib.Plugins.Essentials.Endpoints; +using VNLib.Plugins.Essentials.Accounts.MFA; using VNLib.Plugins.Extensions.Validation; using VNLib.Plugins.Extensions.Loading; using VNLib.Plugins.Extensions.Loading.Users; -using VNLib.Plugins.Essentials.Endpoints; -using VNLib.Plugins.Essentials.Accounts.MFA; - namespace VNLib.Plugins.Essentials.Accounts.Endpoints { @@ -61,7 +60,6 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints internal sealed class PasswordChangeEndpoint : ProtectedWebEndpoint { private readonly IUserManager Users; - private readonly IPasswordHashingProvider Passwords; private readonly MFAConfig? mFAConfig; private readonly IValidator ResetMessValidator; @@ -71,7 +69,6 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints InitPathAndLog(path, pbase.Log); Users = pbase.GetOrCreateSingleton(); - Passwords = pbase.GetOrCreateSingleton(); ResetMessValidator = GetMessageValidator(); mFAConfig = pbase.GetConfigElement(); } @@ -95,10 +92,6 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints return rules; } - /* - * If mfa config - */ - protected override async ValueTask PostAsync(HttpEntity entity) { ValErrWebMessage webm = new(); @@ -118,7 +111,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints } //get the user's entry in the table - using IUser? user = await Users.GetUserAndPassFromIDAsync(entity.Session.UserID); + using IUser? user = await Users.GetUserFromIDAsync(entity.Session.UserID, entity.EventCancellation); if(webm.Assert(user != null, "An error has occured, please log-out and try again")) { @@ -131,10 +124,12 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints return VirtualOk(entity, webm); } + //Validate the user's current password + ERRNO isPassValid = await Users.ValidatePasswordAsync(user, pwReset.Current!, PassValidateFlags.None, entity.EventCancellation); + //Verify the user's old password - if (!Passwords.Verify(user.PassHash, pwReset.Current.AsSpan())) + if (webm.Assert(isPassValid > 0, "Please check your current password")) { - webm.Result = "Please check your current password"; return VirtualOk(entity, webm); } @@ -160,11 +155,8 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints //continue } - //Hash the user's new password - using PrivateString newPassHash = Passwords.Hash(pwReset.NewPassword.AsSpan()); - //Update the user's password - if (!await Users.UpdatePassAsync(user, newPassHash)) + if (!await Users.UpdatePasswordAsync(user, pwReset.NewPassword!, entity.EventCancellation)) { //error webm.Result = "Your password could not be updated"; diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PkiLoginEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PkiLoginEndpoint.cs index 0abe657..4f4c830 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PkiLoginEndpoint.cs +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PkiLoginEndpoint.cs @@ -204,6 +204,9 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints EmailAddress = user.EmailAddress, }; + //Write to log + Log.Verbose("Successful login for user {uid}...", user.UserID[..8]); + //Close response, user is now logged-in return VirtualOk(entity, webm); } @@ -422,8 +425,10 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints { [JsonPropertyName("pubkey")] public string? PublicKey { get; set; } + [JsonPropertyName("clientid")] public string? ClientId { get; set; } + [JsonPropertyName("login")] public string? LoginJwt { get; set; } } -- cgit