diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3bbcbcb5..5a851c7a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,19 +1,23 @@ -# 구현 내용/방법 +## 📝 PR 타입 +- [ ] 기능 추가 +- [ ] 기능 수정 +- [ ] 기능 삭제 +- [ ] 리팩토링 +- [ ] 의존성, 환경 변수, 빌드 관련 코드 업데이트 -> 간단하게 구현한 내용과 방법에 대한 설명 -> -- -- +## 📝 반영 브랜치 + + +- feat/ +- closed -# 리뷰 필요 +## 📝 변경 사항 + -> 나중에 다시 고민해야할 내용이 있는 내용 -> -> 없을 경우 작성 X -> 있을 경우 작성 후 이슈 남기고 해당 PR 링크 -> -- -- +## 📝 테스트 결과 + -close \ No newline at end of file + +## 📝 To Reviewer + diff --git a/src/main/java/synk/meeteam/domain/auth/api/AuthApi.java b/src/main/java/synk/meeteam/domain/auth/api/AuthApi.java new file mode 100644 index 00000000..fb843835 --- /dev/null +++ b/src/main/java/synk/meeteam/domain/auth/api/AuthApi.java @@ -0,0 +1,78 @@ +package synk.meeteam.domain.auth.api; + + +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 jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import synk.meeteam.domain.auth.dto.request.AuthUserRequestDto; +import synk.meeteam.domain.auth.dto.request.SignUpUserRequestDto; +import synk.meeteam.domain.auth.dto.request.VerifyUserRequestDto; +import synk.meeteam.domain.auth.dto.response.AuthUserResponseDto; +import synk.meeteam.domain.auth.dto.response.LogoutUserResponseDto; +import synk.meeteam.domain.auth.dto.response.ReissueUserResponseDto; +import synk.meeteam.domain.auth.dto.response.SignUpUserResponseDto; +import synk.meeteam.domain.user.entity.User; +import synk.meeteam.security.AuthUser; + + +@Tag(name = "auth", description = "인증 관련 API") +public interface AuthApi { + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "로그인에 성공하였습니다"), + @ApiResponse(responseCode = "201", description = "회원가입에 성공하였습니다"), + @ApiResponse(responseCode = "400", description = "유효하지 않은 플랫폼 인가코드입니다, 입력값이 올바르지 않습니다, 올바르지 않은 플랫폼 유형입니다") + } + ) + @Operation(summary = "소셜 로그인(1)") + ResponseEntity login( + @RequestHeader(value = "authorization-code") final String authorizationCode, + @RequestBody @Valid final + AuthUserRequestDto requestDto); + + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "임시 유저 생성 및 이메일 전송에 성공하였습니다."), + } + ) + @Operation(summary = "임시 유저 생성 및 이메일 전송(2)") + ResponseEntity createTempUserAndSendEmail( + @RequestBody @Valid SignUpUserRequestDto requestDto + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "이메일 인증 및 회원가입에 성공하였습니다."), + } + ) + @Operation(summary = "이메일 인증 및 회원가입(3)") + ResponseEntity signUp( + @RequestBody @Valid VerifyUserRequestDto requestDto); + + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "리프레시 토큰 재발급에 성공하였습니다."), + @ApiResponse(responseCode = "400", description = "서비스에서 발급되지 않거나 이미 사용된 리프레시 토큰입니다."), + @ApiResponse(responseCode = "401", description = "기한이 만료된 리프레시 토큰입니다.") + } + ) + @Operation(summary = "액세스 토큰 & 리프레시 토큰 재발급", description = "액세스 토큰 및 리프레시 토큰을 재발급 받습니다.") + ResponseEntity reissue(HttpServletRequest request); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "로그아웃에 성공하였습니다."), + } + ) + @Operation(summary = "로그아웃") + ResponseEntity logout(@AuthUser final User user); +} diff --git a/src/main/java/synk/meeteam/domain/auth/api/AuthController.java b/src/main/java/synk/meeteam/domain/auth/api/AuthController.java index 5b1e9c41..482375df 100644 --- a/src/main/java/synk/meeteam/domain/auth/api/AuthController.java +++ b/src/main/java/synk/meeteam/domain/auth/api/AuthController.java @@ -1,6 +1,5 @@ package synk.meeteam.domain.auth.api; -import static synk.meeteam.domain.auth.exception.AuthExceptionType.INVALID_MAIL_REGEX; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -15,35 +14,35 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import synk.meeteam.domain.auth.api.dto.request.UserAuthRequestDTO; -import synk.meeteam.domain.auth.api.dto.request.UserSignUpRequestDTO; -import synk.meeteam.domain.auth.api.dto.response.UserAuthResponseDTO; -import synk.meeteam.domain.auth.api.dto.response.UserReissueResponseDTO; -import synk.meeteam.domain.auth.api.dto.response.UserSignUpResponseDTO; -import synk.meeteam.domain.auth.exception.AuthException; +import synk.meeteam.domain.auth.dto.request.AuthUserRequestDto; +import synk.meeteam.domain.auth.dto.request.SignUpUserRequestDto; +import synk.meeteam.domain.auth.dto.request.VerifyUserRequestDto; +import synk.meeteam.domain.auth.dto.response.AuthUserResponseDto; +import synk.meeteam.domain.auth.dto.response.LogoutUserResponseDto; +import synk.meeteam.domain.auth.dto.response.ReissueUserResponseDto; +import synk.meeteam.domain.auth.dto.response.SignUpUserResponseDto; import synk.meeteam.domain.auth.service.AuthServiceProvider; import synk.meeteam.domain.auth.service.vo.UserSignUpVO; import synk.meeteam.domain.university.service.UniversityService; import synk.meeteam.domain.user.entity.User; +import synk.meeteam.domain.user.entity.UserVO; import synk.meeteam.domain.user.entity.enums.Role; -import synk.meeteam.domain.user.repository.UserRepository; import synk.meeteam.domain.user.service.UserService; import synk.meeteam.infra.mail.MailService; import synk.meeteam.infra.oauth.service.vo.enums.AuthType; +import synk.meeteam.security.AuthUser; import synk.meeteam.security.jwt.service.JwtService; @RestController @RequiredArgsConstructor @RequestMapping("/auth") @Slf4j -public class AuthController { +public class AuthController implements AuthApi { private final AuthServiceProvider authServiceProvider; private final JwtService jwtService; private final MailService mailService; private final UniversityService universityService; - private final UserRepository userRepository; private final UserService userService; @Value("${spring.security.oauth2.client.naver.client-id}") @@ -51,60 +50,66 @@ public class AuthController { @Value("${spring.security.oauth2.client.naver.redirect-uri}") private String redirectUri; + @Override @PostMapping("/social/login") - public ResponseEntity login( + public ResponseEntity login( @RequestHeader(value = "authorization-code") final String authorizationCode, @RequestBody @Valid final - UserAuthRequestDTO request, HttpServletResponse response) { + AuthUserRequestDto requestDto) { - UserSignUpVO vo = authServiceProvider.getAuthService(request.platformType()) - .saveUserOrLogin(authorizationCode, request); + UserSignUpVO vo = authServiceProvider.getAuthService(requestDto.platformType()) + .saveUserOrLogin(authorizationCode, requestDto); if (vo.role() == Role.GUEST) { - return ResponseEntity.ok(UserAuthResponseDTO + return ResponseEntity.ok(AuthUserResponseDto .of(vo.platformId(), vo.authType(), vo.name(), vo.role(), null, null)); } - UserAuthResponseDTO responseDTO = jwtService.issueToken(vo); - if (responseDTO.authType().equals(AuthType.SIGN_UP)) { - return ResponseEntity.status(HttpStatus.CREATED) - .body(responseDTO); - } + AuthUserResponseDto responseDTO = jwtService.issueToken(vo); return ResponseEntity.ok(responseDTO); } - @PostMapping("/social/sign-up") - public ResponseEntity signUp( - @RequestBody @Valid UserSignUpRequestDTO requestDTO + @Override + @PostMapping("/social/email-verify") + public ResponseEntity createTempUserAndSendEmail( + @RequestBody @Valid SignUpUserRequestDto requestDto ) { - if (!universityService.isValidRegex(requestDTO.universityName(), requestDTO.email())){ - throw new AuthException(INVALID_MAIL_REGEX); - } + Long universityId = universityService.getUniversityId(requestDto.universityName(), requestDto.departmentName(), + requestDto.email()); - userService.updateUniversityInfo(requestDTO); - mailService.sendMail(requestDTO); + authServiceProvider.getAuthService(requestDto.platformType()).updateUniversityInfo(requestDto, universityId); + mailService.sendMail(requestDto, requestDto.platformId()); - return ResponseEntity.ok(UserSignUpResponseDTO.of(requestDTO.platformId())); + return ResponseEntity.ok(SignUpUserResponseDto.of(requestDto.platformId())); } - @GetMapping("/email-verify") - public ResponseEntity verify( - @RequestParam String emailCode) { + @Override + @PostMapping("/sign-up") + public ResponseEntity signUp( + @RequestBody @Valid VerifyUserRequestDto requestDto) { + + UserVO userVO = mailService.verify(requestDto.emailCode()); + User user = authServiceProvider.getAuthService(userVO.getPlatformType()) + .createSocialUser(userVO, requestDto.nickName()); - User user = mailService.verify(emailCode); UserSignUpVO vo = UserSignUpVO.of(user, user.getPlatformType(), user.getRole(), AuthType.SIGN_UP); - UserAuthResponseDTO responseDTO = jwtService.issueToken(vo); + AuthUserResponseDto responseDTO = jwtService.issueToken(vo); return ResponseEntity.status(HttpStatus.CREATED).body(responseDTO); } + @Override @PostMapping("/reissue") - public ResponseEntity reissue(HttpServletRequest request, - HttpServletResponse response) { - UserReissueResponseDTO userReissueResponseDTO = jwtService.reissueToken(request, response); - return ResponseEntity.ok().body(userReissueResponseDTO); + public ResponseEntity reissue(HttpServletRequest request) { + ReissueUserResponseDto reissueUserResponseDto = jwtService.reissueToken(request); + return ResponseEntity.ok().body(reissueUserResponseDto); } + @Override + @PostMapping("/logout") + public ResponseEntity logout(@AuthUser final User user) { + return ResponseEntity.ok(jwtService.logout(user)); + } @GetMapping("/authTest") public String authTest(HttpServletRequest request, HttpServletResponse response) { diff --git a/src/main/java/synk/meeteam/domain/auth/api/dto/request/UserAuthRequestDTO.java b/src/main/java/synk/meeteam/domain/auth/api/dto/request/UserAuthRequestDTO.java deleted file mode 100644 index 4bc8a451..00000000 --- a/src/main/java/synk/meeteam/domain/auth/api/dto/request/UserAuthRequestDTO.java +++ /dev/null @@ -1,7 +0,0 @@ -package synk.meeteam.domain.auth.api.dto.request; - -import jakarta.validation.constraints.NotNull; -import synk.meeteam.domain.user.entity.enums.PlatformType; - -public record UserAuthRequestDTO(@NotNull PlatformType platformType) { -} \ No newline at end of file diff --git a/src/main/java/synk/meeteam/domain/auth/api/dto/request/UserSignUpRequestDTO.java b/src/main/java/synk/meeteam/domain/auth/api/dto/request/UserSignUpRequestDTO.java deleted file mode 100644 index 5a61470e..00000000 --- a/src/main/java/synk/meeteam/domain/auth/api/dto/request/UserSignUpRequestDTO.java +++ /dev/null @@ -1,15 +0,0 @@ -package synk.meeteam.domain.auth.api.dto.request; - -import jakarta.validation.constraints.NotNull; -import synk.meeteam.domain.user.entity.enums.PlatformType; - -public record UserSignUpRequestDTO( - @NotNull String platformId, - @NotNull PlatformType platformType, - @NotNull String email, - @NotNull String universityName, - @NotNull String departmentName, - @NotNull int admissionYear - -) { -} diff --git a/src/main/java/synk/meeteam/domain/auth/api/dto/request/UserVerifyRequestDTO.java b/src/main/java/synk/meeteam/domain/auth/api/dto/request/UserVerifyRequestDTO.java deleted file mode 100644 index 6a33f312..00000000 --- a/src/main/java/synk/meeteam/domain/auth/api/dto/request/UserVerifyRequestDTO.java +++ /dev/null @@ -1,12 +0,0 @@ -package synk.meeteam.domain.auth.api.dto.request; - -import jakarta.validation.constraints.NotNull; -import synk.meeteam.domain.user.entity.enums.PlatformType; - -public record UserVerifyRequestDTO( - @NotNull String platformId, - @NotNull String emailCode, - @NotNull PlatformType platformType - - ) { -} diff --git a/src/main/java/synk/meeteam/domain/auth/api/dto/response/UserAuthResponseDTO.java b/src/main/java/synk/meeteam/domain/auth/api/dto/response/UserAuthResponseDTO.java deleted file mode 100644 index a6c226a0..00000000 --- a/src/main/java/synk/meeteam/domain/auth/api/dto/response/UserAuthResponseDTO.java +++ /dev/null @@ -1,13 +0,0 @@ -package synk.meeteam.domain.auth.api.dto.response; - -import jakarta.validation.constraints.NotNull; -import synk.meeteam.domain.user.entity.enums.Role; -import synk.meeteam.infra.oauth.service.vo.enums.AuthType; - -public record UserAuthResponseDTO(@NotNull String platformId, @NotNull AuthType authType, @NotNull String userName, - @NotNull Role role, String accessToken, String refreshToken) { - public static UserAuthResponseDTO of(String platformId, AuthType authType, String userName, Role role, - String accessToken, String refreshToken) { - return new UserAuthResponseDTO(platformId, authType, userName, role, accessToken, refreshToken); - } -} diff --git a/src/main/java/synk/meeteam/domain/auth/api/dto/response/UserReissueResponseDTO.java b/src/main/java/synk/meeteam/domain/auth/api/dto/response/UserReissueResponseDTO.java deleted file mode 100644 index 7373f8e8..00000000 --- a/src/main/java/synk/meeteam/domain/auth/api/dto/response/UserReissueResponseDTO.java +++ /dev/null @@ -1,10 +0,0 @@ -package synk.meeteam.domain.auth.api.dto.response; - -import jakarta.validation.constraints.NotNull; - -public record UserReissueResponseDTO(@NotNull String platformId, @NotNull String accessToken, @NotNull String refreshToken) { - public static UserReissueResponseDTO of(String platformId, String accessToken, - String refreshToken) { - return new UserReissueResponseDTO(platformId, accessToken, refreshToken); - } -} diff --git a/src/main/java/synk/meeteam/domain/auth/api/dto/response/UserSignUpResponseDTO.java b/src/main/java/synk/meeteam/domain/auth/api/dto/response/UserSignUpResponseDTO.java deleted file mode 100644 index 8d40eeb6..00000000 --- a/src/main/java/synk/meeteam/domain/auth/api/dto/response/UserSignUpResponseDTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package synk.meeteam.domain.auth.api.dto.response; - -import jakarta.validation.constraints.NotNull; - -public record UserSignUpResponseDTO( - @NotNull String platformId - ) { - public static UserSignUpResponseDTO of(String platformId) { - return new UserSignUpResponseDTO(platformId); - } -} diff --git a/src/main/java/synk/meeteam/domain/auth/dto/request/AuthUserRequestDto.java b/src/main/java/synk/meeteam/domain/auth/dto/request/AuthUserRequestDto.java new file mode 100644 index 00000000..0603029b --- /dev/null +++ b/src/main/java/synk/meeteam/domain/auth/dto/request/AuthUserRequestDto.java @@ -0,0 +1,13 @@ +package synk.meeteam.domain.auth.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import synk.meeteam.domain.user.entity.enums.PlatformType; + +@Schema(name = "AuthUserRequestDto", description = "소셜 로그인 요청 Dto") +public record AuthUserRequestDto( + @NotNull + @Schema(description = "소셜 플랫폼 타입", example = "NAVER") + PlatformType platformType) { + +} \ No newline at end of file diff --git a/src/main/java/synk/meeteam/domain/auth/dto/request/SignUpUserRequestDto.java b/src/main/java/synk/meeteam/domain/auth/dto/request/SignUpUserRequestDto.java new file mode 100644 index 00000000..7d3fbb00 --- /dev/null +++ b/src/main/java/synk/meeteam/domain/auth/dto/request/SignUpUserRequestDto.java @@ -0,0 +1,30 @@ +package synk.meeteam.domain.auth.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import synk.meeteam.domain.user.entity.enums.PlatformType; + +@Schema(name = "SignUpUserRequestDto", description = "임시 회원 가입 및 이메일 인증 요청 Dto") +public record SignUpUserRequestDto( + + @NotNull + @Schema(description = "플랫폼 Id", example = "Di7lChMGxjZVTai6d76Ho1YLDU_xL8tl1CfdPMV5SQM") + String platformId, + @NotNull + @Schema(description = "플랫폼 타입", example = "NAVER") + PlatformType platformType, + @NotNull + @Schema(description = "학사 이메일", example = "thdalsrb123@kw.ac.kr") + String email, + @NotNull + @Schema(description = "학교 이름", example = "광운대학교") + String universityName, + @NotNull + @Schema(description = "학과 이름", example = "소프트웨어학부") + String departmentName, + @NotNull + @Schema(description = "입학년도", example = "2018") + int admissionYear + +) { +} diff --git a/src/main/java/synk/meeteam/domain/auth/dto/request/VerifyUserRequestDto.java b/src/main/java/synk/meeteam/domain/auth/dto/request/VerifyUserRequestDto.java new file mode 100644 index 00000000..0a1e4183 --- /dev/null +++ b/src/main/java/synk/meeteam/domain/auth/dto/request/VerifyUserRequestDto.java @@ -0,0 +1,16 @@ +package synk.meeteam.domain.auth.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "VerifyUserRequestDto", description = "이메일 인증 확인 및 회원가입 요청") +public record VerifyUserRequestDto( + @NotNull + @Schema(description = "이메일 코드", example = "0a2c1be3-5b99-47d1-bd02-6bd4754a7688") + String emailCode, + @NotNull + @Schema(description = "닉네임", example = "송민규짱짱맨") + String nickName + + ) { +} diff --git a/src/main/java/synk/meeteam/domain/auth/dto/response/AuthUserResponseDto.java b/src/main/java/synk/meeteam/domain/auth/dto/response/AuthUserResponseDto.java new file mode 100644 index 00000000..fde22731 --- /dev/null +++ b/src/main/java/synk/meeteam/domain/auth/dto/response/AuthUserResponseDto.java @@ -0,0 +1,30 @@ +package synk.meeteam.domain.auth.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import synk.meeteam.domain.user.entity.enums.Role; +import synk.meeteam.infra.oauth.service.vo.enums.AuthType; + +@Schema(name = "AuthUserResponseDto", description = "소셜 로그인 응답 Dto") +public record AuthUserResponseDto( + @NotNull + @Schema(description = "플랫폼 Id", example = "Di7lChMGxjZVTai6d76Ho1YLDU_xL8tl1CfdPMV5SQM") + String platformId, + @NotNull + @Schema(description = "인가 타입(LOGIN or SIGN_UP)", example = "LOGIN") + AuthType authType, + @NotNull + @Schema(description = "유저 이름", example = "송민규") + String userName, + @NotNull + @Schema(description = "역할, GUEST는 임시 유저", example = "USER") + Role role, + @Schema(description = "액세스 토큰", example = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJBY2Nlc3NUb2tlbiIsInBsYXRmb3JtSWQiOiJEaTdsQ2hNR3hqWlZUYWk2ZDc2SG8xWUxEVV94TDh0bDFDZmRQTVY1U1FNIiwicGxhdGZvcm1UeXBlIjoiTkFWRVIiLCJpYXQiOjE3MDYyODA1MjMsImV4cCI6MTgxNDI4MDUyM30.doPtAdLQMZ8NeuhRAOg7GNMBBtFZzPOOZp60HskGtZ0") + String accessToken, + @Schema(description = "리프레시 토큰", example = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJSZWZyZXNoVG9rZW4iLCJpYXQiOjE3MDYyODA1MjMsImV4cCI6MTcwNjg4NTMyM30.yvftTGVld0ZMnv1a79wpuzuTo8EJ1zOHoSlT_jfH3cs") + String refreshToken) { + public static AuthUserResponseDto of(String platformId, AuthType authType, String userName, Role role, + String accessToken, String refreshToken) { + return new AuthUserResponseDto(platformId, authType, userName, role, accessToken, refreshToken); + } +} diff --git a/src/main/java/synk/meeteam/domain/auth/dto/response/LogoutUserResponseDto.java b/src/main/java/synk/meeteam/domain/auth/dto/response/LogoutUserResponseDto.java new file mode 100644 index 00000000..c5cce1ea --- /dev/null +++ b/src/main/java/synk/meeteam/domain/auth/dto/response/LogoutUserResponseDto.java @@ -0,0 +1,15 @@ +package synk.meeteam.domain.auth.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "LogoutUserResponseDto", description = "로그아웃 요청 Dto") +public record LogoutUserResponseDto( + @NotNull + @Schema(description = "플랫폼 Id", example = "Di7lChMGxjZVTai6d76Ho1YLDU_xL8tl1CfdPMV5SQM") + String PlatformId +) { + public static LogoutUserResponseDto of(String platformId) { + return new LogoutUserResponseDto(platformId); + } +} diff --git a/src/main/java/synk/meeteam/domain/auth/dto/response/ReissueUserResponseDto.java b/src/main/java/synk/meeteam/domain/auth/dto/response/ReissueUserResponseDto.java new file mode 100644 index 00000000..d8e69e11 --- /dev/null +++ b/src/main/java/synk/meeteam/domain/auth/dto/response/ReissueUserResponseDto.java @@ -0,0 +1,21 @@ +package synk.meeteam.domain.auth.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "ReissueUserResponseDto", description = "토큰 재발급 요청 Dto") +public record ReissueUserResponseDto( + @NotNull + @Schema(description = "플랫폼 Id", example = "Di7lChMGxjZVTai6d76Ho1YLDU_xL8tl1CfdPMV5SQM") + String platformId, + @NotNull + @Schema(description = "액세스 토큰", example = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJBY2Nlc3NUb2tlbiIsInBsYXRmb3JtSWQiOiJEaTdsQ2hNR3hqWlZUYWk2ZDc2SG8xWUxEVV94TDh0bDFDZmRQTVY1U1FNIiwicGxhdGZvcm1UeXBlIjoiTkFWRVIiLCJpYXQiOjE3MDYyODA1MjMsImV4cCI6MTgxNDI4MDUyM30.doPtAdLQMZ8NeuhRAOg7GNMBBtFZzPOOZp60HskGtZ0") + String accessToken, + @NotNull + @Schema(description = "리프레시 토큰", example = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJSZWZyZXNoVG9rZW4iLCJpYXQiOjE3MDYyODA1MjMsImV4cCI6MTcwNjg4NTMyM30.yvftTGVld0ZMnv1a79wpuzuTo8EJ1zOHoSlT_jfH3cs") + String refreshToken) { + public static ReissueUserResponseDto of(String platformId, String accessToken, + String refreshToken) { + return new ReissueUserResponseDto(platformId, accessToken, refreshToken); + } +} diff --git a/src/main/java/synk/meeteam/domain/auth/dto/response/SignUpUserResponseDto.java b/src/main/java/synk/meeteam/domain/auth/dto/response/SignUpUserResponseDto.java new file mode 100644 index 00000000..5d3d0f20 --- /dev/null +++ b/src/main/java/synk/meeteam/domain/auth/dto/response/SignUpUserResponseDto.java @@ -0,0 +1,15 @@ +package synk.meeteam.domain.auth.dto.response; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; + +@Schema(name = "SignUpUserResponseDto", description = "회원가입 응답 Dto") +public record SignUpUserResponseDto( + @NotNull + @Schema(description = "플랫폼 Id", example = "Di7lChMGxjZVTai6d76Ho1YLDU_xL8tl1CfdPMV5SQM") + String platformId + ) { + public static SignUpUserResponseDto of(String platformId) { + return new SignUpUserResponseDto(platformId); + } +} diff --git a/src/main/java/synk/meeteam/domain/auth/exception/AuthExceptionType.java b/src/main/java/synk/meeteam/domain/auth/exception/AuthExceptionType.java index 1f4f9098..1039b2f5 100644 --- a/src/main/java/synk/meeteam/domain/auth/exception/AuthExceptionType.java +++ b/src/main/java/synk/meeteam/domain/auth/exception/AuthExceptionType.java @@ -31,11 +31,7 @@ public enum AuthExceptionType implements ExceptionType { */ NOT_FOUND_USER(HttpStatus.NOT_FOUND, "유효한 유저를 찾지 못했습니다."), - NOT_FOUND_REFRESH_TOKEN(HttpStatus.NOT_FOUND, "유효한 리프레시 토큰을 찾지 못했습니다."), - NOT_FOUND_EMAIL_CODE(HttpStatus.NOT_FOUND, "유효한 이메일 코드를 찾지 못했습니다."), - NOT_FOUND_UNIVERSITY_AND_DEPARTMENT(HttpStatus.NOT_FOUND, "유효한 학교명 및 학과명을 찾지 못했습니다."); - - + NOT_FOUND_REFRESH_TOKEN(HttpStatus.NOT_FOUND, "유효한 리프레시 토큰을 찾지 못했습니다."); private final HttpStatus status; private final String message; diff --git a/src/main/java/synk/meeteam/domain/auth/service/AuthService.java b/src/main/java/synk/meeteam/domain/auth/service/AuthService.java index d3343dff..0aa10e43 100644 --- a/src/main/java/synk/meeteam/domain/auth/service/AuthService.java +++ b/src/main/java/synk/meeteam/domain/auth/service/AuthService.java @@ -3,47 +3,87 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import synk.meeteam.domain.auth.api.dto.request.UserAuthRequestDTO; +import synk.meeteam.domain.auth.dto.request.AuthUserRequestDto; +import synk.meeteam.domain.auth.dto.request.SignUpUserRequestDto; import synk.meeteam.domain.auth.service.vo.UserSignUpVO; +import synk.meeteam.domain.university.entity.University; +import synk.meeteam.domain.university.repository.UniversityRepository; import synk.meeteam.domain.user.entity.User; +import synk.meeteam.domain.user.entity.UserVO; import synk.meeteam.domain.user.entity.enums.PlatformType; import synk.meeteam.domain.user.entity.enums.Role; import synk.meeteam.domain.user.repository.UserRepository; -import synk.meeteam.security.jwt.utils.PasswordUtil; +import synk.meeteam.infra.redis.repository.RedisUserRepository; @Service @RequiredArgsConstructor public abstract class AuthService { private final UserRepository userRepository; + private final RedisUserRepository redisUserRepository; + private final UniversityRepository universityRepository; @Transactional - public abstract UserSignUpVO saveUserOrLogin(String platformType, UserAuthRequestDTO request); + public abstract UserSignUpVO saveUserOrLogin(String platformType, AuthUserRequestDto request); protected User getUser(PlatformType platformType, String platformId) { return userRepository.findByPlatformIdAndPlatformType(platformId, platformType) .orElse(null); } - protected User saveUser(UserAuthRequestDTO request, String email, String name, String id, + protected User saveTempUser(AuthUserRequestDto request, String email, String name, String id, String phoneNumber) { - User newUser = createSocialUser(email, name, request.platformType(), id, phoneNumber); - return userRepository.saveAndFlush(newUser); - } - - private static User createSocialUser(String email, String name, PlatformType platformType, String id, - String phoneNumber) { - String password = PasswordUtil.generateRandomPassword(); + UserVO tempSocialUser = createTempSocialUser(email, name, request.platformType(), id, phoneNumber); + redisUserRepository.save(tempSocialUser); return User.builder() .email(email) .name(name) - .nickname("tmpNickName") - .password(password) .phoneNumber(phoneNumber) - .admissionYear(0) - .platformType(platformType) + .platformType(request.platformType()) .platformId(id) .role(Role.GUEST) .build(); } + + private static UserVO createTempSocialUser(String email, String name, PlatformType platformType, String id, + String phoneNumber){ + return UserVO.builder() + .email(email) + .name(name) + .phoneNumber(phoneNumber) + .platformType(platformType) + .platformId(id) + .build(); + } + + public void updateUniversityInfo(SignUpUserRequestDto requestDTO, Long universityId) { + + UserVO userVO = redisUserRepository.findByPlatformIdOrElseThrowException(requestDTO.platformId()); + + userVO.updateUniversityId(universityId); + userVO.updateEmail(requestDTO.email()); + userVO.updateAdmissionYear(requestDTO.admissionYear()); + + redisUserRepository.save(userVO); + } + + public User createSocialUser(UserVO userVO, String nickName) { + University foundUniversity = universityRepository.findByIdOrElseThrowException( + userVO.getUniversityId()); + + User newUser = User.builder() + .email(userVO.getEmail()) + .name(userVO.getName()) + .nickname(nickName) + .phoneNumber(userVO.getPhoneNumber()) + .admissionYear(userVO.getAdmissionYear()) + .university(foundUniversity) + .role(Role.USER) + .platformType(userVO.getPlatformType()) + .platformId(userVO.getPlatformId()) + .build(); + + return userRepository.saveAndFlush(newUser); + + } } diff --git a/src/main/java/synk/meeteam/domain/auth/service/vo/UserSignUpVO.java b/src/main/java/synk/meeteam/domain/auth/service/vo/UserSignUpVO.java index b4e2e51b..a60ce3db 100644 --- a/src/main/java/synk/meeteam/domain/auth/service/vo/UserSignUpVO.java +++ b/src/main/java/synk/meeteam/domain/auth/service/vo/UserSignUpVO.java @@ -12,7 +12,6 @@ public record UserSignUpVO(Long userId, String email, String name, PlatformType String platformId, String phoneNumber, AuthType authType) { public static UserSignUpVO of(User user, PlatformType platformType, Role role, AuthType authType) { return UserSignUpVO.builder() - .userId(user.getId()) .email(user.getEmail()) .name(user.getName()) .platformType(platformType) diff --git a/src/main/java/synk/meeteam/domain/university/exception/UniversityExceptionType.java b/src/main/java/synk/meeteam/domain/university/exception/UniversityExceptionType.java index b7ee6a82..0c69af73 100644 --- a/src/main/java/synk/meeteam/domain/university/exception/UniversityExceptionType.java +++ b/src/main/java/synk/meeteam/domain/university/exception/UniversityExceptionType.java @@ -8,7 +8,10 @@ @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public enum UniversityExceptionType implements ExceptionType { - NOT_FOUND_EMAIL_REGEX(HttpStatus.NOT_FOUND, "유효한 학교 이메일이 아닙니다."); + NOT_FOUND_EMAIL_REGEX(HttpStatus.NOT_FOUND, "유효한 학교 이메일이 아닙니다."), + NOT_FOUND_UNIVERSITY_ID(HttpStatus.NOT_FOUND, "해당 학교 Id를 찾을 수 없습니다."), + NOT_FOUND_UNIVERSITY_AND_DEPARTMENT(HttpStatus.NOT_FOUND, "유효한 학교명 및 학과명을 찾지 못했습니다."); + private final HttpStatus status; private final String message; diff --git a/src/main/java/synk/meeteam/domain/university/repository/UniversityRepository.java b/src/main/java/synk/meeteam/domain/university/repository/UniversityRepository.java index 3aa83e26..ab9d69bb 100644 --- a/src/main/java/synk/meeteam/domain/university/repository/UniversityRepository.java +++ b/src/main/java/synk/meeteam/domain/university/repository/UniversityRepository.java @@ -1,7 +1,8 @@ package synk.meeteam.domain.university.repository; -import static synk.meeteam.domain.auth.exception.AuthExceptionType.NOT_FOUND_UNIVERSITY_AND_DEPARTMENT; import static synk.meeteam.domain.university.exception.UniversityExceptionType.NOT_FOUND_EMAIL_REGEX; +import static synk.meeteam.domain.university.exception.UniversityExceptionType.NOT_FOUND_UNIVERSITY_AND_DEPARTMENT; +import static synk.meeteam.domain.university.exception.UniversityExceptionType.NOT_FOUND_UNIVERSITY_ID; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -24,4 +25,10 @@ default University findByUniversityNameAndDepartmentNameOrElseThrowException(Str return findByUniversityNameAndDepartmentName(universityName, departmentName).orElseThrow( () -> new AuthException(NOT_FOUND_UNIVERSITY_AND_DEPARTMENT)); } + + Optional findById(Long universityId); + + default University findByIdOrElseThrowException(Long universityId) { + return findById(universityId).orElseThrow(() -> new UniversityException(NOT_FOUND_UNIVERSITY_ID)); + } } diff --git a/src/main/java/synk/meeteam/domain/university/service/UniversityService.java b/src/main/java/synk/meeteam/domain/university/service/UniversityService.java index 56fb686e..c1a203af 100644 --- a/src/main/java/synk/meeteam/domain/university/service/UniversityService.java +++ b/src/main/java/synk/meeteam/domain/university/service/UniversityService.java @@ -1,7 +1,10 @@ package synk.meeteam.domain.university.service; +import static synk.meeteam.domain.auth.exception.AuthExceptionType.INVALID_MAIL_REGEX; + import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import synk.meeteam.domain.auth.exception.AuthException; import synk.meeteam.domain.university.entity.University; import synk.meeteam.domain.university.repository.UniversityRepository; @@ -11,14 +14,20 @@ public class UniversityService { private final UniversityRepository universityRepository; - public boolean isValidRegex(String universityName, String email){ - University foundUniversity = universityRepository.findByUniversityNameOrElseThrowException(universityName); + public Long getUniversityId(String universityName, String departmentName, String email) { + University foundUniversity = universityRepository.findByUniversityNameAndDepartmentNameOrElseThrowException( + universityName, departmentName); + String regex = extractEmailRegex(email); - return foundUniversity.getEmailRegex().equals(regex); + if (!foundUniversity.getEmailRegex().equals(regex)) { + throw new AuthException(INVALID_MAIL_REGEX); + } + + return foundUniversity.getId(); } - private String extractEmailRegex(String email){ + private String extractEmailRegex(String email) { String[] split = email.split("@"); return split[1]; } diff --git a/src/main/java/synk/meeteam/domain/user/entity/User.java b/src/main/java/synk/meeteam/domain/user/entity/User.java index a06d1d9d..a6ae69c7 100644 --- a/src/main/java/synk/meeteam/domain/user/entity/User.java +++ b/src/main/java/synk/meeteam/domain/user/entity/User.java @@ -11,6 +11,7 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.AccessLevel; @@ -29,6 +30,7 @@ @Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "USERS") public class User extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -53,7 +55,7 @@ public class User extends BaseTimeEntity { @Column(length = 100) private String password; - @Column(length = 11) + @Column(length = 15) private String phoneNumber; //한줄 소개 @@ -75,12 +77,6 @@ public class User extends BaseTimeEntity { //평가 점수 private String evaluationScore; - //계정 타입 - @NotNull - @Enumerated(EnumType.STRING) - @Column(length = 15) - private UserType type; - //학사 정보 @ManyToOne(fetch = LAZY, optional = false) @JoinColumn(name = "university_id") @@ -123,14 +119,14 @@ public void passwordEncode(PasswordEncoder passwordEncoder) { @Builder public User(String email, String name, String nickname, String password, String phoneNumber, - Integer admissionYear, UserType type, Role role, PlatformType platformType, String platformId) { + Integer admissionYear, Role role, PlatformType platformType, String platformId, University university) { this.email = email; + this.university = university; this.name = name; this.nickname = nickname; this.password = password; this.phoneNumber = phoneNumber; this.admissionYear = admissionYear; - this.type = type; this.role = role; this.platformType = platformType; this.platformId = platformId; diff --git a/src/main/java/synk/meeteam/domain/user/entity/UserType.java b/src/main/java/synk/meeteam/domain/user/entity/UserType.java deleted file mode 100644 index 84c8a40b..00000000 --- a/src/main/java/synk/meeteam/domain/user/entity/UserType.java +++ /dev/null @@ -1,6 +0,0 @@ -package synk.meeteam.domain.user.entity; - -public enum UserType { - ORIGINAL, // 일반 계정 - THIRD_PARTY // 타사 연동 계정 -} diff --git a/src/main/java/synk/meeteam/domain/user/entity/UserVO.java b/src/main/java/synk/meeteam/domain/user/entity/UserVO.java new file mode 100644 index 00000000..6e6ea34a --- /dev/null +++ b/src/main/java/synk/meeteam/domain/user/entity/UserVO.java @@ -0,0 +1,60 @@ +package synk.meeteam.domain.user.entity; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; +import synk.meeteam.domain.user.entity.enums.PlatformType; + +@Getter +@NoArgsConstructor +@RedisHash(value = "user", timeToLive = 60480000) +public class UserVO { + + @Id + @Indexed + private String platformId; + + private String email; + + private String name; + + private String phoneNumber; + + private PlatformType platformType; + + private Long universityId; + + private int admissionYear; + + @Indexed + private String emailCode; + + @Builder + public UserVO(String platformId, String email, String name, String phoneNumber, PlatformType platformType) { + this.platformId = platformId; + this.email = email; + this.name = name; + this.phoneNumber = phoneNumber; + this.platformType = platformType; + } + + public void updateEmail(String email) { + this.email = email; + } + + public void updateUniversityId(Long universityId) { + this.universityId = universityId; + } + + public void updateAdmissionYear(int admissionYear) { + this.admissionYear = admissionYear; + } + + public void updateEmailCode(String emailCode) { + this.emailCode = emailCode; + } + +} diff --git a/src/main/java/synk/meeteam/domain/user/service/UserService.java b/src/main/java/synk/meeteam/domain/user/service/UserService.java index 9272f8ec..6cbd85ca 100644 --- a/src/main/java/synk/meeteam/domain/user/service/UserService.java +++ b/src/main/java/synk/meeteam/domain/user/service/UserService.java @@ -2,28 +2,9 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import synk.meeteam.domain.auth.api.dto.request.UserSignUpRequestDTO; -import synk.meeteam.domain.university.entity.University; -import synk.meeteam.domain.university.repository.UniversityRepository; -import synk.meeteam.domain.user.entity.User; -import synk.meeteam.domain.user.repository.UserRepository; @Service @RequiredArgsConstructor public class UserService { - private final UserRepository userRepository; - private final UniversityRepository universityRepository; - public void updateUniversityInfo(UserSignUpRequestDTO requestDTO) { - University university = universityRepository.findByUniversityNameAndDepartmentNameOrElseThrowException( - requestDTO.universityName(), - requestDTO.departmentName()); - - User foundUser = userRepository.findByPlatformIdAndPlatformTypeOrElseThrowException( - requestDTO.platformId(), requestDTO.platformType()); - foundUser.updateEmail(requestDTO.email()); - foundUser.updateAdmissionYear(requestDTO.admissionYear()); - foundUser.updateUniversity(university); - userRepository.save(foundUser); - } } diff --git a/src/main/java/synk/meeteam/infra/mail/MailService.java b/src/main/java/synk/meeteam/infra/mail/MailService.java index 4db89cc0..7cd716c2 100644 --- a/src/main/java/synk/meeteam/infra/mail/MailService.java +++ b/src/main/java/synk/meeteam/infra/mail/MailService.java @@ -1,26 +1,27 @@ package synk.meeteam.infra.mail; import static synk.meeteam.domain.auth.exception.AuthExceptionType.INVALID_MAIL_SERVICE; -import static synk.meeteam.domain.auth.exception.AuthExceptionType.INVALID_VERIFY_MAIL; +import static synk.meeteam.infra.mail.MailText.CHAR_SET; +import static synk.meeteam.infra.mail.MailText.FRONT_DOMAIN; +import static synk.meeteam.infra.mail.MailText.MAIL_CONTENT_POSTFIX; +import static synk.meeteam.infra.mail.MailText.MAIL_CONTENT_PREFIX; +import static synk.meeteam.infra.mail.MailText.MAIL_TITLE; +import static synk.meeteam.infra.mail.MailText.SENDER; +import static synk.meeteam.infra.mail.MailText.SENDER_ADDRESS; +import static synk.meeteam.infra.mail.MailText.SUB_TYPE; import jakarta.mail.internet.InternetAddress; import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage.RecipientType; -import java.util.Optional; import java.util.UUID; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.mail.javamail.JavaMailSender; import org.springframework.stereotype.Service; -import synk.meeteam.domain.auth.api.dto.request.UserSignUpRequestDTO; -import synk.meeteam.domain.auth.api.dto.request.UserVerifyRequestDTO; +import synk.meeteam.domain.auth.dto.request.SignUpUserRequestDto; import synk.meeteam.domain.auth.exception.AuthException; -import synk.meeteam.domain.user.entity.User; -import synk.meeteam.domain.user.entity.enums.Role; -import synk.meeteam.domain.user.repository.UserRepository; -import synk.meeteam.infra.redis.repository.RedisTokenRepository; -import synk.meeteam.infra.redis.repository.RedisVerifyRepository; -import synk.meeteam.security.jwt.service.vo.TokenVO; +import synk.meeteam.domain.user.entity.UserVO; +import synk.meeteam.infra.redis.repository.RedisUserRepository; @Service @RequiredArgsConstructor @@ -28,38 +29,27 @@ public class MailService { private final JavaMailSender mailSender; - private final RedisVerifyRepository redisVerifyRepository; - private final UserRepository userRepository; + private final RedisUserRepository redisUserRepository; - public void sendMail(UserSignUpRequestDTO requestDTO) { - String token = UUID.randomUUID().toString(); + public void sendMail(SignUpUserRequestDto requestDTO, String platformId) { + String newEmailCode = UUID.randomUUID().toString(); - MailVO mailVO = MailVO.builder() - .platformId(requestDTO.platformId()) - .platformType(requestDTO.platformType()) - .emailCode(token) - .email(requestDTO.email()) - .build(); - - redisVerifyRepository.save(mailVO); + UserVO userVO = redisUserRepository.findByPlatformIdOrElseThrowException(platformId); + userVO.updateEmailCode(newEmailCode); + redisUserRepository.save(userVO); String receiverMail = requestDTO.email(); MimeMessage message = mailSender.createMimeMessage(); try { message.addRecipients(RecipientType.TO, receiverMail);// 보내는 대상 - message.setSubject("Meeteam 회원가입 이메일 인증");// 제목 + message.setSubject(MAIL_TITLE);// 제목 - String body = "
" - + "

