using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Security.Cryptography; using Domain.Entities.Members.ValueObject; using Domain.Entities.Wallets; using NanoidDotNet; /// /// 사용자단 일반 회원 정보 /// namespace Domain.Entities.Members { public class Member { [Key] public int ID { get; private set; } // 회원 등급 정보 [ForeignKey(nameof(MemberGradeID))] public virtual MemberGrade? MemberGrade { get; private set; } // 알림/약관 동의 여부 public virtual MemberApprove MemberApprove { get; private set; } = null!; // 회원 집계 정보 public virtual MemberStats MemberStats { get; private set; } = null!; // 지갑 public virtual Wallet? Wallet { get; private set; } // 채널 public virtual Channel? Channel { get; private set; } public int? MemberGradeID { get; private set; } public string SID { get; private set; } = Nanoid.Generate(size: 8); // 무작위 문자열 public string Email { get; private set; } = default!; public string? Name { get; private set; } public string? FullName { get; private set; } public string? FirstName { get; private set; } public string? LastName { get; private set; } public string? Password { get; private set; } = string.Empty; // 관리자 수정 시 필요 public string? PasswordHash { get; private set; } public string? Intro { get; private set; } public string? Summary { get; private set; } public string? Phone { get; private set; } public DateOnly? Birthday { get; private set; } public Gender? Gender { get; private set; } public string? Thumb { get; private set; } public string? Icon { get; private set; } public bool IsEmailVerified { get; private set; } = false; public bool IsAuthCertified { get; private set; } = false; public bool IsDenied { get; private set; } = false; public bool IsAdmin { get; private set; } = false; public bool IsWithdraw { get; private set; } = false; public bool IsCreator { get; private set; } = false; public string? DeviceInfo { get; private set; } public string? SignupIP { get; private set; } public string? LastLoginIp { get; private set; } public string? IpAddress { get; private set; } public string? UserAgent { get; private set; } public DateTime? LastLoginAt { get; private set; } public DateTime? LastEmailChangedAt { get; private set; } public DateTime? LastNameChangedAt { get; private set; } public DateTime? LastSummaryChangedAt { get; private set; } public DateTime? LastIntroChangedAt { get; private set; } public DateTime? EmailVerifiedAt { get; private set; } public DateTime? AuthCertifiedAt { get; private set; } public DateTime PasswordUpdatedAt { get; private set; } public DateTime CreatedAt { get; private set; } = DateTime.UtcNow; public DateTime? UpdatedAt { get; private set; } public DateTime? DeletedAt { get; private set; } public DateTime? DeniedAt { get; private set; } private Member() { } private Member(string email) { if (string.IsNullOrWhiteSpace(email)) { throw new ArgumentException("Email is required.", nameof(email)); } if (email.Length > 60) { throw new ArgumentOutOfRangeException(nameof(email)); } Email = email; PasswordUpdatedAt = DateTime.UtcNow; } public static Member Create(string email) { return new(email); } public static Member Create(string email, string password) { var member = new Member(email); member.SetPassword(password); return member; } public void SetPassword(string password) { PasswordHash = HashPassword(password); PasswordUpdatedAt = DateTime.UtcNow; } public bool VerifyPassword(string password) { if (string.IsNullOrEmpty(PasswordHash)) { return false; } return VerifyHashedPassword(PasswordHash, password); } public void SetEmail(string email) { Email = email; IsEmailVerified = false; LastEmailChangedAt = DateTime.UtcNow; UpdatedAt = DateTime.UtcNow; } public void SetName(string? name) { Name = name; LastNameChangedAt = DateTime.UtcNow; UpdatedAt = DateTime.UtcNow; } public void SetSummary(string? summary) { Summary = summary; LastSummaryChangedAt = DateTime.UtcNow; UpdatedAt = DateTime.UtcNow; } public void SetIntro(string? intro) { Intro = intro; LastIntroChangedAt = DateTime.UtcNow; UpdatedAt = DateTime.UtcNow; } public void SetThumb(string? thumb) { Thumb = thumb; UpdatedAt = DateTime.UtcNow; } public void SetCreator(bool isCreator) { IsCreator = isCreator; UpdatedAt = DateTime.UtcNow; } public void MarkEmailVerified() { IsEmailVerified = true; EmailVerifiedAt = DateTime.UtcNow; UpdatedAt = DateTime.UtcNow; } public void Withdraw() { IsWithdraw = true; DeletedAt = DateTime.UtcNow; UpdatedAt = DateTime.UtcNow; } // PBKDF2 (RFC 2898) - 순수 C# 비밀번호 해싱 private static string HashPassword(string password) { var salt = RandomNumberGenerator.GetBytes(16); var hash = Rfc2898DeriveBytes.Pbkdf2( password, salt, iterations: 100_000, HashAlgorithmName.SHA256, outputLength: 32 ); // salt + hash → Base64 var combined = new byte[salt.Length + hash.Length]; Buffer.BlockCopy(salt, 0, combined, 0, salt.Length); Buffer.BlockCopy(hash, 0, combined, salt.Length, hash.Length); return Convert.ToBase64String(combined); } // 비밀번호 검증 private static bool VerifyHashedPassword(string hashedPassword, string password) { var combined = Convert.FromBase64String(hashedPassword); if (combined.Length != 48) // 16(salt) + 32(hash) { return false; } var salt = combined[..16]; var storedHash = combined[16..]; var hash = Rfc2898DeriveBytes.Pbkdf2( password, salt, iterations: 100_000, HashAlgorithmName.SHA256, outputLength: 32 ); return CryptographicOperations.FixedTimeEquals(storedHash, hash); } } }