using System.Diagnostics.CodeAnalysis; using VNLib.Hashing; using VNLib.Net.Http; using VNLib.Utils.Memory; using VNLib.Utils.Extensions; using VNLib.Plugins.Essentials.Extensions; namespace VNLib.Plugins.Essentials.Sessions.VNCache { /// /// implementation, using /// http cookies as session id storage /// internal sealed class WebSessionIdFactoryImpl : IWebSessionIdFactory { public TimeSpan ValidFor { get; } public string GenerateSessionId(IHttpEvent entity) { //Random hex hash string cookie = RandomHash.GetRandomBase32(_tokenSize); //Set the session id cookie entity.Server.SetCookie(SessionCookieName, cookie, ValidFor, secure: true, httpOnly: true); //return session-id value from cookie value return ComputeSessionIdFromCookie(cookie); } bool ISessionIdFactory.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(SessionCookieName, out string? cookie) && (cookie.Length + SessionIdPrefix.Length) <= _bufferSize) { //Compute session id from token sessionId = ComputeSessionIdFromCookie(cookie); 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; } } private readonly string SessionCookieName; private readonly string SessionIdPrefix; private readonly int _bufferSize; private readonly int _tokenSize; /// /// Initialzies a new web session Id factory /// /// The size of the cookie in bytes /// The name of the session cookie /// The session-id internal prefix /// The time the session cookie is valid for public WebSessionIdFactoryImpl(uint cookieSize, string sessionCookieName, string sessionIdPrefix, TimeSpan validFor) { ValidFor = validFor; SessionCookieName = sessionCookieName; SessionIdPrefix = sessionIdPrefix; _tokenSize = (int)cookieSize; //Calc buffer size _bufferSize = Math.Max(32, ((int)cookieSize * 3) + sessionIdPrefix.Length); } private string ComputeSessionIdFromCookie(string sessionId) { //Buffer to copy data to using UnsafeMemoryHandle buffer = Memory.UnsafeAlloc(_bufferSize, true); //Writer to accumulate data ForwardOnlyWriter writer = new(buffer.Span); //Append prefix and session id writer.Append(SessionIdPrefix); writer.Append(sessionId); //Compute base64 hash of token and return ManagedHash.ComputeBase64Hash(writer.AsSpan(), HashAlg.SHA256); } } }