Skip to content

Conversation

@minwoo1999
Copy link
Member

@minwoo1999 minwoo1999 commented Nov 30, 2025

πŸ”— κ΄€λ ¨ 이슈


πŸ“˜ μž‘μ—… μœ ν˜•

  • ✨ Feature (κΈ°λŠ₯ μΆ”κ°€)

  • 🐞 Bugfix (버그 μˆ˜μ •)

  • πŸ”§ Refactor (μ½”λ“œ λ¦¬νŒ©ν† λ§)

  • βš™οΈ Chore (ν™˜κ²½ μ„€μ •)

  • πŸ“ Docs (λ¬Έμ„œ μž‘μ„± 및 μˆ˜μ •)

  • βœ… Test (κΈ°λŠ₯ ν…ŒμŠ€νŠΈ)

  • 🎨 style (μ½”λ“œ μŠ€νƒ€μΌ μˆ˜μ •)


πŸ“™ μž‘μ—… λ‚΄μ—­

  • Google OAuth 기반 둜그인 κΈ°λŠ₯ μΆ”κ°€

  • GoogleAuthCredentials, GoogleSignInStrategy μ‹ κ·œ κ΅¬ν˜„

  • Google UserInfo 쑰회용 GoogleApiManager μΆ”κ°€

  • ProviderType에 GOOGLE enum μΆ”κ°€

  • Google UserInfo DTO(GoogleUserInfo) μ‹ κ·œ 생성

  • ꡬ글 둜그인 λ²„νŠΌ 및 ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™” 슀크립트 μΆ”κ°€ (HTML μ—…λ°μ΄νŠΈ)

  • application.yml에 Google UserInfo API URL μ„€μ • μΆ”κ°€

  • OAuth2 Client μ˜μ‘΄μ„± μΆ”κ°€


πŸ§ͺ ν…ŒμŠ€νŠΈ λ‚΄μ—­

  • λΈŒλΌμš°μ €μ—μ„œ Google 둜그인 정상 λ™μž‘ 확인

  • Google OAuth 토큰 β†’ μ„œλ²„ μš”μ²­ β†’ μ‚¬μš©μž 정보 정상 쑰회 확인

  • κΈ°μ‘΄ Kakao/Apple 둜그인 영ν–₯ μ—†μŒ

  • 인증 μ‹€νŒ¨ 및 μ˜ˆμ™Έ 처리 λ‘œκΉ… 확인


βœ… PR 체크리슀트

  • 컀밋 λ©”μ‹œμ§€κ°€ λͺ…ν™•ν•©λ‹ˆλ‹€

  • PR 제λͺ©μ΄ μ»¨λ²€μ…˜μ— λ§žμŠ΅λ‹ˆλ‹€

  • κ΄€λ ¨ 이슈 번호λ₯Ό μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€

  • κΈ°λŠ₯이 μ •μƒμ μœΌλ‘œ μž‘λ™ν•©λ‹ˆλ‹€

  • λΆˆν•„μš”ν•œ μ½”λ“œλ₯Ό μ œκ±°ν–ˆμŠ΅λ‹ˆλ‹€


πŸ’¬ μΆ”κ°€ μ„€λͺ… or 리뷰 포인트

  • Google UserInfo μ‘λ‹΅μ˜ ν•„λ“œ(email nullable) μ²˜λ¦¬μ— λŒ€ν•œ 의견 λΆ€νƒλ“œλ¦½λ‹ˆλ‹€.

Summary by CodeRabbit

릴리슀 λ…ΈνŠΈ

  • μƒˆλ‘œμš΄ κΈ°λŠ₯
    • Google 계정을 μ΄μš©ν•œ 둜그인 κΈ°λŠ₯ μΆ”κ°€
    • 둜그인 νŽ˜μ΄μ§€μ— Google 둜그인 λ²„νŠΌ μΆ”κ°€

✏️ Tip: You can customize this high-level summary in your review settings.

