Handler.cs 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. using SharedKernel;
  2. using SharedKernel.Results;
  3. using Application.Abstractions.Authentication;
  4. using Application.Abstractions.Data;
  5. using Microsoft.EntityFrameworkCore;
  6. using Microsoft.Extensions.Options;
  7. using Microsoft.Extensions.Logging;
  8. using MediatR;
  9. namespace Application.Features.Api.Auth.Login;
  10. internal sealed class Handler(
  11. IAppDbContext db,
  12. IJwtTokenProvider jwtTokenProvider,
  13. IOptions<AppSettings> options,
  14. ILogger<Handler> logger
  15. ) : IRequestHandler<Command, Result<Response>> {
  16. private readonly AppSettings.JwtSection _jwt = options.Value.JWT;
  17. public async Task<Result<Response>> Handle(Command request, CancellationToken ct)
  18. {
  19. // 유효성 검사
  20. if (string.IsNullOrWhiteSpace(request.Email))
  21. {
  22. return Result.Failure<Response>(Error.Problem("Auth.EmailRequired", "이메일은 필수입니다."));
  23. }
  24. if (string.IsNullOrWhiteSpace(request.Password))
  25. {
  26. return Result.Failure<Response>(Error.Problem("Auth.PasswordRequired", "비밀번호는 필수입니다."));
  27. }
  28. // Member 조회 (비밀번호 검증을 위해 Tracking 모드)
  29. var email = request.Email.Trim().ToLower();
  30. var member = await db.Member.FirstOrDefaultAsync(m => m.Email == email, ct);
  31. if (member is null)
  32. {
  33. return Result.Failure<Response>(Error.Unauthorized("Auth.InvalidCredentials", "이메일 또는 비밀번호가 올바르지 않습니다."));
  34. }
  35. // 비밀번호 검증
  36. if (!member.VerifyPassword(request.Password))
  37. {
  38. return Result.Failure<Response>(Error.Unauthorized("Auth.InvalidCredentials", "이메일 또는 비밀번호가 올바르지 않습니다."));
  39. }
  40. // JWT 토큰 생성
  41. var accessToken = jwtTokenProvider.CreateAccessToken(member.ID, member.Email, member.Name);
  42. var refreshToken = jwtTokenProvider.CreateRefreshToken();
  43. var expiresAt = DateTime.UtcNow.AddMinutes(_jwt.AccessTokenExpiration);
  44. // RefreshToken 저장
  45. var refreshTokenEntity = Domain.Entities.Members.RefreshToken.Create(
  46. member.ID,
  47. refreshToken,
  48. DateTime.UtcNow.AddDays(_jwt.RefreshTokenExpiration)
  49. );
  50. await db.RefreshToken.AddAsync(refreshTokenEntity, ct);
  51. // 로그인 횟수 증가
  52. var memberStats = await db.MemberStats.FirstOrDefaultAsync(x => x.MemberID == member.ID, ct);
  53. if (memberStats is not null)
  54. {
  55. memberStats.LoginCount++;
  56. }
  57. await db.SaveChangesAsync(ct);
  58. logger.LogInformation("{0} 로그인", member.Email);
  59. return Result.Success(new Response(accessToken, refreshToken, expiresAt));
  60. }
  61. }