using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using bitforum.DTOs.Request; using bitforum.DTOs.Response; using bitforum.Helpers; using bitforum.Services; using bitforum.Constants; using bitforum.Models.Log; using System.Web; namespace bitforum.Controllers.API { [ApiController] [Route("api/[controller]")] public class AuthController : ControllerBase { private readonly ILogger _logger; private readonly DefaultDbContext _db; private readonly IJwtAuthService _jwtAuthService; private readonly IMailService _mailService; private ResultDto _result = new ResultDto(); public AuthController(ILogger logger, DefaultDbContext db, IJwtAuthService jwtAuthService, IMailService mailService) { _logger = logger; _db = db; _jwtAuthService = jwtAuthService; _mailService = mailService; } // 인증번호 검증 [HttpPost("verification")] public async Task> Verification([FromBody] VerificationNumberDto request) { try { // 유효성 검사 if (!ModelState.IsValid) { _result.Errors = ModelState.GetErrors(); throw new Exception("인증 거부"); } // 회원 확인 var member = await _db.Member.FirstOrDefaultAsync(c => c.Email == request.Email); if (member is null) { throw new Exception("인증 불가"); } // 인증번호 확인 var isValid = await _mailService.VerifyCodeAsync(request.Email, request.Code, request.Type); if (!isValid) { throw new Exception("인증 실패"); } var cookie = new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.None }; switch (request.Type) { case VerificationType.Registration: cookie.Expires = DateTime.UtcNow.AddMinutes(5); break; case VerificationType.ForgotPassword: cookie.Expires = DateTime.UtcNow.AddMinutes(10); break; } Response.Cookies.Append($"isVerified-{request.Type}", "true", cookie); Console.WriteLine("Set-Cookie Header:", Response.Headers["Set-Cookie"]); _result.Message = "인증 성공"; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } // 인증번호 다시 보내기 [HttpPost("resend-verify")] public async Task> ResendVerify([FromBody] ResendVerifyNumberDto request) { try { // 유효성 검사 if (!ModelState.IsValid) { _result.Errors = ModelState.GetErrors(); throw new Exception("잘못된 접근입니다."); } // 회원 확인 var member = await _db.Member.FirstOrDefaultAsync(c => c.Email == request.Email); if (member is null) { throw new Exception("회원을 조회할 수 없습니다."); } switch (request.Type) { // 회원가입 이메일 인증 메일 발송 case VerificationType.Registration: await _mailService.SendRegisterEmailAsync(member); break; // 비밀번호 재설정 이메일 발송 case VerificationType.ForgotPassword: await _mailService.SendForgotPasswordEmailAsync(member); break; } _result.Message = "인증 재전송 완료"; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } [HttpGet("verify-token")] public ActionResult VerifyAccessToken() { try { if (!Request.Cookies.TryGetValue("accessToken", out var accessToken)) { throw new Exception("잘못된 접근입니다."); } var isValid = _jwtAuthService.ValidateAccessToken(accessToken); if (!isValid) { throw new Exception("`AccessToken`이 유효하지 않습니다."); } var member = _jwtAuthService.GetMemberFromAccessToken(accessToken); if (member is null) { throw new Exception("회원 정보를 찾을 수 없습니다."); } _logger.LogInformation("AccessToken 유효!"); _result.Message = "OK"; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } // 재로그인 토큰 재발급 [HttpGet("refresh-token")] public async Task> RefreshToken() { try { if (!Request.Cookies.TryGetValue("refreshToken", out var refreshToken)) { throw new Exception("잘못된 접근입니다."); } var isValid = await _jwtAuthService.ValidateRefreshToken(refreshToken); if (!isValid) { throw new Exception("확인할 수 없는 요청입니다."); } var member = await _jwtAuthService.GetMemberFromRefreshToken(refreshToken); if (member is null) { throw new Exception("회원 정보를 찾을 수 없습니다."); } // JWT 토큰 발급 await _jwtAuthService.SetRefreshTokenAndCookieAsync(member); _logger.LogInformation("RefreshToken 유효!"); _result.Message = "OK"; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } [HttpGet("verify-email/{token}")] public async Task> VerifyEmail([FromRoute] string token) { try { if (string.IsNullOrEmpty(token)) { _result.Errors = ModelState.GetErrors(); throw new Exception("유효성 검사에 실패했습니다."); } token = HttpUtility.UrlDecode(token); // 인증주소 확인 var tokenData = await _mailService.VerifyTokenAsync(token, VerificationType.ChangedEmail); if (tokenData is null) { throw new Exception("인증 실패"); } if (string.IsNullOrEmpty(tokenData?.AdditionalData?.Email)) { throw new Exception("최소 필수 정보가 누락되었습니다."); } // 회원 조회 var member = await _db.Member.FirstOrDefaultAsync(c => c.Email == tokenData.AdditionalData.Email); if (member is null) { throw new Exception("회원 정보를 찾을 수 없습니다."); } await _db.EmailChangeLog.AddAsync(new EmailChangeLog { MemberID = member.ID, BeforeEmail = member.Email, AfterEmail = tokenData.Email, CreatedAt = DateTime.UtcNow }); member.Email = tokenData.Email; member.LastEmailChangedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); await _mailService.SendChangedEmailAsync(member); // 이메일 변경 완료 메일 발송 _result.Message = "이메일이 변경되었습니다."; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } } }