diff --git a/src/main/java/es/princip/ringus/application/auth/service/AuthService.java b/src/main/java/es/princip/ringus/application/auth/service/AuthService.java index 945e87b..4d50ef6 100644 --- a/src/main/java/es/princip/ringus/application/auth/service/AuthService.java +++ b/src/main/java/es/princip/ringus/application/auth/service/AuthService.java @@ -37,6 +37,7 @@ public SignUpResponse signUp(SignUpRequest request, HttpSession session){ emailSessionRepository.deleteById(sessionId); + verificationService.deleteSession(sessionId); return new SignUpResponse(member.getId()); } diff --git a/src/main/java/es/princip/ringus/application/auth/service/EmailVerificationService.java b/src/main/java/es/princip/ringus/application/auth/service/EmailVerificationService.java index 6496e54..e77d5cf 100644 --- a/src/main/java/es/princip/ringus/application/auth/service/EmailVerificationService.java +++ b/src/main/java/es/princip/ringus/application/auth/service/EmailVerificationService.java @@ -8,6 +8,7 @@ import es.princip.ringus.domain.exception.SignUpErrorCode; import es.princip.ringus.domain.member.MemberRepository; import es.princip.ringus.global.exception.CustomRuntimeException; +import es.princip.ringus.presentation.auth.dto.request.GenerateCodeRequest; import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpSession; import jakarta.transaction.Transactional; @@ -35,14 +36,21 @@ public void init(){ } @Transactional - public void generateVerificationCode(String email) { - if(memberRepository.existsByEmail(email)){ - throw new CustomRuntimeException(SignUpErrorCode.DUPLICATE_EMAIL); + public void generateVerificationCode(GenerateCodeRequest request) { + if(request.isPasswordReset()) { + if (!memberRepository.existsByEmail(request.email())) { + throw new CustomRuntimeException(SignUpErrorCode.NOT_FOUND_MEMBER); + } + } + else { + if(memberRepository.existsByEmail(request.email())) { + throw new CustomRuntimeException(SignUpErrorCode.DUPLICATE_EMAIL); + } } - EmailVerification verification = EmailVerification.of(email); + EmailVerification verification = EmailVerification.of(request.email()); - emailSendService.sendMimeMessage(email, verification.getVerificationCode()); + emailSendService.sendMimeMessage(request.email(), verification.getVerificationCode()); verificationRepository.save(verification); } @@ -91,6 +99,13 @@ public void verifySession(String email, HttpSession session){ throw new CustomRuntimeException(EmailErrorCode.SESSION_EMAIL_MISMATCH); } - sessionRepository.delete(emailSession); + } + + @Transactional + public void deleteSession(String email) { + if(!sessionRepository.existsById(email)) { + throw new CustomRuntimeException(EmailErrorCode.SESSION_NOT_FOUND); + } + sessionRepository.deleteById(email); } } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/application/member/service/MemberService.java b/src/main/java/es/princip/ringus/application/member/service/MemberService.java index 6760f8a..8abd147 100644 --- a/src/main/java/es/princip/ringus/application/member/service/MemberService.java +++ b/src/main/java/es/princip/ringus/application/member/service/MemberService.java @@ -1,5 +1,6 @@ package es.princip.ringus.application.member.service; +import es.princip.ringus.application.auth.service.EmailVerificationService; import es.princip.ringus.domain.exception.MemberErrorCode; import es.princip.ringus.domain.exception.SignUpErrorCode; import es.princip.ringus.domain.member.Member; @@ -16,6 +17,8 @@ import es.princip.ringus.presentation.member.dto.MemberResponse; import es.princip.ringus.presentation.member.dto.MenteeProfileResponse; import es.princip.ringus.presentation.member.dto.MentorProfileResponse; +import es.princip.ringus.presentation.member.dto.PasswordUpdateRequest; +import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; @@ -31,9 +34,10 @@ public class MemberService { private final MemberRepository memberRepository; private final MentorRepository mentorRepository; - private final MenteeRepository menteeRepository; + private final MenteeRepository menteeRepository; private final PasswordEncoder passwordEncoder; private final MentoringRepository mentoringRepository; + private final EmailVerificationService emailVerificationService; /** * 회원 저장 (이메일 인증 후 회원가입 진행) @@ -77,6 +81,23 @@ public MemberResponse getMember(Long memberId) { return MemberResponse.of(member); } + @Transactional + public void updatePassword(PasswordUpdateRequest request, HttpSession session) { + emailVerificationService.verifySession(request.email(), session); + + Member member = memberRepository.findByEmail(request.email()) + .orElseThrow(() -> new CustomRuntimeException(SignUpErrorCode.NOT_FOUND_MEMBER)); + + if (passwordEncoder.matches(request.newPassword(), member.getPassword())) { + throw new CustomRuntimeException(MemberErrorCode.DUPLICATE_EXISTING_PASSWORD); + } + + member.updatePassword(request.newPassword(), passwordEncoder); + + emailVerificationService.deleteSession(request.email()); + + } + public boolean isUniqueNickname(String nickname) { return !mentorRepository.existsByNickname(nickname) && !menteeRepository.existsByNickname(nickname); } diff --git a/src/main/java/es/princip/ringus/domain/exception/EmailErrorCode.java b/src/main/java/es/princip/ringus/domain/exception/EmailErrorCode.java index 87ab72a..d9fa9fa 100644 --- a/src/main/java/es/princip/ringus/domain/exception/EmailErrorCode.java +++ b/src/main/java/es/princip/ringus/domain/exception/EmailErrorCode.java @@ -5,7 +5,7 @@ public enum EmailErrorCode implements ErrorCode { - SUCESSS_VALILDATION(HttpStatus.OK, "성공적으로 인증되었습니다"), + SUCESSS_VAILDATION(HttpStatus.OK, "성공적으로 인증되었습니다"), TTL_EXPIRED(HttpStatus.FORBIDDEN, "TTL 만료"), ERROR_EXCEEDED_ATTEMPTS(HttpStatus.FORBIDDEN, "인증번호 틀림 횟수 5회 이상, 새로운 인증번호를 발급 받아주세요"), ERROR_INVALID_CODE(HttpStatus.BAD_REQUEST, "인증번호가 틀립니다"), diff --git a/src/main/java/es/princip/ringus/domain/exception/MemberErrorCode.java b/src/main/java/es/princip/ringus/domain/exception/MemberErrorCode.java index 8e65533..4bd2a77 100644 --- a/src/main/java/es/princip/ringus/domain/exception/MemberErrorCode.java +++ b/src/main/java/es/princip/ringus/domain/exception/MemberErrorCode.java @@ -7,7 +7,9 @@ public enum MemberErrorCode implements ErrorCode { SESSION_EXPIRED(HttpStatus.UNAUTHORIZED, "세션이 거부됨"), MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "멤버를 찾을 수 없음"), - MEMBER_TYPE_DIFFERENT(HttpStatus.BAD_REQUEST, "다른 멤버 타입"); + MEMBER_TYPE_DIFFERENT(HttpStatus.BAD_REQUEST, "다른 멤버 타입"), + INVAILD_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호가 유효하지 않음"), + DUPLICATE_EXISTING_PASSWORD(HttpStatus.BAD_REQUEST, "이미 사용 중인 비밀번호입니다."); MemberErrorCode(HttpStatus status, String message) { this.status = status; diff --git a/src/main/java/es/princip/ringus/domain/member/Member.java b/src/main/java/es/princip/ringus/domain/member/Member.java index 077c531..0096d6b 100644 --- a/src/main/java/es/princip/ringus/domain/member/Member.java +++ b/src/main/java/es/princip/ringus/domain/member/Member.java @@ -101,4 +101,8 @@ public boolean isMentor() { public boolean isMentee() { return this.memberType == MemberType.ROLE_MENTEE; } + + public void updatePassword(String newPassword, PasswordEncoder passwordEncoder) { + this.password = passwordEncoder.encode(newPassword); + } } diff --git a/src/main/java/es/princip/ringus/global/exception/GlobalExceptionHandler.java b/src/main/java/es/princip/ringus/global/exception/GlobalExceptionHandler.java index 1281d01..7cd91f0 100644 --- a/src/main/java/es/princip/ringus/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/es/princip/ringus/global/exception/GlobalExceptionHandler.java @@ -95,6 +95,7 @@ public ResponseEntity> handleCustomRuntimeException(CustomRu Map response = new LinkedHashMap<>(); response.put("status", ex.getStatus().value()); response.put("message", ex.getMessage()); + response.put("code", ex.getCode()); return ResponseEntity.status(ex.getStatus()).body(response); } diff --git a/src/main/java/es/princip/ringus/global/util/PasswordVaildator.java b/src/main/java/es/princip/ringus/global/util/PasswordVaildator.java new file mode 100644 index 0000000..cee393c --- /dev/null +++ b/src/main/java/es/princip/ringus/global/util/PasswordVaildator.java @@ -0,0 +1,15 @@ +package es.princip.ringus.global.util; + +import es.princip.ringus.domain.exception.MemberErrorCode; +import es.princip.ringus.global.exception.CustomRuntimeException; +import java.util.regex.Pattern; + +public class PasswordVaildator { + private static final String PASSWORD_PATTERN = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*_+=/])[A-Za-z\\d!@#$%^&*_+=/]{8,20}$"; + + public static void validate(String password) { + if (!Pattern.matches(PASSWORD_PATTERN, password)) { + throw new CustomRuntimeException(MemberErrorCode.INVAILD_PASSWORD); + } + } +} \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/presentation/auth/AuthController.java b/src/main/java/es/princip/ringus/presentation/auth/AuthController.java index 36c7fcd..c0c4fe4 100644 --- a/src/main/java/es/princip/ringus/presentation/auth/AuthController.java +++ b/src/main/java/es/princip/ringus/presentation/auth/AuthController.java @@ -4,6 +4,7 @@ import es.princip.ringus.application.auth.service.EmailVerificationService; import es.princip.ringus.global.util.ApiResponseWrapper; import es.princip.ringus.global.util.CookieUtil; +import es.princip.ringus.global.util.PasswordVaildator; import es.princip.ringus.presentation.auth.dto.request.EmailVerifyRequest; import es.princip.ringus.presentation.auth.dto.request.GenerateCodeRequest; import es.princip.ringus.presentation.auth.dto.request.LoginRequest; @@ -14,6 +15,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import jakarta.validation.Valid; +import java.net.URI; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -23,8 +25,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.net.URI; - @Slf4j @RestController @RequiredArgsConstructor @@ -38,6 +38,8 @@ public class AuthController implements AuthControllerDocs{ @PostMapping("/signup") public ResponseEntity signUp(@Valid @RequestBody SignUpRequest request, HttpSession session, HttpServletResponse httpResponse) { + PasswordVaildator.validate(request.password()); + SignUpResponse response = authService.signUp(request, session); CookieUtil.deleteCookie(httpResponse, "JSESSIONID"); @@ -76,7 +78,7 @@ public ResponseEntity> logout(HttpSession session, Http @PostMapping("/email/code") public ResponseEntity> requestCode(@Valid @RequestBody GenerateCodeRequest request) { - emailVerificationService.generateVerificationCode(request.email()); + emailVerificationService.generateVerificationCode(request); return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "인증번호가 발급되었습니다")); } diff --git a/src/main/java/es/princip/ringus/presentation/auth/dto/request/GenerateCodeRequest.java b/src/main/java/es/princip/ringus/presentation/auth/dto/request/GenerateCodeRequest.java index e3880ed..fa1bcea 100644 --- a/src/main/java/es/princip/ringus/presentation/auth/dto/request/GenerateCodeRequest.java +++ b/src/main/java/es/princip/ringus/presentation/auth/dto/request/GenerateCodeRequest.java @@ -5,5 +5,8 @@ public record GenerateCodeRequest( @Email @NotBlank - String email + String email, + + @NotBlank + Boolean isPasswordReset ) { } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/presentation/auth/dto/request/SignUpRequest.java b/src/main/java/es/princip/ringus/presentation/auth/dto/request/SignUpRequest.java index 7e0dde3..336b167 100644 --- a/src/main/java/es/princip/ringus/presentation/auth/dto/request/SignUpRequest.java +++ b/src/main/java/es/princip/ringus/presentation/auth/dto/request/SignUpRequest.java @@ -17,10 +17,6 @@ public record SignUpRequest( @NotBlank(message = "비밀번호를 입력해주세욘.") @Size(min = 8, max = 20, message = "비밀번호는 8~20자여야 합니다.") - @Pattern( - regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*_+=/])[A-Za-z\\d!@#$%^&*_+=/]{8,20}$", - message = "비밀번호는 8~20자의 대소문자, 숫자, 특수문자로 구성해야 합니다." - ) String password, @NotNull Set serviceTerms diff --git a/src/main/java/es/princip/ringus/presentation/member/MemberController.java b/src/main/java/es/princip/ringus/presentation/member/MemberController.java index cd77070..5894a50 100644 --- a/src/main/java/es/princip/ringus/presentation/member/MemberController.java +++ b/src/main/java/es/princip/ringus/presentation/member/MemberController.java @@ -4,7 +4,12 @@ import es.princip.ringus.global.annotation.SessionCheck; import es.princip.ringus.global.annotation.SessionMemberId; import es.princip.ringus.global.util.ApiResponseWrapper; +import es.princip.ringus.global.util.CookieUtil; +import es.princip.ringus.global.util.PasswordVaildator; import es.princip.ringus.presentation.member.dto.MemberResponse; +import es.princip.ringus.presentation.member.dto.PasswordUpdateRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -39,4 +44,22 @@ public ResponseEntity> isUniqueNickname(@RequestPara return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, response)); } + @SessionCheck + @PatchMapping("/password") + public ResponseEntity> updatePassword( + @RequestBody PasswordUpdateRequest request, + HttpSession session, + HttpServletResponse httpResponse + ){ + PasswordVaildator.validate(request.newPassword()); + + memberService.updatePassword(request, session); + + CookieUtil.deleteCookie(httpResponse, "JSESSIONID"); + + session.invalidate(); + + return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "비밀번호가 변경되었습니다.")); + } + } \ No newline at end of file diff --git a/src/main/java/es/princip/ringus/presentation/member/dto/PasswordUpdateRequest.java b/src/main/java/es/princip/ringus/presentation/member/dto/PasswordUpdateRequest.java new file mode 100644 index 0000000..8555d10 --- /dev/null +++ b/src/main/java/es/princip/ringus/presentation/member/dto/PasswordUpdateRequest.java @@ -0,0 +1,6 @@ +package es.princip.ringus.presentation.member.dto; + +public record PasswordUpdateRequest( + String email, + String newPassword +) { }