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.Auth.Login; 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.Email)) { return Result.Failure(Error.Problem("Auth.EmailRequired", "이메일은 필수입니다.")); } if (string.IsNullOrWhiteSpace(request.Password)) { return Result.Failure(Error.Problem("Auth.PasswordRequired", "비밀번호는 필수입니다.")); } // Member 조회 (비밀번호 검증을 위해 Tracking 모드) var email = request.Email.Trim().ToLower(); var member = await db.Member.FirstOrDefaultAsync(m => m.Email == email, ct); if (member is null) { return Result.Failure(Error.Unauthorized("Auth.InvalidCredentials", "이메일 또는 비밀번호가 올바르지 않습니다.")); } // 비밀번호 검증 if (!member.VerifyPassword(request.Password)) { return Result.Failure(Error.Unauthorized("Auth.InvalidCredentials", "이메일 또는 비밀번호가 올바르지 않습니다.")); } // JWT 토큰 생성 var accessToken = jwtTokenProvider.CreateAccessToken(member.ID, member.Email, member.Name); var refreshToken = jwtTokenProvider.CreateRefreshToken(); var expiresAt = DateTime.UtcNow.AddMinutes(_jwt.AccessTokenExpiration); // RefreshToken 저장 var refreshTokenEntity = RefreshTokenEntity.Create( member.ID, refreshToken, DateTime.UtcNow.AddDays(_jwt.RefreshTokenExpiration) ); await db.RefreshToken.AddAsync(refreshTokenEntity, ct); await db.SaveChangesAsync(ct); return Result.Success(new Response(accessToken, refreshToken, expiresAt)); } }