using Microsoft.AspNetCore.Mvc; using bitforum.DTOs.Request; using bitforum.DTOs.Response; using bitforum.Constants; using bitforum.Helpers; using bitforum.Services; using bitforum.Repository; namespace bitforum.Controllers.API.Auth { [ApiController] [Route("api/auth")] public class PasswordController : ControllerBase { private readonly ILogger _logger; private readonly DefaultDbContext _db; private readonly IConfigService _configService; private readonly IMailService _mailService; private readonly IMemberRepository _memberRepository; private ResultDto _result = new ResultDto(); public PasswordController(ILogger logger, DefaultDbContext db, IConfigService configService, IMailService mailService, IEmailVerifyNumberRepository emailVerifyNumberRepository, IMemberRepository memberRepository) { _logger = logger; _db = db; _configService = configService; _mailService = mailService; _memberRepository = memberRepository; } // 비밀번호 재설정 요청 [HttpGet("forgot-password/{email}")] public async Task> ForgotPassword([FromRoute] string email) { try { if (string.IsNullOrEmpty(email)) { throw new Exception("잘못된 접근입니다."); } // 이메일 거부 목록 확인 if (_configService.IsDeniedEmail(email)) { throw new Exception($"`{email}`은 조회할 수 없습니다."); } // 회원 확인 var member = await _memberRepository.FindMemberByEmail(email); if (member is null) { throw new Exception("회원 정보를 찾을 수 없습니다."); } // 차단 회원은 거부 if (member.IsDenied) { throw new Exception("권한이 없습니다."); } await _mailService.SendForgotPasswordEmailAsync(member); _result.Message = "이메일 인증 확인 후 비밀번호를 변경할 수 있습니다."; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } // 비밀번호 변경 처리 [HttpPost("reset-password")] public async Task> ResetPassword([FromBody] ResetPasswordDto request) { try { var cookieName = $"isVerified-{VerificationType.ForgotPassword}"; if (!Request.Cookies.ContainsKey(cookieName) || Request.Cookies[cookieName] != "true") { throw new Exception("사전 인증을 먼저 수행하세요."); } // 유효성 검사 if (!ModelState.IsValid) { _result.Errors = ModelState.GetErrors(); throw new Exception("유효성 검사에 실패하였습니다."); } // 회원 확인 var member = await _memberRepository.FindMemberByEmail(request.Email); if (member is null) { throw new Exception("회원 정보를 찾을 수 없습니다."); } var newPassword = BCrypt.Net.BCrypt.HashPassword(request.Password); if (member.Password == newPassword) { throw new Exception("기존 비밀번호와 동일합니다."); } var isValid = _configService.IsPasswordPolicyValid(request.Password); if (!isValid) { string message = ""; int minLengthConfig = Config.Register.PasswordMinLength ?? 6; int uppercaseLengthConfig = Config.Register.PasswordUppercaseLength ?? 0; int numbersLengthConfig = Config.Register.PasswordNumbersLength ?? 0; int specialCharsLengthConfig = Config.Register.PasswordSpecialcharsLength ?? 0; // 위에 데이터로 안내 문구 만들어줘 if (minLengthConfig > 0) { message += $"최소 {minLengthConfig}자 이상, "; } if (uppercaseLengthConfig > 0) { message += $"대문자 {uppercaseLengthConfig}자 이상, "; } if (numbersLengthConfig > 0) { message += $"숫자 {numbersLengthConfig}자 이상, "; } if (specialCharsLengthConfig > 0) { message += $"특수문자 {specialCharsLengthConfig}자 이상, "; } message = message.TrimEnd(',', ' '); throw new Exception($"입력하신 비밀번호는 사용하실 수 없습니다. 다음 조건을 충족해주세요.\n\n{message}"); } member.Password = newPassword; member.PasswordUpdatedAt = DateTime.UtcNow; await _db.SaveChangesAsync(); await _mailService.SendChangedPasswordEmailAsync(member); // 쿠키 삭제 Response.Cookies.Delete(cookieName); _result.Message = "비밀번호가 변경되었습니다."; } catch (Exception e) { _logger.LogError(e, e.Message); _result.Ok = false; _result.Status = StatusCodes.Status400BadRequest; _result.Message = e.Message; } return _result; } } }