aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2023-09-09 19:47:51 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2023-09-09 19:47:51 -0400
commit0f8e932e40910bfd7172632b62c61e7dc6801b25 (patch)
tree6b1539231ca10ee5e0fada05ea2672fb83c696f0
parent1fe67b21fd3e0fe9e7063cd03e43e1583fce3ce1 (diff)
session detatch support, cookie man and commands
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs234
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LogoutEndpoint.cs28
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/MFA/UserMFAExtensions.cs10
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs215
4 files changed, 326 insertions, 161 deletions
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs
index f61647f..96b56b4 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/AccountsEntryPoint.cs
@@ -23,20 +23,21 @@
*/
using System;
-using System.Linq;
-using System.Collections.Generic;
+using System.Text.Json;
using System.ComponentModel.Design;
+using VNLib.Utils;
using VNLib.Utils.Memory;
using VNLib.Utils.Logging;
using VNLib.Plugins.Attributes;
using VNLib.Plugins.Essentials.Users;
using VNLib.Plugins.Essentials.Middleware;
+using VNLib.Plugins.Essentials.Accounts.MFA;
using VNLib.Plugins.Essentials.Accounts.Endpoints;
+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.Plugins.Essentials.Accounts.SecurityProvider;
namespace VNLib.Plugins.Essentials.Accounts
{
@@ -45,6 +46,8 @@ namespace VNLib.Plugins.Essentials.Accounts
public override string PluginName => "Essentials.Accounts";
+ private bool SetupMode => PluginConfig.TryGetProperty("setup_mode", out JsonElement el) && el.GetBoolean();
+
private AccountSecProvider? _securityProvider;
[ServiceConfigurator]
@@ -83,7 +86,7 @@ namespace VNLib.Plugins.Essentials.Accounts
if (this.HasConfigForType<PasswordChangeEndpoint>())
{
this.Route<PasswordChangeEndpoint>();
- }
+ }
if (this.HasConfigForType<MFAEndpoint>())
{
@@ -104,6 +107,11 @@ namespace VNLib.Plugins.Essentials.Accounts
Log.Information("Configuring the account security provider service");
}
+ if (SetupMode)
+ {
+ Log.Warn("Setup mode is enabled, this is not recommended for production use");
+ }
+
//Write loaded to log
Log.Information("Plugin loaded");
}
@@ -118,90 +126,115 @@ namespace VNLib.Plugins.Essentials.Accounts
protected override async void ProcessHostCommand(string cmd)
{
- //Only process commands if the plugin is in debug mode
- if (!this.IsDebug())
+ //Only process commands if the plugin is in setup mode
+ if (!SetupMode)
{
return;
}
try
{
+ //Create argument parser
+ ArgumentList args = new(cmd.Split(' '));
+
IUserManager Users = this.GetOrCreateSingleton<UserManager>();
IPasswordHashingProvider Passwords = this.GetOrCreateSingleton<ManagedPasswordHashing>();
- //get args as a list
- List<string> args = cmd.Split(' ').ToList();
+ string? username = args.GetArgument("-u");
+ string? password = args.GetArgument("-p");
+
if (args.Count < 3)
{
- Log.Warn("No command specified");
+ Log.Warn("Not enough arguments, use the help command to view available commands");
+ return;
}
- switch (args[2].ToLower())
+
+ switch (args[2].ToLower(null))
{
+ case "help":
+ const string help = @"
+
+Command help for {name}
+
+Usage: p {name} <command> [options]
+
+Commands:
+ create -u <username> -p <password> Create a new user
+ reset-password -u <username> -p <password> -l <priv level> Reset a user's password
+ delete -u <username> Delete a user
+ disable-mfa -u <username> Disable a user's MFA configuration
+ enable-totp -u <username> -s <base32 secret> Enable TOTP MFA for a user
+ set-privilege -u <username> -l <priv level> Set a user's privilege level
+ help Display this help message
+";
+ Log.Information(help, PluginName);
+ break;
//Create new user
- case "create":
+ case "create":
{
- int uid = args.IndexOf("-u");
- int pwd = args.IndexOf("-p");
- if (uid < 0 || pwd < 0)
+ if (username == null || password == null)
{
Log.Warn("You are missing required argument values. Format 'create -u <username> -p <password>'");
- return;
+ break;
}
- string username = args[uid + 1].Trim();
- string randomUserId = AccountUtil.GetRandomUserId();
- //Password as privatestring DANGEROUS to refs
- using (PrivateString password = (PrivateString)args[pwd + 1].Trim()!)
+
+ string? privilege = args.GetArgument("-l");
+
+ if(!ulong.TryParse(privilege, out ulong privLevel))
{
- //Hash the password
- using PrivateString passHash = Passwords.Hash(password);
- //Create the user
- using IUser user = await Users.CreateUserAsync(randomUserId, username, AccountUtil.MINIMUM_LEVEL, passHash);
- //Set active flag
- user.Status = UserStatus.Active;
- //Set local account
- user.SetAccountOrigin(AccountUtil.LOCAL_ACCOUNT_ORIGIN);
-
- await user.ReleaseAsync();
+ privLevel = AccountUtil.MINIMUM_LEVEL;
}
- Log.Information("Successfully created user {id}", username);
+ //Hash the password
+ using PrivateString passHash = Passwords.Hash(password);
+ //Create the user
+ using IUser user = await Users.CreateUserAsync(username, passHash, privLevel);
+
+ //Set active flag
+ user.Status = UserStatus.Active;
+ //Set local account
+ user.SetAccountOrigin(AccountUtil.LOCAL_ACCOUNT_ORIGIN);
+
+ await user.ReleaseAsync();
+
+ Log.Information("Successfully created user {id}", username);
}
break;
- case "reset":
+ case "reset-password":
{
- int uid = args.IndexOf("-u");
- int pwd = args.IndexOf("-p");
- if (uid < 0 || pwd < 0)
+ if (username == null || password == null)
{
- Log.Warn("You are missing required argument values. Format 'reset -u <username> -p <password>'");
- return;
+ Log.Warn("You are missing required argument values. Format 'create -u <username> -p <password>'");
+ break;
}
- 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)
{
- //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.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();
+ if(username == null)
+ {
+ Log.Warn("You are missing required argument values. Format 'delete -u <username>'");
+ break;
+ }
+
//Get user
- using IUser? user = await Users.GetUserFromEmailAsync(userId);
+ using IUser? user = await Users.GetUserFromEmailAsync(username);
if (user == null)
{
@@ -213,10 +246,99 @@ namespace VNLib.Plugins.Essentials.Accounts
user.Delete();
//Release user
await user.ReleaseAsync();
+
+ Log.Information("Successfully deleted user {id}", username);
+ }
+ break;
+ case "disable-mfa":
+ {
+ if (username == null)
+ {
+ Log.Warn("You are missing required argument values. Format 'disable-mfa -u <username>'");
+ break;
+ }
+
+ //Get user
+ using IUser? user = await Users.GetUserFromEmailAsync(username);
+
+ if (user == null)
+ {
+ Log.Warn("The specified user does not exist");
+ break;
+ }
+
+ user.MFADisable();
+ await user.ReleaseAsync();
+
+ Log.Information("Successfully disabled MFA for {id}", username);
+ }
+ break;
+ case "enable-totp":
+ {
+ string? secret = args.GetArgument("-s");
+
+ if (username == null || secret == null)
+ {
+ Log.Warn("You are missing required argument values. Format 'enable-totp -u <username> -s <secret>'");
+ break;
+ }
+
+ //Get user
+ using IUser? user = await Users.GetUserFromEmailAsync(username);
+
+ if (user == null)
+ {
+ Log.Warn("The specified user does not exist");
+ break;
+ }
+
+ try
+ {
+ byte[] sec = VnEncoding.FromBase32String(secret) ?? throw new Exception("");
+ }
+ catch
+ {
+ Log.Error("Your TOTP secret is not valid base32");
+ break;
+ }
+
+ //Update the totp secret and flush changes
+ user.MFASetTOTPSecret(secret);
+ await user.ReleaseAsync();
+
+ Log.Information("Successfully set TOTP secret for {id}", username);
+ }
+ break;
+ case "set-privilege":
+ {
+ if (username == null)
+ {
+ Log.Warn("You are missing required argument values. Format 'set-privilege -u <username> -l <privilege level>'");
+ break;
+ }
+
+ string? privilege = args.GetArgument("-l");
+ if (!ulong.TryParse(privilege, out ulong privLevel))
+ {
+ Log.Warn("You are missing required argument values. Format 'set-privilege -u <username> -l <privilege level>'");
+ break;
+ }
+
+ //Get user
+ using IUser? user = await Users.GetUserFromEmailAsync(username);
+ if (user == null)
+ {
+ Log.Warn("The specified user does not exist");
+ break;
+ }
+
+ user.Privileges = privLevel;
+ await user.ReleaseAsync();
+ Log.Information("Successfully set privilege level for {id}", username);
}
break;
default:
- Log.Warn("Uknown command");
+ Log.Warn("Uknown command, use the help command");
break;
}
}
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LogoutEndpoint.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LogoutEndpoint.cs
index 9c304cd..e5adb17 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LogoutEndpoint.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/Endpoints/LogoutEndpoint.cs
@@ -31,7 +31,7 @@ using VNLib.Plugins.Essentials.Endpoints;
namespace VNLib.Plugins.Essentials.Accounts.Endpoints
{
[ConfigurationName("logout_endpoint")]
- internal class LogoutEndpoint : ProtectedWebEndpoint
+ internal class LogoutEndpoint : UnprotectedWebEndpoint
{
public LogoutEndpoint(PluginBase pbase, IConfigScope config)
@@ -43,9 +43,29 @@ namespace VNLib.Plugins.Essentials.Accounts.Endpoints
protected override VfReturnType Post(HttpEntity entity)
{
- entity.InvalidateLogin();
- entity.CloseResponse(HttpStatusCode.OK);
- return VfReturnType.VirtualSkip;
+ /*
+ * If a connection is not properly authorized to modify the session
+ * we can invalidate the client by detaching the session. This
+ * should cause the session to remain in tact but the client will
+ * be detached.
+ *
+ * This prevents attacks where connection with just a stolen session
+ * id can cause the client's session to be invalidated.
+ */
+
+ if (entity.IsClientAuthorized(AuthorzationCheckLevel.Critical))
+ {
+ entity.InvalidateLogin();
+ entity.CloseResponse(HttpStatusCode.OK);
+ return VfReturnType.VirtualSkip;
+ }
+ else
+ {
+ //Detatch the session to cause client only invalidation
+ entity.Session.Detach();
+ entity.CloseResponse(HttpStatusCode.OK);
+ return VfReturnType.VirtualSkip;
+ }
}
}
}
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/MFA/UserMFAExtensions.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/MFA/UserMFAExtensions.cs
index 0b52f54..bd434ae 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/MFA/UserMFAExtensions.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/MFA/UserMFAExtensions.cs
@@ -57,6 +57,16 @@ namespace VNLib.Plugins.Essentials.Accounts.MFA
return !(string.IsNullOrWhiteSpace(user[TOTP_KEY_ENTRY]) && string.IsNullOrWhiteSpace(user[WEBAUTHN_KEY_ENTRY]));
}
+ /// <summary>
+ /// Disables all forms of MFA for the current user
+ /// </summary>
+ /// <param name="user"></param>
+ public static void MFADisable(this IUser user)
+ {
+ user[TOTP_KEY_ENTRY] = null!;
+ user[WEBAUTHN_KEY_ENTRY] = null!;
+ }
+
#region totp
/// <summary>
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
index 41c7e93..688d84d 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
@@ -42,8 +42,8 @@ using FluentValidation;
using VNLib.Hashing;
using VNLib.Hashing.IdentityUtility;
-using VNLib.Utils;
using VNLib.Net.Http;
+using VNLib.Utils;
using VNLib.Utils.Memory;
using VNLib.Utils.Extensions;
using VNLib.Plugins.Essentials.Users;
@@ -72,17 +72,20 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
public static readonly RSAEncryptionPadding ClientEncryptonPadding = RSAEncryptionPadding.OaepSHA256;
private readonly AccountSecConfig _config;
+ private readonly CookieHandler _cookieHandler;
public AccountSecProvider(PluginBase plugin)
{
//Setup default config
_config = new();
+ _cookieHandler = new(_config);
}
public AccountSecProvider(PluginBase pbase, IConfigScope config)
{
//Parse config if defined
_config = config.DeserialzeAndValidate<AccountSecConfig>();
+ _cookieHandler = new(_config);
}
/*
@@ -92,8 +95,27 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
///<inheritdoc/>
public ValueTask<HttpMiddlewareResult> ProcessAsync(HttpEntity entity)
{
- //Reconcile cookies on every request we enabled
- ReconcileCookies(entity);
+ //Session must be set and web based for checks
+ if (entity.Session.IsSet && entity.Session.SessionType == SessionType.Web)
+ {
+ //See if the session might be elevated
+ if (!string.IsNullOrWhiteSpace(entity.Session.LoginHash))
+ {
+ //If the session stored a user-agent, make sure it matches the connection
+ if (entity.Session.UserAgent != null && !entity.Session.UserAgent.Equals(entity.Server.UserAgent, StringComparison.Ordinal))
+ {
+ entity.CloseResponse(System.Net.HttpStatusCode.Forbidden);
+ return ValueTask.FromResult(HttpMiddlewareResult.Complete);
+ }
+ }
+
+ //If the session is new, or not supposed to be logged in, clear the login cookies if they were set
+ if (entity.Session.IsNew || string.IsNullOrEmpty(entity.Session.LoginHash) || string.IsNullOrEmpty(entity.Session.Token))
+ {
+ ExpireCookies(entity);
+ }
+ }
+
//Always continue
return ValueTask.FromResult(HttpMiddlewareResult.Continue);
}
@@ -101,6 +123,7 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
#region Interface Impl
+ ///<inheritdoc/>
IClientAuthorization IAccountSecurityProvider.AuthorizeClient(HttpEntity entity, IClientSecInfo clientInfo, IUser user)
{
//Validate client info
@@ -124,7 +147,9 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
* status for the user cookie. This is not required if the user is already
* logged in
*/
- string loginCookie = SetLoginCookie(entity, user.IsLocalAccount());
+
+ string loginCookie = SetLoginCookie(entity);
+ SetClientStatusCookie(entity, user.IsLocalAccount());
//Store the login hash in the user's session
entity.Session.LoginHash = loginCookie;
@@ -145,8 +170,9 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
LoginSecurityString = loginCookie,
SecurityToken = authTokens,
};
- }
+ }
+ ///<inheritdoc/>
void IAccountSecurityProvider.InvalidateLogin(HttpEntity entity)
{
//Client should also destroy the session
@@ -158,6 +184,7 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
entity.Session[PUBLIC_KEY_SIG_KEY_ENTRY] = null!;
}
+ ///<inheritdoc/>
bool IAccountSecurityProvider.IsClientAuthorized(HttpEntity entity, AuthorzationCheckLevel level)
{
//Session must be loaded and not-new for an authorization to exist
@@ -177,6 +204,7 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
};
}
+ ///<inheritdoc/>
IClientAuthorization IAccountSecurityProvider.ReAuthorizeClient(HttpEntity entity)
{
//Confirm session is configured
@@ -214,12 +242,14 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
};
}
+ ///<inheritdoc/>
ERRNO IAccountSecurityProvider.TryEncryptClientData(HttpEntity entity, ReadOnlySpan<byte> data, Span<byte> outputBuffer)
{
//Recover the signed public key, already does session checks
return TryGetPublicKey(entity, out string? pubKey) ? TryEncryptClientData(pubKey, data, outputBuffer) : ERRNO.E_FAIL;
}
+ ///<inheritdoc/>
ERRNO IAccountSecurityProvider.TryEncryptClientData(IClientSecInfo entity, ReadOnlySpan<byte> data, Span<byte> outputBuffer)
{
//Use the public key supplied by the csecinfo
@@ -357,21 +387,6 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
#endregion
#region Cookies
-
- private void ReconcileCookies(HttpEntity entity)
- {
- //Only handle cookies if session is loaded and is a web based session
- if (!entity.Session.IsSet || entity.Session.SessionType != SessionType.Web)
- {
- return;
- }
-
- //If the session is new, or not supposed to be logged in, clear the login cookies if they were set
- if (entity.Session.IsNew || string.IsNullOrEmpty(entity.Session.LoginHash) || string.IsNullOrEmpty(entity.Session.Token))
- {
- ExpireCookies(entity);
- }
- }
private bool VerifyLoginCookie(HttpEntity entity)
{
@@ -421,48 +436,19 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
//Expire login cookie if set
if (entity.Server.RequestCookies.ContainsKey(_config.LoginCookieName))
{
- HttpCookie pkCookie = new(_config.LoginCookieName, string.Empty)
- {
- Domain = _config.CookieDomain,
- Path = _config.CookiePath,
- ValidFor = TimeSpan.Zero,
- SameSite = CookieSameSite.SameSite,
- HttpOnly = true,
- Secure = true
- };
-
- entity.Server.SetCookie(in pkCookie);
+ _cookieHandler.ExpireCookie(entity, _config.LoginCookieName);
}
+
//Expire the LI cookie if set
if (entity.Server.RequestCookies.ContainsKey(_config.ClientStatusCookieName))
{
- HttpCookie pkCookie = new(_config.ClientStatusCookieName, string.Empty)
- {
- Domain = _config.CookieDomain,
- Path = _config.CookiePath,
- ValidFor = TimeSpan.Zero,
- SameSite = CookieSameSite.SameSite,
- HttpOnly = true,
- Secure = true
- };
-
- entity.Server.SetCookie(in pkCookie);
+ _cookieHandler.ExpireCookie(entity, _config.ClientStatusCookieName);
}
+
//Expire pupkey cookie
if (entity.Server.RequestCookies.ContainsKey(_config.PubKeyCookieName))
{
- //Init exipiration cookie
- HttpCookie pkCookie = new(_config.PubKeyCookieName, string.Empty)
- {
- Domain = _config.CookieDomain,
- Path = _config.CookiePath,
- ValidFor = TimeSpan.Zero,
- SameSite = CookieSameSite.SameSite,
- HttpOnly = true,
- Secure = true
- };
-
- entity.Server.SetCookie(in pkCookie);
+ _cookieHandler.ExpireCookie(entity, _config.PubKeyCookieName);
}
}
@@ -535,45 +521,24 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
/// </summary>/
/// <param name="ev">The event to log-in</param>
/// <param name="localAccount">Does the session belong to a local user account</param>
- private string SetLoginCookie(HttpEntity ev, bool? localAccount = null)
+ private string SetLoginCookie(HttpEntity ev)
{
//Get the new random cookie value
string loginString = RandomHash.GetRandomBase64(_config.LoginCookieSize);
- //Configure the login cookie
- HttpCookie loginCookie = new(_config.LoginCookieName, loginString)
- {
- Domain = _config.CookieDomain,
- Path = _config.CookiePath,
- ValidFor = _config.AuthorizationValidFor,
- SameSite = CookieSameSite.SameSite,
- HttpOnly = true,
- Secure = true
- };
+ //Set the cookie for the login key
+ _cookieHandler.SetCookie(ev, _config.LoginCookieName, loginString, true);
- //Set login cookie and session login hash
- ev.Server.SetCookie(in loginCookie);
+ return loginString;
+ }
+ private void SetClientStatusCookie(HttpEntity entity, bool? localAccount = null)
+ {
//If not set get from session storage
- localAccount ??= ev.Session.HasLocalAccount();
-
- //setup status cookie
- HttpCookie statusCookie = new(_config.ClientStatusCookieName, localAccount.Value ? "1" : "2")
- {
- Domain = _config.CookieDomain,
- Path = _config.CookiePath,
- ValidFor = _config.AuthorizationValidFor,
- SameSite = CookieSameSite.SameSite,
- Secure = true,
-
- //Allowed to be http
- HttpOnly = false
- };
-
- //Set the client identifier cookie to a value indicating a local account
- ev.Server.SetCookie(in statusCookie);
+ localAccount ??= entity.Session.HasLocalAccount();
- return loginString;
+ //set client status cookie via handler
+ _cookieHandler.SetCookie(entity, _config.ClientStatusCookieName, localAccount.Value ? "1" : "2", false);
}
#region Client Encryption Key
@@ -620,20 +585,7 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
//Compile the jwt for the cookie value
string jwtValue = jwt.Compile();
- //Setup cookie the same as login cookies
- HttpCookie cookie = new(_config.PubKeyCookieName, jwtValue)
- {
- Domain = _config.CookieDomain,
- Path = _config.CookiePath,
- SameSite = CookieSameSite.SameSite,
- ValidFor = _config.AuthorizationValidFor,
-
- HttpOnly = true,
- Secure = true,
- };
-
- //set the cookie
- entity.Server.SetCookie(in cookie);
+ _cookieHandler.SetCookie(entity, _config.PubKeyCookieName, jwtValue, true);
//Return the signing key
return base32SigningKey;
@@ -650,7 +602,9 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
}
//Get the jwt cookie
- if (!entity.Server.GetCookie(_config.PubKeyCookieName, out string? pubKeyJwt))
+ string? pubKeyJwt = _cookieHandler.GetCookie(entity, _config.PubKeyCookieName);
+
+ if (string.IsNullOrWhiteSpace(pubKeyJwt))
{
return false;
}
@@ -693,7 +647,6 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
return true;
}
-
#endregion
@@ -840,8 +793,68 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
private sealed class Authorization : IClientAuthorization
{
+ ///<inheritdoc/>
public string? LoginSecurityString { get; init; }
+
+ ///<inheritdoc/>
public ClientSecurityToken SecurityToken { get; init; }
}
+
+ record class CookieHandler(AccountSecConfig Config)
+ {
+
+ /// <summary>
+ /// Expires a cookie with the given name
+ /// </summary>
+ /// <param name="entity">The entity to expire the cookie on</param>
+ /// <param name="cookieName">The name of the cookie to expire</param>
+ public void ExpireCookie(HttpEntity entity, string cookieName)
+ {
+ HttpCookie cookie = new(cookieName, string.Empty)
+ {
+ Domain = Config.CookieDomain,
+ Path = Config.CookiePath,
+ ValidFor = TimeSpan.Zero,
+ SameSite = CookieSameSite.SameSite,
+ HttpOnly = true,
+ Secure = true
+ };
+
+ entity.Server.SetCookie(in cookie);
+ }
+
+ /// <summary>
+ /// Sets a cookie with the given name and value
+ /// </summary>
+ /// <param name="entity">The entity to set the cookie on</param>
+ /// <param name="name">The name of the cookie to set</param>
+ /// <param name="value">The value of the cookie to set</param>
+ /// <param name="httpOnly">A value that indicates of the httponly flag should be set on the cookie</param>
+ public void SetCookie(HttpEntity entity, string name, string value, bool httpOnly)
+ {
+ HttpCookie cookie = new(name, value)
+ {
+ Domain = Config.CookieDomain,
+ Path = Config.CookiePath,
+ ValidFor = Config.AuthorizationValidFor,
+ SameSite = CookieSameSite.SameSite,
+ HttpOnly = httpOnly,
+ Secure = true
+ };
+ entity.Server.SetCookie(in cookie);
+ }
+
+ /// <summary>
+ /// Gets the value of a cookie with the given name
+ /// </summary>
+ /// <param name="entity">The entity to get the cookie from</param>
+ /// <param name="name">The name of the cooke to retrieve</param>
+ /// <returns>The cookie value if found, null otherwise</returns>
+ public string? GetCookie(HttpEntity entity, string name)
+ {
+ _ = entity.Server.GetCookie(name, out string? value);
+ return value;
+ }
+ }
}
}