wip session auth
This commit is contained in:
parent
aa4394fd21
commit
7b6c155a73
23 changed files with 321 additions and 90 deletions
51
Femto.Modules.Auth/Models/LongTermSession.cs
Normal file
51
Femto.Modules.Auth/Models/LongTermSession.cs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue