feat: add AppUser, AppRole, RefreshToken entities

This commit is contained in:
Chris Chen
2026-05-25 19:02:22 -07:00
parent 5a789fb0c2
commit 40d740d6e0
3 changed files with 58 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
using Microsoft.AspNetCore.Identity;
namespace ROLAC.API.Entities;
public class AppRole : IdentityRole
{
public string? Description { get; set; }
}
+21
View File
@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Identity;
namespace ROLAC.API.Entities;
public class AppUser : IdentityUser
{
/// <summary>Links this login account to a church member record. Null for admin-only accounts.</summary>
public int? MemberId { get; set; }
/// <summary>UI language preference: 'en' or 'zh-TW'.</summary>
public string LanguagePreference { get; set; } = "en";
/// <summary>False = account suspended (returns 403 even with correct password).</summary>
public bool IsActive { get; set; } = true;
public DateTime? LastLoginAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public ICollection<RefreshToken> RefreshTokens { get; set; } = new List<RefreshToken>();
}
+29
View File
@@ -0,0 +1,29 @@
namespace ROLAC.API.Entities;
public class RefreshToken
{
public int Id { get; set; }
public string UserId { get; set; } = null!;
public AppUser User { get; set; } = null!;
/// <summary>SHA-256 hex of the raw token sent to the client. Never store raw tokens.</summary>
public string TokenHash { get; set; } = null!;
public DateTime ExpiresAt { get; set; }
public DateTime CreatedAt { get; set; }
/// <summary>Set when this token is revoked (logout or rotation).</summary>
public DateTime? RevokedAt { get; set; }
/// <summary>Points to the hash of the token that replaced this one during rotation.</summary>
public string? ReplacedByHash { get; set; }
public string? DeviceInfo { get; set; }
public string? IpAddress { get; set; }
// Computed helpers — NOT mapped to DB columns (ignored in OnModelCreating)
public bool IsExpired => DateTime.UtcNow >= ExpiresAt;
public bool IsRevoked => RevokedAt.HasValue;
public bool IsActive => !IsRevoked && !IsExpired;
}