Skip to content
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

로그인 권한 없이 비밀번호 변경하기 API #114

Merged
merged 1 commit into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading