using System.ComponentModel.DataAnnotations.Schema; 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); private static TimeSpan RefreshBuffer { get; } = TimeSpan.FromDays(5); 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; } public bool IsInvalidated { get; private set; } [NotMapped] public bool ExpiresSoon => this.Expires < DateTimeOffset.UtcNow + RefreshBuffer; 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 = ComputeHash(verifier), UserId = userId, Expires = DateTimeOffset.UtcNow + TokenTimeout, }; return (longTermSession, verifier); } public bool CheckVerifier(string verifier) { if (this.IsInvalidated) return false; if (this.Expires < DateTimeOffset.UtcNow) return false; return ComputeHash(verifier).SequenceEqual(this.HashedVerifier); } private static byte[] ComputeHash(string verifier) { using var sha256 = System.Security.Cryptography.SHA256.Create(); var hashedVerifier = sha256.ComputeHash(Encoding.UTF8.GetBytes(verifier)); return hashedVerifier; } public void Invalidate() { this.IsInvalidated = true; } }