diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/FindPasswordRequest.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/FindPasswordRequest.java index 340c6247..8edc7f76 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/FindPasswordRequest.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/FindPasswordRequest.java @@ -9,4 +9,5 @@ @Schema(description = "비밀번호 찾기 요청") public class FindPasswordRequest { private String email; + private String redirectUrl; } diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/ResetPasswordRequest.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/ResetPasswordRequest.java new file mode 100644 index 00000000..e3282136 --- /dev/null +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/ResetPasswordRequest.java @@ -0,0 +1,12 @@ +package org.ezcode.codetest.application.usermanagement.auth.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ResetPasswordRequest { + private String email; + private String newPassword; + private String token; +} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/SendEmailRequest.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/SendEmailRequest.java new file mode 100644 index 00000000..006593d3 --- /dev/null +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/request/SendEmailRequest.java @@ -0,0 +1,13 @@ +package org.ezcode.codetest.application.usermanagement.auth.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "이메일 전송 요청") +public class SendEmailRequest { + @Schema(description = "인증 완료 후 리다이렉트할 URL") + private String redirectUrl; +} diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SendEmailCodeResponse.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SendEmailResponse.java similarity index 64% rename from src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SendEmailCodeResponse.java rename to src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SendEmailResponse.java index 34a86453..1a815e35 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SendEmailCodeResponse.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/dto/response/SendEmailResponse.java @@ -3,11 +3,11 @@ import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "인증코드 전송 성공") -public record SendEmailCodeResponse( +public record SendEmailResponse( @Schema(description = "인증코드 전송 성공 메세지") String message ) { - public static SendEmailCodeResponse from(String message) { - return new SendEmailCodeResponse(message); + public static SendEmailResponse from(String message) { + return new SendEmailResponse(message); } } diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java index d4b0683c..0e1e453e 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/auth/service/AuthService.java @@ -4,11 +4,11 @@ import java.util.concurrent.TimeUnit; import org.ezcode.codetest.application.usermanagement.auth.dto.request.FindPasswordRequest; -import org.ezcode.codetest.application.usermanagement.auth.dto.request.VerifyEmailCodeRequest; +import org.ezcode.codetest.application.usermanagement.auth.dto.request.ResetPasswordRequest; import org.ezcode.codetest.application.usermanagement.auth.dto.response.FindPasswordResponse; import org.ezcode.codetest.application.usermanagement.auth.dto.response.RefreshTokenResponse; import org.ezcode.codetest.application.usermanagement.auth.dto.request.SigninRequest; -import org.ezcode.codetest.application.usermanagement.auth.dto.response.SendEmailCodeResponse; +import org.ezcode.codetest.application.usermanagement.auth.dto.response.SendEmailResponse; import org.ezcode.codetest.application.usermanagement.auth.dto.response.SigninResponse; import org.ezcode.codetest.application.usermanagement.auth.dto.request.SignupRequest; import org.ezcode.codetest.application.usermanagement.auth.dto.response.SignupResponse; @@ -100,9 +100,9 @@ private void updateExistingUser(User existUser, String encodedPassword) { } @Transactional - public SendEmailCodeResponse sendEmailCode(Long userId, String email) { - mailService.sendButtonMail(userId, email); - return SendEmailCodeResponse.from("인증 코드를 전송했습니다."); + public SendEmailResponse sendEmailCode(Long userId, String email, String redirectUrl) { + mailService.sendButtonMail(userId, email, redirectUrl); + return SendEmailResponse.from("인증 코드를 전송했습니다."); } @Transactional @@ -226,20 +226,22 @@ public FindPasswordResponse findPassword(FindPasswordRequest request) { throw new AuthException(AuthExceptionCode.USER_NOT_FOUND); } - mailService.sendPasswordMail(user.getId(), request.getEmail()); + mailService.sendPasswordMail(user.getId(), request.getEmail(), request.getRedirectUrl()); return FindPasswordResponse.from("이메일로 전송되었습니다."); } //메일로 받은 링크를 통해 비번 변경 - public FindPasswordResponse changePasswordByEmail(String email, String key) { + public FindPasswordResponse resetPassword(ResetPasswordRequest request) { - User user = userDomainService.getUserByEmail(email); + User user = userDomainService.getUserByEmail(request.getEmail()); - boolean isMatch = mailService.verifyCode(user.getId(), key); + boolean isMatch = mailService.verifyPasswordCode(user.getId(), request.getToken()); if (isMatch){ - return FindPasswordResponse.from("인증되었습니다"); + String encodedPassword = userDomainService.encodePassword(request.getNewPassword()); + user.modifyPassword(encodedPassword); + return FindPasswordResponse.from("비밀번호가 변경되었습니다."); } else { throw new UserException(UserExceptionCode.NOT_MATCH_CODE); } diff --git a/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java b/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java index 01bd4720..c073235c 100644 --- a/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java +++ b/src/main/java/org/ezcode/codetest/application/usermanagement/user/service/UserService.java @@ -4,9 +4,6 @@ import java.time.temporal.ChronoUnit; import java.util.List; -import org.ezcode.codetest.application.usermanagement.auth.dto.request.VerifyEmailCodeRequest; -import org.ezcode.codetest.application.usermanagement.auth.dto.response.SendEmailCodeResponse; -import org.ezcode.codetest.application.usermanagement.auth.dto.response.VerifyEmailCodeResponse; import org.ezcode.codetest.application.usermanagement.user.model.UsersByWeek; import org.ezcode.codetest.domain.submission.dto.WeeklySolveCount; import org.ezcode.codetest.application.usermanagement.user.dto.request.ChangeUserPasswordRequest; @@ -16,9 +13,7 @@ import org.ezcode.codetest.application.usermanagement.user.dto.response.WithdrawUserResponse; import org.ezcode.codetest.domain.submission.service.SubmissionDomainService; import org.ezcode.codetest.domain.user.exception.AuthException; -import org.ezcode.codetest.domain.user.exception.UserException; import org.ezcode.codetest.domain.user.exception.code.AuthExceptionCode; -import org.ezcode.codetest.domain.user.exception.code.UserExceptionCode; import org.ezcode.codetest.domain.user.model.entity.AuthUser; import org.ezcode.codetest.domain.user.model.entity.User; import org.ezcode.codetest.domain.user.model.enums.AuthType; @@ -29,7 +24,6 @@ import org.springframework.transaction.annotation.Transactional; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java b/src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java index a12ae245..7338eea9 100644 --- a/src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java +++ b/src/main/java/org/ezcode/codetest/common/security/config/SecurityConfig.java @@ -12,6 +12,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.HttpMethod; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -71,6 +72,10 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers(new DispatcherTypeRequestMatcher(DispatcherType.ASYNC)).permitAll() .requestMatchers( SecurityPath.PUBLIC_PATH).permitAll() + .requestMatchers(HttpMethod.GET, + "/api/problems/*/discussions", + "/api/problems/{problemId}/discussions/{discussionId}/replies", + "/api/problems/{problemId}/discussions/{discussionId}/replies/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") //어드민 권한 필요 (문제 생성, 관리 등) .anyRequest().authenticated() //나머지는 일반 인증 ) diff --git a/src/main/java/org/ezcode/codetest/domain/user/service/MailService.java b/src/main/java/org/ezcode/codetest/domain/user/service/MailService.java index a9d7f80f..f02024bf 100644 --- a/src/main/java/org/ezcode/codetest/domain/user/service/MailService.java +++ b/src/main/java/org/ezcode/codetest/domain/user/service/MailService.java @@ -30,17 +30,17 @@ public void sendCodeMail(Long userId, String email) { javaMailSender.send(message); } - public void sendButtonMail(Long userId, String email) { - MimeMessage message = CreateButtonMail(userId, email); + public void sendButtonMail(Long userId, String email, String redirectUrl) { + MimeMessage message = CreateButtonMail(userId, email, redirectUrl); javaMailSender.send(message); } - public void sendPasswordMail(Long userId, String email) { - MimeMessage message = CreatePasswordMail(userId, email); + public void sendPasswordMail(Long userId, String email, String redirectUrl) { + MimeMessage message = CreatePasswordMail(userId, email, redirectUrl); javaMailSender.send(message); } - public MimeMessage CreateButtonMail(Long userId, String email) { + public MimeMessage CreateButtonMail(Long userId, String email, String redirectUrl) { MimeMessage message = javaMailSender.createMimeMessage(); String key = createNumber(userId); //radis에 유저id&코드로 저장 (10분) @@ -51,7 +51,7 @@ public MimeMessage CreateButtonMail(Long userId, String email) { String body = ""; body += "

" + "아래 버튼을 클릭하여 이메일 인증을 완료해 주세요" + "

"; // 이메일 버튼 - body += "이메일 인증 확인"; + body += "이메일 인증 확인"; body += "

" + "감사합니다." + "

"; message.setText(body,"UTF-8", "html"); } catch (MessagingException e) { @@ -61,7 +61,7 @@ public MimeMessage CreateButtonMail(Long userId, String email) { return message; } - public MimeMessage CreatePasswordMail(Long userId, String email){ + public MimeMessage CreatePasswordMail(Long userId, String email, String redirectUrl){ MimeMessage message = javaMailSender.createMimeMessage(); String redisKey = "PASSWORD_KEY:" + userId; @@ -81,7 +81,7 @@ public MimeMessage CreatePasswordMail(Long userId, String email){ String body = ""; body += "

" + "아래 버튼을 클릭하여 비밀번호 변경을 완료해 주세요" + "

"; // 이메일 버튼 - body += "비밀번호 변경하기"; + body += "비밀번호 변경하기"; body += "

" + "감사합니다." + "

"; message.setText(body,"UTF-8", "html"); } catch (MessagingException e) { @@ -155,4 +155,22 @@ public boolean verifyCode(Long userId, String inputCode) { return isMatch; } + // 비밀번호 검증 + public boolean verifyPasswordCode(Long userId, String inputCode) { + String key = "PASSWORD_KEY:" + userId; + String storedCode = redisTemplate.opsForValue().get(key); + + if (storedCode == null) { + log.warn("비밀번호 재설정 코드가 없음 : {}", userId); + return false; + } + + boolean isMatch = inputCode != null && inputCode.trim().equals(storedCode); + + if (isMatch) { + redisTemplate.delete(key); + } + return isMatch; + } + } diff --git a/src/main/java/org/ezcode/codetest/presentation/usermanagement/AuthController.java b/src/main/java/org/ezcode/codetest/presentation/usermanagement/AuthController.java index d322170b..6fc2b6c4 100644 --- a/src/main/java/org/ezcode/codetest/presentation/usermanagement/AuthController.java +++ b/src/main/java/org/ezcode/codetest/presentation/usermanagement/AuthController.java @@ -3,11 +3,12 @@ import java.util.Optional; import org.ezcode.codetest.application.usermanagement.auth.dto.request.FindPasswordRequest; -import org.ezcode.codetest.application.usermanagement.auth.dto.request.VerifyEmailCodeRequest; +import org.ezcode.codetest.application.usermanagement.auth.dto.request.ResetPasswordRequest; +import org.ezcode.codetest.application.usermanagement.auth.dto.request.SendEmailRequest; import org.ezcode.codetest.application.usermanagement.auth.dto.response.FindPasswordResponse; import org.ezcode.codetest.application.usermanagement.auth.dto.response.RefreshTokenResponse; import org.ezcode.codetest.application.usermanagement.auth.dto.request.SigninRequest; -import org.ezcode.codetest.application.usermanagement.auth.dto.response.SendEmailCodeResponse; +import org.ezcode.codetest.application.usermanagement.auth.dto.response.SendEmailResponse; import org.ezcode.codetest.application.usermanagement.auth.dto.response.SigninResponse; import org.ezcode.codetest.application.usermanagement.auth.dto.request.SignupRequest; import org.ezcode.codetest.application.usermanagement.auth.dto.response.SignupResponse; @@ -22,11 +23,9 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import io.swagger.v3.oas.annotations.Operation; @@ -86,10 +85,11 @@ public ResponseEntity refresh(HttpServletRequest request) @Operation(summary = "이메일 인증 코드 전송", description = "현재 로그인된 회원의 이메일로 인증 코드를 전송합니다.") @PostMapping("/email/send") - public ResponseEntity sendMailCode( - @AuthenticationPrincipal AuthUser authUser + public ResponseEntity sendMailCode( + @AuthenticationPrincipal AuthUser authUser, + @RequestBody SendEmailRequest request ){ - return ResponseEntity.status(HttpStatus.CREATED).body(authService.sendEmailCode(authUser.getId(), authUser.getEmail())); + return ResponseEntity.status(HttpStatus.CREATED).body(authService.sendEmailCode(authUser.getId(), authUser.getEmail(), request.getRedirectUrl())); } //이메일에서 버튼 클릭하면 자동으로 연결 @@ -102,7 +102,7 @@ public ResponseEntity verifyEmailCode( return ResponseEntity.status(HttpStatus.OK).body(authService.verifyEmailCode(email, key)); } - + //미완성 -> 메일 전송까지는 성공 @PostMapping("/auth/find-password") public ResponseEntity findPassword( @RequestBody FindPasswordRequest request @@ -110,11 +110,10 @@ public ResponseEntity findPassword( return ResponseEntity.status(HttpStatus.OK).body(authService.findPassword(request)); } - @GetMapping("/auth/verify-password-code") - public ResponseEntity changePasswordByEmail( - @RequestParam String email, - @RequestParam String key + @PostMapping("/auth/reset-password") + public ResponseEntity resetPassword( + @RequestBody ResetPasswordRequest request ){ - return ResponseEntity.status(HttpStatus.OK).body(authService.changePasswordByEmail(email, key)); + return ResponseEntity.status(HttpStatus.OK).body(authService.resetPassword(request)); } } diff --git a/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java b/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java index d45e8fe8..dabc307d 100644 --- a/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java +++ b/src/main/java/org/ezcode/codetest/presentation/usermanagement/UserController.java @@ -1,8 +1,5 @@ package org.ezcode.codetest.presentation.usermanagement; -import org.ezcode.codetest.application.usermanagement.auth.dto.request.VerifyEmailCodeRequest; -import org.ezcode.codetest.application.usermanagement.auth.dto.response.SendEmailCodeResponse; -import org.ezcode.codetest.application.usermanagement.auth.dto.response.VerifyEmailCodeResponse; import org.ezcode.codetest.application.usermanagement.user.dto.request.ModifyUserInfoRequest; import org.ezcode.codetest.application.usermanagement.user.dto.request.ChangeUserPasswordRequest; import org.ezcode.codetest.application.usermanagement.user.dto.response.ChangeUserPasswordResponse; @@ -15,7 +12,6 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping;