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 @@ -50,7 +50,9 @@ private static OAuthUserInfo ofKakao(final Map<String, Object> attributes) {
String name = (String) profile.get("name");

return OAuthUserInfo.builder()
.name(name)
.name("김모수")
.phoneNumber("010-1234-5678")
.birthDay(LocalDate.EPOCH)
.email("test123@gmali.com")
Comment on lines +53 to 56

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

These user details are hardcoded. This appears to be for testing purposes and must be reverted before merging to production. These values should be dynamically retrieved from the OAuth provider's response.

For example:

  • name should come from the profile object (as it was before).
  • phoneNumber, birthDay, and email should be extracted from the kakaoAccount object.

Also, the email test123@gmali.com seems to have a typo and should probably be gmail.com.

.build();
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
import life.mosu.mosuserver.domain.user.UserJpaRepository;
import life.mosu.mosuserver.global.exception.CustomRuntimeException;
import life.mosu.mosuserver.global.exception.ErrorCode;
import life.mosu.mosuserver.presentation.user.dto.ChangePasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.ChangePasswordResponse;
import life.mosu.mosuserver.presentation.user.dto.FindLoginIdRequest;
import life.mosu.mosuserver.presentation.user.dto.FindLoginIdResponse;
import life.mosu.mosuserver.presentation.user.dto.FindPasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.request.ChangePasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.request.FindLoginIdRequest;
import life.mosu.mosuserver.presentation.user.dto.request.FindPasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.response.ChangePasswordResponse;
import life.mosu.mosuserver.presentation.user.dto.response.FindLoginIdResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import life.mosu.mosuserver.domain.user.UserJpaRepository;
import life.mosu.mosuserver.global.exception.CustomRuntimeException;
import life.mosu.mosuserver.global.exception.ErrorCode;
import life.mosu.mosuserver.presentation.user.dto.response.UserInfoResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -35,4 +36,11 @@ public void syncUserInfoFromProfile(UserJpaEntity user, ProfileJpaEntity profile
profile.getPhoneNumber(), profile.getBirth());
}
}

public UserInfoResponse getUserInfo(Long userId) {
UserJpaEntity user = userJpaRepository.findById(userId)
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.USER_NOT_FOUND));

return UserInfoResponse.from(user);
Comment on lines +41 to +44

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There's an opportunity to reduce code duplication here. The logic to find a user by ID or throw an exception is already encapsulated in the getUserOrThrow(Long userId) method. You can reuse it here to make the code more concise and maintainable.

Suggested change
UserJpaEntity user = userJpaRepository.findById(userId)
.orElseThrow(() -> new CustomRuntimeException(ErrorCode.USER_NOT_FOUND));
return UserInfoResponse.from(user);
UserJpaEntity user = getUserOrThrow(userId);
return UserInfoResponse.from(user);

}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package life.mosu.mosuserver.presentation.auth;

import jakarta.validation.Valid;
import java.util.UUID;
import life.mosu.mosuserver.application.auth.SignUpService;
import life.mosu.mosuserver.application.auth.kmc.KmcEventTxService;
import life.mosu.mosuserver.application.auth.provider.OneTimeTokenProvider;
import life.mosu.mosuserver.global.util.ApiResponseWrapper;
import life.mosu.mosuserver.global.util.CookieBuilderUtil;
import life.mosu.mosuserver.infra.kmc.KmcProperties;
import life.mosu.mosuserver.presentation.auth.dto.SignUpAccountRequest;
import life.mosu.mosuserver.presentation.auth.dto.Token;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -20,7 +25,11 @@ public class MasterController {

private final static String ACCESS_TOKEN_COOKIE_NAME = "accessToken";
private final static String REFRESH_TOKEN_COOKIE_NAME = "refreshToken";

private final SignUpService signUpService;
private final KmcProperties kmcProperties;
private final KmcEventTxService eventTxService;
private final OneTimeTokenProvider tokenProvider;

@PostMapping("/master")
public ResponseEntity<ApiResponseWrapper<Void>> masterSignUp(
Expand All @@ -33,10 +42,26 @@ public ResponseEntity<ApiResponseWrapper<Void>> masterSignUp(
.body(ApiResponseWrapper.success(HttpStatus.CREATED, "회원가입 성공"));
}

@GetMapping("/master/kmc")
public ResponseEntity<ApiResponseWrapper<String>> kmcSignUp(
) {
final String certNum = UUID.randomUUID().toString().replace("-", "");
String token = tokenProvider.generateOneTimeToken(certNum);
try {
eventTxService.publishIssueEvent(certNum, kmcProperties.getExpireTime());


} catch (Exception ex) {
eventTxService.publishFailureEvent(certNum);
throw new RuntimeException("KMC 인증 요청 생성에 실패했습니다.", ex);
}
Comment on lines +54 to +57

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Catching a generic Exception is discouraged as it can hide the actual problems and catch unexpected runtime exceptions like NullPointerException. It's better to catch more specific exceptions that publishIssueEvent might throw.

Additionally, instead of throwing a generic RuntimeException, consider using the project's CustomRuntimeException with a dedicated ErrorCode. This will ensure consistent error handling and response formatting across the application.

Lastly, the success message "프로필 등록 성공" seems misleading. This endpoint appears to initiate an authentication process, so a message like "KMC 인증 요청 생성 성공" would be more accurate.


return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "프로필 등록 성공", token));
}

private HttpHeaders applyTokenHeader(Token token) {
HttpHeaders headers = new HttpHeaders(
HttpHeaders headers = new HttpHeaders();

);
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createCookie(
ACCESS_TOKEN_COOKIE_NAME,
token.accessToken(),
Expand All @@ -49,5 +74,4 @@ private HttpHeaders applyTokenHeader(Token token) {
));
return headers;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ private HttpHeaders applyTokenHeader(Token token) {
HttpHeaders headers = new HttpHeaders(

);
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.temporaryCookieString(
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createCookie(
ACCESS_TOKEN_COOKIE_NAME,
token.accessToken(),
token.accessTokenExpireTime()
));
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.temporaryCookieString(
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createCookie(
REFRESH_TOKEN_COOKIE_NAME,
token.refreshToken(),
token.refreshTokenExpireTime()
Comment on lines +47 to 55

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The switch from CookieBuilderUtil.temporaryCookieString to CookieBuilderUtil.createCookie significantly reduces the security of the cookies being set.

createCookie sets secure(false) and doesn't specify domain or SameSite attributes. This means the access and refresh tokens could be sent over unencrypted HTTP connections, and cross-domain behavior might be broken or less secure.

For sensitive tokens, it's crucial to use secure(true). The temporaryCookieString method seems more appropriate for production environments. If this change was not intentional, it should be reverted.

Suggested change
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createCookie(
ACCESS_TOKEN_COOKIE_NAME,
token.accessToken(),
token.accessTokenExpireTime()
));
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.temporaryCookieString(
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createCookie(
REFRESH_TOKEN_COOKIE_NAME,
token.refreshToken(),
token.refreshTokenExpireTime()
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.temporaryCookieString(
ACCESS_TOKEN_COOKIE_NAME,
token.accessToken(),
token.accessTokenExpireTime()
));
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.temporaryCookieString(
REFRESH_TOKEN_COOKIE_NAME,
token.refreshToken(),
token.refreshTokenExpireTime()
));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

import static life.mosu.mosuserver.global.util.EncodeUtil.passwordEncode;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import life.mosu.mosuserver.domain.profile.Gender;
import life.mosu.mosuserver.domain.user.AuthProvider;
import life.mosu.mosuserver.domain.user.UserJpaEntity;
import life.mosu.mosuserver.domain.user.UserRole;
import life.mosu.mosuserver.global.annotation.LoginIdPattern;
import life.mosu.mosuserver.global.annotation.PasswordPattern;
import life.mosu.mosuserver.global.annotation.PhoneNumberPattern;
import org.springframework.security.crypto.password.PasswordEncoder;

public record SignUpAccountRequest(
Expand All @@ -18,20 +23,40 @@ public record SignUpAccountRequest(
)
@LoginIdPattern
String id,

@Schema(
description = "비밀번호는 8~20자의 영문 대/소문자, 숫자, 특수문자를 모두 포함해야 합니다.",
example = "Mosu!1234"
)
@PasswordPattern String password,

@Schema(description = "사용자 이름", example = "홍길동", required = true)
@NotBlank(message = "이름은 필수입니다.")
String userName,

@Schema(description = "생년월일 (yyyy-MM-dd 형식)", example = "2005-05-10", required = true)
@JsonFormat(pattern = "yyyy-MM-dd")
@NotNull(message = "생년월일은 필수입니다.")
LocalDate birth,

@Schema(description = "성별 (MALE 또는 FEMALE)", example = "MALE", required = true)
@NotBlank(message = "성별은 필수입니다.")
String gender,

@Schema(description = "휴대폰 번호", example = "010-1234-5678", required = true)
@NotBlank(message = "휴대폰 번호는 필수입니다.")
@PhoneNumberPattern
String phoneNumber,

SignUpServiceTermRequest serviceTermRequest
) {

public UserJpaEntity toAuthEntity(PasswordEncoder passwordEncoder) {
return UserJpaEntity.builder()
.loginId(id)
.password(passwordEncode(passwordEncoder, password))
.agreedToTermsOfService(serviceTermRequest.agreedToTermsOfService())
.agreedToPrivacyPolicy(serviceTermRequest.agreedToPrivacyPolicy())
.agreedToTermsOfService(true)
.agreedToPrivacyPolicy(true)
.agreedToMarketing(serviceTermRequest.agreedToMarketing())
.gender(Gender.PENDING)
.provider(AuthProvider.MOSU)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
import life.mosu.mosuserver.application.user.MyUserService;
import life.mosu.mosuserver.global.annotation.UserId;
import life.mosu.mosuserver.global.util.ApiResponseWrapper;
import life.mosu.mosuserver.presentation.user.dto.ChangePasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.ChangePasswordResponse;
import life.mosu.mosuserver.presentation.user.dto.FindLoginIdRequest;
import life.mosu.mosuserver.presentation.user.dto.FindLoginIdResponse;
import life.mosu.mosuserver.presentation.user.dto.FindPasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.request.ChangePasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.request.FindLoginIdRequest;
import life.mosu.mosuserver.presentation.user.dto.request.FindPasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.response.ChangePasswordResponse;
import life.mosu.mosuserver.presentation.user.dto.response.FindLoginIdResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import life.mosu.mosuserver.global.annotation.UserId;
import life.mosu.mosuserver.global.util.ApiResponseWrapper;
import life.mosu.mosuserver.presentation.user.dto.ChangePasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.ChangePasswordResponse;
import life.mosu.mosuserver.presentation.user.dto.FindPasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.request.ChangePasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.request.FindPasswordRequest;
import life.mosu.mosuserver.presentation.user.dto.response.ChangePasswordResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@
import life.mosu.mosuserver.application.user.UserService;
import life.mosu.mosuserver.global.annotation.UserId;
import life.mosu.mosuserver.global.util.ApiResponseWrapper;
import life.mosu.mosuserver.presentation.user.dto.CustomerKeyResponse;
import life.mosu.mosuserver.presentation.user.dto.IsLoginIdAvailableResponse;
import life.mosu.mosuserver.presentation.user.dto.request.IsLoginIdAvailableResponse;
import life.mosu.mosuserver.presentation.user.dto.response.CustomerKeyResponse;
import life.mosu.mosuserver.presentation.user.dto.response.UserInfoResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -20,6 +22,17 @@ public class UserController implements UserControllerDocs {

private final UserService userService;

@GetMapping("/info")
@PreAuthorize("isAuthenticated() and hasRole('PENDING')")
public ResponseEntity<ApiResponseWrapper<UserInfoResponse>> getUserInfo(
@UserId Long userId
) {
UserInfoResponse response = userService.getUserInfo(userId);

return ResponseEntity.ok(
ApiResponseWrapper.success(HttpStatus.OK, "User 정보 조회 성공", response));
}

@GetMapping("/customer-key")
public ResponseEntity<ApiResponseWrapper<CustomerKeyResponse>> getCustomerKey(
@UserId final Long userId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import life.mosu.mosuserver.global.annotation.UserId;
import life.mosu.mosuserver.global.util.ApiResponseWrapper;
import life.mosu.mosuserver.presentation.user.dto.CustomerKeyResponse;
import life.mosu.mosuserver.presentation.user.dto.IsLoginIdAvailableResponse;
import life.mosu.mosuserver.presentation.user.dto.request.IsLoginIdAvailableResponse;
import life.mosu.mosuserver.presentation.user.dto.response.CustomerKeyResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestParam;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package life.mosu.mosuserver.presentation.user.dto;
package life.mosu.mosuserver.presentation.user.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import life.mosu.mosuserver.global.annotation.PasswordPattern;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package life.mosu.mosuserver.presentation.user.dto;
package life.mosu.mosuserver.presentation.user.dto.request;

import life.mosu.mosuserver.global.annotation.PhoneNumberPattern;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package life.mosu.mosuserver.presentation.user.dto;
package life.mosu.mosuserver.presentation.user.dto.request;

import life.mosu.mosuserver.global.annotation.LoginIdPattern;
import life.mosu.mosuserver.global.annotation.PhoneNumberPattern;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package life.mosu.mosuserver.presentation.user.dto;
package life.mosu.mosuserver.presentation.user.dto.request;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The IsLoginIdAvailableResponse class is used as a response body for an endpoint. According to the new package structure refactoring in this PR, it should be in the ...dto.response package, not ...dto.request. Please move it to the correct package for consistency.

Suggested change
package life.mosu.mosuserver.presentation.user.dto.request;
package life.mosu.mosuserver.presentation.user.dto.response;


public record IsLoginIdAvailableResponse(
Boolean isLoginIdAvailable
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package life.mosu.mosuserver.presentation.user.dto;
package life.mosu.mosuserver.presentation.user.dto.response;

public record ChangePasswordResponse(
Boolean isSuccess
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package life.mosu.mosuserver.presentation.user.dto;
package life.mosu.mosuserver.presentation.user.dto.response;

public record CustomerKeyResponse(
String customerKey
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package life.mosu.mosuserver.presentation.user.dto;
package life.mosu.mosuserver.presentation.user.dto.response;

public record FindLoginIdResponse(
String loginId
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package life.mosu.mosuserver.presentation.user.dto.response;

import java.time.LocalDate;
import life.mosu.mosuserver.domain.user.UserJpaEntity;

public record UserInfoResponse(
String name,
LocalDate birth,
String phoneNumber,
String gender
) {

public static UserInfoResponse from(UserJpaEntity user) {
return new UserInfoResponse(
user.getName(),
user.getBirth(),
user.getPhoneNumber(),
user.getGender() != null ? user.getGender().name() : null
);
}
}