Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough인증 사용자가 OAuth 연동 상태를 조회하고 새 OAuth 계정을 연동할 수 있는 API와 엔티티/레포지토리/서비스, OAuth 로그인 흐름의 연동(LINK) 처리 및 DB 마이그레이션이 추가되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant UserController
participant OAuthTokenVerifier
participant UserOAuthAccountService
participant UserOAuthAccountRepository
participant Database
rect rgba(100,150,200,0.5)
Note over Client,Database: API 기반 연동 (POST /users/me/oauth/links)
Client->>UserController: POST /users/me/oauth/links (provider, accessToken, idToken)
UserController->>OAuthTokenVerifier: 검증 및 providerId, email 추출
OAuthTokenVerifier-->>UserController: providerId, email
UserController->>UserOAuthAccountService: linkOAuthAccount(userId, provider, providerId, email)
UserOAuthAccountService->>UserOAuthAccountRepository: 조회/저장/삭제/복원 로직 호출
UserOAuthAccountRepository->>Database: SELECT/INSERT/UPDATE/DELETE
Database-->>UserOAuthAccountRepository: 결과
UserOAuthAccountService-->>UserController: 완료
UserController-->>Client: 200 OK / 에러 코드
end
sequenceDiagram
participant Browser
participant OAuth2RedirectUriSaveFilter
participant OAuth Provider
participant OAuth2LoginSuccessHandler
participant OAuthLoginHelper
participant UserOAuthAccountService
participant Database
rect rgba(150,100,200,0.5)
Note over Browser,Database: OAuth 로그인 → 연동 모드 (oauth_mode=LINK)
Browser->>OAuth2RedirectUriSaveFilter: /oauth2/authorization/...?oauth_mode=LINK&redirect_uri=...
OAuth2RedirectUriSaveFilter-->>Browser: 세션에 oauth_mode=LINK 저장 후 리다이렉트
Browser->>OAuth Provider: OAuth 인증
OAuth Provider-->>OAuth2LoginSuccessHandler: 콜백 (OAuth 사용자 정보)
OAuth2LoginSuccessHandler->>OAuthLoginHelper: 사용자 정보 처리 (providerId, email 추출)
OAuthLoginHelper->>UserOAuthAccountService: providerId 기반 복구/연동 처리
UserOAuthAccountService->>Database: 계정 생성/업데이트/복원
Database-->>UserOAuthAccountService: 완료
OAuth2LoginSuccessHandler-->>Browser: redirect?oauth_link=success 또는 oauth_link=failed
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
이 PR은 계정 연동 기능을 추가합니다. 사용자가 여러 OAuth 제공자(Google, Kakao, Naver, Apple)를 하나의 계정에 연동할 수 있도록 user_oauth_account 테이블과 관련 서비스/엔드포인트를 추가합니다. OAuth 콜백 흐름에 link 모드를 도입하고, 수동 연동을 위한 REST API도 제공합니다.
Changes:
user_oauth_account테이블을 추가하고, 기존 사용자의 OAuth 정보를 마이그레이션하며, 연동 상태 조회(GET /users/me/oauth/links) 및 연동 수행(POST /users/me/oauth/links) API를 추가- OAuth 인증 흐름에
oauth_mode=link파라미터를 통한 연동 모드를 지원하도록OAuth2RedirectUriSaveFilter와OAuth2LoginSuccessHandler를 확장 - 로그인 시
user_oauth_account테이블을 통해 연동된 계정으로 사용자를 찾는 로직을OAuthLoginHelper.findUserByProvider에 추가
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
V43__add_user_oauth_account_table.sql |
OAuth 계정 연동 테이블 생성 및 기존 데이터 마이그레이션 |
UserOAuthAccount.java |
OAuth 연동 계정 JPA 엔티티 |
UserOAuthAccountRepository.java |
OAuth 연동 계정 레포지토리 인터페이스 |
UserOAuthAccountService.java |
연동 상태 조회 및 연동 처리 서비스 |
OAuthLoginHelper.java |
로그인 시 연동 테이블을 통한 사용자 조회 및 자동 연동 보장 로직 |
OAuth2RedirectUriSaveFilter.java |
oauth_mode 세션 저장 지원 |
OAuth2LoginSuccessHandler.java |
연동 모드 처리, OAuthContext 도입, extractProviderId 리팩토링 |
UserController.java / UserApi.java |
연동 상태 조회 및 수동 연동 API 엔드포인트 |
OAuthLinkRequest.java / OAuthLinkStatusResponse.java / OAuthProviderLinkStatus.java |
연동 관련 DTO |
RefreshTokenService.java |
extractUserId public 메서드 추가 |
UserService.java |
회원가입 시 linkPrimaryOAuthAccount 호출 |
SecurityPaths.java |
테스트 페이지 public 경로 추가 |
WebConfig.java |
테스트 페이지 리소스 핸들러 추가 |
oauth-link-test.html |
OAuth 연동 테스트 페이지 |
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
Outdated
Show resolved
Hide resolved
src/main/java/gg/agit/konect/global/auth/oauth/OAuth2LoginSuccessHandler.java
Show resolved
Hide resolved
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java`:
- Around line 57-69: The current non-atomic check-then-insert in
UserOAuthAccountService (using
userOAuthAccountRepository.findUserByProviderAndProviderId and then
userOAuthAccountRepository.save) can race under concurrent requests; wrap the
save call with handling for DataIntegrityViolationException and convert that
exception to CustomException.of(ApiResponseCode.OAUTH_PROVIDER_ALREADY_LINKED),
or alternatively perform a locked lookup/update (e.g., a provider/providerId
SELECT ... FOR UPDATE equivalent in your repository) before inserting to ensure
the provider/providerId uniqueness is enforced at the application level; make
this change around the code that calls findUserByProviderAndProviderId,
findByUserIdAndProvider, and save so concurrent inserts map to the proper
business error instead of a 500.
- Around line 88-96: The current save path
(userOAuthAccountRepository.findByUserIdAndProvider ->
save(UserOAuthAccount.of(...)) ) does not validate that provider+providerId
isn't already owned by another user; update the flow to first call
findUserByProviderAndProviderId(provider, providerId) to pre-check ownership,
and if it exists and its userId differs throw the OAUTH_PROVIDER_ALREADY_LINKED
error (same behavior as linkOAuthAccount), otherwise proceed to either
updateOauthEmail on the existing account or save UserOAuthAccount.of(...);
ensure you reference userOAuthAccountRepository.findByUserIdAndProvider,
findUserByProviderAndProviderId, updateOauthEmail, UserOAuthAccount.of,
providerId and throw OAUTH_PROVIDER_ALREADY_LINKED accordingly.
In
`@src/main/java/gg/agit/konect/global/auth/oauth/OAuth2LoginSuccessHandler.java`:
- Around line 180-198: linkOAuthAccount(...) succeeds but saving Apple refresh
token in saveAppleRefreshTokenForUser(...) can throw non-CustomException errors
which currently bypass the catch and change redirect behavior; adjust by moving
the Apple token save into its own try-catch: after calling
userOAuthAccountService.linkOAuthAccount(currentUserId, provider, providerId,
email) wrap the Provider.APPLE branch and saveAppleRefreshTokenForUser(provider,
user, appleRefreshToken) in a separate try { ... } catch (Exception e) {
logger.error("Failed to save Apple refresh token for user {}", currentUserId,
e); } so that failures to persist the token are logged but do not convert a
successful link into a failed redirect
(response.sendRedirect(oauthLoginHelper.appendQueryParameter(safeRedirect,
"oauth_link", "success")) remains outside that inner try).
In `@src/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.java`:
- Around line 155-168: Race on insert can cause DataIntegrityViolationException;
surround the userOAuthAccountRepository.save(UserOAuthAccount.of(...)) call in a
try/catch that catches DataIntegrityViolationException, then re-query the
repository (e.g., via
userOAuthAccountRepository.findByProviderAndProviderId(provider, providerId) or
findByUserIdAndProvider) to determine the owner: if the existing record belongs
to the same user, treat as idempotent and return, otherwise convert and throw
the appropriate business exception with code OAUTH_PROVIDER_ALREADY_LINKED; keep
the existing update path that modifies oauthEmail for linked.isPresent()
unchanged and only apply this exception-handling logic around the new-save path
in OAuthLoginHelper.
- Around line 160-165: 현재 OAuthLoginHelper의 분기에서 providerId가
account.getProviderId()와 불일치할 때 단순히 return하여 다른 provider의 연결 시도가 무시되므로 보안 리스크가
발생합니다; 수정 방법은 providerId 불일치 분기에서 조용히 반환하지 말고 즉시 OAUTH_PROVIDER_ALREADY_LINKED
예외를 던져 로그인 흐름을 중단하도록 변경하고, 기존 로직(조건문 내부의 account.updateOauthEmail(oauthEmail)와
userOAuthAccountRepository.save(account))은 providerId 일치 및 oauthEmail 유효성 검사 시에만
실행되게 유지하세요.
In `@src/main/java/gg/agit/konect/global/config/SecurityPaths.java`:
- Line 8: Remove "/oauth-link-test.html" from the PUBLIC_PATHS constant in
SecurityPaths and instead expose it only under a dev/local profile check: locate
the PUBLIC_PATHS array (symbol PUBLIC_PATHS in class SecurityPaths) and delete
that entry, then add logic that conditionally registers or appends a
DEV_ONLY_PUBLIC_PATHS list (e.g., a new constant DEV_PUBLIC_PATHS containing
"/oauth-link-test.html") only when the active Spring profile matches dev or
local (use the existing profile/config access code or Environment bean); ensure
production builds never include the dev-only path in the interceptor exclusion
list.
In `@src/main/resources/db/migration/V43__add_user_oauth_account_table.sql`:
- Around line 10-11: The FK constraint fk_user_oauth_account_user is missing an
explicit ON DELETE clause; update the constraint definition (the FOREIGN KEY ...
REFERENCES users(id) for fk_user_oauth_account_user) to include the desired
behavior (e.g., ON DELETE CASCADE) so the migration explicitly matches other
migrations (V1/V4/V20) and documents the delete semantics.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 44dae6d3-23a8-4139-8e67-6f08ddfd9ee3
📒 Files selected for processing (18)
src/main/java/gg/agit/konect/domain/user/controller/UserApi.javasrc/main/java/gg/agit/konect/domain/user/controller/UserController.javasrc/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.javasrc/main/java/gg/agit/konect/domain/user/dto/OAuthLinkStatusResponse.javasrc/main/java/gg/agit/konect/domain/user/dto/OAuthProviderLinkStatus.javasrc/main/java/gg/agit/konect/domain/user/model/UserOAuthAccount.javasrc/main/java/gg/agit/konect/domain/user/repository/UserOAuthAccountRepository.javasrc/main/java/gg/agit/konect/domain/user/service/RefreshTokenService.javasrc/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.javasrc/main/java/gg/agit/konect/domain/user/service/UserService.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuth2LoginSuccessHandler.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuth2RedirectUriSaveFilter.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.javasrc/main/java/gg/agit/konect/global/code/ApiResponseCode.javasrc/main/java/gg/agit/konect/global/config/SecurityPaths.javasrc/main/java/gg/agit/konect/global/config/WebConfig.javasrc/main/resources/db/migration/V43__add_user_oauth_account_table.sqlsrc/main/resources/static/oauth-link-test.html
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
src/main/java/**/*.java
⚙️ CodeRabbit configuration file
src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.
- 코멘트는 반드시 한국어로 작성한다.
- 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
- 각 코멘트 첫 줄에 심각도를
[LEVEL: high|medium|low]형식으로 반드시 표기한다.- 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
- 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
- 가능하면 재현 조건 및 실패 시나리오도 포함한다.
- 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
- 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
- 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
- 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
- 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.
Files:
src/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuth2RedirectUriSaveFilter.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.javasrc/main/java/gg/agit/konect/domain/user/service/RefreshTokenService.javasrc/main/java/gg/agit/konect/domain/user/repository/UserOAuthAccountRepository.javasrc/main/java/gg/agit/konect/global/config/WebConfig.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuth2LoginSuccessHandler.javasrc/main/java/gg/agit/konect/domain/user/dto/OAuthProviderLinkStatus.javasrc/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.javasrc/main/java/gg/agit/konect/domain/user/service/UserService.javasrc/main/java/gg/agit/konect/domain/user/dto/OAuthLinkStatusResponse.javasrc/main/java/gg/agit/konect/domain/user/controller/UserApi.javasrc/main/java/gg/agit/konect/global/config/SecurityPaths.javasrc/main/java/gg/agit/konect/domain/user/controller/UserController.javasrc/main/java/gg/agit/konect/global/code/ApiResponseCode.javasrc/main/java/gg/agit/konect/domain/user/model/UserOAuthAccount.java
**/*
⚙️ CodeRabbit configuration file
**/*: 공통 리뷰 톤 가이드:
- 모든 코멘트는 첫 줄에
[LEVEL: ...]태그를 포함한다.- 과장된 표현 없이 사실 기반으로 작성한다.
- 한 코멘트에는 하나의 이슈만 다룬다.
- 코드 예시가 필요하면 최소 수정 예시를 제시한다.
- 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.
Files:
src/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.javasrc/main/resources/static/oauth-link-test.htmlsrc/main/java/gg/agit/konect/global/auth/oauth/OAuth2RedirectUriSaveFilter.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.javasrc/main/java/gg/agit/konect/domain/user/service/RefreshTokenService.javasrc/main/java/gg/agit/konect/domain/user/repository/UserOAuthAccountRepository.javasrc/main/java/gg/agit/konect/global/config/WebConfig.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuth2LoginSuccessHandler.javasrc/main/java/gg/agit/konect/domain/user/dto/OAuthProviderLinkStatus.javasrc/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.javasrc/main/java/gg/agit/konect/domain/user/service/UserService.javasrc/main/java/gg/agit/konect/domain/user/dto/OAuthLinkStatusResponse.javasrc/main/resources/db/migration/V43__add_user_oauth_account_table.sqlsrc/main/java/gg/agit/konect/domain/user/controller/UserApi.javasrc/main/java/gg/agit/konect/global/config/SecurityPaths.javasrc/main/java/gg/agit/konect/domain/user/controller/UserController.javasrc/main/java/gg/agit/konect/global/code/ApiResponseCode.javasrc/main/java/gg/agit/konect/domain/user/model/UserOAuthAccount.java
src/main/resources/db/migration/**/*.sql
⚙️ CodeRabbit configuration file
src/main/resources/db/migration/**/*.sql: Flyway 마이그레이션 리뷰 규칙:
- 버전 파일명 규칙(V{number}__{description}.sql) 위반 여부를 우선 확인한다.
- 이미 배포된 마이그레이션 수정/재번호 부여 위험이 있으면 반드시 차단 코멘트를 남긴다.
- 파괴적 변경(drop, rename 등)은 롤백 가능성과 운영 영향 관점에서 검토한다.
Files:
src/main/resources/db/migration/V43__add_user_oauth_account_table.sql
🔇 Additional comments (11)
src/main/java/gg/agit/konect/domain/user/service/UserService.java (1)
65-65: [LEVEL: low] LGTM - 트랜잭션 범위 내 OAuth 연동 처리
signup메서드가@Transactional로 선언되어 있어linkPrimaryOAuthAccount호출 시 예외가 발생하면 사용자 생성도 함께 롤백됩니다. 적절한 트랜잭션 설계입니다.Also applies to: 101-102
src/main/resources/db/migration/V43__add_user_oauth_account_table.sql (1)
20-36: [LEVEL: low] LGTM - 마이그레이션 데이터 이전 로직기존 사용자의 OAuth 데이터를 안전하게 마이그레이션합니다.
deleted_at IS NULL및NOT EXISTS조건으로 활성 사용자만 대상으로 하고 중복 삽입을 방지합니다.src/main/java/gg/agit/konect/domain/user/controller/UserApi.java (1)
58-64: [LEVEL: low] LGTM - OAuth 연동 API 추가
@UserId어노테이션을 사용하여 인증된 사용자만 접근 가능하도록 보호되어 있습니다. Context snippet에서 확인한LoginCheckInterceptor와LoginUserArgumentResolver가 JWT 검증을 수행합니다.src/main/java/gg/agit/konect/global/auth/oauth/OAuth2RedirectUriSaveFilter.java (1)
29-42: [LEVEL: low] LGTM - OAuth 모드 세션 처리
oauth_mode가 유효한redirect_uri와 함께 제공될 때만 세션에 저장되며, LINK 모드가 아닌 경우 기존 속성을 제거하여 세션 오염을 방지합니다.src/main/java/gg/agit/konect/domain/user/controller/UserController.java (2)
90-117: [LEVEL: low] LGTM - OAuth 연동 엔드포인트 구현인증된 사용자 검증(
@UserId), provider 유효성 검사, 토큰 검증 후 서비스 위임까지 적절한 흐름입니다. Verifier 조회가 stream으로 되어 있으나 provider 개수가 4개로 고정되어 성능 문제 없습니다.
153-163: [LEVEL: low] LGTM - Provider 변환 메서드
null/빈 문자열 검증 후 대문자 변환하여 enum 매핑합니다.IllegalArgumentException을CustomException으로 래핑하여 일관된 에러 응답을 보장합니다.src/main/java/gg/agit/konect/domain/user/repository/UserOAuthAccountRepository.java (1)
14-26: [LEVEL: low] LGTM - Repository 구조 및 CRUD 메서드Spring Data Repository 패턴을 따르며, JOIN FETCH로 N+1 문제를 방지했습니다.
save와delete메서드가 명시적으로 선언되어 있어 인터페이스 사용 의도가 명확합니다.Also applies to: 58-60
src/main/java/gg/agit/konect/global/auth/oauth/OAuth2LoginSuccessHandler.java (3)
201-213: [LEVEL: low] LGTM - 현재 사용자 ID 추출 로직refresh token 쿠키에서 사용자 ID를 추출하며, 토큰이 없거나 유효하지 않은 경우
null을 반환하여 호출부에서 적절히 처리합니다.CustomException캐치로 예외 상황에서도 안전하게 동작합니다.
215-231: [LEVEL: low] LGTM - OAuth 컨텍스트 소비 및 정리세션에서 redirect_uri와 oauth_mode를 읽은 후 즉시 제거하여 재사용을 방지합니다.
OAuth2RedirectUriSaveFilter의 상수를 참조하여 키 불일치를 방지했습니다.
256-268: [LEVEL: low] LGTM - Provider ID 추출 리팩터링Apple과 다른 provider의 providerId 추출 로직을 통합했습니다. Apple의 경우
sub속성을 우선 확인하고 fallback으로getName()을 사용하는 방어적 처리가 적용되었습니다.src/main/java/gg/agit/konect/domain/user/model/UserOAuthAccount.java (1)
23-79: [LEVEL: low] LGTM - UserOAuthAccount 엔티티 설계JPA 엔티티가 적절하게 설계되었습니다.
LAZYfetch로 N+1 방지,BaseEntity상속으로 감사 컬럼 재사용, unique constraints가 마이그레이션 파일과 일치합니다.updateOauthEmail메서드로 email 변경만 허용하여 불변성을 최대한 유지했습니다.
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
Outdated
Show resolved
Hide resolved
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
Outdated
Show resolved
Hide resolved
src/main/java/gg/agit/konect/global/auth/oauth/OAuth2LoginSuccessHandler.java
Show resolved
Hide resolved
src/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.java
Outdated
Show resolved
Hide resolved
src/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.java`:
- Around line 13-17: OAuthLinkRequest 클래스의 accessToken 및 idToken 필드에는 Swagger에만
필수로 표시된(requiredMode = REQUIRED) 반면 서버측 Bean Validation이 없어 null/빈 값이 통과될 수
있습니다; OAuthLinkRequest의 accessToken 및 idToken 멤버에 provider 필드처럼 `@NotBlank` 어노테이션을
추가하거나 실제로 optional이면 requiredMode 값을 조정하여 스펙과 검증을 일치시키세요. Ensure the `@NotBlank`
import is added and validation will be applied when binding requests.
In
`@src/main/resources/db/migration/V44__add_on_delete_cascade_to_user_oauth_account_fk.sql`:
- Around line 1-5: The migration runs ALTER TABLE on user_oauth_account twice
(DROP FOREIGN KEY fk_user_oauth_account_user then ADD CONSTRAINT
fk_user_oauth_account_user), which increases metadata lock time; merge these
into a single ALTER TABLE user_oauth_account statement that performs both
operations in one command (DROP FOREIGN KEY fk_user_oauth_account_user, ADD
CONSTRAINT fk_user_oauth_account_user ... WITH the desired ON DELETE CASCADE) so
the DROP and ADD occur atomically and reduce locking time.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: f8fc72a5-6611-4728-a310-eff339b07185
📒 Files selected for processing (7)
src/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.javasrc/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuth2LoginSuccessHandler.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.javasrc/main/java/gg/agit/konect/global/code/ApiResponseCode.javasrc/main/java/gg/agit/konect/global/config/WebConfig.javasrc/main/resources/db/migration/V44__add_on_delete_cascade_to_user_oauth_account_fk.sql
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
src/main/resources/db/migration/**/*.sql
⚙️ CodeRabbit configuration file
src/main/resources/db/migration/**/*.sql: Flyway 마이그레이션 리뷰 규칙:
- 버전 파일명 규칙(V{number}__{description}.sql) 위반 여부를 우선 확인한다.
- 이미 배포된 마이그레이션 수정/재번호 부여 위험이 있으면 반드시 차단 코멘트를 남긴다.
- 파괴적 변경(drop, rename 등)은 롤백 가능성과 운영 영향 관점에서 검토한다.
Files:
src/main/resources/db/migration/V44__add_on_delete_cascade_to_user_oauth_account_fk.sql
**/*
⚙️ CodeRabbit configuration file
**/*: 공통 리뷰 톤 가이드:
- 모든 코멘트는 첫 줄에
[LEVEL: ...]태그를 포함한다.- 과장된 표현 없이 사실 기반으로 작성한다.
- 한 코멘트에는 하나의 이슈만 다룬다.
- 코드 예시가 필요하면 최소 수정 예시를 제시한다.
- 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.
Files:
src/main/resources/db/migration/V44__add_on_delete_cascade_to_user_oauth_account_fk.sqlsrc/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.javasrc/main/java/gg/agit/konect/global/config/WebConfig.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.javasrc/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.javasrc/main/java/gg/agit/konect/global/code/ApiResponseCode.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuth2LoginSuccessHandler.java
src/main/java/**/*.java
⚙️ CodeRabbit configuration file
src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.
- 코멘트는 반드시 한국어로 작성한다.
- 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
- 각 코멘트 첫 줄에 심각도를
[LEVEL: high|medium|low]형식으로 반드시 표기한다.- 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
- 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
- 가능하면 재현 조건 및 실패 시나리오도 포함한다.
- 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
- 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
- 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
- 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
- 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.
Files:
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.javasrc/main/java/gg/agit/konect/global/config/WebConfig.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.javasrc/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.javasrc/main/java/gg/agit/konect/global/code/ApiResponseCode.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuth2LoginSuccessHandler.java
🔇 Additional comments (6)
src/main/java/gg/agit/konect/global/code/ApiResponseCode.java (1)
112-113: LGTM!CONFLICT(409) 상태 코드와 에러 메시지가 OAuth 연동 충돌 시나리오에 적절하게 정의되어 있습니다.
src/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.java (1)
180-200: LGTM!이전 리뷰에서 지적된 동시성 이슈가 해결되었습니다:
providerId불일치 시OAUTH_PROVIDER_ALREADY_LINKED예외 발생DataIntegrityViolationException캐치 후 비즈니스 예외로 변환하여 500 응답 방지src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java (2)
52-88: LGTM!이전 리뷰에서 지적된 동시성 및 검증 이슈가 해결되었습니다:
provider+providerId소유자 선검증 추가DataIntegrityViolationException캐치 후 비즈니스 예외 변환providerId불일치 검증 추가
106-142: LGTM!
linkPrimaryOAuthAccount에 이전에 누락되었던provider+providerId소유자 검증 및 동시성 처리가linkOAuthAccount와 동일하게 추가되었습니다.src/main/java/gg/agit/konect/global/auth/oauth/OAuth2LoginSuccessHandler.java (1)
180-216: LGTM!이전 리뷰에서 지적된 Apple refresh token 저장 실패 처리가 해결되었습니다:
linkOAuthAccount성공 후 Apple 토큰 저장은 별도 try-catch로 분리- 토큰 저장 실패 시 경고 로그만 남기고 연동 성공 리다이렉트 유지
- 예상치 못한 예외도 catch하여 실패 리다이렉트로 처리 (500 응답 방지)
src/main/resources/db/migration/V44__add_on_delete_cascade_to_user_oauth_account_fk.sql (1)
6-7: [LEVEL: INFO]ON DELETE CASCADE자체는 참조 무결성 정합성 측면에서 타당합니다.사용자 삭제 시 연관
user_oauth_account정리를 DB 레벨에서 강제하는 방향은 의도에 맞습니다.
src/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.java
Outdated
Show resolved
Hide resolved
src/main/resources/db/migration/V44__add_on_delete_cascade_to_user_oauth_account_fk.sql
Show resolved
Hide resolved
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
Show resolved
Hide resolved
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
Outdated
Show resolved
Hide resolved
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
Outdated
Show resolved
Hide resolved
src/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.java
Outdated
Show resolved
Hide resolved
src/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.java
Outdated
Show resolved
Hide resolved
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java`:
- Around line 88-100: The code currently only checks user_oauth_account rows and
misses the User's primary OAuth fields, allowing duplicate or conflicting links;
update the link flow (methods restoreOrCleanupWithdrawnByLinkedProvider,
validateProviderOwnership, and the logic around
userOAuthAccountRepository.findByUserIdAndProvider / saveWithConflictHandling)
to also fetch and inspect the User entity's stored provider and providerId
(e.g., user.getProvider(), user.getProviderId()) before creating or altering
accounts, and enforce: if the User already has the same provider with a
different providerId throw OAUTH_PROVIDER_ALREADY_LINKED; if that providerId
belongs to another User throw OAUTH_ACCOUNT_ALREADY_LINKED; adjust
validateProviderOwnership to check both sources and ensure
saveWithConflictHandling respects these checks or runs them in the same
transactional boundary.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: b0bcb5dc-b736-47ad-97c8-8f065b6b64b2
📒 Files selected for processing (4)
src/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.javasrc/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.javasrc/main/java/gg/agit/konect/global/code/ApiResponseCode.java
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/**/*.java
⚙️ CodeRabbit configuration file
src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.
- 코멘트는 반드시 한국어로 작성한다.
- 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
- 각 코멘트 첫 줄에 심각도를
[LEVEL: high|medium|low]형식으로 반드시 표기한다.- 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
- 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
- 가능하면 재현 조건 및 실패 시나리오도 포함한다.
- 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
- 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
- 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
- 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
- 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.
Files:
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.javasrc/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.javasrc/main/java/gg/agit/konect/global/code/ApiResponseCode.java
**/*
⚙️ CodeRabbit configuration file
**/*: 공통 리뷰 톤 가이드:
- 모든 코멘트는 첫 줄에
[LEVEL: ...]태그를 포함한다.- 과장된 표현 없이 사실 기반으로 작성한다.
- 한 코멘트에는 하나의 이슈만 다룬다.
- 코드 예시가 필요하면 최소 수정 예시를 제시한다.
- 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.
Files:
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.javasrc/main/java/gg/agit/konect/global/auth/oauth/OAuthLoginHelper.javasrc/main/java/gg/agit/konect/domain/user/dto/OAuthLinkRequest.javasrc/main/java/gg/agit/konect/global/code/ApiResponseCode.java
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java`:
- Around line 89-100: The two-step ownership checks
(validatePrimaryProviderOwnership, validateProviderOwnership) and separate reads
of User and UserOAuthAccount
(userOAuthAccountRepository.findByUserIdAndProvider) allow race conditions
because primary ownership and oauth account storage are not serialized; fix by
making provider+providerId a single source-of-truth and/or serializing access:
add a DB-level unique constraint on (provider, providerId) in the
user_oauth_account table, then update saveWithConflictHandling and the flow in
UserOAuthAccountService so all checks and the insert/update happen inside one
transaction (or acquire a row/table lock on the provider/providerId prior to
validatePrimaryProviderOwnership and validateProviderOwnership), and handle
unique-constraint violations atomically to avoid dual ownership races. Ensure
isAlreadyPrimaryProviderLinked still returns quickly but do the authoritative
check against user_oauth_account within the same locked/transactional context.
- Around line 148-152: The try/catch is bypassing Spring's exception translation
because entityManager.flush() is called directly after
userOAuthAccountRepository.save(...); replace the save + entityManager.flush
pattern with userOAuthAccountRepository.saveAndFlush(UserOAuthAccount.of(...))
inside the try block (in UserOAuthAccountService) so Spring Data will translate
JPA persistence exceptions into DataIntegrityViolationException and the existing
catch (DataIntegrityViolationException e) for
ApiResponseCode.OAUTH_PROVIDER_ALREADY_LINKED will work as intended.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 010b0f85-c0ef-4cde-8e1a-6920ac08f559
📒 Files selected for processing (1)
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/**/*.java
⚙️ CodeRabbit configuration file
src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.
- 코멘트는 반드시 한국어로 작성한다.
- 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
- 각 코멘트 첫 줄에 심각도를
[LEVEL: high|medium|low]형식으로 반드시 표기한다.- 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
- 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
- 가능하면 재현 조건 및 실패 시나리오도 포함한다.
- 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
- 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
- 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
- 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
- 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.
Files:
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
**/*
⚙️ CodeRabbit configuration file
**/*: 공통 리뷰 톤 가이드:
- 모든 코멘트는 첫 줄에
[LEVEL: ...]태그를 포함한다.- 과장된 표현 없이 사실 기반으로 작성한다.
- 한 코멘트에는 하나의 이슈만 다룬다.
- 코드 예시가 필요하면 최소 수정 예시를 제시한다.
- 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.
Files:
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
Show resolved
Hide resolved
261ebd8 to
6a23aed
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java (1)
99-107:⚠️ Potential issue | 🔴 Critical[LEVEL: high] Line 99-107은
User와user_oauth_account를 각각 조회만 하고 실제 저장은 후자에만 걸려 있어, 같은provider/providerId로 OAuth 가입/로그인과 연동 요청이 동시에 들어오면 두 트랜잭션이 모두 검증을 통과할 수 있습니다.
재현되면 한 외부 계정이 서로 다른 사용자에게 주 계정과 연동 계정으로 이중 귀속되어 로그인·복구 불변식이 깨집니다.
제안: 백필 완료 전이라도 두 경로를 같은 소유권 저장소/잠금 키로 직렬화하거나, 소유권을 한 테이블로 일원화해 단일 유니크 제약으로 보장하세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java` around lines 99 - 107, The current sequence (restoreOrCleanupWithdrawnByLinkedProvider, validatePrimaryProviderOwnership, validateProviderOwnership, then UserOAuthAccountRepository.findByUserIdAndProvider followed by saveWithConflictHandling) allows a race where two concurrent OAuth bind/login flows both pass checks and create conflicting ownership; serialize ownership changes by acquiring a single ownership lock or mutex keyed on the provider+providerId (or the user id+provider) around the validate+find+save sequence, or refactor so ownership lives in one table with a DB-level UNIQUE constraint and perform the insert/update in a single transactional upsert to enforce single ownership atomically; apply this locking/upsert around the code path that calls validatePrimaryProviderOwnership, validateProviderOwnership, the findByUserIdAndProvider call, and saveWithConflictHandling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java`:
- Around line 151-154: The catch-all conversion of
DataIntegrityViolationException in UserOAuthAccountService (around the
saveAndFlush(UserOAuthAccount.of(...)) call) masks different unique-constraint
violations; update the catch to unwrap the root cause (e.g., check e.getCause()
and if it's a ConstraintViolationException / SQLException inspect constraint
name or SQL state/message) and map violations to the correct API codes: throw
CustomException.of(ApiResponseCode.OAUTH_ACCOUNT_ALREADY_LINKED) when the
constraint indicates the provider/providerId pair is already owned by another
user (ownership conflict), otherwise throw
CustomException.of(ApiResponseCode.OAUTH_PROVIDER_ALREADY_LINKED) for collisions
within the same provider; keep existing behavior for unknown causes by
rethrowing or falling back to the original mapping.
---
Duplicate comments:
In
`@src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java`:
- Around line 99-107: The current sequence
(restoreOrCleanupWithdrawnByLinkedProvider, validatePrimaryProviderOwnership,
validateProviderOwnership, then
UserOAuthAccountRepository.findByUserIdAndProvider followed by
saveWithConflictHandling) allows a race where two concurrent OAuth bind/login
flows both pass checks and create conflicting ownership; serialize ownership
changes by acquiring a single ownership lock or mutex keyed on the
provider+providerId (or the user id+provider) around the validate+find+save
sequence, or refactor so ownership lives in one table with a DB-level UNIQUE
constraint and perform the insert/update in a single transactional upsert to
enforce single ownership atomically; apply this locking/upsert around the code
path that calls validatePrimaryProviderOwnership, validateProviderOwnership, the
findByUserIdAndProvider call, and saveWithConflictHandling.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c91b5704-f351-4135-a49e-5d8cd98f1256
📒 Files selected for processing (3)
src/main/java/gg/agit/konect/domain/user/repository/UserOAuthAccountRepository.javasrc/main/java/gg/agit/konect/domain/user/scheduler/UserOAuthAccountCleanupScheduler.javasrc/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/**/*.java
⚙️ CodeRabbit configuration file
src/main/java/**/*.java: 아래 원칙으로 리뷰 코멘트를 작성한다.
- 코멘트는 반드시 한국어로 작성한다.
- 반드시 수정이 필요한 항목만 코멘트로 남기고, 단순 취향 차이는 지적하지 않는다.
- 각 코멘트 첫 줄에 심각도를
[LEVEL: high|medium|low]형식으로 반드시 표기한다.- 심각도 기준: high=운영 장애 가능, medium=품질 저하, low=개선 권고.
- 각 코멘트는 "문제 -> 영향 -> 제안" 순서로 3문장 이내로 간결하게 작성한다.
- 가능하면 재현 조건 및 실패 시나리오도 포함한다.
- 제안은 현재 코드베이스(Spring Boot + JPA + Flyway) 패턴과 일치해야 한다.
- 보안, 트랜잭션 경계, 예외 처리, N+1, 성능 회귀 가능성을 우선 점검한다.
- 가독성: 변수/메서드 이름이 의도를 바로 드러내는지, 중첩과 메서드 길이가 과도하지 않은지 점검한다.
- 단순화: 불필요한 추상화, 중복 로직, 과한 방어 코드가 있으면 더 단순한 대안을 제시한다.
- 확장성: 새 요구사항 추가 시 변경 범위가 최소화되는 구조인지(하드코딩 분기/값 여부 포함) 점검한다.
Files:
src/main/java/gg/agit/konect/domain/user/repository/UserOAuthAccountRepository.javasrc/main/java/gg/agit/konect/domain/user/scheduler/UserOAuthAccountCleanupScheduler.javasrc/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
**/*
⚙️ CodeRabbit configuration file
**/*: 공통 리뷰 톤 가이드:
- 모든 코멘트는 첫 줄에
[LEVEL: ...]태그를 포함한다.- 과장된 표현 없이 사실 기반으로 작성한다.
- 한 코멘트에는 하나의 이슈만 다룬다.
- 코드 예시가 필요하면 최소 수정 예시를 제시한다.
- 가독성/단순화/확장성 이슈를 발견하면 우선순위를 높여 코멘트한다.
Files:
src/main/java/gg/agit/konect/domain/user/repository/UserOAuthAccountRepository.javasrc/main/java/gg/agit/konect/domain/user/scheduler/UserOAuthAccountCleanupScheduler.javasrc/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java
| try { | ||
| userOAuthAccountRepository.saveAndFlush(UserOAuthAccount.of(user, provider, providerId, oauthEmail)); | ||
| } catch (DataIntegrityViolationException e) { | ||
| throw CustomException.of(ApiResponseCode.OAUTH_PROVIDER_ALREADY_LINKED); |
There was a problem hiding this comment.
[LEVEL: medium] Line 151-154는 DataIntegrityViolationException을 전부 OAUTH_PROVIDER_ALREADY_LINKED로 변환해, 다른 사용자가 같은 provider/providerId를 먼저 선점한 동시성 충돌도 동일 provider 내 계정 교체와 같은 오류로 응답합니다.
재현되면 클라이언트는 실제 소유권 충돌인데도 잘못된 에러 코드를 받아 분기 처리와 사용자 안내가 어긋납니다.
제안: 예외의 root cause에서 깨진 unique key를 구분해 소유권 충돌은 OAUTH_ACCOUNT_ALREADY_LINKED, 동일 provider 충돌은 OAUTH_PROVIDER_ALREADY_LINKED로 나눠 매핑하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@src/main/java/gg/agit/konect/domain/user/service/UserOAuthAccountService.java`
around lines 151 - 154, The catch-all conversion of
DataIntegrityViolationException in UserOAuthAccountService (around the
saveAndFlush(UserOAuthAccount.of(...)) call) masks different unique-constraint
violations; update the catch to unwrap the root cause (e.g., check e.getCause()
and if it's a ConstraintViolationException / SQLException inspect constraint
name or SQL state/message) and map violations to the correct API codes: throw
CustomException.of(ApiResponseCode.OAUTH_ACCOUNT_ALREADY_LINKED) when the
constraint indicates the provider/providerId pair is already owned by another
user (ownership conflict), otherwise throw
CustomException.of(ApiResponseCode.OAUTH_PROVIDER_ALREADY_LINKED) for collisions
within the same provider; keep existing behavior for unknown causes by
rethrowing or falling back to the original mapping.
🔍 개요
🚀 주요 변경 내용
계정을 다른 OAuth와 연동할 수 있습니다.
user_oauth_account테이블을 추가하여 연동된 계정을 관리합니다.이미 회원가입된 OAuth에 대해서는 연동이 불가합니다.
같은
provider면서 다른 계정의 연동은 불가합니다.OAUTH_PROVIDER_ALREADY_LINKED에러 발생탈퇴 계정은 연동 매핑을 통해 7일 복구 정책을 적용하고, 기간 만료 시에는 재가입 가능하도록 매핑 정리를 추가했습니다.
user_oauth_account테이블의 컬럼을 제거 후 회원가입으로 유도합니다.연동 상태는
GET /users/me/oauth/links를 통해 확인가능합니다.계정 연동은
/oauth2/authorization/{provider}?redirect_uri={복귀URL}&oauth_mode=link경로 접속을 하여 인증하면 됩니다.POST /users/me/oauth/links엔드포인트도 추가했습니다.💬 참고 사항
✅ Checklist (완료 조건)