Skip to content

Commit

Permalink
Merge pull request #114 from Ajou-Hertz/feature/#113-reset-password
Browse files Browse the repository at this point in the history
로그인 권한 없이 비밀번호 변경하기 API
  • Loading branch information
tinon1004 authored May 9, 2024
2 parents 565054f + fe3dab7 commit 9ce110e
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class SecurityConfig {
AUTH_WHITE_LIST.put("/api/auth/codes/verify", POST);
AUTH_WHITE_LIST.put("/api/users/existence", GET);
AUTH_WHITE_LIST.put("/api/users/email", GET);
AUTH_WHITE_LIST.put("/api/users/password", PUT);
AUTH_WHITE_LIST.put("/api/administrative-areas/sido", GET);
AUTH_WHITE_LIST.put("/api/administrative-areas/sgg", GET);
AUTH_WHITE_LIST.put("/api/administrative-areas/emd", GET);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.ajou.hertz.domain.user.controller;

import com.ajou.hertz.common.validator.Password;
import com.ajou.hertz.common.validator.PhoneNumber;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class UpdatePasswordWithoutAuthenticationRequest {

@Schema(description = "전화번호", example = "01012345678")
@PhoneNumber
@NotBlank
private String phoneNumber;

@Schema(description = "비밀번호", example = "newpwd1234!!")
@NotBlank
@Password
private String password;

@Schema(description = "SMS 본인인증 시 발급받은 인증번호", example = "1a2b3c4d")
@NotBlank
private String userAuthCode;
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import com.ajou.hertz.domain.user.dto.UserDto;
import com.ajou.hertz.domain.user.dto.request.SignUpRequest;
import com.ajou.hertz.domain.user.dto.request.UpdateContactLinkRequest;
import com.ajou.hertz.domain.user.dto.request.UpdatePasswordRequest;
import com.ajou.hertz.domain.user.dto.request.UpdateMyPasswordRequest;
import com.ajou.hertz.domain.user.dto.response.SellerInfoResponse;
import com.ajou.hertz.domain.user.dto.response.UserEmailResponse;
import com.ajou.hertz.domain.user.dto.response.UserExistenceResponse;
Expand Down Expand Up @@ -161,18 +161,31 @@ public UserResponse updateContactLinkV1(
}

@Operation(
summary = "비밀번호 변경",
description = "회원의 비밀번호를 변경합니다.",
summary = "비밀번호 변경",
description = "회원 비밀번호를 변경합니다."
)
@PutMapping(value = "/password", headers = API_VERSION_HEADER_NAME + "=" + 1)
public UserResponse updatePasswordWithoutAuthenticationV1(
@RequestBody @Valid UpdatePasswordWithoutAuthenticationRequest updatePasswordWithoutAuthenticationRequest
) {
// TODO: 유저 본인인증 코드로 비밀번호 변경 가능한, 유효한 요청인지 검증하는 코드 추가 필요
UserDto userUpdated = userCommandService.updatePassword(updatePasswordWithoutAuthenticationRequest);
return UserResponse.from(userUpdated);
}

@Operation(
summary = "내 비밀번호 변경",
description = "내 비밀번호를 변경합니다.",
security = @SecurityRequirement(name = "access-token")
)
@PutMapping(value = "/me/password", headers = API_VERSION_HEADER_NAME + "=" + 1)
public UserResponse updatePasswordV1(
@RequestBody @Valid UpdatePasswordRequest updatePasswordRequest,
public UserResponse updateMyPasswordV1(
@RequestBody @Valid UpdateMyPasswordRequest updateMyPasswordRequest,
@AuthenticationPrincipal UserPrincipal userPrincipal
) {
UserDto userUpdated = userCommandService.updatePassword(
userPrincipal.getUserId(),
updatePasswordRequest.getPassword()
updateMyPasswordRequest.getPassword()
);
return UserResponse.from(userUpdated);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class UpdatePasswordRequest {
public class UpdateMyPasswordRequest {

@Schema(description = "비밀번호", example = "newpwd1234!!")
@NotBlank
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.UUID;

import com.ajou.hertz.domain.user.controller.UpdatePasswordWithoutAuthenticationRequest;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -175,4 +176,15 @@ public UserDto updatePassword(Long userId, String password) {
return UserDto.from(user);
}

/**
* 유저의 비밀번호를 변경합니다.
*
* @param updatePasswordRequest 유저 비밀번호 변경에 필요한 정보(전화번호, 변경할 비밀번호)
* @return 변경된 유저 정보
*/
public UserDto updatePassword(UpdatePasswordWithoutAuthenticationRequest updatePasswordRequest) {
User user = userQueryService.getByPhone(updatePasswordRequest.getPhoneNumber());
user.changePassword(passwordEncoder.encode(updatePasswordRequest.getPassword()));
return UserDto.from(user);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package com.ajou.hertz.domain.user.service;

import java.util.Optional;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ajou.hertz.domain.user.dto.UserDto;
import com.ajou.hertz.domain.user.entity.User;
import com.ajou.hertz.domain.user.exception.UserNotFoundByEmailException;
import com.ajou.hertz.domain.user.exception.UserNotFoundByIdException;
import com.ajou.hertz.domain.user.exception.UserNotFoundByPhoneException;
import com.ajou.hertz.domain.user.repository.UserRepository;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@RequiredArgsConstructor
@Transactional
Expand All @@ -32,6 +30,17 @@ public User getById(Long id) {
return userRepository.findById(id).orElseThrow(() -> new UserNotFoundByIdException(id));
}

/**
* 전화번호로 user entity를 조회한다.
*
* @param phone 조회하고자 하는 user의 전화번호
* @return 조회한 user entity
* @throws UserNotFoundByPhoneException 일치하는 유저를 찾지 못한 경우
*/
public User getByPhone(String phone) {
return userRepository.findByPhone(phone).orElseThrow(() -> new UserNotFoundByPhoneException(phone));
}

/**
* Email로 user entity를 조회한다.
*
Expand Down Expand Up @@ -77,7 +86,7 @@ public UserDto getDtoByEmail(String email) {
* @throws UserNotFoundByPhoneException 일치하는 유저를 찾지 못한 경우
*/
public UserDto getDtoByPhone(String phone) {
User user = userRepository.findByPhone(phone).orElseThrow(() -> new UserNotFoundByPhoneException(phone));
User user = getByPhone(phone);
return UserDto.from(user);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.List;
import java.util.Set;

import com.ajou.hertz.domain.user.controller.UpdatePasswordWithoutAuthenticationRequest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -275,6 +276,35 @@ public void securitySetUp() throws Exception {
verifyEveryMocksShouldHaveNoMoreInteractions();
}

@Test
void 전화번호와_신규_비밀번호가_주어지고_로그인하지_않은_사용자가_기존_비밀번호를_업데이트한다() throws Exception {
// given
long userId = 1L;
String phoneNumber = "01012345678";
String newPassword = "newPwd11!!";
UpdatePasswordWithoutAuthenticationRequest updatePasswordRequest =
createUpdatePasswordWithoutAuthenticationRequest(phoneNumber, newPassword);
UserDto expectedResult = createUserDto(userId);
given(userCommandService.updatePassword(any(UpdatePasswordWithoutAuthenticationRequest.class)))
.willReturn(expectedResult);

// when & then
mvc.perform(
put("/api/users/password")
.header(API_VERSION_HEADER_NAME, 1)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(updatePasswordRequest))
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(expectedResult.getId()))
.andExpect(jsonPath("$.email").value(expectedResult.getEmail()))
.andExpect(jsonPath("$.birth").value(expectedResult.getBirth().toString()))
.andExpect(jsonPath("$.gender").value(expectedResult.getGender().name()))
.andExpect(jsonPath("$.contactLink").value(expectedResult.getContactLink()));
then(userCommandService).should().updatePassword(any(UpdatePasswordWithoutAuthenticationRequest.class));
verifyEveryMocksShouldHaveNoMoreInteractions();
}

@Test
void 주어진_유저의_id와_새로운_비밀번호로_기존_비밀번호를_업데이트한다() throws Exception {
// given
Expand Down Expand Up @@ -354,6 +384,17 @@ private SignUpRequest createSignUpRequest() throws Exception {
);
}

private UpdatePasswordWithoutAuthenticationRequest createUpdatePasswordWithoutAuthenticationRequest(
String phoneNumber,
String newPassword
) throws Exception {
return ReflectionUtils.createUpdatePasswordWithoutAuthenticationRequest(
phoneNumber,
newPassword,
"1a2s3d4f"
);
}

private UserDto createUserDto(long id) throws Exception {
return ReflectionUtils.createUserDto(
id,
Expand Down Expand Up @@ -382,5 +423,4 @@ private List<InstrumentDto> createInstrumentDtoList() {
List<InstrumentDto> instrumentDtos = new ArrayList<>();
return instrumentDtos;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Set;
import java.util.stream.Stream;

import com.ajou.hertz.domain.user.controller.UpdatePasswordWithoutAuthenticationRequest;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -258,6 +259,28 @@ static Stream<Arguments> testDataForCreateNewUserWithKakao() throws Exception {
assertThat(updatedUserDto.getPassword()).isEqualTo(newPassword);
}

@Test
void 주어진_전화번호에_해당하는_유저의_비밀번호를_변경한다() throws Exception {
// given
Long userId = 1L;
String phoneNumber = "01012345678";
String newPassword = "newPwd1234!!";
UpdatePasswordWithoutAuthenticationRequest updatePasswordRequest =
createUpdatePasswordWithoutAuthenticationRequest(phoneNumber, newPassword);
User user = createUser(userId, "$2a$abc123", "12345");
given(userQueryService.getByPhone(phoneNumber)).willReturn(user);
given(passwordEncoder.encode(newPassword)).willReturn(newPassword);

// when
UserDto updatedUserDto = sut.updatePassword(updatePasswordRequest);

// then
then(userQueryService).should().getByPhone(phoneNumber);
then(passwordEncoder).should().encode(newPassword);
verifyEveryMocksShouldHaveNoMoreInteractions();
assertThat(updatedUserDto.getPassword()).isEqualTo(newPassword);
}

private void verifyEveryMocksShouldHaveNoMoreInteractions() {
then(userQueryService).shouldHaveNoMoreInteractions();
then(userRepository).shouldHaveNoMoreInteractions();
Expand Down Expand Up @@ -299,6 +322,17 @@ private SignUpRequest createSignUpRequest() throws Exception {
return createSignUpRequest("[email protected]", "01012345678");
}

private UpdatePasswordWithoutAuthenticationRequest createUpdatePasswordWithoutAuthenticationRequest(
String phoneNumber,
String newPassword
) throws Exception {
return ReflectionUtils.createUpdatePasswordWithoutAuthenticationRequest(
phoneNumber,
newPassword,
"1a2s3d4f"
);
}

private static KakaoUserInfoResponse createKakaoUserInfoResponse(String gender) {
return new KakaoUserInfoResponse(
"12345",
Expand Down
13 changes: 13 additions & 0 deletions src/test/java/com/ajou/hertz/util/ReflectionUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.List;
import java.util.Set;

import com.ajou.hertz.domain.user.controller.UpdatePasswordWithoutAuthenticationRequest;
import org.springframework.lang.Nullable;
import org.springframework.web.multipart.MultipartFile;

Expand Down Expand Up @@ -666,6 +667,18 @@ public static SignUpRequest createSignUpRequest(
return constructor.newInstance(email, password, birth, gender, phone);
}

public static UpdatePasswordWithoutAuthenticationRequest createUpdatePasswordWithoutAuthenticationRequest(
String phoneNumber,
String password,
String userAuthCode
) throws Exception {
Constructor<UpdatePasswordWithoutAuthenticationRequest> constructor = UpdatePasswordWithoutAuthenticationRequest.class.getDeclaredConstructor(
String.class, String.class, String.class
);
constructor.setAccessible(true);
return constructor.newInstance(phoneNumber, password, userAuthCode);
}

public static LoginRequest createLoginRequest(String email, String password) throws Exception {
Constructor<LoginRequest> constructor = LoginRequest.class.getDeclaredConstructor(String.class, String.class);
constructor.setAccessible(true);
Expand Down

0 comments on commit 9ce110e

Please sign in to comment.