aboutsummaryrefslogtreecommitdiff
path: root/plugins/VNLib.Plugins.Essentials.Accounts
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/VNLib.Plugins.Essentials.Accounts')
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs26
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs60
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs26
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PasswordResetEndpoint.cs26
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/PkiLoginEndpoint.cs5
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs4
6 files changed, 75 insertions, 72 deletions
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs
index 5f171cd..d524902 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs
@@ -29,7 +29,6 @@ using System.ComponentModel.Design;
using FluentValidation.Results;
using VNLib.Utils;
-using VNLib.Utils.Memory;
using VNLib.Utils.Logging;
using VNLib.Plugins.Attributes;
using VNLib.Plugins.Essentials.Users;
@@ -40,9 +39,11 @@ using VNLib.Plugins.Essentials.Accounts.SecurityProvider;
using VNLib.Plugins.Extensions.Loading;
using VNLib.Plugins.Extensions.Loading.Users;
using VNLib.Plugins.Extensions.Loading.Routing;
+using VNLib.Utils.Memory;
namespace VNLib.Plugins.Essentials.Accounts
{
+
public sealed class AccountsEntryPoint : PluginBase
{
@@ -139,7 +140,6 @@ namespace VNLib.Plugins.Essentials.Accounts
ArgumentList args = new(cmd.Split(' '));
IUserManager Users = this.GetOrCreateSingleton<UserManager>();
- IPasswordHashingProvider Passwords = this.GetOrCreateSingleton<ManagedPasswordHashing>();
string? username = args.GetArgument("-u");
string? password = args.GetArgument("-p");
@@ -187,13 +187,18 @@ Commands:
privLevel = AccountUtil.MINIMUM_LEVEL;
}
- //Hash the password
- using PrivateString passHash = Passwords.Hash(password);
+ //Create the user creation request
+ UserCreationRequest creation = new()
+ {
+ EmailAddress = username,
+ InitialStatus = UserStatus.Active,
+ Privileges = privLevel,
+ Password = PrivateString.ToPrivateString(password, false)
+ };
+
//Create the user
- using IUser user = await Users.CreateUserAsync(username, passHash, privLevel);
-
- //Set active flag
- user.Status = UserStatus.Active;
+ using IUser user = await Users.CreateUserAsync(creation, null);
+
//Set local account
user.SetAccountOrigin(AccountUtil.LOCAL_ACCOUNT_ORIGIN);
@@ -210,9 +215,6 @@ Commands:
break;
}
- //Hash the password
- using PrivateString passHash = Passwords.Hash(password);
-
//Get the user
using IUser? user = await Users.GetUserFromEmailAsync(username);
@@ -223,7 +225,7 @@ Commands:
}
//Set the password
- await Users.UpdatePassAsync(user, passHash);
+ await Users.UpdatePasswordAsync(user, password);
Log.Information("Successfully reset password for {id}", username);
}
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<ManagedPasswordHashing>();
+
Users = pbase.GetOrCreateSingleton<UserManager>();
MultiFactor = pbase.GetConfigElement<MFAConfig>();
_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<bool> 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<UserManager>();
MultiFactor = pbase.GetConfigElement<MFAConfig>();
- Passwords = pbase.GetOrCreateSingleton<ManagedPasswordHashing>();
}
protected override async ValueTask<VfReturnType> 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<PasswordResetMesage> ResetMessValidator;
@@ -71,7 +69,6 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
InitPathAndLog(path, pbase.Log);
Users = pbase.GetOrCreateSingleton<UserManager>();
- Passwords = pbase.GetOrCreateSingleton<ManagedPasswordHashing>();
ResetMessValidator = GetMessageValidator();
mFAConfig = pbase.GetConfigElement<MFAConfig>();
}
@@ -95,10 +92,6 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
return rules;
}
- /*
- * If mfa config
- */
-
protected override async ValueTask<VfReturnType> 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; }
}
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
index d9c1703..0d1a714 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
@@ -757,7 +757,7 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
Domain = Config.CookieDomain,
Path = Config.CookiePath,
ValidFor = TimeSpan.Zero,
- SameSite = CookieSameSite.SameSite,
+ SameSite = CookieSameSite.Strict,
HttpOnly = true,
Secure = entity.IsSecure
};
@@ -779,7 +779,7 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
Domain = Config.CookieDomain,
Path = Config.CookiePath,
ValidFor = Config.AuthorizationValidFor,
- SameSite = CookieSameSite.SameSite,
+ SameSite = CookieSameSite.Strict,
HttpOnly = httpOnly,
Secure = entity.IsSecure
};