안녕하세요. Meeteam 입니다

" - + "
" - + "

아래 링크를 클릭하면 이메일 인증이 완료됩니다.

" - + "인증 링크" - + "

"; + String body = createMailContent(newEmailCode); - message.setText(body, "utf-8", "html");// 내용, charset 타입, subtype + message.setText(body, CHAR_SET, SUB_TYPE);// 내용, charset 타입, subtype // 보내는 사람의 이메일 주소, 보내는 사람 이름 - message.setFrom(new InternetAddress("thdalsrb79@naver.com", "Meeteam"));// 보내는 사람 + message.setFrom(new InternetAddress(SENDER_ADDRESS, SENDER));// 보내는 사람 mailSender.send(message); // 메일 전송 } catch (Exception e) { log.info("{}", e); @@ -67,14 +57,11 @@ public void sendMail(UserSignUpRequestDTO requestDTO) { } } - public User verify(String token) { - MailVO mailVO = redisVerifyRepository.findByEmailCodeOrElseThrowException(token); - - User foundUser = userRepository.findByPlatformIdAndPlatformTypeOrElseThrowException( - mailVO.getPlatformId(), mailVO.getPlatformType()); - foundUser.updateRole(Role.USER); - userRepository.save(foundUser); + private static String createMailContent(String emailCode){ + return MAIL_CONTENT_PREFIX + FRONT_DOMAIN + emailCode + MAIL_CONTENT_POSTFIX; + } - return foundUser; + public UserVO verify(String emailCode) { + return redisUserRepository.findByEmailCodeOrElseThrowException(emailCode); } } diff --git a/src/main/java/synk/meeteam/infra/mail/MailText.java b/src/main/java/synk/meeteam/infra/mail/MailText.java new file mode 100644 index 00000000..3cc1b319 --- /dev/null +++ b/src/main/java/synk/meeteam/infra/mail/MailText.java @@ -0,0 +1,21 @@ +package synk.meeteam.infra.mail; + +public class MailText { + public static final String MAIL_TITLE = "Meeteam 회원가입 이메일 인증"; + public static final String MAIL_CONTENT_PREFIX = "
" + + "

