using System.Text.Json.Serialization; namespace Azaion.Common.Entities; public class User { public Guid Id { get; set; } public string Email { get; set; } = null!; [JsonIgnore] public string PasswordHash { get; set; } = null!; public string? Hardware { get; set; } public RoleEnum Role { get; set; } public DateTime CreatedAt { get; set; } public DateTime? LastLogin { get; set; } public UserConfig? UserConfig { get; set; } = null!; public bool IsEnabled { get; set; } // AZ-537 — consecutive failed-login counter and active lockout deadline. public int FailedLoginCount { get; set; } public DateTime? LockoutUntil { get; set; } // AZ-534 — TOTP-based 2FA. mfa_secret is encrypted at rest; recovery codes are // stored as a JSONB array of { hash, used_at } objects. mfa_last_used_window // is the RFC 6238 time-step counter of the most recently accepted code, // used to reject in-window replays. [JsonIgnore] public bool MfaEnabled { get; set; } [JsonIgnore] public string? MfaSecret { get; set; } [JsonIgnore] public string? MfaRecoveryCodes { get; set; } public DateTime? MfaEnrolledAt { get; set; } [JsonIgnore] public long? MfaLastUsedWindow { get; set; } public static string GetCacheKey(string email) => string.IsNullOrEmpty(email) ? "" : $"{nameof(User)}.{email}"; } public class UserConfig { public UserQueueOffsets? QueueOffsets { get; set; } = new(); } public class UserQueueOffsets { public ulong AnnotationsOffset { get; set; } public ulong AnnotationsConfirmOffset { get; set; } public ulong AnnotationsCommandsOffset { get; set; } }