using Microsoft.EntityFrameworkCore; using System.Security.Cryptography; using System.Text.Json; using bitforum.Constants; using bitforum.Models.Account; namespace bitforum.Repository { public interface IEmailVerifyTokenRepository { /// /// 새 토큰 생성 및 저장 /// public Task GenerateTokenAsync(string email, VerificationType type, AdditionalData? additional = null); /// /// 토큰 검증 및 사용 처리 /// public Task ValidateTokenAsync(string token, VerificationType type); /// /// 만료된 토큰 삭제 /// public Task CleanupExpiredTokensAsync(); } public class EmailVerifyTokenRepository : IEmailVerifyTokenRepository { private readonly DefaultDbContext _db; private readonly int _tokenExpirationMinutes = 60; public EmailVerifyTokenRepository(DefaultDbContext db) { _db = db; } public async Task GenerateTokenAsync(string email, VerificationType type, AdditionalData? additional = null) { _db.EmailVerifyToken.RemoveRange( _db.EmailVerifyToken.Where(x => x.Email == email && x.Type == type) ); var token = Convert.ToBase64String(RandomNumberGenerator.GetBytes(64)); var expiration = DateTime.UtcNow.AddMinutes(_tokenExpirationMinutes); _db.EmailVerifyToken.Add(new EmailVerifyToken { Email = email, Token = token, Expiration = expiration, Type = type, Additional = additional == null ? null : JsonSerializer.Serialize(additional) }); await _db.SaveChangesAsync(); return token; } public async Task ValidateTokenAsync(string token, VerificationType type) { var data = await _db.EmailVerifyToken.FirstOrDefaultAsync(x => x.Token == token && x.Type == type && !x.IsVerified && x.Expiration > DateTime.UtcNow); if (data == null) { return null; } data.IsVerified = true; await _db.SaveChangesAsync(); return data; } public async Task CleanupExpiredTokensAsync() { _db.EmailVerifyToken.RemoveRange( _db.EmailVerifyToken.Where(x => x.Expiration < DateTime.UtcNow) ); await _db.SaveChangesAsync(); } } }