diff options
13 files changed, 64 insertions, 25 deletions
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/Endpoints/RegistrationEntpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/Endpoints/RegistrationEntpoint.cs index 5c22344..c39165c 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/Endpoints/RegistrationEntpoint.cs +++ b/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/Endpoints/RegistrationEntpoint.cs @@ -47,7 +47,7 @@ using VNLib.Plugins.Extensions.Loading.Users; using VNLib.Plugins.Extensions.Validation; using VNLib.Plugins.Extentions.TransactionalEmail; using VNLib.Plugins.Essentials.Accounts.Registration.TokenRevocation; -using static VNLib.Plugins.Essentials.Accounts.AccountManager; +using static VNLib.Plugins.Essentials.Accounts.AccountUtil; namespace VNLib.Plugins.Essentials.Accounts.Registration.Endpoints @@ -73,7 +73,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Registration.Endpoints private readonly TimeSpan RegExpiresSec; /// <summary> - /// Creates back-end functionality for a "registration" or "sign-up" page that integrates with the <see cref="AccountManager"/> plugin + /// Creates back-end functionality for a "registration" or "sign-up" page that integrates with the <see cref="AccountUtil"/> plugin /// </summary> /// <param name="Path">The path identifier</param> /// <exception cref="ArgumentException"></exception> @@ -186,7 +186,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Registration.Endpoints DateTimeOffset iat = DateTimeOffset.FromUnixTimeSeconds(reg.RootElement.GetProperty("iat").GetInt64()); //Verify IAT against expiration at second resolution - if (webm.Assert(iat.Add(RegExpiresSec) > DateTimeOffset.UtcNow, FAILED_AUTH_ERR)) + if (webm.Assert(iat.Add(RegExpiresSec) > entity.RequestedTimeUtc, FAILED_AUTH_ERR)) { entity.CloseResponse(webm); return VfReturnType.VirtualSkip; @@ -273,7 +273,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Registration.Endpoints } //Get exact timestamp - DateTimeOffset timeStamp = DateTimeOffset.UtcNow; + DateTimeOffset timeStamp = entity.RequestedTimeUtc; //generate random nonce for entropy string entropy = EntropyNonce; diff --git a/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/VNLib.Plugins.Essentials.Accounts.Registration.csproj b/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/VNLib.Plugins.Essentials.Accounts.Registration.csproj index 981d252..bdd9fde 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/VNLib.Plugins.Essentials.Accounts.Registration.csproj +++ b/plugins/VNLib.Plugins.Essentials.Accounts.Registration/src/VNLib.Plugins.Essentials.Accounts.Registration.csproj @@ -36,7 +36,7 @@ <ItemGroup> <ProjectReference Include="..\..\..\..\..\core\lib\Plugins.Essentials\src\VNLib.Plugins.Essentials.csproj" /> <ProjectReference Include="..\..\..\..\Emails.Transactional\lib\Emails.Transactional.Client\src\Emails.Transactional.Client.csproj" /> - <ProjectReference Include="..\..\..\..\Emails.Transactional\lib\Emails.Transactional.Extensions\src\VNLib.Plugins.Extentions.TransactionalEmail.csproj" /> + <ProjectReference Include="..\..\..\..\Emails.Transactional\lib\Emails.Transactional.Extensions\src\Emails.Transactional.Client.Extensions.csproj" /> <ProjectReference Include="..\..\..\..\Extensions\lib\VNLib.Plugins.Extensions.Data\src\VNLib.Plugins.Extensions.Data.csproj" /> <ProjectReference Include="..\..\..\..\Extensions\lib\VNLib.Plugins.Extensions.Loading.Sql\src\VNLib.Plugins.Extensions.Loading.Sql.csproj" /> <ProjectReference Include="..\..\..\..\Extensions\lib\VNLib.Plugins.Extensions.Loading\src\VNLib.Plugins.Extensions.Loading.csproj" /> diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs index ed79476..f4401a9 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs @@ -110,18 +110,18 @@ namespace VNLib.Plugins.Essentials.Accounts return; } string username = args[uid + 1].Trim(); - string randomUserId = AccountManager.GetRandomUserId(); + string randomUserId = AccountUtil.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); + using IUser user = await Users.CreateUserAsync(randomUserId, username, AccountUtil.MINIMUM_LEVEL, passHash); //Set active flag user.Status = UserStatus.Active; //Set local account - user.SetAccountOrigin(AccountManager.LOCAL_ACCOUNT_ORIGIN); + user.SetAccountOrigin(AccountUtil.LOCAL_ACCOUNT_ORIGIN); await user.ReleaseAsync(); } diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/KeepAliveEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/KeepAliveEndpoint.cs index fe5a65b..0ff0869 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/KeepAliveEndpoint.cs +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/KeepAliveEndpoint.cs @@ -27,14 +27,19 @@ using System.Net; using System.Text.Json; using System.Collections.Generic; +using VNLib.Utils.Extensions; using VNLib.Plugins.Essentials.Endpoints; +using VNLib.Plugins.Essentials.Extensions; using VNLib.Plugins.Extensions.Loading; + namespace VNLib.Plugins.Essentials.Accounts.Endpoints { [ConfigurationName("keepalive_endpoint")] internal sealed class KeepAliveEndpoint : ProtectedWebEndpoint { + readonly TimeSpan tokenRegenTime; + /* * Endpoint does not use a log, so IniPathAndLog is never called * and path verification happens verbosly @@ -43,6 +48,8 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints { string? path = config["path"].GetString(); + tokenRegenTime = config["token_refresh_sec"].GetTimeSpan(TimeParseType.Seconds); + InitPathAndLog(path, pbase.Log); } @@ -56,6 +63,24 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints //Allow post to update user's credentials protected override VfReturnType Post(HttpEntity entity) { + //Get the last token update + DateTimeOffset lastTokenUpdate = entity.Session.LastTokenUpgrade(); + + //See if its expired + if (lastTokenUpdate.Add(tokenRegenTime) < entity.RequestedTimeUtc) + { + //if so updaet token + WebMessage webm = new() + { + Token = entity.RegenerateClientToken(), + Success = true + }; + + //Send the update message to the client + entity.CloseResponse(webm); + return VfReturnType.VirtualSkip; + } + //Return okay entity.CloseResponse(HttpStatusCode.OK); return VfReturnType.VirtualSkip; diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs index 4100620..f973fe8 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LoginEndpoint.cs @@ -44,7 +44,7 @@ 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; +using static VNLib.Plugins.Essentials.Accounts.AccountUtil; namespace VNLib.Plugins.Essentials.Accounts.Endpoints @@ -378,11 +378,14 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints private static string EncryptSecret(string pubKey, byte[] secret) { //Alloc buffer for secret - using IMemoryHandle<byte> buffer = Memory.SafeAlloc<byte>(4096); + using IMemoryHandle<byte> buffer = MemoryUtil.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]); } @@ -391,11 +394,13 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints { //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) { @@ -403,6 +408,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints user.FailedLoginCount(0); return false; } + //Count has been exceeded, and has not timed out yet return true; } diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs index 6ebb024..df20084 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/MFAEndpoint.cs @@ -162,11 +162,13 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints //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); + UnsafeMemoryHandle<byte> outputBuffer = MemoryUtil.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"; @@ -174,6 +176,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints Log.Warn("TOTP secret encryption failed, for requested user {uid}", entity.Session.UserID); break; } + webm.Result = new TOTPUpdateMessage() { Issuer = MultiFactor.IssuerName, @@ -183,6 +186,7 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints //Convert the secret to base64 string to send to client Base64EncSecret = Convert.ToBase64String(outputBuffer.Span[..(int)count]) }; + //set success flag webm.Success = true; } diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/MFA/UserMFAExtensions.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/MFA/UserMFAExtensions.cs index 1ec9953..ee623e2 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/MFA/UserMFAExtensions.cs +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/MFA/UserMFAExtensions.cs @@ -112,7 +112,7 @@ namespace VNLib.Plugins.Essentials.Accounts.MFA return false; } //Alloc buffer with zero o - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(base32Secret.Length, true); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.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); @@ -273,9 +273,11 @@ namespace VNLib.Plugins.Essentials.Accounts.MFA //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); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(base64string.Length, true); + //Recover base64 ERRNO count = VnEncoding.TryFromBase64Chars(base64string, buffer.Span); + //Compare return CryptographicOperations.FixedTimeEquals(signature, buffer.Span[..(int)count]); } @@ -300,8 +302,10 @@ namespace VNLib.Plugins.Essentials.Accounts.MFA //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) { diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/VNLib.Plugins.Essentials.Accounts.csproj b/plugins/VNLib.Plugins.Essentials.Accounts/src/VNLib.Plugins.Essentials.Accounts.csproj index 98ba1ab..f7c7909 100644 --- a/plugins/VNLib.Plugins.Essentials.Accounts/src/VNLib.Plugins.Essentials.Accounts.csproj +++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/VNLib.Plugins.Essentials.Accounts.csproj @@ -5,7 +5,7 @@ <RootNamespace>VNLib.Plugins.Essentials.Accounts</RootNamespace> <Copyright>Copyright © 2022 Vaughn Nugent</Copyright> <Authors>Vaughn Nugent</Authors> - <AssemblyName>Accounts</AssemblyName> + <AssemblyName>Essentials.Accounts</AssemblyName> <PackageId>VNLib.Plugins.Essentials.Accounts</PackageId> <Version>1.0.1.5</Version> @@ -49,7 +49,7 @@ </ItemGroup> <ItemGroup> - <None Update="Accounts.json"> + <None Update="Essentials.Accounts.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> </ItemGroup> diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/RouteComparer.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/RouteComparer.cs index 189da62..e214e14 100644 --- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/RouteComparer.cs +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/RouteComparer.cs @@ -26,7 +26,7 @@ using System.Collections.Generic; using VNLib.Plugins.Essentials.Content.Routing.Model; -using static VNLib.Plugins.Essentials.Accounts.AccountManager; +using static VNLib.Plugins.Essentials.Accounts.AccountUtil; namespace VNLib.Plugins.Essentials.Content.Routing { diff --git a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs index 4dc320a..7620809 100644 --- a/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs +++ b/plugins/VNLib.Plugins.Essentials.Content.Routing/src/Router.cs @@ -36,7 +36,7 @@ using VNLib.Utils.Logging; using VNLib.Plugins.Extensions.Loading.Sql; using VNLib.Plugins.Extensions.Loading.Events; using VNLib.Plugins.Essentials.Content.Routing.Model; -using static VNLib.Plugins.Essentials.Accounts.AccountManager; +using static VNLib.Plugins.Essentials.Accounts.AccountUtil; namespace VNLib.Plugins.Essentials.Content.Routing { diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientAccessTokenState.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientAccessTokenState.cs index e5de597..8a7aea3 100644 --- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientAccessTokenState.cs +++ b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientAccessTokenState.cs @@ -69,7 +69,7 @@ namespace VNLib.Plugins.Essentials.SocialOauth public override int GetHashCode() => Token!.GetHashCode(StringComparison.Ordinal); void ICacheable.Evicted() { - Memory.UnsafeZeroMemory(Nonce); + MemoryUtil.UnsafeZeroMemory(Nonce); } void INonce.ComputeNonce(Span<byte> buffer) diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientRequestState.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientRequestState.cs index 2f35e48..ba369c2 100644 --- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientRequestState.cs +++ b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/ClientRequestState.cs @@ -74,8 +74,8 @@ namespace VNLib.Plugins.Essentials.SocialOauth void ICacheable.Evicted() { //Zero secrets on eviction - Memory.UnsafeZeroMemory(State); - Memory.UnsafeZeroMemory(_rawKey); + MemoryUtil.UnsafeZeroMemory(State); + MemoryUtil.UnsafeZeroMemory(_rawKey); } } }
\ No newline at end of file diff --git a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs index 6815bf3..79e3b1b 100644 --- a/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs +++ b/plugins/VNLib.Plugins.Essentials.SocialOauth/src/SocialOauthBase.cs @@ -238,7 +238,7 @@ namespace VNLib.Plugins.Essentials.SocialOauth void ICacheable.Evicted() { //Erase nonce - Memory.UnsafeZeroMemory(RawNonce); + MemoryUtil.UnsafeZeroMemory(RawNonce); } public override bool Equals(object? obj) @@ -422,11 +422,11 @@ namespace VNLib.Plugins.Essentials.SocialOauth //Generate a new random passowrd incase the user wants to use a local account to log in sometime in the future PrivateString passhash = Config.Passwords.Hash(randomPass); //overwite the password bytes - Memory.InitializeBlock(randomPass.AsSpan()); + MemoryUtil.InitializeBlock(randomPass.AsSpan()); try { //Create the user with the specified email address, minimum privilage level, and an empty password - user = await Config.Users.CreateUserAsync(userLogin.UserId!, userAccount.EmailAddress, AccountManager.MINIMUM_LEVEL, passhash, entity.EventCancellation); + user = await Config.Users.CreateUserAsync(userLogin.UserId!, userAccount.EmailAddress, AccountUtil.MINIMUM_LEVEL, passhash, entity.EventCancellation); //Set active status user.Status = UserStatus.Active; //Store the new profile @@ -570,7 +570,7 @@ namespace VNLib.Plugins.Essentials.SocialOauth private string BuildUrl(string base32Nonce, string pubKey, ReadOnlySpan<char> scheme, ReadOnlySpan<char> redirectAuthority, Encoding enc) { //Char buffer for base32 and url building - using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(8192, true); + using UnsafeMemoryHandle<byte> buffer = MemoryUtil.UnsafeAlloc<byte>(8192, true); //get bin buffer slice Span<byte> binBuffer = buffer.Span[1024..]; @@ -609,7 +609,7 @@ namespace VNLib.Plugins.Essentials.SocialOauth //Encode the url to binary int byteCount = enc.GetBytes(url, encodingBuffer); //Encrypt the binary - ERRNO count = AccountManager.TryEncryptClientData(pubKey, encodingBuffer[..byteCount], in encryptionBuffer); + ERRNO count = AccountUtil.TryEncryptClientData(pubKey, encodingBuffer[..byteCount], in encryptionBuffer); //base64 encode the encrypted return Convert.ToBase64String(encryptionBuffer[0..(int)count]); } |