-
Notifications
You must be signed in to change notification settings - Fork 8
feat: 비밀번호 변경 API 구현 #448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: 비밀번호 변경 API 구현 #448
Changes from 5 commits
ddf8c9b
a191838
7edef0c
8b0f39b
aa5cbcf
b6daeb3
03b08fa
24170c8
2f0675f
cb212d9
c1403c3
d0f6df0
a1888c2
7df664e
99a1f6d
8833132
a58199e
43a720b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.example.solidconnection.siteuser.dto; | ||
|
|
||
| import com.example.solidconnection.siteuser.dto.validation.PasswordConfirmation; | ||
| import jakarta.validation.constraints.NotBlank; | ||
|
|
||
| @PasswordConfirmation | ||
| public record PasswordUpdateRequest( | ||
| @NotBlank(message = "현재 비밀번호를 입력해주세요.") | ||
| String currentPassword, | ||
|
|
||
| @NotBlank(message = "새 비밀번호를 입력해주세요.") | ||
| // @Password // todo: #435 merge 후 | ||
| String newPassword, | ||
|
whqtker marked this conversation as resolved.
|
||
|
|
||
| @NotBlank(message = "새 비밀번호를 다시 한번 입력해주세요.") | ||
| String confirmNewPassword | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 정말 아주 사소한 부분이긴 한데요..! 😅
로 알고 있습니다! 그래서 이 필드 이름을 newPasswordConfirmation 으로 하는게 어떨지 조심스럽게 제안드립니다..
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 ... 변수명 고민하다가 동사형으로 써 버렸네요
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ) { | ||
|
|
||
| } | ||
|
whqtker marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.example.solidconnection.siteuser.dto.validation; | ||
|
|
||
| import jakarta.validation.Constraint; | ||
| import jakarta.validation.Payload; | ||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| @Constraint(validatedBy = PasswordConfirmationValidator.class) | ||
| @Target({ElementType.TYPE}) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| public @interface PasswordConfirmation { | ||
|
|
||
| String message() default "비밀번호 변경 과정에서 오류가 발생했습니다."; | ||
|
|
||
| Class<?>[] groups() default {}; | ||
|
|
||
| Class<? extends Payload>[] payload() default {}; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.example.solidconnection.siteuser.dto.validation; | ||
|
|
||
| import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_NOT_CHANGED; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_NOT_CONFIRMED; | ||
|
|
||
| import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; | ||
| import jakarta.validation.ConstraintValidator; | ||
| import jakarta.validation.ConstraintValidatorContext; | ||
| import java.util.Objects; | ||
|
|
||
| public class PasswordConfirmationValidator implements ConstraintValidator<PasswordConfirmation, PasswordUpdateRequest> { | ||
|
|
||
| @Override | ||
| public boolean isValid(PasswordUpdateRequest request, ConstraintValidatorContext context) { | ||
| context.disableDefaultConstraintViolation(); | ||
|
|
||
| if (isNewPasswordNotConfirmed(request)) { | ||
| addConstraintViolation(context, PASSWORD_NOT_CONFIRMED.getMessage(), "confirmNewPassword"); | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| if (isPasswordUnchanged(request)) { | ||
| addConstraintViolation(context, PASSWORD_NOT_CHANGED.getMessage(), "newPassword"); | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| private boolean isNewPasswordNotConfirmed(PasswordUpdateRequest request) { | ||
| return !Objects.equals(request.newPassword(), request.confirmNewPassword()); | ||
| } | ||
|
|
||
| private boolean isPasswordUnchanged(PasswordUpdateRequest request) { | ||
| return Objects.equals(request.currentPassword(), request.newPassword()); | ||
| } | ||
|
|
||
| private void addConstraintViolation(ConstraintValidatorContext context, String message, String propertyName) { | ||
| context.buildConstraintViolationWithTemplate(message) | ||
| .addPropertyNode(propertyName) | ||
| .addConstraintViolation(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
|
|
||
| import static com.example.solidconnection.common.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_MISMATCH; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.USER_NOT_FOUND; | ||
|
|
||
| import com.example.solidconnection.common.exception.CustomException; | ||
|
|
@@ -10,11 +11,13 @@ | |
| import com.example.solidconnection.s3.service.S3Service; | ||
| import com.example.solidconnection.siteuser.domain.SiteUser; | ||
| import com.example.solidconnection.siteuser.dto.MyPageResponse; | ||
| import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; | ||
| import com.example.solidconnection.siteuser.repository.SiteUserRepository; | ||
| import com.example.solidconnection.university.repository.LikedUnivApplyInfoRepository; | ||
| import java.time.LocalDateTime; | ||
| import java.time.format.DateTimeFormatter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
@@ -26,6 +29,7 @@ public class MyPageService { | |
| public static final int MIN_DAYS_BETWEEN_NICKNAME_CHANGES = 7; | ||
| public static final DateTimeFormatter NICKNAME_LAST_CHANGE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); | ||
|
|
||
| private final PasswordEncoder passwordEncoder; | ||
| private final SiteUserRepository siteUserRepository; | ||
| private final LikedUnivApplyInfoRepository likedUnivApplyInfoRepository; | ||
| private final S3Service s3Service; | ||
|
|
@@ -87,4 +91,21 @@ private boolean isDefaultProfileImage(String profileImageUrl) { | |
| String prefix = "profile/"; | ||
| return profileImageUrl == null || !profileImageUrl.startsWith(prefix); | ||
| } | ||
|
|
||
| @Transactional | ||
| public void updatePassword(long siteUserId, PasswordUpdateRequest request) { | ||
| SiteUser user = siteUserRepository.findById(siteUserId) | ||
| .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); | ||
|
|
||
| // 사용자의 비밀번호와 request의 currentPassword가 동일한지 검증 | ||
| validateCurrentPasswordSame(user.getPassword(), request.currentPassword()); | ||
|
|
||
| user.updatePassword(passwordEncoder.encode(request.newPassword())); | ||
| } | ||
|
|
||
| private void validateCurrentPasswordSame(String userPassword, String currentPassword) { | ||
| if (!passwordEncoder.matches(userPassword, currentPassword)) { | ||
| throw new CustomException(PASSWORD_MISMATCH); | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 규혁님 말씀에 동의합니다~
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. !! 이건 처음 알았네요 변경하였습니다
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package com.example.solidconnection.siteuser.dto.validation; | ||
|
|
||
| import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_NOT_CHANGED; | ||
| import static com.example.solidconnection.common.exception.ErrorCode.PASSWORD_NOT_CONFIRMED; | ||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| import com.example.solidconnection.siteuser.dto.PasswordUpdateRequest; | ||
| import jakarta.validation.ConstraintViolation; | ||
| import jakarta.validation.Validation; | ||
| import jakarta.validation.Validator; | ||
| import jakarta.validation.ValidatorFactory; | ||
| import java.util.Set; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| @DisplayName("비밀번호 변경 유효성 검사 테스트") | ||
| class PasswordConfirmationValidatorTest { | ||
|
|
||
| private Validator validator; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); | ||
| validator = factory.getValidator(); | ||
| } | ||
|
|
||
| @Test | ||
| void 유효한_비밀번호_변경_요청은_검증을_통과한다() { | ||
| // given | ||
| PasswordUpdateRequest request = new PasswordUpdateRequest("currentPassword123", "newPassword123!", "newPassword123!"); | ||
|
|
||
| // when | ||
| Set<ConstraintViolation<PasswordUpdateRequest>> violations = validator.validate(request); | ||
|
|
||
| // then | ||
| assertThat(violations).isEmpty(); | ||
| } | ||
|
|
||
| @Nested | ||
| class 유효하지_않은_비밀번호_변경_테스트 { | ||
|
|
||
| @Test | ||
| void 새로운_비밀번호와_확인_비밀번호가_일치하지_않으면_검증에_실패한다() { | ||
| // given | ||
| PasswordUpdateRequest request = new PasswordUpdateRequest("currentPassword123", "newPassword123!", "differentPassword123!"); | ||
|
|
||
| // when | ||
| Set<ConstraintViolation<PasswordUpdateRequest>> violations = validator.validate(request); | ||
|
|
||
| // then | ||
| assertThat(violations).hasSize(1); | ||
| ConstraintViolation<PasswordUpdateRequest> violation = violations.iterator().next(); | ||
| assertThat(violation.getMessage()).isEqualTo(PASSWORD_NOT_CONFIRMED.getMessage()); | ||
| assertThat(violation.getPropertyPath().toString()).isEqualTo("confirmNewPassword"); | ||
| } | ||
|
Comment on lines
+53
to
+59
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "message"는 따로 상수로 빼도 좋겠네요! 추가로 저는 개인적으로 만 있어도 충분히 검증이 되는 거 같다는 생각이긴 한데 어떻게 생각하시나요?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. validator 역할을 테스트하는 것에 초점을 맞추자면 제거해도 무방할 것 같습니다 !
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| @Test | ||
| void 현재_비밀번호와_새로운_비밀번호가_같으면_검증에_실패한다() { | ||
| // given | ||
| PasswordUpdateRequest request = new PasswordUpdateRequest("currentPassword123", "currentPassword123", "currentPassword123"); | ||
|
|
||
| // when | ||
| Set<ConstraintViolation<PasswordUpdateRequest>> violations = validator.validate(request); | ||
|
|
||
| // then | ||
| assertThat(violations).hasSize(1); | ||
| ConstraintViolation<PasswordUpdateRequest> violation = violations.iterator().next(); | ||
| assertThat(violation.getMessage()).isEqualTo(PASSWORD_NOT_CHANGED.getMessage()); | ||
| assertThat(violation.getPropertyPath().toString()).isEqualTo("newPassword"); | ||
| } | ||
| } | ||
| } | ||

Uh oh!
There was an error while loading. Please reload this page.