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

비밀번호 변경 기능 추가 구현 #169

Merged
merged 4 commits into from
Jun 11, 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
@@ -1,5 +1,6 @@
package freshtrash.freshtrashbackend.controller;

import freshtrash.freshtrashbackend.dto.request.ChangePasswordRequest;
import freshtrash.freshtrashbackend.dto.request.MemberRequest;
import freshtrash.freshtrashbackend.dto.response.MemberResponse;
import freshtrash.freshtrashbackend.dto.security.MemberPrincipal;
Expand All @@ -11,6 +12,8 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.Valid;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/members")
Expand Down Expand Up @@ -42,4 +45,15 @@ public ResponseEntity<MemberResponse> updateMember(
fileService.deleteOrNotOldFile(oldFileName, memberResponse.fileName());
return ResponseEntity.ok(memberResponse);
}

/**
* 비밀번호 변경
*/
@PutMapping("/change-password")
public ResponseEntity<Void> changePassword(
@AuthenticationPrincipal MemberPrincipal memberPrincipal,
@RequestBody @Valid ChangePasswordRequest changePasswordRequest) {
memberService.changePassword(changePasswordRequest, memberPrincipal);
return ResponseEntity.ok(null);
}
}
JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package freshtrash.freshtrashbackend.dto.request;

import javax.validation.constraints.Pattern;

public record ChangePasswordRequest(
@Pattern(
regexp = "(?=.*[0-9])(?=.*[a-z])(?=.*\\W)(?=\\S+$).{8,20}",
message = "비밀번호는 영문자와 숫자, 특수기호가 적어도 1개 이상 포함된 8자~20자의 비밀번호여야 합니다.")
String oldPassword,
@Pattern(
regexp = "(?=.*[0-9])(?=.*[a-z])(?=.*\\W)(?=\\S+$).{8,20}",
message = "비밀번호는 영문자와 숫자, 특수기호가 적어도 1개 이상 포함된 8자~20자의 비밀번호여야 합니다.")
String newPassword) {}
JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

