Skip to content

Commit

Permalink
✏️ OAuth 계정 연동 실패 해결 및 테스트 케이스 추가 (#45)
Browse files Browse the repository at this point in the history
* rename: user sync mapper 불필요한 주석 제거

* feat: oauth service create 메서드 추가

* feat: oauth sign mapper 내에서 entity 생성 메서드 호출

* rename: auth api 일반 회원가입 이력 존재 시 예외 문서 추가

* rename: oauth api 소셜 로그인 이력 존재 시 예외 문서 추가

* test: [2] 소셜 로그인 이력이 있는 경우, 200 ok를 반환하고 oauth 필드가 true고 username 필드가 존재

* rename: 소셜인증 회원가입, 계정 연동 시 성공 응답 반환 문서 추가

* test: [3-1] 일반 회원가입 테스트

* test: [3-2] 소셜 계정 연동 회원가입 테스트

* test: 일반 회원가입 전화 검증 api 예외 테스트 케이스 추가

* test: url별로 inner 클래스로 테스트 분리

* test: auth test order 지정

* chore: test 환경에서 sql log 출력 옵션 true로 변경

* test: oauth controller 통합 테스트 내부 클래스 구분

* chore: wiremock 의존성 추가

* test: feign mock test 적용 (실제로 사용은 안 함)

* test: [1] 소셜 로그인 통합 테스트

* fix: oauth link 시, 기존 계정 없는 경우 예외 처리

* test: [4-1] 소셜 회원가입 계정 연동

* feat: oauth entity tostring 재정의

* fix: oauth 회원가입 시, 기존 계정이 하나라도 존재하면 예외 처리

* test: [4-2] 소셜 회원가입

* test: 저장된 oauth 정보 조회 추가
  • Loading branch information
psychology50 authored Apr 13, 2024
1 parent 5f585c3 commit 2434130
Show file tree
Hide file tree
Showing 14 changed files with 1,015 additions and 41 deletions.
1 change: 1 addition & 0 deletions pennyway-app-external-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ dependencies {
testImplementation "org.testcontainers:junit-jupiter:1.19.7"
testImplementation "org.testcontainers:mysql:1.19.7"
testImplementation "com.redis.testcontainers:testcontainers-redis-junit:1.6.4"
testImplementation "org.springframework.cloud:spring-cloud-contract-wiremock:4.1.2"
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ public interface AuthApi {
}
""")
})),
@ApiResponse(responseCode = "400", content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name = "일반 회원가입 계정이 이미 존재함", value = """
{
"code": "4004",
"message": "이미 회원가입한 유저입니다."
}
""")
})),
@ApiResponse(responseCode = "401", content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name = "검증 실패", value = """
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.headers.Header;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand Down Expand Up @@ -107,6 +109,14 @@ public interface OauthApi {
}
""")
})),
@ApiResponse(responseCode = "400", content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name = "해당 provider로 로그인한 이력이 이미 존재함", value = """
{
"code": "4004",
"message": "이미 해당 제공자로 가입된 사용자입니다."
}
""")
})),
@ApiResponse(responseCode = "401", content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name = "인증코드 불일치", value = """
{
Expand All @@ -130,11 +140,45 @@ public interface OauthApi {
@Parameter(name = "provider", description = "소셜 제공자", examples = {
@ExampleObject(name = "카카오", value = "kakao"), @ExampleObject(name = "애플", value = "apple"), @ExampleObject(name = "구글", value = "google")
}, required = true, in = ParameterIn.QUERY)
@ApiResponse(responseCode = "200", description = "로그인 성공",
headers = {
@Header(name = "Set-Cookie", description = "리프레시 토큰", schema = @Schema(type = "string"), required = true),
@Header(name = "Authorization", description = "액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true)
},
content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name = "성공", value = """
{
"code": "2000",
"data": {
"user": {
"id": 1
}
}
}
""")
}))
ResponseEntity<?> linkAuth(@RequestParam Provider provider, @RequestBody @Validated SignUpReq.SyncWithAuth request);

@Operation(summary = "[4-2] 소셜 회원가입", description = "회원 정보 입력 후 회원가입")
@Parameter(name = "provider", description = "소셜 제공자", examples = {
@ExampleObject(name = "카카오", value = "kakao"), @ExampleObject(name = "애플", value = "apple"), @ExampleObject(name = "구글", value = "google")
}, required = true, in = ParameterIn.QUERY)
@ApiResponse(responseCode = "200", description = "로그인 성공",
headers = {
@Header(name = "Set-Cookie", description = "리프레시 토큰", schema = @Schema(type = "string"), required = true),
@Header(name = "Authorization", description = "액세스 토큰", schema = @Schema(type = "string", format = "jwt"), required = true)
},
content = @Content(mediaType = "application/json", examples = {
@ExampleObject(name = "성공", value = """
{
"code": "2000",
"data": {
"user": {
"id": 1
}
}
}
""")
}))
ResponseEntity<?> signUp(@RequestParam Provider provider, @RequestBody @Validated SignUpReq.Oauth request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ public User saveUser(SignUpReq.OauthInfo request, Pair<Boolean, String> isSignUp
if (isSignUpUser.getLeft().equals(Boolean.TRUE)) {
user = userService.readUserByUsername(isSignUpUser.getRight())
.orElseThrow(() -> new UserErrorException(UserErrorCode.NOT_FOUND));
Oauth.of(provider, oauthId, user);
} else {
user = User.builder()
.username(request.username())
Expand All @@ -51,9 +50,11 @@ public User saveUser(SignUpReq.OauthInfo request, Pair<Boolean, String> isSignUp
.role(Role.USER)
.profileVisibility(ProfileVisibility.PUBLIC).build();
userService.createUser(user);
Oauth.of(provider, oauthId, user);
}

Oauth oauth = Oauth.of(provider, oauthId, user);
oauthService.createOauth(oauth);

return user;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,16 @@ public Pair<Boolean, String> isGeneralSignUpAllowed(String phone) {
public Pair<Boolean, String> isOauthSignUpAllowed(Provider provider, String phone) {
Optional<User> user = userService.readUserByPhone(phone);

// user 정보 없으면 Pair.of(Boolean.FALSE, null) 반환
if (user.isEmpty()) {
log.info("회원가입 이력이 없는 사용자입니다. phone: {}", phone);
return Pair.of(Boolean.FALSE, null);
}

// 같은 provider로 가입한 정보가 있는지 확인
if (oauthService.isExistOauthAccount(user.get().getId(), provider)) {
log.info("이미 동일한 Provider로 가입된 사용자입니다. phone: {}, provider: {}", phone, provider);
return null;
}

// user 정보 있으면 Pair.of(Boolean.TRUE, user.get().getUsername()) 반환
return Pair.of(Boolean.TRUE, user.get().getUsername());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ public Pair<Long, Jwts> signUp(Provider provider, SignUpReq.OauthInfo request) {
phoneVerificationMapper.isValidCode(PhoneVerificationDto.VerifyCodeReq.from(request), PhoneVerificationType.getOauthSignUpTypeByProvider(provider));
Pair<Boolean, String> isSignUpUser = checkSignUpUserNotOauthByProvider(provider, request.phone());

if (isSignUpUser.getLeft().equals(Boolean.FALSE) && request.username() == null)
throw new OauthException(OauthErrorCode.INVALID_OAUTH_SYNC_REQUEST);
if (isSignUpUser.getLeft().equals(Boolean.TRUE) && request.username() != null)
throw new OauthException(OauthErrorCode.INVALID_OAUTH_SYNC_REQUEST);

OidcDecodePayload payload = oauthOidcHelper.getPayload(provider, request.idToken());
User user = userOauthSignMapper.saveUser(request, isSignUpUser, provider, payload.sub());
phoneVerificationService.delete(request.phone(), PhoneVerificationType.getOauthSignUpTypeByProvider(provider));
Expand Down
Loading

0 comments on commit 2434130

Please sign in to comment.