using SharedKernel; using SharedKernel.Results; using Application.Abstractions.Authentication; using Application.Abstractions.Data; using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using RefreshTokenEntity = Domain.Entities.Members.RefreshToken; namespace Application.Features.Api.Auth.RefreshToken; internal sealed class Handler( IAppDbContext db, IJwtTokenProvider jwtTokenProvider, IOptions options ) : IRequestHandler> { private readonly AppSettings.JwtSection _jwt = options.Value.JWT; public async Task> Handle(Command request, CancellationToken ct) { if (string.IsNullOrWhiteSpace(request.RefreshToken)) { return Result.Failure(Error.Problem("Auth.TokenRequired", "리프레시 토큰은 필수입니다.")); } // 기존 RefreshToken 조회 var existingToken = await db.RefreshToken.Include(x => x.Member).FirstOrDefaultAsync(x => x.Token == request.RefreshToken, ct); if (existingToken is null) { return Result.Failure(Error.NotFound("Auth.TokenNotFound", "유효하지 않은 리프레시 토큰입니다.")); } if (!existingToken.IsActive) { return Result.Failure(Error.Problem("Auth.TokenExpired", "만료되었거나 취소된 리프레시 토큰입니다.")); } if (existingToken.Member is null) { return Result.Failure(Error.NotFound("Auth.MemberNotFound", "회원 정보를 찾을 수 없습니다.")); } var member = existingToken.Member; // 기존 토큰 폐기 existingToken.Revoke(); // 새 토큰 생성 var accessToken = jwtTokenProvider.CreateAccessToken(member.ID, member.Email, member.Name); var newRefreshToken = jwtTokenProvider.CreateRefreshToken(); var expiresAt = DateTime.UtcNow.AddMinutes(_jwt.AccessTokenExpiration); // 새 RefreshToken 저장 var refreshTokenEntity = RefreshTokenEntity.Create( member.ID, newRefreshToken, DateTime.UtcNow.AddDays(_jwt.RefreshTokenExpiration) ); await db.RefreshToken.AddAsync(refreshTokenEntity, ct); await db.SaveChangesAsync(ct); return Result.Success(new Response( accessToken, newRefreshToken, expiresAt )); } }