From 859eabd83c445381c4f5ab10a22a5388a1abfde7 Mon Sep 17 00:00:00 2001 From: SEAN Date: Wed, 25 Feb 2026 10:08:45 +0900 Subject: [PATCH] =?UTF-8?q?improvement:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=B6=84=EA=B8=B0=20=EA=B3=84=EC=95=BD=20=ED=99=95=EC=9E=A5=20?= =?UTF-8?q?(#177)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LoginResponseDto에 nextAction, emailVerified, verifySessionId, emailSent 추가 - 미인증 유저 로그인 시 verify session/인증코드 생성 + 메일 발송 - Swagger Description에 분기 설명 추가 Closes #177 --- SPMS.API/Controllers/AuthController.cs | 4 +- .../DTOs/Auth/LoginResponseDto.cs | 12 ++++++ SPMS.Application/Services/AuthService.cs | 41 ++++++++++++++++++- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/SPMS.API/Controllers/AuthController.cs b/SPMS.API/Controllers/AuthController.cs index 026d674..814d752 100644 --- a/SPMS.API/Controllers/AuthController.cs +++ b/SPMS.API/Controllers/AuthController.cs @@ -51,7 +51,9 @@ public class AuthController : ControllerBase [AllowAnonymous] [SwaggerOperation( Summary = "관리자 로그인", - Description = "이메일과 비밀번호로 로그인하여 JWT 토큰을 발급받습니다.")] + Description = "이메일과 비밀번호로 로그인하여 JWT 토큰을 발급받습니다. " + + "응답의 nextAction으로 화면 분기: GO_DASHBOARD(대시보드), VERIFY_EMAIL(이메일 인증 필요). " + + "미인증 유저는 verifySessionId와 emailSent가 함께 반환됩니다.")] [SwaggerResponse(200, "로그인 성공", typeof(ApiResponse))] [SwaggerResponse(401, "로그인 실패")] public async Task LoginAsync([FromBody] LoginRequestDto request) diff --git a/SPMS.Application/DTOs/Auth/LoginResponseDto.cs b/SPMS.Application/DTOs/Auth/LoginResponseDto.cs index c5e8b51..0738fc5 100644 --- a/SPMS.Application/DTOs/Auth/LoginResponseDto.cs +++ b/SPMS.Application/DTOs/Auth/LoginResponseDto.cs @@ -13,6 +13,18 @@ public class LoginResponseDto [JsonPropertyName("expires_in")] public int ExpiresIn { get; set; } + [JsonPropertyName("next_action")] + public string NextAction { get; set; } = string.Empty; + + [JsonPropertyName("email_verified")] + public bool EmailVerified { get; set; } + + [JsonPropertyName("verify_session_id")] + public string? VerifySessionId { get; set; } + + [JsonPropertyName("email_sent")] + public bool? EmailSent { get; set; } + [JsonPropertyName("admin")] public AdminInfoDto? Admin { get; set; } } diff --git a/SPMS.Application/Services/AuthService.cs b/SPMS.Application/Services/AuthService.cs index b08e535..9c509cc 100644 --- a/SPMS.Application/Services/AuthService.cs +++ b/SPMS.Application/Services/AuthService.cs @@ -155,12 +155,51 @@ public class AuthService : IAuthService _adminRepository.Update(admin); await _unitOfWork.SaveChangesAsync(); - // 6. 응답 반환 + // 6. 분기 판정 + verify session 생성 + var nextAction = "GO_DASHBOARD"; + string? verifySessionId = null; + bool? emailSent = null; + + if (!admin.EmailVerified) + { + nextAction = "VERIFY_EMAIL"; + + // 인증코드 생성/저장 + var verificationCode = Random.Shared.Next(100000, 999999).ToString(); + await _tokenStore.StoreAsync( + $"email_verify:{admin.Email}", + verificationCode, + TimeSpan.FromHours(1)); + + // Verify Session 생성 + verifySessionId = Guid.NewGuid().ToString("N"); + await _tokenStore.StoreAsync( + $"verify_session:{verifySessionId}", + admin.Email, + TimeSpan.FromHours(1)); + + // 메일 발송 (실패해도 로그인은 유지) + emailSent = true; + try + { + await _emailService.SendVerificationCodeAsync(admin.Email, verificationCode); + } + catch + { + emailSent = false; + } + } + + // 7. 응답 반환 return new LoginResponseDto { AccessToken = accessToken, RefreshToken = refreshToken, ExpiresIn = _jwtSettings.ExpiryMinutes * 60, + NextAction = nextAction, + EmailVerified = admin.EmailVerified, + VerifySessionId = verifySessionId, + EmailSent = emailSent, Admin = new AdminInfoDto { AdminCode = admin.AdminCode,