feat(domain): add RefreshToken entity with factory methods and IsActive logic
This commit is contained in:
72
src/api/SIGCM2.Domain/Entities/RefreshToken.cs
Normal file
72
src/api/SIGCM2.Domain/Entities/RefreshToken.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
namespace SIGCM2.Domain.Entities;
|
||||
|
||||
public sealed class RefreshToken
|
||||
{
|
||||
public int Id { get; init; }
|
||||
public int UsuarioId { get; init; }
|
||||
public string TokenHash { get; init; } = null!;
|
||||
public Guid FamilyId { get; init; }
|
||||
public DateTime IssuedAt { get; init; }
|
||||
public DateTime ExpiresAt { get; init; }
|
||||
public DateTime? RevokedAt { get; private set; }
|
||||
public int? ReplacedById { get; private set; }
|
||||
public string CreatedByIp { get; init; } = null!;
|
||||
public string? UserAgent { get; init; }
|
||||
|
||||
/// <summary>Factory for a brand-new session (login). Generates a new FamilyId.</summary>
|
||||
public static RefreshToken IssueForNewFamily(
|
||||
int usuarioId,
|
||||
string tokenHash,
|
||||
DateTime now,
|
||||
TimeSpan ttl,
|
||||
string createdByIp,
|
||||
string? userAgent)
|
||||
=> new()
|
||||
{
|
||||
UsuarioId = usuarioId,
|
||||
TokenHash = tokenHash,
|
||||
FamilyId = Guid.NewGuid(),
|
||||
IssuedAt = now,
|
||||
ExpiresAt = now + ttl,
|
||||
CreatedByIp = createdByIp,
|
||||
UserAgent = userAgent,
|
||||
};
|
||||
|
||||
/// <summary>Factory for a rotation. Inherits FamilyId and ExpiresAt (absolute TTL).</summary>
|
||||
public static RefreshToken IssueRotation(
|
||||
RefreshToken previous,
|
||||
string newTokenHash,
|
||||
DateTime now,
|
||||
string createdByIp,
|
||||
string? userAgent)
|
||||
=> new()
|
||||
{
|
||||
UsuarioId = previous.UsuarioId,
|
||||
TokenHash = newTokenHash,
|
||||
FamilyId = previous.FamilyId,
|
||||
IssuedAt = now,
|
||||
ExpiresAt = previous.ExpiresAt, // ABSOLUTE — inherited from original
|
||||
CreatedByIp = createdByIp,
|
||||
UserAgent = userAgent,
|
||||
};
|
||||
|
||||
/// <summary>Returns true if ExpiresAt <= now (expired).</summary>
|
||||
public bool IsExpired(DateTime now) => now >= ExpiresAt;
|
||||
|
||||
/// <summary>Returns true if the token has been explicitly revoked.</summary>
|
||||
public bool IsRevoked => RevokedAt.HasValue;
|
||||
|
||||
/// <summary>Returns true if the token is neither revoked nor expired.</summary>
|
||||
public bool IsActive(DateTime now) => !IsRevoked && !IsExpired(now);
|
||||
|
||||
/// <summary>
|
||||
/// Marks the token as revoked with the given timestamp and optional successor id.
|
||||
/// Should only be called by the repository when reconstructing or persisting state.
|
||||
/// This is NOT the way to revoke in business logic — use the repository SQL methods.
|
||||
/// </summary>
|
||||
public void MarkAsPersistedRevocation(DateTime revokedAt, int? replacedById)
|
||||
{
|
||||
RevokedAt = revokedAt;
|
||||
ReplacedById = replacedById;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user