public record LoginRequest(@NotBlank @Email String email, @NotBlank String password) {
}
public record LoginRequest(
@NotBlank @Email String email,
@Pattern(
regexp = "(?=.*[0-9])(?=.*[a-z])(?=.*\\W)(?=\\S+$).{8,20}",
message = "비밀번호는 영문자와 숫자, 특수기호가 적어도 1개 이상 포함된 8자~20자의 비밀번호여야 합니다.")
String password) {}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public enum ErrorCode {
ALREADY_EXISTS_REVIEW(HttpStatus.BAD_REQUEST, "이미 리뷰가 등록되었습니다."),

// Mail
AUTH_CODE_UNMATCHED(HttpStatus.BAD_REQUEST, "잘못된 인증코드입니다."),
UNMATCHED_AUTH_CODE(HttpStatus.BAD_REQUEST, "인증코드가 일치하지 않습니다."),
NOT_FOUND_AUTH_CODE(HttpStatus.NOT_FOUND, "인증코드가 만료되었거나 존재하지 않습니다."),
EMPTY_AUTH_CODE(HttpStatus.BAD_REQUEST, "인증코드가 입력되지 않았습니다."),
MAIL_SEND_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "메일전송에 실패했습니다"),
Expand All @@ -36,6 +36,7 @@ public enum ErrorCode {
NOT_FOUND_MEMBER(HttpStatus.NOT_FOUND, "유저 정보가 존재하지 않습니다."),
ALREADY_EXISTS_EMAIL(HttpStatus.BAD_REQUEST, "이미 존재하는 이메일입니다."),
ALREADY_EXISTS_NICKNAME(HttpStatus.BAD_REQUEST, "이미 존재하는 닉네임입니다."),
UNMATCHED_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호가 일치하지 않습니다."),

// Alarm
ALARM_CONNECT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "알람을 위한 연결 시도 실패"),
JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
""")
void updateFlagCount(Long memberId, int flagLimit);

@Query(nativeQuery = true, value = "update members m set m.user_role = ?2 where m.id = ?1")
void updateUserRoleById(Long targetMemberId, UserRole userRole);
@Query(nativeQuery = true, value = "update members m set m.password = ?2 where m.email = ?1")
void updatePasswordByEmail(String email, String encodedPassword);
}
JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void verifyEmailCode(String email, String code) {
}

if (!emailCodeCache.code().equals(code)) {
throw new MailException(ErrorCode.AUTH_CODE_UNMATCHED);
throw new MailException(ErrorCode.UNMATCHED_AUTH_CODE);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package freshtrash.freshtrashbackend.service;

import freshtrash.freshtrashbackend.dto.request.ChangePasswordRequest;
import freshtrash.freshtrashbackend.dto.request.MemberRequest;
import freshtrash.freshtrashbackend.dto.response.LoginResponse;
import freshtrash.freshtrashbackend.dto.security.MemberPrincipal;
Expand All @@ -14,13 +15,15 @@
import freshtrash.freshtrashbackend.security.TokenProvider;
import freshtrash.freshtrashbackend.utils.FileUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.Objects;

@Slf4j
@Service
@RequiredArgsConstructor
public class MemberService {
Expand Down Expand Up @@ -136,10 +139,18 @@ public FlagCountSummary updateFlagCount(Long memberId, int flagLimit) {
.orElseThrow(() -> new MemberException(ErrorCode.NOT_FOUND_MEMBER));
}

public void updatePassword(String email, String temporaryPassword) {
Member member = getMemberByEmail(email);
member.setPassword(encoder.encode(temporaryPassword));
memberRepository.save(member);
public void updatePassword(String email, String newPassword) {
memberRepository.updatePasswordByEmail(email, encoder.encode(newPassword));
}

public void changePassword(ChangePasswordRequest changePasswordRequest, MemberPrincipal memberPrincipal) {
// 이전 비밀번호 일치 여부 확인
if (!encoder.matches(changePasswordRequest.oldPassword(), memberPrincipal.password())) {
log.warn("기존 비밀번호가 일치하지 않습니다.");
throw new MemberException(ErrorCode.UNMATCHED_PASSWORD);
}
// 비밀번호 변경
updatePassword(memberPrincipal.email(), changePasswordRequest.newPassword());
}

/**
JadeKim042386 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import freshtrash.freshtrashbackend.dto.security.MemberPrincipal;
import freshtrash.freshtrashbackend.entity.Address;
import freshtrash.freshtrashbackend.entity.constants.*;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.time.LocalDateTime;

Expand Down Expand Up @@ -39,11 +40,12 @@ public static ProductRequest createProductRequest() {
}

public static MemberPrincipal createMemberPrincipal() {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
return MemberPrincipal.builder()
.id(1L)
.authorities(UserRole.USER)
.nickname("nickname")
.password("pw")
.password(encoder.encode("qwer1234!!"))
.email("[email protected]")
.address(Fixture.createAddress())
.rating(4)
Expand Down Expand Up @@ -83,4 +85,8 @@ public static AuctionRequest createAuctionRequest() {
public static BiddingRequest createBiddingRequest(int biddingPrice) {
return new BiddingRequest(biddingPrice);
}

public static ChangePasswordRequest createChangePasswordRequest(String oldPassword, String newPassword) {
return new ChangePasswordRequest(oldPassword, newPassword);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,4 @@ void bidding() throws Exception {
.andExpect(status().isOk());
// then
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import freshtrash.freshtrashbackend.Fixture.Fixture;
import freshtrash.freshtrashbackend.Fixture.FixtureDto;
import freshtrash.freshtrashbackend.config.TestSecurityConfig;
import freshtrash.freshtrashbackend.dto.request.ChangePasswordRequest;
import freshtrash.freshtrashbackend.dto.request.MemberRequest;
import freshtrash.freshtrashbackend.dto.security.MemberPrincipal;
import freshtrash.freshtrashbackend.entity.Member;
Expand All @@ -27,8 +28,7 @@
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;
import static org.springframework.security.test.context.support.TestExecutionEvent.TEST_EXECUTION;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(MemberApi.class)
Expand Down Expand Up @@ -94,7 +94,8 @@ void given_memberRequestAndImgFile_when_updateMember_then_returnUpdatedMemberVal
// when
mvc.perform(multipart(HttpMethod.PUT, "/api/v1/members")
.file("imgFile", imgFile.getBytes())
.file(Fixture.createMultipartFileOfJson("memberRequest", objectMapper.writeValueAsString(memberRequest)))
.file(Fixture.createMultipartFileOfJson(
"memberRequest", objectMapper.writeValueAsString(memberRequest)))
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.nickname").value(memberRequest.nickname()))
Expand All @@ -111,4 +112,20 @@ void given_memberRequestAndImgFile_when_updateMember_then_returnUpdatedMemberVal
.value(memberRequest.address().getDetail()));
// then
}

@Test
@DisplayName("비밀번호 변경 요청")
@WithUserDetails(value = "[email protected]", setupBefore = TEST_EXECUTION)
void given_changePasswordRequestAndLoginUser_when_then_changePassword() throws Exception {
// given
ChangePasswordRequest changePasswordRequest = FixtureDto.createChangePasswordRequest("1234asdf!", "asdf1234!");
MemberPrincipal memberPrincipal = FixtureDto.createMemberPrincipal();
willDoNothing().given(memberService).changePassword(eq(changePasswordRequest), eq(memberPrincipal));
// when
mvc.perform(put("/api/v1/members/change-password")
.content(objectMapper.writeValueAsString(changePasswordRequest))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
// then
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import freshtrash.freshtrashbackend.Fixture.Fixture;
import freshtrash.freshtrashbackend.Fixture.FixtureDto;
import freshtrash.freshtrashbackend.dto.request.ChangePasswordRequest;
import freshtrash.freshtrashbackend.dto.request.MemberRequest;
import freshtrash.freshtrashbackend.dto.security.MemberPrincipal;
import freshtrash.freshtrashbackend.entity.Member;
import freshtrash.freshtrashbackend.exception.MemberException;
import freshtrash.freshtrashbackend.exception.constants.ErrorCode;
import freshtrash.freshtrashbackend.repository.MemberCacheRepository;
import freshtrash.freshtrashbackend.repository.MemberRepository;
import org.junit.jupiter.api.DisplayName;
Expand All @@ -14,13 +17,16 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willDoNothing;

@ExtendWith(MockitoExtension.class)
class MemberServiceTest {
Expand All @@ -36,6 +42,9 @@ class MemberServiceTest {
@Mock
private MemberCacheRepository memberCacheRepository;

@Mock
private PasswordEncoder encoder;

@Test
@DisplayName("member 정보 조회")
void given_memberId_when_getMember_then_memberIsNotNull() {
Expand Down Expand Up @@ -64,4 +73,38 @@ void given_memberRequestAndImgFile_when_updateMember_then_memberRequestEqualsToU
assertThat(member.getNickname()).isEqualTo(memberRequest.nickname());
assertThat(member.getAddress()).isEqualTo(memberRequest.address());
}

@Test
@DisplayName("비밀번호 변경")
void given_changePasswordRequestAndPrincipal_when_matchedOldPassword_then_changePassword() {
// given
MemberPrincipal memberPrincipal = FixtureDto.createMemberPrincipal();
ChangePasswordRequest changePasswordRequest =
FixtureDto.createChangePasswordRequest("qwer1234!!", "asdf1234!!");
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String encodedNewPassword = bCryptPasswordEncoder.encode(changePasswordRequest.newPassword());
given(encoder.matches(changePasswordRequest.oldPassword(), memberPrincipal.password()))
.willReturn(true);
given(encoder.encode(changePasswordRequest.newPassword())).willReturn(encodedNewPassword);
willDoNothing().given(memberRepository).updatePasswordByEmail(memberPrincipal.email(), encodedNewPassword);
// when
assertThatCode(() -> memberService.changePassword(changePasswordRequest, memberPrincipal))
.doesNotThrowAnyException();
// then
}

@Test
@DisplayName("비밀번호 변경 - 비밀번호 불일치")
void given_changePasswordRequestAndPrincipal_when_unmatchedOldPassword_then_throwException() {
// given
MemberPrincipal memberPrincipal = FixtureDto.createMemberPrincipal();
ChangePasswordRequest changePasswordRequest = FixtureDto.createChangePasswordRequest("qwer1234!", "asdf1234!!");
given(encoder.matches(changePasswordRequest.oldPassword(), memberPrincipal.password()))
.willReturn(false);
// when
assertThatThrownBy(() -> memberService.changePassword(changePasswordRequest, memberPrincipal))
.isInstanceOf(MemberException.class)
.hasFieldOrPropertyWithValue("errorCode", ErrorCode.UNMATCHED_PASSWORD);
// then
}
}