aboutsummaryrefslogtreecommitdiff
path: root/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints')
-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
4 files changed, 59 insertions, 58 deletions
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; }
}