안녕하세요. Meeteam 입니다

" + + "
" + + "

아래 링크를 클릭하면 이메일 인증이 완료됩니다.

" + + "인증 링크" + + "

"; + + public static final String SENDER_ADDRESS = "thdalsrb79@naver.com"; + public static final String SENDER = "Meeteam"; + public static final String CHAR_SET = "utf-8"; + public static final String SUB_TYPE = "html"; + + + +} diff --git a/src/main/java/synk/meeteam/infra/mail/MailVO.java b/src/main/java/synk/meeteam/infra/mail/MailVO.java deleted file mode 100644 index db508103..00000000 --- a/src/main/java/synk/meeteam/infra/mail/MailVO.java +++ /dev/null @@ -1,32 +0,0 @@ -package synk.meeteam.infra.mail; - -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.Id; -import org.springframework.data.redis.core.RedisHash; -import org.springframework.data.redis.core.index.Indexed; -import synk.meeteam.domain.user.entity.enums.PlatformType; - -@Getter -@NoArgsConstructor -@RedisHash(value = "mail", timeToLive = 600) -public class MailVO { - @Id - @Indexed - private String emailCode; - - private String email; - - private String platformId; - - private PlatformType platformType; - - @Builder - public MailVO(String emailCode, String email, String platformId, PlatformType platformType) { - this.emailCode = emailCode; - this.email = email; - this.platformId = platformId; - this.platformType = platformType; - } -} diff --git a/src/main/java/synk/meeteam/infra/oauth/service/NaverAuthService.java b/src/main/java/synk/meeteam/infra/oauth/service/NaverAuthService.java index 3f7c11f3..129a88a7 100644 --- a/src/main/java/synk/meeteam/infra/oauth/service/NaverAuthService.java +++ b/src/main/java/synk/meeteam/infra/oauth/service/NaverAuthService.java @@ -5,17 +5,19 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.reactive.function.client.WebClient; -import synk.meeteam.domain.auth.api.dto.request.UserAuthRequestDTO; +import synk.meeteam.domain.auth.dto.request.AuthUserRequestDto; import synk.meeteam.domain.auth.exception.AuthException; import synk.meeteam.domain.auth.exception.AuthExceptionType; import synk.meeteam.domain.auth.service.AuthService; import synk.meeteam.domain.auth.service.vo.UserSignUpVO; +import synk.meeteam.domain.university.repository.UniversityRepository; import synk.meeteam.domain.user.entity.User; import synk.meeteam.domain.user.entity.enums.Role; import synk.meeteam.domain.user.repository.UserRepository; import synk.meeteam.infra.oauth.service.vo.NaverMemberVO; import synk.meeteam.infra.oauth.service.vo.NaverTokenVO; import synk.meeteam.infra.oauth.service.vo.enums.AuthType; +import synk.meeteam.infra.redis.repository.RedisUserRepository; @Service @Transactional(readOnly = true) @@ -34,12 +36,12 @@ public class NaverAuthService extends AuthService { @Value("${spring.security.oauth2.client.naver.token-uri.path}") private String tokenUriPath; - public NaverAuthService(UserRepository userRepository) { - super(userRepository); + public NaverAuthService(UserRepository userRepository, RedisUserRepository redisUserRepository, UniversityRepository universityRepository) { + super(userRepository, redisUserRepository, universityRepository); } @Override - public UserSignUpVO saveUserOrLogin(String authorizationCode, UserAuthRequestDTO request) { + public UserSignUpVO saveUserOrLogin(String authorizationCode, AuthUserRequestDto request) { String accessToken = getAccessToken(authorizationCode, clientId, clientSecret, state).getAccess_token(); NaverMemberVO naverMemberInfo = getNaverUserInfo(accessToken); User foundUser = getUser(request.platformType(), naverMemberInfo.getResponse().getId()); @@ -48,8 +50,9 @@ public UserSignUpVO saveUserOrLogin(String authorizationCode, UserAuthRequestDTO return UserSignUpVO.of(foundUser, request.platformType(), Role.USER, AuthType.LOGIN); } - User savedUser = saveUser(request, naverMemberInfo.getResponse().getEmail(), - naverMemberInfo.getResponse().getName(),naverMemberInfo.getResponse().getId(), + // redis 사용, 무조건 새로 회원가입하는 경우 + User savedUser = saveTempUser(request, naverMemberInfo.getResponse().getEmail(), + naverMemberInfo.getResponse().getName(), naverMemberInfo.getResponse().getId(), naverMemberInfo.getResponse().getMobile()); return UserSignUpVO.of(savedUser, request.platformType(), Role.GUEST, AuthType.SIGN_UP); diff --git a/src/main/java/synk/meeteam/infra/redis/config/RedisConfig.java b/src/main/java/synk/meeteam/infra/redis/config/RedisConfig.java index c0f8756c..2ac5a3dd 100644 --- a/src/main/java/synk/meeteam/infra/redis/config/RedisConfig.java +++ b/src/main/java/synk/meeteam/infra/redis/config/RedisConfig.java @@ -5,8 +5,11 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisKeyValueAdapter; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; @Configuration +@EnableRedisRepositories(enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP) public class RedisConfig { @Value("${spring.data.redis.host}") diff --git a/src/main/java/synk/meeteam/infra/redis/exception/RedisException.java b/src/main/java/synk/meeteam/infra/redis/exception/RedisException.java new file mode 100644 index 00000000..75f1f3db --- /dev/null +++ b/src/main/java/synk/meeteam/infra/redis/exception/RedisException.java @@ -0,0 +1,10 @@ +package synk.meeteam.infra.redis.exception; + +import synk.meeteam.global.common.exception.BaseCustomException; +import synk.meeteam.global.common.exception.ExceptionType; + +public class RedisException extends BaseCustomException { + public RedisException(ExceptionType exceptionType) { + super(exceptionType); + } +} diff --git a/src/main/java/synk/meeteam/infra/redis/exception/RedisExceptionType.java b/src/main/java/synk/meeteam/infra/redis/exception/RedisExceptionType.java new file mode 100644 index 00000000..bddfb1f9 --- /dev/null +++ b/src/main/java/synk/meeteam/infra/redis/exception/RedisExceptionType.java @@ -0,0 +1,26 @@ +package synk.meeteam.infra.redis.exception; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import synk.meeteam.global.common.exception.ExceptionType; + +@RequiredArgsConstructor +public enum RedisExceptionType implements ExceptionType { + INVALID_VERIFY_MAIL(HttpStatus.BAD_REQUEST, "잘못된 이메일 코드 입니다."), + NOT_FOUND_TEMP_USER(HttpStatus.NOT_FOUND, "해당 학사 인증을 요청한 유저를 찾을 수 없습니다."), + NOT_FOUND_EMAIL_CODE(HttpStatus.NOT_FOUND, "해당 이메일 코드를 찾을 수 없습니다."); + + + private final HttpStatus status; + private final String message; + + @Override + public HttpStatus httpStatus() { + return status; + } + + @Override + public String message() { + return message; + } +} diff --git a/src/main/java/synk/meeteam/infra/redis/repository/RedisTokenRepository.java b/src/main/java/synk/meeteam/infra/redis/repository/RedisTokenRepository.java index ccd76dae..835f3421 100644 --- a/src/main/java/synk/meeteam/infra/redis/repository/RedisTokenRepository.java +++ b/src/main/java/synk/meeteam/infra/redis/repository/RedisTokenRepository.java @@ -11,10 +11,10 @@ @Repository public interface RedisTokenRepository extends CrudRepository { - Optional findByPlatformId(String memberId); + Optional findByPlatformId(String platformId); - default TokenVO findByPlatformIdOrElseThrowException(String memberId) { - return findByPlatformId(memberId) + default TokenVO findByPlatformIdOrElseThrowException(String platformId) { + return findByPlatformId(platformId) .filter(tokenVO -> !tokenVO.isBlack()) .orElseThrow( () -> new AuthException(NOT_FOUND_REFRESH_TOKEN)); diff --git a/src/main/java/synk/meeteam/infra/redis/repository/RedisUserRepository.java b/src/main/java/synk/meeteam/infra/redis/repository/RedisUserRepository.java new file mode 100644 index 00000000..490b04fb --- /dev/null +++ b/src/main/java/synk/meeteam/infra/redis/repository/RedisUserRepository.java @@ -0,0 +1,25 @@ +package synk.meeteam.infra.redis.repository; + +import static synk.meeteam.infra.redis.exception.RedisExceptionType.NOT_FOUND_EMAIL_CODE; +import static synk.meeteam.infra.redis.exception.RedisExceptionType.NOT_FOUND_TEMP_USER; + +import java.util.Optional; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; +import synk.meeteam.domain.user.entity.UserVO; +import synk.meeteam.infra.redis.exception.RedisException; + +@Repository +public interface RedisUserRepository extends CrudRepository { + Optional findByPlatformId(String platformId); + + default UserVO findByPlatformIdOrElseThrowException(String platformId) { + return findByPlatformId(platformId).orElseThrow(() -> new RedisException(NOT_FOUND_TEMP_USER)); + } + + Optional findByEmailCode(String emailCode); + + default UserVO findByEmailCodeOrElseThrowException(String emailCode) { + return findByEmailCode(emailCode).orElseThrow(() -> new RedisException(NOT_FOUND_EMAIL_CODE)); + } +} diff --git a/src/main/java/synk/meeteam/infra/redis/repository/RedisVerifyRepository.java b/src/main/java/synk/meeteam/infra/redis/repository/RedisVerifyRepository.java deleted file mode 100644 index 231a6b68..00000000 --- a/src/main/java/synk/meeteam/infra/redis/repository/RedisVerifyRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package synk.meeteam.infra.redis.repository; - -import static synk.meeteam.domain.auth.exception.AuthExceptionType.INVALID_VERIFY_MAIL; - -import java.util.Optional; -import org.springframework.data.repository.CrudRepository; -import synk.meeteam.domain.auth.exception.AuthException; -import synk.meeteam.infra.mail.MailVO; - -public interface RedisVerifyRepository extends CrudRepository { - Optional findByEmailCode(String emailCode); - - default MailVO findByEmailCodeOrElseThrowException(String emailCode) { - return findByEmailCode(emailCode).orElseThrow(() -> new AuthException(INVALID_VERIFY_MAIL)); - } -} diff --git a/src/main/java/synk/meeteam/security/jwt/service/JwtService.java b/src/main/java/synk/meeteam/security/jwt/service/JwtService.java index 18ac7e75..640befea 100644 --- a/src/main/java/synk/meeteam/security/jwt/service/JwtService.java +++ b/src/main/java/synk/meeteam/security/jwt/service/JwtService.java @@ -11,7 +11,6 @@ import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.MalformedJwtException; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import java.util.Date; import java.util.Optional; import lombok.Getter; @@ -20,11 +19,13 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import synk.meeteam.domain.auth.api.dto.response.UserAuthResponseDTO; -import synk.meeteam.domain.auth.api.dto.response.UserReissueResponseDTO; +import synk.meeteam.domain.auth.dto.response.AuthUserResponseDto; +import synk.meeteam.domain.auth.dto.response.LogoutUserResponseDto; +import synk.meeteam.domain.auth.dto.response.ReissueUserResponseDto; import synk.meeteam.domain.auth.exception.AuthException; import synk.meeteam.domain.auth.exception.AuthExceptionType; import synk.meeteam.domain.auth.service.vo.UserSignUpVO; +import synk.meeteam.domain.user.entity.User; import synk.meeteam.domain.user.entity.enums.PlatformType; import synk.meeteam.domain.user.entity.enums.Role; import synk.meeteam.domain.user.repository.UserRepository; @@ -62,19 +63,20 @@ public class JwtService { private final RedisTokenRepository redisTokenRepository; @Transactional - public UserAuthResponseDTO issueToken(UserSignUpVO vo) { + public AuthUserResponseDto issueToken(UserSignUpVO vo) { String accessToken = jwtTokenProvider.createAccessToken(vo.platformId(), vo.platformType(), accessTokenExpirationPeriod); if (vo.role().equals(Role.USER)) { String refreshToken = jwtTokenProvider.createRefreshToken(refreshTokenExpirationPeriod); updateRefreshTokenByPlatformId(vo.platformId(), refreshToken); - return UserAuthResponseDTO.of(vo.platformId(), vo.authType(), vo.name(), Role.USER, accessToken, refreshToken); + return AuthUserResponseDto.of(vo.platformId(), vo.authType(), vo.name(), Role.USER, accessToken, refreshToken); } throw new AuthException(AuthExceptionType.UNAUTHORIZED_MEMBER_LOGIN); } - public UserReissueResponseDTO reissueToken(HttpServletRequest request, HttpServletResponse response) { + @Transactional + public ReissueUserResponseDto reissueToken(HttpServletRequest request) { String refreshToken = extractRefreshToken(request); String accessToken = extractAccessToken(request); @@ -98,7 +100,16 @@ public UserReissueResponseDTO reissueToken(HttpServletRequest request, HttpServl updateRefreshTokenByPlatformId(platformId, newRefreshToken); - return UserReissueResponseDTO.of(platformId, newAccessToken, newRefreshToken); + return ReissueUserResponseDto.of(platformId, newAccessToken, newRefreshToken); + } + + @Transactional + public LogoutUserResponseDto logout(User user){ + TokenVO foundRefreshToken = redisTokenRepository.findByPlatformIdOrElseThrowException(user.getPlatformId()); + foundRefreshToken.updateRefreshToken(null); + redisTokenRepository.save(foundRefreshToken); + + return LogoutUserResponseDto.of(user.getPlatformId()); } @@ -133,7 +144,7 @@ private String extractAccessToken(HttpServletRequest request) { } - @Transactional + public void updateRefreshTokenByPlatformId(String platformId, String newRefreshToken) { redisTokenRepository.findByPlatformId(String.valueOf(platformId)) .ifPresent(refreshToken -> {