aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar vnugent <public@vaughnnugent.com>2024-04-08 21:41:38 -0400
committerLibravatar vnugent <public@vaughnnugent.com>2024-04-08 21:41:38 -0400
commit44803e06d1aa45496c04127930aa8897272d42f6 (patch)
tree62dfb4864c45b683ba58ccc41d8277ce5f8ce911
parent1082bd146549a1aff47877bcd28e6be1ce0ef5e9 (diff)
fix: dangling/expired session security check and cookie cleanup
-rw-r--r--lib/vnlib.browser/src/index.ts3
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts.AppData/README.md3
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/VNLib.Plugins.Essentials.Accounts.AppData.csproj2
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs51
-rw-r--r--plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/ClientWebAuthManager.cs13
5 files changed, 45 insertions, 27 deletions
diff --git a/lib/vnlib.browser/src/index.ts b/lib/vnlib.browser/src/index.ts
index 47cd9e9..de0f651 100644
--- a/lib/vnlib.browser/src/index.ts
+++ b/lib/vnlib.browser/src/index.ts
@@ -37,6 +37,9 @@ export * from './social'
//Forward session public exports
export * from './session'
+//App-data
+export * from './app-data'
+
//Axios exports
export { useAxios } from './axios'
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/README.md b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/README.md
index 3e3dac7..0702226 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/README.md
+++ b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/README.md
@@ -1,6 +1,9 @@
# VNLib.Plugins.Essentials.Accounts.AppData
*An Essentials plugin that provides endpoints for web-application synchronized storage such as user preferences.*
+> [!WARNING]
+> This plugin is still in early development and is not yet ready for production use.
+
## Builds
Debug build w/ symbols & xml docs, release builds, NuGet packages, and individually packaged source code are available on my website (link below).
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/VNLib.Plugins.Essentials.Accounts.AppData.csproj b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/VNLib.Plugins.Essentials.Accounts.AppData.csproj
index c86f114..580faba 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/VNLib.Plugins.Essentials.Accounts.AppData.csproj
+++ b/plugins/VNLib.Plugins.Essentials.Accounts.AppData/src/VNLib.Plugins.Essentials.Accounts.AppData.csproj
@@ -16,7 +16,7 @@
<PackageId>VNLib.Plugins.Essentials.Accounts.AppData</PackageId>
<Authors>Vaughn Nugent</Authors>
<Company>Vaughn Nugent</Company>
- <Product>Essentials user accounts an web authentication plugin</Product>
+ <Product>Essentials Account-Based client data storage</Product>
<Copyright>Copyright © 2024 Vaughn Nugent</Copyright>
<PackageProjectUrl>https://www.vaughnnugent.com/resources/software/modules/Plugins.Essentials</PackageProjectUrl>
<RepositoryUrl>https://github.com/VnUgE/Plugins.Essentials/tree/master/plugins/VNLib.Plugins.Essentials.Accounts.AppData</RepositoryUrl>
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
index 2fc2568..4f8bcd3 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/AccountSecProvider.cs
@@ -104,33 +104,40 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
{
//Expired
ExpireCookies(entity, true);
-
+
//Verbose because this is a normal occurance
if (_logger.IsEnabled(LogLevel.Verbose))
{
_logger.Verbose("Session {id} expired", session.SessionID[..8]);
}
}
- else
+ else if (session.IsNew)
{
- //See if the session might be elevated
- if (!ClientWebAuthManager.IsSessionElevated(in session))
- {
- //If the session stored a user-agent, make sure it matches the connection
- if (session.UserAgent != null && !session.UserAgent.Equals(entity.Server.UserAgent, StringComparison.Ordinal))
- {
- _logger.Debug("Denied authorized connection from {ip} because user-agent changed", entity.TrustedRemoteIp);
- return ValueTask.FromResult(FileProcessArgs.Deny);
- }
- }
-
- //If the session is new, or not supposed to be logged in, clear the login cookies if they were set
- if (session.IsNew || string.IsNullOrEmpty(session.Token))
+ //explicitly expire cookies on new sessions
+ ExpireCookies(entity, false);
+ }
+ //See if the session might be elevated
+ else if (ClientWebAuthManager.IsSessionElevated(in session))
+ {
+ //If the session stored a user-agent, make sure it matches the connection
+ if (session.UserAgent != null && !session.UserAgent.Equals(entity.Server.UserAgent, StringComparison.Ordinal))
{
- //Do not force clear cookies (saves bandwidth)
- ExpireCookies(entity, false);
+ _logger.Debug("Denied authorized connection from {ip} because user-agent changed", entity.TrustedRemoteIp);
+ return ValueTask.FromResult(FileProcessArgs.Deny);
}
}
+ else
+ {
+ /*
+ * Attempts to clear client cookies if the session is not elevated
+ * and the client may still have cookies set from a previous session
+ *
+ * Cookies are only sent if the client also sent login cookies to avoid
+ * sending cookies on every request
+ */
+ ExpireCookies(entity, false);
+ }
+
}
//Always continue otherwise
@@ -169,7 +176,7 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
ArgumentNullException.ThrowIfNull(clientInfo.PublicKey, nameof(clientInfo.PublicKey));
ArgumentNullException.ThrowIfNull(clientInfo.ClientId, nameof(clientInfo.ClientId));
- if (!entity.Session.IsSet || entity.Session.IsNew || entity.Session.SessionType != SessionType.Web)
+ if (!IsSessionStateValid(in entity.Session))
{
throw new ArgumentException("The session is no configured for authorization");
}
@@ -189,7 +196,7 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
IClientAuthorization IAccountSecurityProvider.ReAuthorizeClient(HttpEntity entity)
{
//Confirm session is configured
- if (!entity.Session.IsSet || entity.Session.IsNew || entity.Session.SessionType != SessionType.Web)
+ if (!IsSessionStateValid(in entity.Session))
{
throw new InvalidOperationException("The session is not configured for authorization");
}
@@ -219,7 +226,7 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
bool IAccountSecurityProvider.IsClientAuthorized(HttpEntity entity, AuthorzationCheckLevel level)
{
//Session must be loaded and not-new for an authorization to exist
- if(!entity.Session.IsSet || entity.Session.IsNew)
+ if(!IsSessionStateValid(in entity.Session))
{
return false;
}
@@ -249,7 +256,9 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
{
//Use the public key supplied by the csecinfo
return RsaClientDataEncryption.TryEncrypt(entity.PublicKey, data, outputBuffer);
- }
+ }
+
+ private static bool IsSessionStateValid(in SessionInfo session) => session.IsSet && !session.IsNew && session.SessionType == SessionType.Web;
#endregion
diff --git a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/ClientWebAuthManager.cs b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/ClientWebAuthManager.cs
index c4b0c26..2c2058d 100644
--- a/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/ClientWebAuthManager.cs
+++ b/plugins/VNLib.Plugins.Essentials.Accounts/src/SecurityProvider/ClientWebAuthManager.cs
@@ -34,6 +34,7 @@
using System;
using System.Linq;
using System.Text.Json;
+using System.Diagnostics;
using VNLib.Hashing;
using VNLib.Hashing.IdentityUtility;
@@ -286,7 +287,7 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
}
//Get the client signature
- string? base32Sig = GetSigningKey(in entity.Session);
+ string? base32Sig = GetSigningKey(in entity.Session);
if (string.IsNullOrWhiteSpace(base32Sig))
{
@@ -352,11 +353,12 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
private bool VerifyConnectionOTPInternal(HttpEntity entity)
{
- //Get the token from the client header, the client should always sent this
- string? signedMessage = entity.Server.Headers[_config.TokenHeaderName];
+ Debug.Assert(IsSessionValid(in entity.Session), "Session was assumed to be valid for this call");
- //Make sure a session is loaded
- if (!entity.Session.IsSet || entity.Session.IsNew || string.IsNullOrWhiteSpace(signedMessage))
+ //Get the token from the client header, the client should always sent this
+ string? signedMessage = GetOTPHeaderValue(entity);
+
+ if (string.IsNullOrWhiteSpace(signedMessage))
{
return false;
}
@@ -540,6 +542,7 @@ namespace VNLib.Plugins.Essentials.Accounts.SecurityProvider
=> string.IsNullOrWhiteSpace(GetLoginToken(in session)) == false;
private void SetPubkeyCookie(HttpEntity entity, string value) => _pubkeyCookie.SetCookie(entity, value);
+ private string? GetOTPHeaderValue(HttpEntity entity) => entity.Server.Headers[_config.TokenHeaderName];
private static void SetSigningKey(ref readonly SessionInfo session, string? value) => session[PUBLIC_KEY_SIG_KEY_ENTRY] = value!;
private static void SetLoginToken(ref readonly SessionInfo session, string? value) => session[LOGIN_TOKEN_ENTRY] = value!;