Skip to content
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 @@ -124,4 +124,36 @@ public DefaultResponse<String> checkVerificationCode(@Valid @RequestBody EmailRe
emailCommandService.checkEmail(request);
return DefaultResponse.noContent();
}

@Operation(summary = "비밀번호 찾기 API", description = "이메일 인증 이후 이메일과 새로운 비밀번호로 비밀번호 변경")
@ApiResponses({
@ApiResponse(responseCode = "204", description = "비밀번호 변경 성공"),
@ApiResponse(
responseCode = "400",
description = """
다음과 같은 이유로 실패할 수 있습니다:
- AUTH400_2: 소셜 로그인으로 가입된 사용자입니다.
- MEMBER400_1: 이전 비밀번호와 동일합니다.
"""
),
@ApiResponse(
responseCode = "401",
description = """
다음과 같은 이유로 실패할 수 있습니다:
- EMAIL401_2: 비밀번호 재설정에 이메일 인증을 하지 않았습니다.
"""
),
@ApiResponse(
responseCode = "404",
description = """
다음과 같은 이유로 실패할 수 있습니다:
- MEMBER404_1: 사용자를 찾지 못했습니다.
"""
),
})
@PostMapping("/passwords")
public DefaultResponse<Void> findPassword(@RequestBody AuthRequestDTO.FindPassword request) {
authCommandService.findPassword(request);
return DefaultResponse.noContent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,11 @@ public record SignUp(
) {

}

public record FindPassword(
String email,
String newPassword
) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestBody;
import org.withtime.be.withtimebe.domain.auth.dto.request.AuthRequestDTO;

public interface AuthCommandService {
void signUp(AuthRequestDTO.SignUp request);
void reissueToken(HttpServletRequest request, HttpServletResponse response);
void logout(HttpServletRequest request, HttpServletResponse response);
void findPassword(@RequestBody AuthRequestDTO.FindPassword request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.withtime.be.withtimebe.domain.member.entity.Social;
import org.withtime.be.withtimebe.domain.member.repository.MemberRepository;
import org.withtime.be.withtimebe.domain.member.repository.SocialRepository;
import org.withtime.be.withtimebe.domain.member.service.command.MemberCommandService;
import org.withtime.be.withtimebe.global.error.code.*;
import org.withtime.be.withtimebe.global.error.exception.*;
import org.withtime.be.withtimebe.global.security.constants.AuthenticationConstants;
Expand All @@ -29,6 +30,7 @@ public class AuthCommandServiceImpl implements AuthCommandService {
private final PasswordEncoder passwordEncoder;
private final MemberRepository memberRepository;
private final SocialRepository socialRepository;
private final MemberCommandService memberCommandService;
private final TokenCommandService tokenCommandService;
private final TokenStorageCommandService tokenStorageCommandService;
private final TokenQueryService tokenQueryService;
Expand All @@ -39,7 +41,7 @@ public class AuthCommandServiceImpl implements AuthCommandService {
public void signUp(AuthRequestDTO.SignUp request) {
validateSignUp(request);

Member member = memberRepository.save(AuthConverter.toLocalMember(request.email(), request.username(), request.socialId() != null ? passwordEncoder.encode(request.password()) : null, request.phoneNumber(), request.gender(), request.birth()));
Member member = memberRepository.save(AuthConverter.toLocalMember(request.email(), request.username(), request.socialId() != null ? null : passwordEncoder.encode(request.password()), request.phoneNumber(), request.gender(), request.birth()));
if (request.socialId() != null) {
Social social = socialRepository.findById(request.socialId()).orElseThrow(() ->
new SocialException(SocialErrorCode.NOT_FOUND_SOCIAL));
Expand Down Expand Up @@ -82,6 +84,14 @@ public void logout(HttpServletRequest request, HttpServletResponse response) {
CookieUtil.deleteCookie(request, response, AuthenticationConstants.REFRESH_TOKEN_NAME);
}

@Override
public void findPassword(AuthRequestDTO.FindPassword request) {
if (!emailVerificationCodeStorageQueryService.isVerified(request.email())) {
throw new EmailException(EmailErrorCode.UNVERIFIED_EMAIL);
}
memberCommandService.changePassword(request.email(), request.newPassword());
}

private void reissueTokenInCookie(HttpServletRequest request, HttpServletResponse response, Long userId) {
Member member = memberRepository.findById(userId).orElseThrow(() ->
new MemberException(MemberErrorCode.NOT_FOUND)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.withtime.be.withtimebe.domain.member.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.namul.api.payload.response.DefaultResponse;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.withtime.be.withtimebe.domain.member.converter.MemberConverter;
import org.withtime.be.withtimebe.domain.member.dto.MemberRequestDTO;
import org.withtime.be.withtimebe.domain.member.dto.MemberResponseDTO;
import org.withtime.be.withtimebe.domain.member.entity.Member;
import org.withtime.be.withtimebe.domain.member.service.command.MemberCommandService;
import org.withtime.be.withtimebe.global.security.annotation.AuthenticatedMember;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/members")
@Tag(name = "사용자 API")
public class MemberController {

private final MemberCommandService memberCommandService;

@Operation(summary = "비밀번호 변경 API", description = "현재 비밀번호가 맞으면 새로운 비밀번호로 변경")
@ApiResponses({
@ApiResponse(responseCode = "204", description = "비밀번호 변경 성공"),
@ApiResponse(
responseCode = "400",
description = """
다음과 같은 이유로 실패할 수 있습니다:
- AUTH400_2: 소셜 로그인으로 가입된 사용자입니다.
- MEMBER400_1: 이전 비밀번호와 동일합니다.
"""
),
@ApiResponse(
responseCode = "401",
description = """
다음과 같은 이유로 실패할 수 있습니다:
- AUTH401_2: 현재 비밀번호가 맞지 않습니다.
"""
),
@ApiResponse(
responseCode = "404",
description = """
다음과 같은 이유로 실패할 수 있습니다:
- MEMBER404_1: 사용자를 찾지 못했습니다.
"""
),
})
@PatchMapping("/passwords")
public DefaultResponse<Void> changePassword(@AuthenticatedMember Member member,
@RequestBody MemberRequestDTO.ChangePassword request) {
memberCommandService.changePassword(member, request);
return DefaultResponse.noContent();
}

@Operation(summary = "사용자 정보 변경 API", description = "사용자 정보 변경, 사용자를 쿠키로 인식하여 정보를 변경")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "정보 변경 성공"),
@ApiResponse(
responseCode = "404",
description = """
다음과 같은 이유로 실패할 수 있습니다:
- MEMBER404_1: 사용자를 찾지 못했습니다.
"""
)
})
@PatchMapping("/infos")
public DefaultResponse<MemberResponseDTO.ChangeInfo> changeInfo(@AuthenticatedMember Member member,
@RequestBody MemberRequestDTO.ChangeInfo request) {
Member updatedMember = memberCommandService.changeInfo(member.getId(), request);
return DefaultResponse.ok(MemberConverter.toChangeInfo(updatedMember));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.withtime.be.withtimebe.domain.member.converter;

import org.withtime.be.withtimebe.domain.member.dto.MemberResponseDTO;
import org.withtime.be.withtimebe.domain.member.entity.Member;

public class MemberConverter {

public static MemberResponseDTO.ChangeInfo toChangeInfo(Member member) {
return MemberResponseDTO.ChangeInfo.builder()
.username(member.getUsername())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.withtime.be.withtimebe.domain.member.dto;

public record MemberRequestDTO() {

public record ChangePassword(
String nowPassword,
String newPassword
) {

}

public record ChangeInfo(
String username
) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.withtime.be.withtimebe.domain.member.dto;

import lombok.Builder;

public record MemberResponseDTO() {
@Builder
public record ChangeInfo(
String username
) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ public class Member extends BaseEntity {
@Column(name = "password")
private String password;

@Column(name = "nickname")
private String nickname;

@Column(name = "gender")
@Enumerated(EnumType.STRING)
private Gender gender;
Expand All @@ -58,4 +55,12 @@ public class Member extends BaseEntity {
@Enumerated(EnumType.STRING)
@Column(name = "role", nullable = false)
private Role role;

public void changeUsername(String newUsername) {
this.username= newUsername;
}

public void changePassword(String encodedPassword) {
this.password = encodedPassword;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.withtime.be.withtimebe.domain.member.service.command;

import org.withtime.be.withtimebe.domain.member.dto.MemberRequestDTO;
import org.withtime.be.withtimebe.domain.member.entity.Member;

public interface MemberCommandService {
void changePassword(Member member, MemberRequestDTO.ChangePassword request);
void changePassword(String email, String password);
Member changeInfo(Long memberId, MemberRequestDTO.ChangeInfo request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.withtime.be.withtimebe.domain.member.service.command;

import lombok.RequiredArgsConstructor;
import org.namul.api.payload.error.exception.ServerApplicationException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.withtime.be.withtimebe.domain.member.dto.MemberRequestDTO;
import org.withtime.be.withtimebe.domain.member.entity.Member;
import org.withtime.be.withtimebe.domain.member.repository.MemberRepository;
import org.withtime.be.withtimebe.global.error.code.AuthErrorCode;
import org.withtime.be.withtimebe.global.error.code.MemberErrorCode;
import org.withtime.be.withtimebe.global.error.exception.AuthException;
import org.withtime.be.withtimebe.global.error.exception.MemberException;

@Service
@RequiredArgsConstructor
@Transactional
public class MemberCommandServiceImpl implements MemberCommandService {

private final PasswordEncoder passwordEncoder;
private final MemberRepository memberRepository;


@Override
public void changePassword(Member member, MemberRequestDTO.ChangePassword request) {
if (member.getPassword() != null && !passwordEncoder.matches(request.nowPassword(), member.getPassword())) {
throw new AuthException(AuthErrorCode.INCORRECT_PASSWORD);
}
this.changePassword(member.getEmail(), request.newPassword());
}

@Override
public void changePassword(String email, String password) {
Member member = memberRepository.findByEmail(email).orElseThrow(() ->
new MemberException(MemberErrorCode.NOT_FOUND));
validateChangePassword(member, password);
member.changePassword(passwordEncoder.encode(password));
}

@Override
public Member changeInfo(Long memberId, MemberRequestDTO.ChangeInfo request) {
Member member = memberRepository.findById(memberId).orElseThrow(() ->
new MemberException(MemberErrorCode.NOT_FOUND));
member.changeUsername(request.username());
return member;
}

public void validateChangePassword(Member member, String password) throws ServerApplicationException {
String memberPassword = member.getPassword();
if (memberPassword == null) {
throw new AuthException(AuthErrorCode.ONLY_AVAILABLE_SOCIAL);
}
else if (passwordEncoder.matches(password, memberPassword)) {
throw new MemberException(MemberErrorCode.SAME_PASSWORD);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.withtime.be.withtimebe.domain.member.service;
package org.withtime.be.withtimebe.domain.member.service.query;

import org.withtime.be.withtimebe.domain.member.entity.Member;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.withtime.be.withtimebe.domain.member.service;
package org.withtime.be.withtimebe.domain.member.service.query;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public enum AuthErrorCode implements BaseErrorCode {
NOT_FOUND_LOGIN_MEMBER(HttpStatus.NOT_FOUND, "AUTH404_1", "해당 이메일을 찾을 수 없습니다."),
FAIL_AUTH_LOGIN(HttpStatus.UNAUTHORIZED, "AUTH401_1", "일반 로그인에 실패했습니다."),
ALREADY_EXIST_EMAIL(HttpStatus.BAD_REQUEST, "AUTH400_1", "이미 존재하는 이메일입니다."),
ONLY_AVAILABLE_SOCIAL(HttpStatus.BAD_REQUEST, "AUTH400_2", "소셜 로그인만 가능합니다.")
ONLY_AVAILABLE_SOCIAL(HttpStatus.BAD_REQUEST, "AUTH400_2", "소셜 로그인만 가능합니다."),
INCORRECT_PASSWORD(HttpStatus.UNAUTHORIZED, "AUTH401_2", "비밀번호가 맞지 않습니다.")
;
private final HttpStatus httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
public enum MemberErrorCode implements BaseErrorCode {

NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER404_1", "사용자를 찾지 못했습니다."),
SAME_PASSWORD(HttpStatus.BAD_REQUEST, "MEMBER400_1", "이전 비밀번호와 동일합니다."),
;

private final HttpStatus httpStatus;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.withtime.be.withtimebe.domain.auth.service.query.TokenStorageQueryService;
import org.withtime.be.withtimebe.domain.member.service.MemberQueryService;
import org.withtime.be.withtimebe.domain.member.service.query.MemberQueryService;
import org.withtime.be.withtimebe.global.security.filter.JsonLoginFilter;
import org.withtime.be.withtimebe.global.security.filter.JwtFilter;
import org.withtime.be.withtimebe.global.security.handler.CustomAccessDeniedHandler;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import org.springframework.web.filter.OncePerRequestFilter;
import org.withtime.be.withtimebe.domain.auth.service.query.TokenStorageQueryService;
import org.withtime.be.withtimebe.domain.member.entity.Member;
import org.withtime.be.withtimebe.domain.member.service.MemberQueryService;
import org.withtime.be.withtimebe.domain.member.service.query.MemberQueryService;
import org.withtime.be.withtimebe.global.security.constants.AuthenticationConstants;
import org.withtime.be.withtimebe.global.security.domain.CustomUserDetails;
import org.withtime.be.withtimebe.global.util.CookieUtil;
Expand Down