aboutsummaryrefslogtreecommitdiff
path: root/Libs/VNLib.Plugins.Essentials.Sessions
diff options
context:
space:
mode:
Diffstat (limited to 'Libs/VNLib.Plugins.Essentials.Sessions')
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions/MemorySession.cs28
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionEntrypoint.cs3
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs91
-rw-r--r--Libs/VNLib.Plugins.Essentials.Sessions/SessionIdFactory.cs59
4 files changed, 111 insertions, 70 deletions
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySession.cs b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySession.cs
index 35e2fea..4e09b04 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySession.cs
+++ b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySession.cs
@@ -16,15 +16,16 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory
{
private readonly Dictionary<string, string> DataStorage;
- private readonly MemorySessionStore SessionStore;
+ private readonly Func<IHttpEvent, string, string> OnSessionUpdate;
- public MemorySession(IPAddress ipAddress, MemorySessionStore SessionStore)
+ public MemorySession(string sessionId, IPAddress ipAddress, Func<IHttpEvent, string, string> onSessionUpdate)
{
//Set the initial is-new flag
DataStorage = new Dictionary<string, string>(10);
- this.SessionStore = SessionStore;
+
+ OnSessionUpdate = onSessionUpdate;
//Get new session id
- SessionID = SessionStore.NewSessionID;
+ SessionID = sessionId;
UserIP = ipAddress;
SessionType = SessionType.Web;
Created = DateTimeOffset.UtcNow;
@@ -45,7 +46,8 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory
{
//Clear storage, and regenerate the sessionid
DataStorage.Clear();
- RegenId(state);
+ //store new sessionid
+ SessionID = OnSessionUpdate(state, SessionID);
//Reset ip-address
UserIP = state.Server.GetTrustedIp();
//Update created-time
@@ -58,26 +60,14 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory
else if (Flags.IsSet(REGEN_ID_MSK))
{
//Regen id without modifying the data store
- RegenId(state);
+ SessionID = OnSessionUpdate(state, SessionID);
}
//Clear flags
Flags.ClearAll();
//Memory session always completes
return ValueTask.FromResult<Task?>(null);
}
-
- private void RegenId(IHttpEvent entity)
- {
- //Get a new session-id
- string newId = SessionStore.NewSessionID;
- //Update the cache entry
- SessionStore.UpdateRecord(newId, this);
- //store new sessionid
- SessionID = newId;
- //set cookie
- SessionStore.SetSessionCookie(entity, this);
- }
-
+
protected override Task OnEvictedAsync()
{
//Clear all session data
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionEntrypoint.cs b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionEntrypoint.cs
index f41d384..df5dd59 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionEntrypoint.cs
+++ b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionEntrypoint.cs
@@ -52,6 +52,9 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory
//Schedule garbage collector
_ = plugin.ScheduleInterval(this, TimeSpan.FromMinutes(1));
+
+ //Call cleanup on exit
+ _ = plugin.UnloadToken.RegisterUnobserved(_sessions.Cleanup);
}
Task IIntervalScheduleable.OnIntervalAsync(ILogProvider log, CancellationToken cancellationToken)
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs
index 15c3002..388f998 100644
--- a/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs
+++ b/Libs/VNLib.Plugins.Essentials.Sessions/MemorySessionStore.cs
@@ -3,7 +3,6 @@ using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
-using VNLib.Hashing;
using VNLib.Net.Http;
using VNLib.Net.Sessions;
using VNLib.Utils;
@@ -11,10 +10,10 @@ using VNLib.Utils.Async;
using VNLib.Utils.Extensions;
using VNLib.Plugins.Essentials.Extensions;
+#nullable enable
namespace VNLib.Plugins.Essentials.Sessions.Memory
{
-
/// <summary>
/// An <see cref="ISessionProvider"/> for in-process-memory backed sessions
/// </summary>
@@ -23,11 +22,13 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory
private readonly Dictionary<string, MemorySession> SessionsStore;
internal readonly MemorySessionConfig Config;
+ internal readonly SessionIdFactory IdFactory;
public MemorySessionStore(MemorySessionConfig config)
{
Config = config;
SessionsStore = new(config.MaxAllowedSessions, StringComparer.Ordinal);
+ IdFactory = new(config.SessionIdSizeBytes, config.SessionCookieID, config.SessionTimeout);
}
///<inheritdoc/>
@@ -36,11 +37,11 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory
static ValueTask SessionHandleClosedAsync(ISession session, IHttpEvent ev)
{
- return (session as MemorySession).UpdateAndRelease(true, ev);
+ return (session as MemorySession)!.UpdateAndRelease(true, ev);
}
- //Check for previous session cookie
- if (entity.Server.RequestCookies.TryGetNonEmptyValue(Config.SessionCookieID, out string sessionId))
+ //Try to get the id for the session
+ if (IdFactory.TryGetSessionId(entity, out string? sessionId))
{
//Try to get the old record or evict it
ERRNO result = SessionsStore.TryGetOrEvictRecord(sessionId, out MemorySession session);
@@ -50,63 +51,51 @@ namespace VNLib.Plugins.Essentials.Sessions.Memory
await session.WaitOneAsync(cancellationToken);
return new (session, SessionHandleClosedAsync);
}
- //Continue creating a new session
+ else
+ {
+ //try to cleanup expired records
+ GC();
+ //Make sure there is enough room to add a new session
+ if (SessionsStore.Count >= Config.MaxAllowedSessions)
+ {
+ entity.Server.SetNoCache();
+ //Set 503 when full
+ entity.CloseResponse(System.Net.HttpStatusCode.ServiceUnavailable);
+ //Cannot service new session
+ return new(null, FileProcessArgs.VirtualSkip, null);
+ }
+ //Initialze a new session
+ session = new(sessionId, entity.Server.GetTrustedIp(), UpdateSessionId);
+ //Increment the semaphore
+ (session as IWaitHandle).WaitOne();
+ //store the session in cache while holding semaphore, and set its expiration
+ SessionsStore.StoreRecord(session.SessionID, session, Config.SessionTimeout);
+ //Init new session handle
+ return new (session, SessionHandleClosedAsync);
+ }
}
-
- //Dont service non browsers for new sessions
- if (!entity.Server.IsBrowser())
+ else
{
return SessionHandle.Empty;
}
-
- //try to cleanup expired records
- SessionsStore.CollectRecords();
- //Make sure there is enough room to add a new session
- if (SessionsStore.Count >= Config.MaxAllowedSessions)
- {
- entity.Server.SetNoCache();
- //Set 503 when full
- entity.CloseResponse(System.Net.HttpStatusCode.ServiceUnavailable);
- //Cannot service new session
- return new(null, FileProcessArgs.VirtualSkip, null);
- }
- //Initialze a new session
- MemorySession ms = new(entity.Server.GetTrustedIp(), this);
- //Set session cookie
- SetSessionCookie(entity, ms);
- //Increment the semaphore
- (ms as IWaitHandle).WaitOne();
- //store the session in cache while holding semaphore, and set its expiration
- SessionsStore.StoreRecord(ms.SessionID, ms, Config.SessionTimeout);
- //Init new session handle
- return new SessionHandle(ms, SessionHandleClosedAsync);
}
- /// <summary>
- /// Gets a new unique sessionid for sessions
- /// </summary>
- internal string NewSessionID => RandomHash.GetRandomHex((int)Config.SessionIdSizeBytes);
-
- internal void UpdateRecord(string newSessId, MemorySession session)
+ private string UpdateSessionId(IHttpEvent entity, string oldId)
{
+ //Generate and set a new sessionid
+ string newid = IdFactory.GenerateSessionId(entity);
+ //Aquire lock on cache
lock (SessionsStore)
{
- //Remove old record from the store
- SessionsStore.Remove(session.SessionID);
- //Insert the new session
- SessionsStore.Add(newSessId, session);
+ //Change the cache lookup id
+ if (SessionsStore.Remove(oldId, out MemorySession? session))
+ {
+ SessionsStore.Add(newid, session);
+ }
}
+ return newid;
}
- /// <summary>
- /// Sets a standard session cookie for an entity/connection
- /// </summary>
- /// <param name="entity">The entity to set the cookie on</param>
- /// <param name="session">The session attached to the </param>
- internal void SetSessionCookie(IHttpEvent entity, MemorySession session)
- {
- //Set session cookie
- entity.Server.SetCookie(Config.SessionCookieID, session.SessionID, null, "/", Config.SessionTimeout, CookieSameSite.Lax, true, true);
- }
+
/// <summary>
/// Evicts all sessions from the current store
/// </summary>
diff --git a/Libs/VNLib.Plugins.Essentials.Sessions/SessionIdFactory.cs b/Libs/VNLib.Plugins.Essentials.Sessions/SessionIdFactory.cs
new file mode 100644
index 0000000..ff0608e
--- /dev/null
+++ b/Libs/VNLib.Plugins.Essentials.Sessions/SessionIdFactory.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+
+using VNLib.Hashing;
+using VNLib.Net.Http;
+using VNLib.Plugins.Essentials.Extensions;
+
+#nullable enable
+
+namespace VNLib.Plugins.Essentials.Sessions.Memory
+{
+ internal sealed class SessionIdFactory : ISessionIdFactory
+ {
+ private readonly int IdSize;
+ private readonly string cookieName;
+ private readonly TimeSpan ValidFor;
+
+ public SessionIdFactory(uint idSize, string cookieName, TimeSpan validFor)
+ {
+ IdSize = (int)idSize;
+ this.cookieName = cookieName;
+ ValidFor = validFor;
+ }
+
+ public string GenerateSessionId(IHttpEvent entity)
+ {
+ //Random hex hash
+ string cookie = RandomHash.GetRandomBase32(IdSize);
+
+ //Set the session id cookie
+ entity.Server.SetCookie(cookieName, cookie, ValidFor, secure: true, httpOnly: true);
+
+ //return session-id value from cookie value
+ return cookie;
+ }
+
+ public bool TryGetSessionId(IHttpEvent entity, [NotNullWhen(true)] out string? sessionId)
+ {
+ //Get authorization token and make sure its not too large to cause a buffer overflow
+ if (entity.Server.GetCookie(cookieName, out sessionId))
+ {
+ return true;
+ }
+ //Only add sessions for user-agents
+ else if (entity.Server.IsBrowser())
+ {
+ //Get a new session id
+ sessionId = GenerateSessionId(entity);
+
+ return true;
+ }
+ else
+ {
+ sessionId = null;
+ return false;
+ }
+ }
+ }
+}