wip session auth

This commit is contained in:
john 2025-05-29 00:39:40 +02:00
parent aa4394fd21
commit 7b6c155a73
23 changed files with 321 additions and 90 deletions

View file

@ -0,0 +1,51 @@
using System.Text;
using static System.Security.Cryptography.RandomNumberGenerator;
namespace Femto.Modules.Auth.Models;
public class LongTermSession
{
private static TimeSpan TokenTimeout { get; } = TimeSpan.FromDays(90);
public int Id { get; private set; }
public string Selector { get; private set; }
public byte[] HashedVerifier { get; private set; }
public DateTimeOffset Expires { get; private set; }
public Guid UserId { get; private set; }
private LongTermSession() {}
public static (LongTermSession, string) Create(Guid userId)
{
var selector = GetString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 12);
var verifier = GetString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 32);
using var sha256 = System.Security.Cryptography.SHA256.Create();
var longTermSession = new LongTermSession
{
Selector = selector,
HashedVerifier = sha256.ComputeHash(Encoding.UTF8.GetBytes(verifier)),
UserId = userId,
Expires = DateTimeOffset.UtcNow + TokenTimeout
};
var rememberMeToken = $"{selector}.{verifier}";
return (longTermSession, rememberMeToken);
}
public bool Validate(string verifier)
{
if (this.Expires < DateTimeOffset.UtcNow)
return false;
using var sha256 = System.Security.Cryptography.SHA256.Create();
var hashedVerifier = sha256.ComputeHash(Encoding.UTF8.GetBytes(verifier));
return hashedVerifier.SequenceEqual(this.HashedVerifier);
}
}

View file

@ -15,7 +15,7 @@ internal class UserIdentity : Entity
public Password? Password { get; private set; }
public ICollection<UserSession> Sessions { get; private set; } = [];
public ICollection<Session> Sessions { get; private set; } = [];
public ICollection<UserRole> Roles { get; private set; } = [];
@ -31,12 +31,6 @@ internal class UserIdentity : Entity
this.AddDomainEvent(new UserWasCreatedEvent(this));
}
public UserIdentity WithPassword(string password)
{
this.SetPassword(password);
return this;
}
public void SetPassword(string password)
{
this.Password = new Password(password);
@ -51,25 +45,6 @@ internal class UserIdentity : Entity
return this.Password.Check(requestPassword);
}
public UserSession PossiblyRefreshSession(string sessionId)
{
var session = this.Sessions.Single(s => s.Id == sessionId);
if (session.ExpiresSoon)
return this.StartNewSession();
return session;
}
public UserSession StartNewSession()
{
var session = UserSession.Create();
this.Sessions.Add(session);
return session;
}
}
public class SetPasswordError(string message, Exception inner) : DomainError(message, inner);

View file

@ -1,21 +1,33 @@
using static System.Security.Cryptography.RandomNumberGenerator;
namespace Femto.Modules.Auth.Models;
internal class UserSession
internal class Session
{
private static TimeSpan SessionTimeout { get; } = TimeSpan.FromMinutes(30);
private static TimeSpan ExpiryBuffer { get; } = TimeSpan.FromMinutes(5);
public string Id { get; private set; }
public Guid UserId { get; private set; }
public DateTimeOffset Expires { get; private set; }
public bool ExpiresSoon => Expires < DateTimeOffset.UtcNow + ExpiryBuffer;
private UserSession() {}
public static UserSession Create()
// true if this session was created with remember me token
// otherwise false
// required to be true to do things like change password etc.
public bool IsStronglyAuthenticated { get; private set; }
public bool ShouldRefresh => this.Expires < DateTimeOffset.UtcNow + ExpiryBuffer;
private Session() { }
public static Session Strong(Guid userId) => new(userId, true);
public static Session Weak(Guid userId) => new(userId, false);
private Session(Guid userId, bool isStrong)
{
return new()
{
Id = Convert.ToBase64String(System.Security.Cryptography.RandomNumberGenerator.GetBytes(32)),
Expires = DateTimeOffset.UtcNow + SessionTimeout
};
this.Id = Convert.ToBase64String(GetBytes(32));
this.UserId = userId;
this.Expires = DateTimeOffset.UtcNow + SessionTimeout;
this.IsStronglyAuthenticated = isStrong;
}
}
}