@minwoo1999 minwoo1999 changed the title [BOOK-469] feat: apis,infra google social login (#141) feat: apis,infra google social login (#141) Nov 30, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 30, 2025

Walkthrough

Google μ†Œμ…œ 둜그인 κΈ°λŠ₯을 κ΅¬ν˜„ν•©λ‹ˆλ‹€. OAuth 토큰 검증, μ‚¬μš©μž 정보 쑰회, νšŒμ›κ°€μž…/λ‘œκ·ΈμΈμ„ κ°€λŠ₯ν•˜κ²Œ ν•˜λŠ” λ§€λ‹ˆμ €, μ „λž΅, REST ν΄λΌμ΄μ–ΈνŠΈ μ»΄ν¬λ„ŒνŠΈλ₯Ό μΆ”κ°€ν•˜κ³  κΈ°μ‘΄ 인증 흐름에 Google 지원을 ν†΅ν•©ν•©λ‹ˆλ‹€.

Changes

Cohort / File(s) μš”μ•½
Google 인증 μ „λž΅ 및 관리
apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/GoogleSignInStrategy.kt, apis/src/main/kotlin/org/yapp/apis/auth/manager/GoogleApiManager.kt, infra/src/main/kotlin/org/yapp/infra/external/oauth/google/GoogleApi.kt, infra/src/main/kotlin/org/yapp/infra/external/oauth/google/GoogleRestClient.kt
Google 둜그인 처리 νŒŒμ΄ν”„λΌμΈ μΆ”κ°€: SignInStrategy κ΅¬ν˜„, OAuth 토큰 검증, μ‚¬μš©μž 정보 쑰회, 였λ₯˜ 처리 및 λ§€ν•‘
Google 자격증λͺ… 및 데이터 λͺ¨λΈ
apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/SignInCredentials.kt, infra/src/main/kotlin/org/yapp/infra/external/oauth/google/response/GoogleUserInfo.kt
GoogleAuthCredentials 데이터 클래슀, GoogleUserInfo 응닡 λͺ¨λΈ μΆ”κ°€
Google OAuth μ„€μ •
apis/src/main/kotlin/org/yapp/apis/config/GoogleOauthProperties.kt, apis/src/main/kotlin/org/yapp/apis/config/PropertiesConfig.kt, apis/src/main/resources/application.yml
μ„€μ • ν”„λ‘œνΌν‹° 바인딩 및 application.ymlμ—μ„œ Google OAuth URL μ •μ˜
인증 제곡자 ν™•μž₯
domain/src/main/kotlin/org/yapp/domain/user/ProviderType.kt, apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt
ProviderType μ—΄κ±°ν˜•μ— GOOGLE μΆ”κ°€, SocialLoginRequestμ—μ„œ Google 자격증λͺ… νŒ©ν† λ¦¬ 둜직 μΆ”κ°€
UI 및 둜그인 νŽ˜μ΄μ§€
apis/src/main/resources/static/kakao-login.html
Google Sign-In λ²„νŠΌ, νƒ­, ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™” 및 토큰 전솑 둜직 μΆ”κ°€
μ˜μ‘΄μ„± 및 λΉŒλ“œ μ„€μ •
infra/build.gradle.kts, build.gradle.kts
Spring Boot OAuth2 Client μ˜μ‘΄μ„± μΆ”κ°€, SonarQube μž‘μ—…μ— SONAR_TOKEN ν™˜κ²½λ³€μˆ˜ κ°€λ“œ μΆ”κ°€

Sequence Diagram(s)

sequenceDiagram
    participant Client as ν΄λΌμ΄μ–ΈνŠΈ (Google SDK)
    participant UI as kakao-login.html
    participant Ctrl as SocialLoginController
    participant Factory as SocialLoginRequest<br/>(Credentials Factory)
    participant Strategy as GoogleSignInStrategy
    participant Manager as GoogleApiManager
    participant GoogleAPI as GoogleApi
    participant RestClient as GoogleRestClient
    participant GoogleServer as Google OAuth Server

    Client->>UI: Google 둜그인 λ²„νŠΌ 클릭
    Client->>GoogleServer: Google 인증 μš”μ²­
    GoogleServer->>Client: id_token λ°˜ν™˜
    Client->>UI: onSignIn 콜백
    UI->>Ctrl: POST /api/v1/auth/signin<br/>(providerType: GOOGLE, token)
    
    Ctrl->>Factory: SocialLoginRequest.createCredentials()
    Factory->>Factory: ProviderType.GOOGLE νŒλ³„
    Factory->>Factory: GoogleAuthCredentials(token) 생성
    Factory-->>Ctrl: GoogleAuthCredentials λ°˜ν™˜
    
    Ctrl->>Strategy: authenticate(GoogleAuthCredentials)
    Strategy->>Strategy: validateCredentials()
    Strategy->>Manager: getUserInfo(accessToken)
    
    Manager->>GoogleAPI: fetchUserInfo(token, userInfoUrl)
    GoogleAPI->>RestClient: getUserInfo(bearerToken, url)
    RestClient->>GoogleServer: GET /userinfo<br/>(Authorization: Bearer token)
    GoogleServer-->>RestClient: { id, email, picture }
    RestClient-->>GoogleAPI: GoogleUserInfo
    GoogleAPI-->>Manager: Result<GoogleUserInfo>
    
    alt 성곡
        Manager-->>Strategy: GoogleUserInfo
        Strategy->>Strategy: createUserInfo(googleUserInfo)
        Strategy-->>Ctrl: UserCreateInfoResponse
        Ctrl-->>UI: 200 OK (νšŒμ›κ°€μž…/둜그인 μ™„λ£Œ)
    else μ‹€νŒ¨ (토큰 무효)
        RestClient->>Manager: HttpClientErrorException
        Manager->>Manager: μ˜ˆμ™Έ λ§€ν•‘
        Manager-->>Strategy: AuthException<br/>(INVALID_OAUTH_TOKEN)
        Strategy-->>Ctrl: AuthException
        Ctrl-->>UI: 401 였λ₯˜
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

주의 깊게 κ²€ν† ν•  μ˜μ—­:

  • GoogleApiManager의 μ˜ˆμ™Έ 처리 둜직: HttpClientErrorException 및 기타 μ˜ˆμ™Έλ₯Ό μ˜¬λ°”λ₯΄κ²Œ AuthException으둜 λ§€ν•‘ν•˜κ³  μžˆλŠ”μ§€ 확인 (INVALID_OAUTH_TOKEN vs OAUTH_SERVER_ERROR)
  • GoogleSignInStrategy의 validateCredentials λ©”μ„œλ“œ: 자격증λͺ… νƒ€μž… 검증 및 μ˜ˆμ™Έ 처리 둜직
  • GoogleOauthProperties의 μ„€μ • 바인딩: application.yml의 oauth.google.url.user-info 섀정이 μ˜¬λ°”λ₯΄κ²Œ μ£Όμž…λ˜λŠ”μ§€ 확인
  • GoogleRestClient의 응닡 처리: null 응닡 처리 및 RestClient λΉŒλ” ꡬ성
  • HTML의 Google SDK 톡합: ν΄λΌμ΄μ–ΈνŠΈ ID 메타 νƒœκ·Έ, 비동기 λ‘œλ“œ ν›…, μ „μ—­ onSignIn 콜백 κ΅¬ν˜„
  • κΈ°μ‘΄ μ½”λ“œ 톡합 지점: SocialLoginRequest의 GOOGLE μΌ€μ΄μŠ€ μ²˜λ¦¬κ°€ λ‹€λ₯Έ μ œκ³΅μžμ™€ 일관성 있게 λ™μž‘ν•˜λŠ”μ§€ 확인

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
βœ… Passed checks (4 passed)
Check name Status Explanation
Description Check βœ… Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check βœ… Passed PR 제λͺ©μ€ Google μ†Œμ…œ 둜그인 κΈ°λŠ₯ μΆ”κ°€λΌλŠ” μ£Όμš” 변경사항을 λͺ…ν™•ν•˜κ²Œ μš”μ•½ν•˜κ³  있으며, κ°„κ²°ν•˜κ³  κ΅¬μ²΄μ μž…λ‹ˆλ‹€.
Linked Issues check βœ… Passed μ—°κ²°λœ 이슈 #141의 μš”κ΅¬μ‚¬ν•­μΈ 'Google OAuth 토큰을 톡해 νšŒμ›κ°€μž… λ˜λŠ” 둜그인 κ°€λŠ₯ν•˜λ„λ‘ κ΅¬ν˜„'이 GoogleAuthCredentials, GoogleSignInStrategy, GoogleApiManager λ“±μ˜ κ΅¬ν˜„μ„ 톡해 μΆ©μ‘±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
Out of Scope Changes check βœ… Passed 변경사항 λŒ€λΆ€λΆ„μ΄ Google μ†Œμ…œ 둜그인 κ΅¬ν˜„κ³Ό 직접 관련이 있으며, build.gradle.kts의 SonarQube ν™˜κ²½λ³€μˆ˜ κ°€λ“œ μΆ”κ°€λ§Œ 간접적 κ΄€λ ¨μ΄μ§€λ§Œ λΆ€μˆ˜μ  κ°œμ„ μž…λ‹ˆλ‹€.
✨ Finishing touches
  • πŸ“ Generate docstrings
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BOOK-469-feature/#141

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.

❀️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

πŸ“œ Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 1167e92 and 1fff654.

πŸ“’ Files selected for processing (9)
  • apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt (2 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/manager/GoogleApiManager.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/GoogleAuthCredentials.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/GoogleSignInStrategy.kt (1 hunks)
  • apis/src/main/resources/application.yml (1 hunks)
  • apis/src/main/resources/static/kakao-login.html (3 hunks)
  • domain/src/main/kotlin/org/yapp/domain/user/ProviderType.kt (1 hunks)
  • infra/build.gradle.kts (1 hunks)
  • infra/src/main/kotlin/org/yapp/infra/external/oauth/google/response/GoogleUserInfo.kt (1 hunks)
🧰 Additional context used
πŸͺ› detekt (1.23.8)
apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/GoogleSignInStrategy.kt

[warning] 27-27: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.

(detekt.exceptions.TooGenericExceptionCaught)


[warning] 27-27: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-validation
πŸ”‡ Additional comments (5)
apis/src/main/resources/static/kakao-login.html (1)

56-95: Google νƒ­/λ²„νŠΌ UI κ΅¬μ‘°λŠ” 일관성 있게 잘 좔가됨

Kakao/Apple κ³Ό λ™μΌν•œ νŒ¨ν„΄μœΌλ‘œ νƒ­κ³Ό λ²„νŠΌμ„ μΆ”κ°€ν•΄μ„œ UX/ꡬ쑰가 μžμ—°μŠ€λŸ½μŠ΅λ‹ˆλ‹€.
λ‹€λ§Œ <meta name="google-signin-client_id"> 의 content 값이 μ‹€μ œ ν™˜κ²½(둜컬/μŠ€ν…Œμ΄μ§•/운영)μ—μ„œ μ–΄λ–»κ²Œ μ£Όμž…λ˜λŠ”μ§€(정적 파일 μˆ˜μ •, ν…œν”Œλ¦Ώ μΉ˜ν™˜ λ“±)만 λͺ…ν™•νžˆ ν•΄ 두면 μ„€μ • λˆ„λ½μœΌλ‘œ μΈν•œ 둜그인 μ‹€νŒ¨λ₯Ό 쀄일 수 μžˆμ„ 것 κ°™μŠ΅λ‹ˆλ‹€.

Also applies to: 99-99

domain/src/main/kotlin/org/yapp/domain/user/ProviderType.kt (1)

7-7: ProviderType.GOOGLE μΆ”κ°€ λ°©ν–₯은 적절 – λ‹€λ₯Έ λΆ„κΈ°λ¬Έμ˜ exhaustiveness 만 확인

ProviderType 에 GOOGLE 을 μΆ”κ°€ν•œ 것은 도메인 λͺ¨λΈ ν™•μž₯ κ΄€μ μ—μ„œ μžμ—°μŠ€λŸ½μŠ΅λ‹ˆλ‹€.
λ‹€λ§Œ 이 enum 을 when (provider) 둜 λΆ„κΈ°ν•˜λ©΄μ„œ else 없이 exhaustive ν•˜κ²Œ μ²˜λ¦¬ν•˜λŠ” μ½”λ“œκ°€ λ‹€λ₯Έ 곳에 μžˆλ‹€λ©΄(예: 둜그인 μ „λž΅ λΌμš°νŒ…, 톡계 집계 λ“±), GOOGLE μΌ€μ΄μŠ€κ°€ λˆ„λ½λ˜μ§€ μ•Šμ•˜λŠ”μ§€ ν•œ 번만 전체 κ²€μƒ‰μœΌλ‘œ 점검해 μ£Όμ„Έμš”.

infra/src/main/kotlin/org/yapp/infra/external/oauth/google/response/GoogleUserInfo.kt (1)

5-12: GoogleUserInfo.email 을 nullable 둜 λ‘” 선택은 νƒ€λ‹Ήν•˜λ©°, 이후 μ •μ±… μ²˜λ¦¬κ°€ μ€‘μš”ν•©λ‹ˆλ‹€

Google UserInfo 응닡은 λ™μ˜ λ²”μœ„λ‚˜ 계정 섀정에 따라 email 이 빠질 수 μžˆμ–΄μ„œ String? 둜 λͺ¨λΈλ§ν•œ 것은 쒋은 μ„ νƒμž…λ‹ˆλ‹€.
λ‹€λ§Œ 이후 νšŒμ›κ°€μž…/둜그인 νλ¦„μ—μ„œ:

  • email 이 null 인 경우 μ–΄λ–€ κΈ°μ€€μœΌλ‘œ μ‚¬μš©μžλ₯Ό 식별할지(예: provider id 기반 식별 + email 은 선택 ν•„λ“œ),
  • μ„œλΉ„μŠ€ 정책상 email 이 ν•„μˆ˜λΌλ©΄, null 일 λ•Œ λͺ…μ‹œμ μœΌλ‘œ AuthException 등을 던져 "이메일 μ œκ³΅μ— λ™μ˜ν•΄ 달라"λŠ” μ‹μ˜ μ—λŸ¬λ₯Ό μ£ΌλŠ”μ§€,

λ₯Ό λͺ…ν™•νžˆ 정리해 λ‘λŠ” 게 μ€‘μš”ν•©λ‹ˆλ‹€. μ§€κΈˆ DTO λ ˆλ²¨μ—μ„œ nullable 둜만 λλ‚˜μ§€ μ•Šλ„λ‘, SignInStrategy λ‹¨μ˜ 처리 λ‘œμ§κΉŒμ§€ ν•œ 번 더 점검해 μ£Όμ„Έμš”.

apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/GoogleSignInStrategy.kt (2)

13-20: LGTM! 클래슀 ꡬ쑰 및 μ˜μ‘΄μ„± μ£Όμž…μ΄ μ˜¬λ°”λ¦…λ‹ˆλ‹€.

Spring μ»΄ν¬λ„ŒνŠΈλ‘œ 적절히 κ΅¬μ„±λ˜μ—ˆκ³ , SignInStrategy μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„μ΄ μ •ν™•ν•©λ‹ˆλ‹€. GoogleApiManager μ˜μ‘΄μ„± μ£Όμž… 및 둜거 μ΄ˆκΈ°ν™”λ„ ν‘œμ€€ νŒ¨ν„΄μ„ λ”°λ₯΄κ³  μžˆμŠ΅λ‹ˆλ‹€.


36-42: LGTM! νƒ€μž… μ•ˆμ „μ„±μ΄ 보μž₯된 자격 증λͺ… κ²€μ¦μž…λ‹ˆλ‹€.

μ•ˆμ „ν•œ μΊμŠ€νŒ…(as?)κ³Ό μ—˜λΉ„μŠ€ μ—°μ‚°μžλ₯Ό μ‚¬μš©ν•˜μ—¬ 잘λͺ»λœ 자격 증λͺ… νƒ€μž…μ„ 적절히 μ²˜λ¦¬ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λͺ…ν™•ν•œ μ—λŸ¬ λ©”μ‹œμ§€λ„ 디버깅에 도움이 λ©λ‹ˆλ‹€.

import org.yapp.apis.auth.exception.AuthErrorCode
import org.yapp.apis.auth.exception.AuthException
import org.yapp.apis.auth.strategy.signin.AppleAuthCredentials
import org.yapp.apis.auth.strategy.signin.GoogleAuthCredentials
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | πŸ”΅ Trivial

Google λΆ„κΈ° λ‘œμ§μ€ κΉ”λ”ν•˜μ§€λ§Œ, 토큰 의미(id_token vs access_token)λ₯Ό λͺ…ν™•νžˆ ν•΄ λ‘λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€

ProviderType.GOOGLE -> GoogleAuthCredentials(request.validOauthToken()) 둜 Kakao 와 λ™μΌν•œ νŒ¨ν„΄μœΌλ‘œ 뢙인 점은 읽기 μ’‹μŠ΅λ‹ˆλ‹€.
λ‹€λ§Œ ν”„λ‘ νŠΈμ—μ„œ ν˜„μž¬ id_token 을 λ³΄λ‚΄λŠ” ꡬ쑰라면, μ—¬κΈ°μ„œ μƒμ„±λ˜λŠ” GoogleAuthCredentials.accessToken 은 μ‹€μ œλ‘œλŠ” id_token 을 κ°€λ¦¬ν‚€κ²Œ 되고, 이후 GoogleApiManager μ—μ„œλŠ” 이λ₯Ό UserInfo 호좜용 access token 으둜 μ‚¬μš©ν•˜κ²Œ λ©λ‹ˆλ‹€.

μ•ž/뒀단 λͺ¨λ‘μ—μ„œ μ–΄λ–€ μ’…λ₯˜μ˜ 토큰을 μ£Όκ³ λ°›λŠ”μ§€(access_token / id_token)λ₯Ό μ£Όμ„μ΄λ‚˜ λ¬Έμ„œ, ν˜Ήμ€ ν•„λ“œλͺ… λ³€κ²½μœΌλ‘œ λͺ…ν™•νžˆ ν•΄ 두면, 이후 μœ μ§€λ³΄μˆ˜ μ‹œ μ˜€ν•΄λ‚˜ 버그λ₯Ό 쀄일 수 μžˆμ„ 것 κ°™μŠ΅λ‹ˆλ‹€.

Also applies to: 55-67

πŸ€– Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/auth/dto/request/SocialLoginRequest.kt
around lines 8 and 55-67, the code uses
GoogleAuthCredentials(request.validOauthToken()) without clarifying whether the
incoming token is an id_token or an access_token; update the DTO and usage to
make the token type explicit: annotate the SocialLoginRequest field with a
comment stating which token is expected, or better rename the parameter/field to
idToken if the frontend sends id_token, add a matching constructor or factory
for GoogleAuthCredentials that accepts an idToken (or separate properties
idToken/accessToken), and update GoogleApiManager calls to use the correct token
property (idToken for verification / JWT parsing, accessToken for userinfo
calls) so the intent is clear and prevents future misuse.

Comment on lines 5 to 11
data class GoogleAuthCredentials(
val accessToken: String,
) : SignInCredentials() {
override fun getProviderType(): ProviderType {
return ProviderType.GOOGLE
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | πŸ”΅ Trivial

accessToken ν•„λ“œλͺ…이 μ‹€μ œ Google 토큰 μ’…λ₯˜μ™€ μΌμΉ˜ν•˜λŠ”μ§€ 점검 ν•„μš”

GoogleAuthCredentials κ°€ ProviderType.GOOGLE 을 λ°˜ν™˜ν•˜λŠ” κ΅¬μ‘°λŠ” λͺ…ν™•ν•˜κ³  μ’‹μŠ΅λ‹ˆλ‹€.
λ‹€λ§Œ ν˜„μž¬ 흐름상 ν”„λ‘ νŠΈμ—μ„œ id_token 을 μ „λ‹¬ν•œλ‹€λ©΄, 이 ν•„λ“œλͺ…(accessToken) 이 μ‹€μ œ κ°’κ³Ό λ§žμ§€ μ•Šκ²Œ λ©λ‹ˆλ‹€. λ‚˜μ€‘μ— Google μͺ½μ—μ„œ access_token κ³Ό id_token 을 λͺ¨λ‘ μ‚¬μš©ν•˜λŠ” μš”κ΅¬μ‚¬ν•­μ΄ 생기면 ν˜Όλ™ μ—¬μ§€κ°€ ν¬λ‹ˆ,

  • μ‹€μ œλ‘œ access_token 을 받을 κ³„νšμ΄λΌλ©΄ ν”„λ‘ νŠΈ/λ°±μ—”λ“œ λͺ¨λ‘ access_token 으둜 μ •λ ¬ν•˜κ±°λ‚˜,
  • ν˜„μž¬ 값이 id_token 이라면 ν•„λ“œλͺ…을 λ°”κΎΈκ±°λ‚˜ KDoc 으둜 λͺ…μ‹œ

ν•˜λŠ” λ°©ν–₯을 ν•œ 번 κ²€ν† ν•΄ λ³΄μ‹œλŠ” 것을 μΆ”μ²œλ“œλ¦½λ‹ˆλ‹€.

Comment on lines +22 to +34
override fun authenticate(credentials: SignInCredentials): UserCreateInfoResponse {
return try {
val googleCredentials = validateCredentials(credentials)
val googleUser = googleApiManager.getUserInfo(googleCredentials.accessToken)
createUserInfo(googleUser)
} catch (exception: Exception) {
log.error("Google authentication failed", exception)
when (exception) {
is AuthException -> throw exception
else -> throw AuthException(AuthErrorCode.FAILED_TO_GET_USER_INFO, exception.message)
}
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | πŸ”΅ Trivial

μ˜ˆμ™Έ 처리 λ²”μœ„ κ²€ν†  ꢌμž₯

ν˜„μž¬ Exception을 catchν•˜μ—¬ λͺ¨λ“  μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ‘œκΉ…κ³Ό 재throw λ‘œμ§μ€ μ μ ˆν•˜μ§€λ§Œ, Google API 호좜 μ‹œ λ°œμƒ κ°€λŠ₯ν•œ ꡬ체적인 μ˜ˆμ™Έ νƒ€μž…(예: HTTP ν΄λΌμ΄μ–ΈνŠΈ μ˜ˆμ™Έ, λ„€νŠΈμ›Œν¬ μ˜ˆμ™Έ λ“±)을 catchν•˜λ©΄ 더 λͺ…ν™•ν•œ μ—λŸ¬ 핸듀링이 κ°€λŠ₯ν•©λ‹ˆλ‹€.

ν˜„μž¬ κ΅¬ν˜„λ„ 방어적이고 μ „λž΅ νŒ¨ν„΄ μ»¨ν…μŠ€νŠΈμ—μ„œ 수용 κ°€λŠ₯ν•˜μ§€λ§Œ, ν•„μš”μ‹œ ꡬ체적인 μ˜ˆμ™Έ νƒ€μž…μœΌλ‘œ μ„ΈλΆ„ν™”ν•˜λŠ” 것을 κ³ λ €ν•΄λ³΄μ„Έμš”.

정적 뢄석 λ„κ΅¬μ˜ κ²½κ³ λ₯Ό μ°Έκ³ ν–ˆμŠ΅λ‹ˆλ‹€.

🧰 Tools
πŸͺ› detekt (1.23.8)

[warning] 27-27: The caught exception is too generic. Prefer catching specific exceptions to the case that is currently handled.

(detekt.exceptions.TooGenericExceptionCaught)


[warning] 27-27: The caught exception is swallowed. The original exception could be lost.

(detekt.exceptions.SwallowedException)

πŸ€– Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/GoogleSignInStrategy.kt
around lines 22 to 34, the current code catches the broad Exception type; narrow
the exception handling by catching specific exceptions (e.g., AuthException
first, HTTP client exceptions such as HttpClientRequestException or Ktor's
ClientRequestException/ServerResponseException, IO/SocketTimeoutException, and
other network-related exceptions) and handle each appropriately: log the error,
rethrow AuthException as-is, and wrap other known exception types into
AuthException with the proper AuthErrorCode and original exception as cause;
keep a final generic catch only if necessary to preserve behavior but prefer
explicit catches to satisfy static analysis and provide clearer error semantics.

Comment on lines +44 to +52
private fun createUserInfo(googleUser: GoogleUserInfo): UserCreateInfoResponse {
return UserCreateInfoResponse.of(
email = googleUser.email ?: ("google_${googleUser.id}@google.com"),
nickname = NicknameGenerator.generate(),
profileImageUrl = googleUser.picture,
providerType = ProviderType.GOOGLE,
providerId = googleUser.id
)
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | πŸ”΅ Trivial

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "GoogleSignInStrategy.kt"

Repository: YAPP-Github/Reed-Server

Length of output: 148


🏁 Script executed:

git ls-files | grep -i google | head -20

Repository: YAPP-Github/Reed-Server

Length of output: 381


🏁 Script executed:

git ls-files | grep -i auth | head -30

Repository: YAPP-Github/Reed-Server

Length of output: 2278


🏁 Script executed:

cat apis/src/main/kotlin/org/yapp/apis/auth/dto/response/UserCreateInfoResponse.kt

Repository: YAPP-Github/Reed-Server

Length of output: 1651


🏁 Script executed:

cat infra/src/main/kotlin/org/yapp/infra/external/oauth/google/response/GoogleUserInfo.kt

Repository: YAPP-Github/Reed-Server

Length of output: 354


🏁 Script executed:

cat apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/GoogleSignInStrategy.kt

Repository: YAPP-Github/Reed-Server

Length of output: 2142


🏁 Script executed:

cat apis/src/main/kotlin/org/yapp/apis/auth/service/UserSignInService.kt

Repository: YAPP-Github/Reed-Server

Length of output: 1272


🏁 Script executed:

rg "email" --type kt apis/src/main/kotlin/org/yapp/apis/auth/ -B 2 -A 2 | head -80

Repository: YAPP-Github/Reed-Server

Length of output: 93


🏁 Script executed:

cat apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/AppleSignInStrategy.kt

Repository: YAPP-Github/Reed-Server

Length of output: 1852


🏁 Script executed:

cat apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/KakaoSignInStrategy.kt

Repository: YAPP-Github/Reed-Server

Length of output: 2136


🏁 Script executed:

rg "FindOrCreateUserRequest" --type kotlin -A 20

Repository: YAPP-Github/Reed-Server

Length of output: 16922


🏁 Script executed:

rg "validate.*email|email.*validate" --type kotlin -i -B 2 -A 5

Repository: YAPP-Github/Reed-Server

Length of output: 1144


🏁 Script executed:

rg "sendEmail|send.*mail|mailService" --type kotlin -i

Repository: YAPP-Github/Reed-Server

Length of output: 49


🏁 Script executed:

rg "class User" --type kotlin | grep -v "Response\|Request\|Info\|Strategy"

Repository: YAPP-Github/Reed-Server

Length of output: 2816


🏁 Script executed:

git log --oneline --all -- apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/GoogleSignInStrategy.kt | head -5

Repository: YAPP-Github/Reed-Server

Length of output: 129


🏁 Script executed:

rg "email" --type kotlin apis/src/main/kotlin/org/yapp/apis/user/ -B 2 -A 2 | head -100

Repository: YAPP-Github/Reed-Server

Length of output: 4728


🏁 Script executed:

cat apis/src/main/kotlin/org/yapp/apis/user/dto/request/FindOrCreateUserRequest.kt

Repository: YAPP-Github/Reed-Server

Length of output: 2399


🏁 Script executed:

cat domain/src/main/kotlin/org/yapp/domain/user/User.kt

Repository: YAPP-Github/Reed-Server

Length of output: 5536


🏁 Script executed:

find . -type f -name "*Test.kt" | grep -i auth | head -10

Repository: YAPP-Github/Reed-Server

Length of output: 49


🏁 Script executed:

rg "getOrDefaultEmail" --type kotlin -B 5 -A 5

Repository: YAPP-Github/Reed-Server

Length of output: 2616


🏁 Script executed:

rg "EmailValidator" --type kotlin -B 3 -A 10

Repository: YAPP-Github/Reed-Server

Length of output: 3437


🏁 Script executed:

find . -type f \( -name "*Test.kt" -o -name "*Tests.kt" \) -exec grep -l "email\|signin\|sign-in" {} \; | head -10

Repository: YAPP-Github/Reed-Server

Length of output: 49


🏁 Script executed:

rg "isValidEmail" --type kotlin -B 5 -A 15

Repository: YAPP-Github/Reed-Server

Length of output: 2687


🏁 Script executed:

rg "EMAIL_PATTERN" --type kotlin -B 2 -A 2

Repository: YAPP-Github/Reed-Server

Length of output: 1070


🏁 Script executed:

find . -type f -name "RegexUtils.kt"

Repository: YAPP-Github/Reed-Server

Length of output: 137


🏁 Script executed:

cat global-utils/src/main/kotlin/org/yapp/globalutils/util/RegexUtils.kt

Repository: YAPP-Github/Reed-Server

Length of output: 423


🏁 Script executed:

rg "providerId.*unique\|unique.*providerId" --type kotlin -i

Repository: YAPP-Github/Reed-Server

Length of output: 49


🏁 Script executed:

rg "findUserByProviderTypeAndProviderId" --type kotlin -B 3 -A 10

Repository: YAPP-Github/Reed-Server

Length of output: 4431


🏁 Script executed:

cat apis/src/main/kotlin/org/yapp/apis/auth/exception/AuthErrorCode.kt | grep -i "email\|user"

Repository: YAPP-Github/Reed-Server

Length of output: 413


Email fallback handling is working as designed; consider documenting the approach.

The synthetic email fallback is intentional and will not cause validation failures. The EmailValidator only performs regex validation (allows underscores and dots), with no MX record checks. Emails like google_123456@google.com pass validation successfully.

However, there are design consistency issues worth addressing:

  1. Redundant fallback logic: GoogleSignInStrategy creates google_<id>@google.com, but FindOrCreateUserRequest.getOrDefaultEmail() independently creates <id>@google.local if email is null. This layering is unnecessary.

  2. Inconsistent formats: Google/Kakao use provider_<id>@provider.com, while FindOrCreateUserRequest uses <id>@provider.local. The .local convention is typically for local networks, not production systems.

  3. Design alignment: AppleSignInStrategy accepts nullable emails directly, while Google/Kakao generate fallbacksβ€”this variation should be intentional and documented.

The core design (allowing nullable emails with providerId as the true identifier) is sound. Consider:

  • Centralizing email fallback logic (remove from GoogleSignInStrategy, keep only in FindOrCreateUserRequest)
  • Using consistent fallback format across all providers
  • Adding a flag to identify synthetic emails (useful if email features are added later)
  • Documenting that providerId, not email, is the primary user identifier
πŸ€– Prompt for AI Agents
In
apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/GoogleSignInStrategy.kt
around lines 44-52, remove the synthetic email fallback so createUserInfo
returns the raw googleUser.email (nullable) rather than generating
"google_<id>@google.com"; centralize fallback logic into
FindOrCreateUserRequest.getOrDefaultEmail() so all providers share one
consistent format, pick and apply a single fallback pattern (e.g.
provider_<id>@provider.local or provider_<id>@provider.com) in that central
method, add a boolean flag (e.g. isSyntheticEmail) to the request/response
objects and set it when the central fallback is used, and update related
callers, tests and documentation to reflect that providerId is the primary
identifier and synthetic email behavior is centralized and consistent.

Comment on lines +11 to +13

implementation(Dependencies.Spring.BOOT_STARTER_OAUTH2_CLIENT)

Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | πŸ”΅ Trivial

Spring OAuth2 Client μ˜μ‘΄μ„± μ‚¬μš© μ—¬λΆ€ 확인 μ œμ•ˆ

ν˜„μž¬ PR μ—μ„œ λ³Έ μ½”λ“œλ§Œ 보면 Google 연동은 RestClient 둜 직접 UserInfo μ—”λ“œν¬μΈνŠΈλ₯Ό ν˜ΈμΆœν•˜κ³  μžˆμ–΄, infra λͺ¨λ“ˆμ΄ BOOT_STARTER_OAUTH2_CLIENT 의 κΈ°λŠ₯을 μ‹€μ œλ‘œ μ“°μ§€ μ•ŠλŠ” κ²ƒμ²˜λŸΌ λ³΄μž…λ‹ˆλ‹€.
ν–₯ν›„ Security OAuth2 Client λ₯Ό μ‚¬μš©ν•  κ³„νšμ΄ μ—†λ‹€λ©΄, 이 μ˜μ‘΄μ„±μ€ μ œκ±°ν•΄μ„œ ν΄λž˜μŠ€νŒ¨μŠ€μ™€ λΉŒλ“œ μ‹œκ°„μ„ μ€„μ΄λŠ” 것도 κ³ λ €ν•΄ μ£Όμ„Έμš”.

@move-hoon
Copy link
Member

@minwoo1999 CIκ°€ κΉ¨μ Έμ„œ ν•œλ²ˆ λ΄μ£Όμ‹œκ³ , major μœ„μ£Όμ˜ λ¦¬λ·°λŠ” λ°˜μ˜ν•˜λ©΄ 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€!

@Component
class GoogleApiManager(
@Value("\${oauth.google.url.user-info}") private val userInfoUrl: String,
private val restClient: RestClient,
Copy link
Member

Choose a reason for hiding this comment

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

restClient ν˜ΈμΆœμ€ μ™ΈλΆ€ api ν˜ΈμΆœμ΄κΈ°μ— infra λͺ¨λ“ˆμ—μ„œ μˆ˜ν–‰ν•˜λŠ”κ²Œ 쒋을 것 κ°™μ•„μš”!
μ˜ˆμ‹œ μ²¨λΆ€ν•©λ‹ˆλ‹€!

class KakaoApiManager(
    private val kakaoApi: KakaoApi,
    private val kakaoOauthProperties: KakaoOauthProperties
) {

Copy link
Member Author

Choose a reason for hiding this comment

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

λ°˜μ˜μ™„λ£Œ!

Comment on lines 5 to 11
data class GoogleAuthCredentials(
val accessToken: String,
) : SignInCredentials() {
override fun getProviderType(): ProviderType {
return ProviderType.GOOGLE
}
}
Copy link
Member

Choose a reason for hiding this comment

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

이거 SignInCredentials sealed class μ•„λž˜μ— λ„£μœΌλ©΄ 쒋을 것 κ°™μ•„μš”!

Copy link
Member Author

Choose a reason for hiding this comment

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

λ°˜μ˜μ™„λ£Œ!

- ν…ŒμŠ€νŠΈ ν”„λ‘œν•„μ— oauth μ„€μ • μΆ”κ°€
- λ‘œμ»¬μ—μ„œ sonar task μŠ€ν‚΅λ˜λ„λ‘ μˆ˜μ •
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (1)
apis/src/main/kotlin/org/yapp/apis/auth/manager/GoogleApiManager.kt (1)

19-39: μ—λŸ¬ 핸듀링이 κ°œμ„ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

이전 λ¦¬λ·°μ—μ„œ μ§€μ λœ μ—λŸ¬ 핸듀링 λ¬Έμ œκ°€ κ°œμ„ λ˜μ—ˆμŠ΅λ‹ˆλ‹€:

  • Result νƒ€μž…μ„ ν™œμš©ν•œ λͺ…μ‹œμ  μ—λŸ¬ 처리
  • HTTP μ—λŸ¬μ™€ 일반 μ˜ˆμ™Έλ₯Ό κ΅¬λΆ„ν•˜μ—¬ μ μ ˆν•œ AuthErrorCode둜 λ§€ν•‘
  • 성곡/μ‹€νŒ¨ μ‹œ λ‘œκΉ… μΆ”κ°€
πŸ“œ Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between ae6b5ca and bed59ba.

πŸ“’ Files selected for processing (6)
  • apis/src/main/kotlin/org/yapp/apis/auth/manager/GoogleApiManager.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/SignInCredentials.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/config/GoogleOauthProperties.kt (1 hunks)
  • apis/src/main/kotlin/org/yapp/apis/config/PropertiesConfig.kt (1 hunks)
  • infra/src/main/kotlin/org/yapp/infra/external/oauth/google/GoogleApi.kt (1 hunks)
  • infra/src/main/kotlin/org/yapp/infra/external/oauth/google/GoogleRestClient.kt (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-validation
πŸ”‡ Additional comments (5)
apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/SignInCredentials.kt (1)

10-10: LGTM - 트레일링 콀마 μΆ”κ°€ 잘 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

Kotlin μ»¨λ²€μ…˜μ„ λ”°λ₯΄λŠ” 쒋은 λ³€κ²½μ‚¬ν•­μž…λ‹ˆλ‹€. ν–₯ν›„ νŒŒλΌλ―Έν„° μΆ”κ°€ μ‹œ diffκ°€ κΉ”λ”ν•΄μ§‘λ‹ˆλ‹€.

Also applies to: 17-17

apis/src/main/kotlin/org/yapp/apis/config/PropertiesConfig.kt (1)

6-8: κ΅¬ν˜„μ΄ μ •ν™•ν•©λ‹ˆλ‹€.

Spring Boot의 ν‘œμ€€ μ„€μ • ν”„λ‘œνΌν‹° 바인딩 νŒ¨ν„΄μ„ μ˜¬λ°”λ₯΄κ²Œ λ”°λ₯΄κ³  μžˆμŠ΅λ‹ˆλ‹€.

infra/src/main/kotlin/org/yapp/infra/external/oauth/google/GoogleApi.kt (1)

10-12: μƒμˆ˜ μ •μ˜κ°€ μ μ ˆν•©λ‹ˆλ‹€.

Bearer 토큰 ν”„λ¦¬ν”½μŠ€ 포맷이 μ˜¬λ°”λ₯΄λ©°, private μ ‘κ·Ό μ œμ–΄μžλ‘œ μΊ‘μŠν™”λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

apis/src/main/kotlin/org/yapp/apis/config/GoogleOauthProperties.kt (1)

5-8: ν”„λ‘œνΌν‹° ꡬ쑰가 λͺ…ν™•ν•©λ‹ˆλ‹€.

ConfigurationProperties μ–΄λ…Έν…Œμ΄μ…˜κ³Ό ν”„λ¦¬ν”½μŠ€ 섀정이 μ˜¬λ°”λ₯΄κ²Œ κ΅¬μ„±λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

apis/src/main/kotlin/org/yapp/apis/auth/manager/GoogleApiManager.kt (1)

12-17: μ˜μ‘΄μ„± ꡬ쑰가 κ°œμ„ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

이전 리뷰 ν”Όλ“œλ°±μ„ λ°˜μ˜ν•˜μ—¬ infra λͺ¨λ“ˆμ˜ GoogleApiλ₯Ό μ‚¬μš©ν•˜λ„λ‘ ꡬ쑰가 κ°œμ„ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

) {
private val log = KotlinLogging.logger {}

fun getUserInfo(accessToken: String): GoogleUserInfo {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | πŸ”΅ Trivial

νŒŒλΌλ―Έν„°μ— λŒ€ν•œ λ¬Έμ„œν™”λ₯Ό μΆ”κ°€ν•˜λŠ” 것을 κ³ λ €ν•˜μ„Έμš”.

이전 λ¦¬λ·°μ—μ„œ 제기된 access_token vs id_token ꡬ뢄에 λŒ€ν•œ λͺ…ν™•μ„± λ¬Έμ œμ™€ κ΄€λ ¨ν•˜μ—¬, νŒŒλΌλ―Έν„° 이름은 λͺ…ν™•ν•˜μ§€λ§Œ KDoc 주석을 μΆ”κ°€ν•˜λ©΄ 더 μ’‹μŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 λ¬Έμ„œν™”λ₯Ό μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

+/**
+ * Google OAuth access token을 μ‚¬μš©ν•˜μ—¬ μ‚¬μš©μž 정보λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€.
+ * @param accessToken Google OAuth 2.0 access token (id_token이 μ•„λ‹˜)
+ * @return 쑰회된 Google μ‚¬μš©μž 정보
+ * @throws AuthException 토큰이 μœ νš¨ν•˜μ§€ μ•Šκ±°λ‚˜ Google μ„œλ²„μ™€ 톡신 μ‹€νŒ¨ μ‹œ
+ */
 fun getUserInfo(accessToken: String): GoogleUserInfo {
πŸ€– Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/auth/manager/GoogleApiManager.kt around
line 19, the public function parameter lacks KDoc explaining whether the token
is an OAuth access_token or an id_token and what format/value is expected; add a
KDoc block for the function that documents the purpose of the function,
clarifies that the parameter accessToken is an OAuth access_token (not an
id_token), describes expected format/expiration and any required scopes, and
briefly states what the returned GoogleUserInfo contains and possible
exceptions/errors thrown.

Comment on lines +25 to +27
override fun getProviderType(): ProviderType {
return ProviderType.GOOGLE
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | πŸ”΅ Trivial

ν‘œν˜„μ‹ λ°”λ””λ‘œ ν†΅μΌν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.

getProviderType() λ©”μ„œλ“œκ°€ 동일 파일 λ‚΄ λ‹€λ₯Έ κ΅¬ν˜„μ²΄(KakaoAuthCredentials, AppleAuthCredentials)μ—μ„œλŠ” ν‘œν˜„μ‹ λ°”λ””(= ProviderType.XXX)λ₯Ό μ‚¬μš©ν•˜λŠ” 반면, μ—¬κΈ°μ„œλ§Œ 블둝 λ°”λ””λ₯Ό μ‚¬μš©ν•˜κ³  μžˆμ–΄ μŠ€νƒ€μΌ 일관성이 λ–¨μ–΄μ§‘λ‹ˆλ‹€.

λ‹€μŒ diffλ₯Ό μ μš©ν•˜μ—¬ 일관성을 κ°œμ„ ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

-    override fun getProviderType(): ProviderType {
-        return ProviderType.GOOGLE
-    }
+    override fun getProviderType(): ProviderType = ProviderType.GOOGLE
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override fun getProviderType(): ProviderType {
return ProviderType.GOOGLE
}
override fun getProviderType(): ProviderType = ProviderType.GOOGLE
πŸ€– Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/auth/strategy/signin/SignInCredentials.kt
around lines 25 to 27, the getProviderType() method uses a block body while
other credential implementations (KakaoAuthCredentials, AppleAuthCredentials)
use expression bodies; change the method from a block body to an expression body
so it reads as a single-line expression returning ProviderType.GOOGLE to match
project style and maintain consistency.

Comment on lines +10 to +12
data class Url(
val userInfo: String
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Url 클래슀λ₯Ό GoogleOauthProperties λ‚΄λΆ€λ‘œ μ€‘μ²©μ‹œν‚€μ„Έμš”.

ν˜„μž¬ Url ν΄λž˜μŠ€κ°€ νŒ¨ν‚€μ§€ λ ˆλ²¨μ— μ •μ˜λ˜μ–΄ μžˆμ–΄ λ‹€μŒ λ¬Έμ œκ°€ λ°œμƒν•©λ‹ˆλ‹€:

  • λ²”μš©μ μΈ μ΄λ¦„μœΌλ‘œ μΈν•œ λ„€μž„μŠ€νŽ˜μ΄μŠ€ 좩돌 μœ„ν—˜
  • GoogleOauthPropertiesμ™€μ˜ 관계가 λͺ…μ‹œμ μ΄μ§€ μ•ŠμŒ
  • μ½”λ“œ ꡬ쑰의 가독성 μ €ν•˜

Url 클래슀λ₯Ό GoogleOauthProperties λ‚΄λΆ€ 클래슀둜 μ€‘μ²©ν•˜μ—¬ 관계λ₯Ό λͺ…ν™•νžˆ ν•˜κ³  λ„€μž„μŠ€νŽ˜μ΄μŠ€λ₯Ό μ •λ¦¬ν•˜μ„Έμš”.

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

 @ConfigurationProperties(prefix = "oauth.google")
 data class GoogleOauthProperties(
     val url: Url
-)
+) {
+    data class Url(
+        val userInfo: String
+    )
+}
-
-data class Url(
-    val userInfo: String
-)
πŸ€– Prompt for AI Agents
In apis/src/main/kotlin/org/yapp/apis/config/GoogleOauthProperties.kt around
lines 10 to 12, the top-level data class Url should be moved inside the
GoogleOauthProperties class to avoid namespace collisions and clarify the
relationship; edit GoogleOauthProperties to declare Url as a nested (or inner if
it needs access to outer instance) data class (e.g., data class Url(val
userInfo: String)) placed within the GoogleOauthProperties body, remove the
top-level Url declaration, and update any references/imports elsewhere to use
GoogleOauthProperties.Url (or GoogleOauthProperties().Url if made inner)
accordingly.

Comment on lines +14 to +21
fun fetchUserInfo(
accessToken: String,
userInfoUrl: String,
): Result<GoogleUserInfo> {
return runCatching {
googleRestClient.getUserInfo(BEARER_PREFIX + accessToken, userInfoUrl)
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | πŸ”΅ Trivial

μ•‘μ„ΈμŠ€ 토큰 검증을 μΆ”κ°€ν•˜μ„Έμš”.

accessToken을 BEARER_PREFIX와 직접 μ—°κ²°ν•˜κΈ° 전에 λ‹€μŒμ„ 확인해야 ν•©λ‹ˆλ‹€:

  • 토큰이 이미 "Bearer " ν”„λ¦¬ν”½μŠ€λ₯Ό ν¬ν•¨ν•˜κ³  μžˆμ§€ μ•Šμ€μ§€
  • 토큰이 λΉ„μ–΄μžˆμ§€ μ•Šμ€μ§€

잘λͺ»λœ Authorization ν—€λ”λ‘œ μΈν•œ API 호좜 μ‹€νŒ¨λ₯Ό λ°©μ§€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 검증 λ‘œμ§μ„ μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

 fun fetchUserInfo(
     accessToken: String,
     userInfoUrl: String,
 ): Result<GoogleUserInfo> {
+    require(accessToken.isNotBlank()) { "Access token must not be blank" }
+    val token = if (accessToken.startsWith("Bearer ", ignoreCase = true)) {
+        accessToken
+    } else {
+        BEARER_PREFIX + accessToken
+    }
     return runCatching {
-        googleRestClient.getUserInfo(BEARER_PREFIX + accessToken, userInfoUrl)
+        googleRestClient.getUserInfo(token, userInfoUrl)
     }
 }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun fetchUserInfo(
accessToken: String,
userInfoUrl: String,
): Result<GoogleUserInfo> {
return runCatching {
googleRestClient.getUserInfo(BEARER_PREFIX + accessToken, userInfoUrl)
}
}
fun fetchUserInfo(
accessToken: String,
userInfoUrl: String,
): Result<GoogleUserInfo> {
require(accessToken.isNotBlank()) { "Access token must not be blank" }
val token = if (accessToken.startsWith("Bearer ", ignoreCase = true)) {
accessToken
} else {
BEARER_PREFIX + accessToken
}
return runCatching {
googleRestClient.getUserInfo(token, userInfoUrl)
}
}
πŸ€– Prompt for AI Agents
In infra/src/main/kotlin/org/yapp/infra/external/oauth/google/GoogleApi.kt
around lines 14 to 21, add validation and normalization for accessToken before
concatenating with BEARER_PREFIX: first trim and ensure it's not blank (return a
failed Result or throw an IllegalArgumentException inside runCatching if blank),
then if it already starts with "Bearer " (case-insensitive) use it as-is,
otherwise prepend BEARER_PREFIX; finally pass the normalized authorization
header into googleRestClient.getUserInfo so we avoid empty or double-prefixed
Authorization headers.

Comment on lines +7 to +11
@Component
class GoogleRestClient(
builder: RestClient.Builder
) {
private val client = builder.build()
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | πŸ”΅ Trivial

νƒ€μž„μ•„μ›ƒ 섀정을 μΆ”κ°€ν•˜μ„Έμš”.

μ™ΈλΆ€ API 호좜 μ‹œ νƒ€μž„μ•„μ›ƒμ΄ μ„€μ •λ˜μ–΄ μžˆμ§€ μ•ŠμœΌλ©΄ 응닡이 μ—†λŠ” 경우 λ¬΄ν•œμ • λŒ€κΈ°ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ—°κ²° νƒ€μž„μ•„μ›ƒκ³Ό 읽기 νƒ€μž„μ•„μ›ƒμ„ μ„€μ •ν•˜μ—¬ μ‹œμŠ€ν…œ μ•ˆμ •μ„±μ„ ν–₯μƒμ‹œν‚€μ„Έμš”.

λ‹€μŒκ³Ό 같이 νƒ€μž„μ•„μ›ƒμ„ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

+import java.time.Duration
+
 @Component
 class GoogleRestClient(
     builder: RestClient.Builder
 ) {
-    private val client = builder.build()
+    private val client = builder
+        .requestFactory(
+            org.springframework.http.client.SimpleClientHttpRequestFactory().apply {
+                setConnectTimeout(Duration.ofSeconds(5))
+                setReadTimeout(Duration.ofSeconds(10))
+            }
+        )
+        .build()

Committable suggestion skipped: line range outside the PR's diff.

πŸ€– Prompt for AI Agents
In
infra/src/main/kotlin/org/yapp/infra/external/oauth/google/GoogleRestClient.kt
around lines 7 to 11, the RestClient is built without any timeout configuration;
update the builder to set sensible connection and read (socket) timeouts before
calling build (for example short connect timeout and a longer read timeout),
using the RestClient.Builder timeout methods available in your HTTP client
(e.g., setConnectTimeout / setReadTimeout or the Duration-based equivalents), so
the client fails fast on network issues and avoids indefinite waits.

Comment on lines +13 to +23
fun getUserInfo(
bearerToken: String,
url: String,
): GoogleUserInfo {
return client.get()
.uri(url)
.header("Authorization", bearerToken)
.retrieve()
.body(GoogleUserInfo::class.java)
?: throw IllegalStateException("Google API 응닡이 null μž…λ‹ˆλ‹€.")
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | πŸ”΅ Trivial

μ˜ˆμ™Έ λ©”μ‹œμ§€λ₯Ό 영문으둜 μž‘μ„±ν•˜κ±°λ‚˜ μ—λŸ¬ μ½”λ“œλ₯Ό μ‚¬μš©ν•˜μ„Έμš”.

Line 22의 IllegalStateException λ©”μ‹œμ§€κ°€ ν•œκΈ€λ‘œ μž‘μ„±λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. 인프라 λ ˆμ΄μ–΄μ˜ μ˜ˆμ™Έ λ©”μ‹œμ§€λŠ” 영문으둜 μž‘μ„±ν•˜κ±°λ‚˜, μƒμœ„ λ ˆμ΄μ–΄μ—μ„œ μ²˜λ¦¬ν•  수 μžˆλ„λ‘ 도메인별 μ˜ˆμ™Έλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€:

     return client.get()
         .uri(url)
         .header("Authorization", bearerToken)
         .retrieve()
         .body(GoogleUserInfo::class.java)
-        ?: throw IllegalStateException("Google API 응닡이 null μž…λ‹ˆλ‹€.")
+        ?: throw IllegalStateException("Google API response body is null")
 }
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fun getUserInfo(
bearerToken: String,
url: String,
): GoogleUserInfo {
return client.get()
.uri(url)
.header("Authorization", bearerToken)
.retrieve()
.body(GoogleUserInfo::class.java)
?: throw IllegalStateException("Google API 응닡이 null μž…λ‹ˆλ‹€.")
}
fun getUserInfo(
bearerToken: String,
url: String,
): GoogleUserInfo {
return client.get()
.uri(url)
.header("Authorization", bearerToken)
.retrieve()
.body(GoogleUserInfo::class.java)
?: throw IllegalStateException("Google API response body is null")
}
πŸ€– Prompt for AI Agents
In
infra/src/main/kotlin/org/yapp/infra/external/oauth/google/GoogleRestClient.kt
around lines 13 to 23, replace the Korean Literal in the thrown
IllegalStateException with an English message or throw a domain-specific
exception so infra-layer errors are consistent and can be handled by upper
layers; update the throw to something like IllegalStateException("Google API
response is null.") or define and throw a specific exception (e.g.,
GoogleApiException("response is null")) and ensure callers can handle the new
exception type.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
10.5% Coverage on New Code (required β‰₯ 80%)

See analysis details on SonarQube Cloud

@minwoo1999 minwoo1999 merged commit 37c5a8d into develop Dec 13, 2025
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BOOK-469/feat] google social login 을 κ΅¬ν˜„ν•©λ‹ˆλ‹€.

3 participants