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
5 changes: 4 additions & 1 deletion app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile

-keep class * extends com.google.gson.TypeAdapter
-keep class com.google.googlesignin.** { *; }
Comment on lines +23 to +24
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸก Minor

๐Ÿงฉ Analysis chain

ProGuard ๊ทœ์น™์ด ์ง€๋‚˜์น˜๊ฒŒ ๊ด‘๋ฒ”์œ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ ๊ทœ์น™์ด ๋งค์šฐ ๋„“์€ ๋ฒ”์œ„๋ฅผ ์œ ์ง€ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค:

  • * extends TypeAdapter: ๋ชจ๋“  TypeAdapter ์„œ๋ธŒํด๋ž˜์Šค ์œ ์ง€
  • com.google.googlesignin.** { *; }: ๋ชจ๋“  Google Sign-In ํด๋ž˜์Šค์™€ ๋ฉค๋ฒ„ ์œ ์ง€

์ด๋Š” ๋‚œ๋…ํ™”๋ฅผ ๋ฐฉํ•ดํ•˜๊ณ  APK ํฌ๊ธฐ๋ฅผ ์ฆ๊ฐ€์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Google Sign-In Credential Manager API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ๋” ๊ตฌ์ฒด์ ์ธ ๊ทœ์น™์œผ๋กœ ์ถฉ๋ถ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ์Šคํฌ๋ฆฝํŠธ๋กœ ์‹ค์ œ ์‚ฌ์šฉ๋˜๋Š” Google Sign-In ํด๋ž˜์Šค๋ฅผ ํ™•์ธํ•˜์„ธ์š”:


๐Ÿ Script executed:

#!/bin/bash
# Google Sign-In ๊ด€๋ จ import ๋ฐ ์‚ฌ์šฉ ํ™•์ธ
rg -nP --type=kotlin -C3 'import.*google\.android\.gms\.auth|import.*googleid' feature/oauth/

Length of output: 1223


๐Ÿ Script executed:

#!/bin/bash
# Search for actual usage of com.google.googlesignin package
rg -nP --type=kotlin --type=java 'com\.google\.googlesignin' .

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Search for Gson TypeAdapter usage
rg -nP --type=kotlin --type=java 'TypeAdapter' .

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check if Credential Manager classes appear in ProGuard rules already
cat app/proguard-rules.pro

Length of output: 847



๐Ÿ Script executed:

#!/bin/bash
# Check build.gradle for dependencies
fd -e gradle | head -20

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Search build files for google-signin or gson dependencies
rg -nP 'google.*signin|gson' build.gradle

Length of output: 52


๐Ÿ Script executed:

#!/bin/bash
# Search for any Credential Manager ProGuard rules or missing rules
rg -nP 'credentials|credential' app/proguard-rules.pro

Length of output: 0


์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ProGuard ๊ทœ์น™์„ ์ œ๊ฑฐํ•˜์„ธ์š”.

๊ฒ€์ฆ ๊ฒฐ๊ณผ, ์ฝ”๋“œ๋ฒ ์ด์Šค ์–ด๋””์—์„œ๋„ ๋‹ค์Œ์ด ์‚ฌ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค:

  • com.google.googlesignin ํŒจํ‚ค์ง€ (๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ: 0๊ฑด)
  • TypeAdapter ํด๋ž˜์Šค (๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ: 0๊ฑด)

ํ˜„์žฌ ๊ตฌํ˜„์€ com.google.android.libraries.identity.googleid (Credential Manager API)๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ผ์ธ 23-24์˜ ๊ทœ์น™์€ ๋” ์ด์ƒ ํ•„์š”ํ•˜์ง€ ์•Š์€ ๋ ˆ๊ฑฐ์‹œ Google Sign-In SDK ๊ด€๋ จ ๊ทœ์น™์ž…๋‹ˆ๋‹ค.

์ด ๊ทœ์น™๋“ค์„ ์ œ๊ฑฐํ•˜๋ฉด APK ํฌ๊ธฐ๋ฅผ ์ค„์ด๊ณ  ์ ์ ˆํ•œ ๋‚œ๋…ํ™”๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค:

-keep class * extends com.google.gson.TypeAdapter
-keep class com.google.googlesignin.** { *; }
๐Ÿค– Prompt for AI Agents
In app/proguard-rules.pro around lines 23 to 24, remove the two unused legacy
ProGuard rules for Google Sign-In and Gson TypeAdapter (the lines keeping
com.google.googlesignin.** and classes extending com.google.gson.TypeAdapter);
delete those two lines, run a clean build to confirm no missing symbol or
runtime obfuscation issues, and if build/tests pass, commit the updated proguard
file.

Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ internal fun Project.configureBuildConfig(
"NAVER_DEVELOPERS_CLIENT_SECRET",
gradleLocalProperties(rootDir, providers).getProperty("naver.developers.client.secret")
)
buildConfigField(
"String",
"GOOGLE_WEB_CLIENT_ID",
gradleLocalProperties(rootDir, providers).getProperty("google.web.client.id")
)
}

buildFeatures {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.teamsolply.solply.buildconfig.impl

import com.teamsolply.solply.buildconfig.BuildConfig.BASE_URL
import com.teamsolply.solply.buildconfig.BuildConfig.GOOGLE_WEB_CLIENT_ID
import com.teamsolply.solply.buildconfig.BuildConfig.KAKAO_NATIVE_KEY
import com.teamsolply.solply.buildconfig.BuildConfig.NAVER_CLIENT_ID
import com.teamsolply.solply.buildconfig.BuildConfig.NAVER_DEVELOPERS_CLIENT_ID
Expand All @@ -14,6 +15,7 @@ class BuildConfigFieldsProviderImpl @Inject constructor() : BuildConfigFieldProv
BuildConfigFields(
baseUrl = BASE_URL,
kakaoNativeKey = KAKAO_NATIVE_KEY,
googleWebClientId = GOOGLE_WEB_CLIENT_ID,
naverClientId = NAVER_CLIENT_ID,
naverDevelopersClientId = NAVER_DEVELOPERS_CLIENT_ID,
naverDevelopersClientSecret = NAVER_DEVELOPERS_CLIENT_SECRET,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.teamsolply.solply.common.buildconfig
data class BuildConfigFields(
val baseUrl: String,
val kakaoNativeKey: String,
val googleWebClientId: String,
val naverClientId: String,
val naverDevelopersClientId: String,
val naverDevelopersClientSecret: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ fun ProfileEditScreen(
onBackButtonClick = onBackButtonClick
)
Spacer(
modifier = Modifier.weight(1f)
modifier = Modifier.height(16.dp)
)
Image(
painter = painterResource(R.drawable.img_basic_profile),
Expand All @@ -138,7 +138,7 @@ fun ProfileEditScreen(
.clip(CircleShape),
contentScale = ContentScale.Fit
)
Spacer(modifier = Modifier.weight(2f))
Spacer(modifier = Modifier.height(32.dp))
Column(
modifier = Modifier.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.Top,
Expand Down Expand Up @@ -166,7 +166,7 @@ fun ProfileEditScreen(
modifier = Modifier.padding(top = 12.dp)
)
}
Spacer(modifier = Modifier.weight(1.5f))
Spacer(modifier = Modifier.height(24.dp))
Column(
modifier = Modifier.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.Top,
Expand Down
6 changes: 6 additions & 0 deletions feature/oauth/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import com.teamsolply.solply.convention.extension.implementation

plugins {
alias(libs.plugins.solply.feature)
}
Expand All @@ -8,5 +10,9 @@ android {

dependencies {
implementation(libs.kakao.login)
implementation(libs.google.id)
implementation(libs.credentials.play.auth)
implementation(libs.credentials)
implementation(projects.domain.oauth)
implementation(projects.core.buildconfig)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.teamsolply.solply.oauth

import android.content.Context
import android.util.Log
import androidx.credentials.CredentialManager
import androidx.credentials.CustomCredential
import androidx.credentials.GetCredentialRequest
import androidx.credentials.GetCredentialResponse
import androidx.credentials.exceptions.GetCredentialException
import com.google.android.libraries.identity.googleid.GetGoogleIdOption
import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
import com.google.android.libraries.identity.googleid.GoogleIdTokenParsingException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.UUID

class GoogleLoginHelper(
private val context: Context
) {

companion object {
const val TAG = "GoogleLogin"
const val WEB_CLIENT_ID = com.teamsolply.solply.buildconfig.BuildConfig.GOOGLE_WEB_CLIENT_ID
// const val SERVER_URL = ""
}

private val credentialManager: CredentialManager = CredentialManager.create(context)
private val nonce = UUID.randomUUID().toString()
private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setServerClientId(WEB_CLIENT_ID) // ์›น ํด๋ผ์ด์–ธํŠธ ID
.setFilterByAuthorizedAccounts(false)
// .setNonce(nonce)
.build()
Comment on lines +29 to +34
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

๐Ÿงฉ Analysis chain

Nonce ์‚ฌ์šฉ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

Line 33์—์„œ setNonce(nonce)๊ฐ€ ์ฃผ์„ ์ฒ˜๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. Nonce๋Š” ์žฌ์ƒ ๊ณต๊ฒฉ(replay attack)์„ ๋ฐฉ์ง€ํ•˜๋Š” ์ค‘์š”ํ•œ ๋ณด์•ˆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ํŠน๋ณ„ํ•œ ์ด์œ ๊ฐ€ ์—†๋‹ค๋ฉด ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

Nonce๋ฅผ ํ™œ์„ฑํ™”ํ•˜์„ธ์š”:

     private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
         .setServerClientId(WEB_CLIENT_ID) // ์›น ํด๋ผ์ด์–ธํŠธ ID
         .setFilterByAuthorizedAccounts(false)
-//        .setNonce(nonce)
+        .setNonce(nonce)
         .build()

์„œ๋ฒ„ ์ธก์—์„œ๋„ ์ด nonce๋ฅผ ๊ฒ€์ฆํ•˜๋„๋ก ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.


์ด ๋ฆฌ๋ทฐ ์ฝ”๋ฉ˜ํŠธ๋ฅผ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด ์ €์žฅ์†Œ๋ฅผ ๋ถ„์„ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค.


๐Ÿ Script executed:

#!/bin/bash
# Search for the GoogleLoginHelper file and examine its content
find . -name "GoogleLoginHelper.kt" -type f 2>/dev/null | head -5

Length of output: 78


๐Ÿ Script executed:

#!/bin/bash
# Search for GetGoogleIdOption imports and usage patterns
rg -n "GetGoogleIdOption" --type kotlin -B3 -A3

Length of output: 1907


๐Ÿ Script executed:

#!/bin/bash
# Search for nonce-related code and comments in the OAuth module
rg -n "nonce" --type kotlin -i -B2 -A2

Length of output: 1167


๐Ÿ Script executed:

#!/bin/bash
# Search for server-side validation of nonce
rg -n "nonce" --type java --type kotlin -B2 -A2

Length of output: 1167


๐Ÿ Script executed:

#!/bin/bash
# Check for build.gradle or pom.xml to identify Google Sign-In library version
find . -name "build.gradle*" -o -name "pom.xml" | xargs grep -l "google\|gms" 2>/dev/null | head -5

Length of output: 96


๐Ÿ Script executed:

#!/bin/bash
# Search for any comments or documentation explaining why nonce was commented out
rg -n "replay\|setNonce" --type kotlin --type java -B5 -A5

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Look for server-side token validation logic
rg -n "verify|validate" --type kotlin --type java -i | grep -i "token\|nonce\|jwt\|google" | head -10

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check Google Play Services dependency version
find . -name "build.gradle*" | xargs cat 2>/dev/null | grep -A2 -B2 "play.*services.*auth"

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Search for any authentication verification controller or service
fd -e "\.kt$" -e "\.java$" | xargs grep -l "GoogleIdToken\|idToken\|verify" | head -10

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Look at the full GoogleLoginHelper.kt file
find . -name "GoogleLoginHelper.kt" -type f 2>/dev/null -exec cat {} \;

Length of output: 3518


๐Ÿ Script executed:

#!/bin/bash
# Search for server endpoint that might validate Google tokens
rg -n "google.*token\|id.*token" --type kotlin --type java -i | head -15

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check if there's documentation or configuration about security settings
find . -name "*.md" -o -name "*.txt" | xargs grep -l "nonce\|security" 2>/dev/null | head -5

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Search for any existing issue tracker references or TODOs related to nonce
rg -n "TODO|FIXME|XXX" --type kotlin | grep -i "nonce\|security\|replay"

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Look for Google Sign-In implementation details
rg -n "credentialResponse\|credential" --type kotlin -B3 -A3 | head -30

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check for any security-related tests or validation logic
fd -e "Test.kt" -e "Test.java" | xargs grep -l "nonce\|GoogleLogin" 2>/dev/null

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Look for the server-side implementation that receives the token
rg -n "id_token\|idToken" --type kotlin --type java | head -15

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Search for GoogleIdTokenVerifier or similar verification
rg -n "Verifier\|Verify" --type java --type kotlin | grep -i google

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check application properties or configuration for security settings
find . -name "application*.yml" -o -name "application*.properties" | xargs cat 2>/dev/null | grep -i nonce

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Look at git history to see if nonce was commented intentionally
git log --oneline feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt 2>/dev/null | head -10

Length of output: 104


๐Ÿ Script executed:

#!/bin/bash
# Check for any existing nonce validation on the backend
rg -n "setNonce\|getNonce\|validateNonce" --type java --type kotlin

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Look for any comments or documentation files
find . -path "*oauth*" -name "*.md" -o -path "*oauth*" -name "README*" 2>/dev/null

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Get broader context about the OAuth implementation
find . -path "*oauth*" -type f \( -name "*.kt" -o -name "*.java" \) | head -20

Length of output: 1603


๐Ÿ Script executed:

#!/bin/bash
# Check for ID token validation on server
rg -n "verify.*IdToken\|validateIdToken" --type java --type kotlin

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Search for Google API client library usage
rg -n "GoogleIdTokenVerifier\|TokenVerifier" --type java

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check if there's any exception handling related to nonce
rg -n "NonceException\|InvalidNonce" --type java --type kotlin

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Look for the complete token exchange process
rg -n "exchange\|credential.*token" --type kotlin -B2 -A2 | head -30

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Search for server endpoint path that handles token verification
rg -n "@PostMapping\|@GetMapping\|@PutMapping" --type kotlin | grep -i "token\|auth\|google\|login"

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check if nonce is being used elsewhere in the codebase
find . -type f \( -name "*.kt" -o -name "*.java" \) -exec grep -l "nonce" {} \;

Length of output: 78


๐Ÿ Script executed:

#!/bin/bash
# Look for documentation on Google Sign-In requirements
find . -name "*.md" -o -name "*.txt" -o -name "*.adoc" 2>/dev/null | xargs grep -l "Google\|OAuth\|Sign-In" 2>/dev/null | head -5

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check for any vulnerability scanning or security audit files
find . -name "*.json" -o -name "*.yaml" | xargs grep -l "security\|vulnerability\|nonce" 2>/dev/null | head -5

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Get imports from GoogleLoginHelper to understand the library version
rg -n "^import" feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt 2>/dev/null || rg -n "^import" . --type kotlin | grep -i google | head -10

Length of output: 709


๐Ÿ Script executed:

#!/bin/bash
# Check the Google Sign-In Android library documentation reference
cat build.gradle.kts 2>/dev/null || cat build.gradle 2>/dev/null || find . -name "build.gradle*" -type f | head -1 | xargs cat

Length of output: 637


๐Ÿ Script executed:

#!/bin/bash
# Look for any recent commit messages about nonce
git log --all --grep="nonce" --oneline 2>/dev/null || echo "No git history available"

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Final check: get the exact structure of the GoogleLoginHelper.kt file around the nonce area
find . -name "GoogleLoginHelper.kt" -type f 2>/dev/null -exec head -50 {} \;

Length of output: 1954


๐Ÿ Script executed:

#!/bin/bash
# Look for server-side Google token verification configuration
find . -name "*.properties" -o -name "*.yml" -o -name "*.yaml" 2>/dev/null | xargs grep -i "google\|client.*id\|secret" 2>/dev/null | head -15

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check for Spring Security or other security framework configuration
rg -n "@EnableWebSecurity\|@Configuration\|securityFilterChain" --type java -B2 -A10 | head -40

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Search for JWT or token parsing logic
rg -n "JwtDecoder\|verifyIdToken\|parseToken" --type java -B2 -A5

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Look for Google client library initialization
rg -n "GoogleCredential\|GoogleAuthorizationCodeFlow\|GoogleClientSecrets" --type java

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check if nonce validation happens in a filter or interceptor
rg -n "OncePerRequestFilter\|HandlerInterceptor" --type java -B2 -A10

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Get comprehensive view of OAuth module structure
find feature/oauth -type f 2>/dev/null | head -30

Length of output: 525


๐Ÿ Script executed:

#!/bin/bash
# Look at documentation or SECURITY.md files
find . -iname "*security*" -type f 2>/dev/null | head -10

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check for any architecture decision records or ADRs
find . -iname "*adr*" -o -iname "*decision*" -type f 2>/dev/null | head -10

Length of output: 94


๐Ÿ Script executed:

#!/bin/bash
# Final comprehensive search for nonce implementation
rg -rn "nonce" --type-list 2>/dev/null | head -5
rg -rn "nonce" . 2>/dev/null | head -20

Length of output: 229


๐Ÿ Script executed:

#!/bin/bash
# Check if setNonce is available in the GetGoogleIdOption API
rg -n "GetGoogleIdOption" . --type kotlin -A20 2>/dev/null | head -40

Length of output: 4825


๐Ÿ Script executed:

#!/bin/bash
# Look for version constraints or documentation
cat README.md 2>/dev/null | grep -i "google\|oauth\|security" || echo "No README or no matches"

Length of output: 71


๐Ÿ Script executed:

#!/bin/bash
# Check all files in the OAuth feature module
ls -la feature/oauth/ 2>/dev/null || find . -path "*oauth*" -type d | head -5

Length of output: 289


๐Ÿ Script executed:

#!/bin/bash
# Last attempt - find the exact file and show context
find . -name "GoogleLoginHelper.kt" 2>/dev/null -exec echo "=== File: {} ===" \; -exec cat {} \;

Length of output: 3611


๐Ÿ Script executed:

#!/bin/bash
# Check for Google Play Services version in dependencies
rg -n "com\.google\.android\.gms" . --type kotlin --type java | head -10

Length of output: 306


๐Ÿ Script executed:

#!/bin/bash
# Look for version strings
rg -n "gms:play-services.*auth" . | head -5

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Check if there's a docs folder
find . -type d -name "docs" -o -type d -name "documentation" 2>/dev/null

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Search for any CHANGELOG or release notes
find . -iname "CHANGELOG*" -o -iname "RELEASE*" 2>/dev/null | xargs grep -i nonce 2>/dev/null

Length of output: 0


๐Ÿ Script executed:

#!/bin/bash
# Verify the exact lines mentioned in the review
sed -n '29,34p' feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt 2>/dev/null || echo "File path may differ"

Length of output: 278


๐ŸŒ Web query:

Google Identity Services GetGoogleIdOption setNonce method API documentation

๐Ÿ’ก Result:

setNonce is a method on GetGoogleIdOption.Builder that sets the nonce used when generating a Google ID token. Use it when you build GetGoogleIdOption to have the ID token include the provided nonce.

Example (Kotlin):
val googleIdOption = GetGoogleIdOption.Builder()
.setServerClientId(WEB_CLIENT_ID)
.setNonce(nonce)
.build()

Docs: GetGoogleIdOption.Builder (method: setNonce). [1][2]

Sources
[1] GetGoogleIdOption.Builder โ€” Google Developers reference.
[2] Authenticate users with Sign in with Google (Android) โ€” example showing setNonce.


๐ŸŒ Web query:

androidx.credentials GetGoogleIdOption.Builder setNonce API

๐Ÿ’ก Result:

Yes โ€” GetGoogleIdOption.Builder has a setNonce(...) method. It accepts a String nonce and sets the nonce used when generating the Google ID token.

Signatures / usage examples:

  • Kotlin: .setNonce(nonce: String?) on GetGoogleIdOption.Builder. [1]
  • Java: public @nonnull GetGoogleIdOption.Builder setNonce(String nonce). [2]

Example (Kotlin):
val googleIdOption = GetGoogleIdOption.Builder()
.setServerClientId(WEB_CLIENT_ID)
.setNonce(nonce)
.build()

Sources:
[1] Android Developers โ€” Credential Manager: GetGoogleIdOption.Builder (example).
[2] Google Developers reference โ€” GetGoogleIdOption.Builder API.


๐ŸŒ Web query:

Google Sign-In nonce replay attack prevention Android 2024 2025

๐Ÿ’ก Result:

Short answer โ€” use Googleโ€™s nonce support + server-side single-use verification and device attestation:

Key points and recommended implementation

  • Include a cryptographically strong nonce in each Android Signโ€‘in with Google request (setNonce / setNonce(nonce)). This nonce is intended to prevent replay attacks. [1][2][3]
  • Generate the nonce serverโ€‘side or in the app using a CSPRNG (unpredictable), encode URLโ€‘safe Base64, and meet provider rules (recommend โ‰ฅ16 bytes). Treat each nonce as singleโ€‘use. Store/hash it serverโ€‘side and mark consumed when an ID token/response is accepted. Reject reused or expired nonces. [2][4]
  • Verify the ID token on your backend: verify signature, aud/client_id, exp/iat, and that the token contains the same nonce you issued. Only accept tokens whose nonce matches a currently valid, unused nonce. [1][3]
  • Optionally harden highโ€‘value flows by combining nonce with a request hash or a serverโ€‘issued unique value and using Play Integrity / SafetyNet attestation (nonce field) to bind the response to the specific request and detect tampering. Check and verify attestation tokens serverโ€‘side. [4][5]
  • For OAuth code flows, also use PKCE on top of the above; for OIDC ID tokens rely on the nonce claim to prevent replay. [3]

Quick checklist

  1. Generate CSPRNG nonce (server or app), URLโ€‘safe Base64, โ‰ฅ16 bytes. [4]
  2. Call setNonce(...) when building the Google signโ€‘in request on Android. [1][2][6]
  3. Exchange/receive ID token on server; verify signature, aud, exp, iat, and nonce == stored value. Mark nonce used. [1][3]
  4. Consider Play Integrity / SafetyNet attestation and request-specific hashes for extra protection. [4][5]

Sources
[1] Google Identity Android docs โ€” BeginSignInRequest / GetSignInIntentRequest nonce docs.
[2] Android Developer: Authenticate users with Sign in with Google (nonce guidance).
[3] Google Signโ€‘in / OpenID Connect guidance (ID token verification / nonce role).
[4] Play Integrity and SafetyNet docs โ€” using nonce to prevent replay/tampering.
[5] Google Signโ€‘in Security bundle / claims (auth_time and additional trust signals).


Nonce ํ™œ์„ฑํ™” ๋ฐ ์„œ๋ฒ„ ์ธก ๊ฒ€์ฆ ๊ตฌํ˜„ ํ•„์š”

ํด๋ผ์ด์–ธํŠธ: GetGoogleIdOption.Builder์˜ setNonce() ๋ฉ”์„œ๋“œ๊ฐ€ Google ๊ณต์‹ API์—์„œ ์ง€์›๋˜๋ฉฐ, nonce๋Š” ์žฌ์ƒ ๊ณต๊ฒฉ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ํ‘œ์ค€ ๋ณด์•ˆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ํ˜„์žฌ nonce๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์œผ๋‚˜(29์ค„) ์ฃผ์„ ์ฒ˜๋ฆฌ๋˜์–ด ์‚ฌ์šฉ๋˜์ง€ ์•Š๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์„œ๋ฒ„: ์ €์žฅ์†Œ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ nonce ๊ฒ€์ฆ ๋กœ์ง์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Google ๋ฌธ์„œ์— ๋”ฐ๋ฅด๋ฉด ์„œ๋ฒ„๋Š” ID ํ† ํฐ์˜ nonce ํด๋ ˆ์ž„์„ ๋ฐœ๊ธ‰๋œ nonce์™€ ๋น„๊ตํ•˜์—ฌ ๊ฒ€์ฆํ•ด์•ผ ํ•˜๋ฉฐ, ๊ฐ nonce๋Š” ๋‹จ์ผ ์‚ฌ์šฉ ์šฉ๋„๋กœ ์ทจ๊ธ‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
        .setServerClientId(WEB_CLIENT_ID) // ์›น ํด๋ผ์ด์–ธํŠธ ID
        .setFilterByAuthorizedAccounts(false)
-//        .setNonce(nonce)
+        .setNonce(nonce)
        .build()

์„œ๋ฒ„ ์ธก์—์„œ ์ด nonce๋ฅผ ID ํ† ํฐ์˜ nonce ํด๋ ˆ์ž„๊ณผ ๋Œ€์กฐํ•˜์—ฌ ๊ฒ€์ฆํ•˜๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ 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
private val nonce = UUID.randomUUID().toString()
private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setServerClientId(WEB_CLIENT_ID) // ์›น ํด๋ผ์ด์–ธํŠธ ID
.setFilterByAuthorizedAccounts(false)
// .setNonce(nonce)
.build()
private val nonce = UUID.randomUUID().toString()
private val googleIdOption: GetGoogleIdOption = GetGoogleIdOption.Builder()
.setServerClientId(WEB_CLIENT_ID) // ์›น ํด๋ผ์ด์–ธํŠธ ID
.setFilterByAuthorizedAccounts(false)
.setNonce(nonce)
.build()
๐Ÿค– Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt
around lines 29โ€“34, the client creates a nonce but it's commented out and also
generated as a single class-level val; enable nonce usage and make it
per-login-request by generating a fresh UUID for each auth attempt and pass it
to GetGoogleIdOption.setNonce(nonce). On the server side, implement persistent
single-use nonce handling: when starting an auth flow persist the nonce (e.g.,
DB/Redis) with an expiry, accept the ID token from the client and decode its
nonce claim, compare it to the stored nonce, reject mismatches or
expired/nonexistent nonces, and delete/mark the nonce as used so it cannot be
reused.

private val request: GetCredentialRequest = GetCredentialRequest.Builder()
.addCredentialOption(googleIdOption)
.build()
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๊ณ ๋ คํ•˜์ง€ ์•Š์€ CoroutineScope๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๋ฅผ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

CoroutineScope(Dispatchers.Main)์„ ์ง์ ‘ ์ƒ์„ฑํ•˜๋ฉด Activity/Fragment๊ฐ€ ์ข…๋ฃŒ๋˜์–ด๋„ ์ฝ”๋ฃจํ‹ด์ด ๊ณ„์† ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ์ ์šฉํ•˜์„ธ์š”:

  1. ๊ถŒ์žฅ: Activity์˜ lifecycleScope๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›๊ธฐ
class GoogleLoginHelper(
    private val context: Context,
    private val scope: CoroutineScope
)
// Usage: GoogleLoginHelper(context, lifecycleScope)
  1. ๋Œ€์•ˆ: SupervisorJob์„ ์‚ฌ์šฉํ•˜๊ณ  ๋ช…์‹œ์ ์œผ๋กœ ์ทจ์†Œ
private val job = SupervisorJob()
private val coroutineScope = CoroutineScope(Dispatchers.Main + job)

fun dispose() {
    job.cancel()
}
๐Ÿค– Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt
around line 38, the helper creates a raw CoroutineScope with Dispatchers.Main
which can outlive Activity/Fragment and cause memory leaks; change the class to
accept a CoroutineScope (preferably lifecycleScope) as a constructor parameter
and use that scope for launches, or alternatively create the scope with a
SupervisorJob combined with Dispatchers.Main and expose a dispose/clear method
that cancels the job; update usages to pass lifecycleScope from
Activities/Fragments when instantiating the helper.


fun requestGoogleLogin(
onSuccess: (String) -> Unit,
onFailure: (String) -> Unit
) {
coroutineScope.launch {
try {
val result = credentialManager.getCredential(
request = request,
context = context
)
handleSignInResult(result, onSuccess, onFailure)
} catch (e: GetCredentialException) {
Log.e("Google Sign-in failed", " ${e.localizedMessage}")
}
}
Comment on lines +51 to +54
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ onFailure ์ฝœ๋ฐฑ์ด ํ˜ธ์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

GetCredentialException์ด ๋ฐœ์ƒํ•˜๋ฉด ๋กœ๊ทธ๋งŒ ๋‚จ๊ธฐ๊ณ  onFailure ์ฝœ๋ฐฑ์„ ํ˜ธ์ถœํ•˜์ง€ ์•Š์•„ UI๊ฐ€ ๋กœ๊ทธ์ธ ์‹คํŒจ๋ฅผ ๊ฐ์ง€ํ•˜์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ๋กœ๊ทธ์ธ์ด ์ง„ํ–‰ ์ค‘์ธ์ง€ ์‹คํŒจํ–ˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์„ธ์š”:

             } catch (e: GetCredentialException) {
                 Log.e("Google Sign-in failed", " ${e.localizedMessage}")
+                onFailure(e.localizedMessage ?: "๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.")
             }
๐Ÿ“ 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
} catch (e: GetCredentialException) {
Log.e("Google Sign-in failed", " ${e.localizedMessage}")
}
}
} catch (e: GetCredentialException) {
Log.e("Google Sign-in failed", " ${e.localizedMessage}")
onFailure(e.localizedMessage ?: "๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.")
}
}
๐Ÿค– Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt
around lines 51โ€“54, the catch block for GetCredentialException currently only
logs the error so the UI never learns the login failed; update the catch to
invoke the provided onFailure callback with the exception (e.g., onFailure(e))
so failure is propagated to the caller, and if onFailure must run on the UI
thread, post/invoke it on the main thread before returning.

}

private fun handleSignInResult(
result: GetCredentialResponse,
onSuccess: (String) -> Unit,
onFailure: (String) -> Unit
) {
when (val credential = result.credential) {
is CustomCredential -> {
if (credential.type == GoogleIdTokenCredential.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL) {
try {
val googleIdTokenCredential =
GoogleIdTokenCredential.createFrom(credential.data)
val idToken = googleIdTokenCredential.idToken
// Log.d(TAG, idToken) //ํ† ํฐ
// Log.d(TAG, googleIdTokenCredential.id) //์ด๋ฉ”์ผ
// googleIdTokenCredential.displayName?.let { Log.d(TAG, it) } //์ด๋ฆ„
onSuccess(idToken) // ์„ฑ๊ณต ์‹œ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜, ์„œ๋ฒ„ ์‘๋‹ต ํ›„ ์‹คํ–‰, ์—ฌ๊ธฐ์„œ๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ์ด๊ณณ์—์„œ ์‹คํ–‰
} catch (e: GoogleIdTokenParsingException) {
// Log.e(TAG, "Received an invalid google id token response", e)
}
Comment on lines +73 to +75
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

์˜ˆ์™ธ๊ฐ€ ์‚ผ์ผœ์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

GoogleIdTokenParsingException์ด ๋ฐœ์ƒํ•ด๋„ ์•„๋ฌด ์ฒ˜๋ฆฌ๋„ ํ•˜์ง€ ์•Š์•„ ์‚ฌ์šฉ์ž๊ฐ€ ์‹คํŒจ๋ฅผ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. Detekt๋„ ์ด๋ฅผ ๊ฒฝ๊ณ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ์‹คํŒจ ์ฝœ๋ฐฑ์„ ํ˜ธ์ถœํ•˜์„ธ์š”:

                     } catch (e: GoogleIdTokenParsingException) {
-//                        Log.e(TAG, "Received an invalid google id token response", e)
+                        Log.e(TAG, "Received an invalid google id token response", e)
+                        onFailure("๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ํ† ํฐ ํŒŒ์‹ฑ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.")
                     }
๐Ÿ“ 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
} catch (e: GoogleIdTokenParsingException) {
// Log.e(TAG, "Received an invalid google id token response", e)
}
} catch (e: GoogleIdTokenParsingException) {
Log.e(TAG, "Received an invalid google id token response", e)
onFailure("๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ํ† ํฐ ํŒŒ์‹ฑ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.")
}
๐Ÿงฐ Tools
๐Ÿช› detekt (1.23.8)

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

(detekt.exceptions.SwallowedException)

๐Ÿค– Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/GoogleLoginHelper.kt
around lines 73-75, the catch block for GoogleIdTokenParsingException currently
swallows the exception; replace it so the exception is logged and the login
failure path is invoked (e.g., call the existing failure callback or listener
with the exception or an appropriate error object). Ensure you do not rethrow
unnecessarily, but propagate the error to the caller by invoking the module's
failure callback (and include the exception in the log message and callback
parameters).

} else {
// Log.e(TAG, "Unexpected type of credential")
onFailure("๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.")
}
}

else -> {
// Log.e(TAG, "Unexpected type of credential")
onFailure("๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ sealed interface OauthIntent : UiIntent {
data object KakaoLoginClick : OauthIntent
data class KakaoLoginSuccess(val provider: String, val accessToken: String) : OauthIntent
data class KakaoLoginFailure(val error: Throwable) : OauthIntent
data object GoogleLoginClick : OauthIntent
data class GoogleLoginSuccess(val provider: String, val accessToken: String) : OauthIntent
data class GoogleLoginFailure(val error: Throwable) : OauthIntent
data class SaveJwtToken(
val accessToken: String,
val refreshToken: String,
Expand All @@ -21,6 +24,7 @@ sealed interface OauthIntent : UiIntent {

sealed interface OauthSideEffect : SideEffect {
data object StartKakaoLogin : OauthSideEffect
data object StartGoogleLogin : OauthSideEffect
data object NavigateToOnBoarding : OauthSideEffect
data object NavigateToPlace : OauthSideEffect
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
Expand Down Expand Up @@ -46,6 +47,7 @@ fun OauthRoute(
viewModel: OauthViewModel = hiltViewModel()
) {
val context = LocalContext.current
val googleLoginHelper = remember { GoogleLoginHelper(context = context) }
val uiState by viewModel.uiState.collectAsStateWithLifecycle()

LaunchedEffectWithLifecycle {
Expand All @@ -63,20 +65,46 @@ fun OauthRoute(
}
)

OauthSideEffect.StartGoogleLogin -> googleLoginHelper.requestGoogleLogin(
onSuccess = { accessToken ->
viewModel.sendIntent(
OauthIntent.GoogleLoginSuccess(
provider = "GOOGLE",
accessToken = accessToken
)
)
},
onFailure = {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸก Minor

์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

onFailure ์ฝœ๋ฐฑ์ด ๋นˆ ๋žŒ๋‹ค {}๋กœ ์ฒ˜๋ฆฌ๋˜์–ด ์žˆ์–ด ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ์‹คํŒจ๋ฅผ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์ตœ์†Œํ•œ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ฑฐ๋‚˜ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ† ์ŠคํŠธ/์Šค๋‚ต๋ฐ”๋กœ ์•Œ๋ ค์ฃผ๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

์˜ˆ์‹œ:

onFailure = { errorMessage ->
    Log.e("OauthScreen", "Google login failed: $errorMessage")
    // TODO: Show error message to user
}

Also applies to: 98-98

๐Ÿค– Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt around
lines 77 and 98, the onFailure callbacks are empty lambdas so login failures are
swallowed; update both callbacks to accept the error (or message) parameter, log
the error (e.g., Log.e with a clear tag and message) and surface feedback to the
user (show a Toast or Snackbar with a user-friendly message); ensure you handle
nullable/unknown error text safely and avoid crashing when showing the UI
feedback.

)

Comment on lines +68 to +79
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ๋กœ์ง์ด ์ค‘๋ณต๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ๋กœ์ง์ด ๋‘ ๊ณณ์— ์ค‘๋ณต ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. Lines 68-79: OauthSideEffect.StartGoogleLogin ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ ์ฒ˜๋ฆฌ
  2. Lines 88-100: googleLoginClick ์ฝœ๋ฐฑ์—์„œ ์ง์ ‘ ํ˜ธ์ถœ

์ด๋Š” ์˜๋„ํ•˜์ง€ ์•Š์€ ๋™์ž‘์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์นด์นด์˜ค ๋กœ๊ทธ์ธ๊ณผ ๋™์ผํ•˜๊ฒŒ ViewModel์„ ํ†ตํ•œ ๋‹จ์ผ ํ”Œ๋กœ์šฐ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์—ฌ ์นด์นด์˜ค ๋กœ๊ทธ์ธ๊ณผ ์ผ๊ด€๋œ ํŒจํ„ด์„ ์œ ์ง€ํ•˜์„ธ์š”:

     OauthScreen(
         kakaoLoginClick = { viewModel.sendIntent(OauthIntent.KakaoLoginClick) },
-        googleLoginClick = {
-            googleLoginHelper.requestGoogleLogin(
-                onSuccess = { accessToken ->
-                    viewModel.sendIntent(
-                        OauthIntent.GoogleLoginSuccess(
-                            provider = "GOOGLE",
-                            accessToken = accessToken
-                        )
-                    )
-                },
-                onFailure = {}
-            )
-        }
+        googleLoginClick = { viewModel.sendIntent(OauthIntent.GoogleLoginClick) }
     )

Also applies to: 88-100

๐Ÿค– Prompt for AI Agents
In feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthScreen.kt around
lines 68-79 and 88-100 there is duplicated Google login logic: the same
googleLoginHelper.requestGoogleLogin call is invoked both from the
OauthSideEffect.StartGoogleLogin handler and from the googleLoginClick callback.
Remove the direct request call from the googleLoginClick callback (lines
~88-100) and instead dispatch a single ViewModel intent from the click handler
(e.g., send an intent like OauthIntent.StartGoogleLogin or similar) so that the
side-effect handler (lines ~68-79) is the sole place that calls
googleLoginHelper.requestGoogleLogin; also propagate success/failure through the
existing ViewModel intents rather than handling login directly in the UI
callback.

OauthSideEffect.NavigateToOnBoarding -> navigateToOnBoarding()
OauthSideEffect.NavigateToPlace -> navigateToPlace()
}
}
}

OauthScreen(
kakaoLoginClick = { viewModel.sendIntent(OauthIntent.KakaoLoginClick) }
kakaoLoginClick = { viewModel.sendIntent(OauthIntent.KakaoLoginClick) },
googleLoginClick = {
googleLoginHelper.requestGoogleLogin(
onSuccess = { accessToken ->
viewModel.sendIntent(
OauthIntent.GoogleLoginSuccess(
provider = "GOOGLE",
accessToken = accessToken
)
)
},
onFailure = {}
)
}
)
}

@Composable
fun OauthScreen(
kakaoLoginClick: () -> Unit,
googleLoginClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
Expand Down Expand Up @@ -157,7 +185,7 @@ fun OauthScreen(
.customClickable(
rippleEnabled = false
) {
kakaoLoginClick()
googleLoginClick()
},
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ class OauthViewModel @Inject constructor(
TODO()
}

OauthIntent.GoogleLoginClick -> postSideEffect(OauthSideEffect.StartGoogleLogin)

is OauthIntent.GoogleLoginSuccess -> postSocialLogin(
provider = intent.provider,
oauthAccessToken = intent.accessToken
)

is OauthIntent.GoogleLoginFailure -> {
TODO()
}
Comment on lines +27 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

Google ๋กœ๊ทธ์ธ ์‹คํŒจ ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

Google ๋กœ๊ทธ์ธ ํ๋ฆ„์ด ์ถ”๊ฐ€๋˜์—ˆ์œผ๋‚˜ GoogleLoginFailure์˜ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ TODO()๋กœ ๋‚จ์•„์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋Ÿฐํƒ€์ž„ ์‹œ NotImplementedError๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

์‚ฌ์šฉ์ž์—๊ฒŒ ์ ์ ˆํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํ‘œ์‹œํ•˜๊ฑฐ๋‚˜ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ๋“ฑ์˜ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

Kakao ๋กœ๊ทธ์ธ ์‹คํŒจ ์ฒ˜๋ฆฌ๋„ ๋™์ผํ•˜๊ฒŒ TODO()๋กœ ๋‚จ์•„์žˆ์Šต๋‹ˆ๋‹ค(Line 24-26). ๋‘ ๊ฒฝ์šฐ ๋ชจ๋‘ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ตฌํ˜„์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๊ตฌํ˜„์„ ๋„์™€๋“œ๋ฆด๊นŒ์š”?

๐Ÿค– Prompt for AI Agents
feature/oauth/src/main/java/com/teamsolply/solply/oauth/OauthViewModel.kt around
lines 24-26 and 28-37: both KakaoLoginFailure and GoogleLoginFailure currently
call TODO() which throws NotImplementedError at runtime; replace each TODO with
concrete failure handling by posting an appropriate side effect (e.g.,
postSideEffect(OauthSideEffect.ShowError(...)) with a user-friendly message and
include/log the underlying error for diagnostics), and ensure any cleanup or
state reset needed after a failed social login is performed (e.g., clear loading
state or reset flags).


is OauthIntent.SaveJwtToken -> {
viewModelScope.launch {
oauthRepository.saveJwtToken(
Expand Down
1 change: 1 addition & 0 deletions feature/oauth/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
<resources>
<string name="kakao_login">์นด์นด์˜ค๋กœ ๊ณ„์†ํ•˜๊ธฐ</string>
<string name="google_login">Google๋กœ ๊ณ„์†ํ•˜๊ธฐ</string>
<string name="apple_login">Apple๋กœ ๊ณ„์†ํ•˜๊ธฐ</string>
</resources>
7 changes: 7 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ coil = "2.7.0"
lottie = "6.4.1"
jsoup = "1.17.2"
kakao-login = "2.19.0"
credentials = "1.6.0-beta03"
googleid = "1.1.1"
Comment on lines +93 to +94
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

โš ๏ธ Potential issue | ๐ŸŸ  Major

๐Ÿงฉ Analysis chain

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฒ„์ „ ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

credentials ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ๋ฒ ํƒ€ ๋ฒ„์ „(1.6.0-beta03)์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์•ˆ์ • ๋ฒ„์ „ ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ๋ช…๋ น์œผ๋กœ ์ตœ์‹  ์•ˆ์ • ๋ฒ„์ „์„ ํ™•์ธํ•˜์„ธ์š”:


๐ŸŒ Web query:

What is the latest stable version of androidx.credentials library?

๐Ÿ’ก Result:

The latest stable release is androidx.credentials:credentials version 1.5.0 (released March 12, 2025). [1]

Sources
[1] Android Jetpack โ€” Credentials release notes.


์ตœ์‹  ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€ ๊ฒ€์ƒ‰์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์•ˆ์ • ๋ฒ„์ „(1.5.0)์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

์ฝ”๋“œ๊ฐ€ ์•„์ง ๋ฒ ํƒ€ ๋‹จ๊ณ„์ธ androidx.credentials:credentials:1.6.0-beta03์„ ์‚ฌ์šฉ ์ค‘์ž…๋‹ˆ๋‹ค. ์ตœ์‹  ์•ˆ์ • ๋ฒ„์ „์€ 1.5.0์ž…๋‹ˆ๋‹ค. ๋ฒ ํƒ€ ๋ฒ„์ „์€ ๋ฏธ์™„์„ฑ ๊ธฐ๋Šฅ๊ณผ ์ž ์žฌ์  ๋ฒ„๊ทธ๊ฐ€ ํฌํ•จ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ์ „์— ์•ˆ์ • ๋ฒ„์ „์œผ๋กœ ๋ณ€๊ฒฝํ•˜์„ธ์š”.

credentials = "1.5.0"์œผ๋กœ ๋ณ€๊ฒฝํ•˜๊ฑฐ๋‚˜, 1.6.0 ๋ฒ ํƒ€ ๋ฒ„์ „ ์‚ฌ์šฉ์ด ํ•„์ˆ˜์ธ ๊ฒฝ์šฐ ๊ทธ ์ด์œ ๋ฅผ ๋ช…์‹œํ•˜์„ธ์š”.

๐Ÿค– Prompt for AI Agents
gradle/libs.versions.toml lines 93-94: the project references the beta artifact
androidx.credentials:credentials 1.6.0-beta03 which is not recommended for
production; update the version to the latest stable release by changing
credentials = "1.6.0-beta03" to credentials = "1.5.0", or if you must keep the
beta, add a short comment next to the entry explaining why the beta is required
and what feature or fix depends on it.

process-pheonix = "3.0.0"
preference = "1.2.1"
collapsing-toolbar = "2.3.5"
Expand Down Expand Up @@ -253,6 +255,11 @@ lottie = { group = "com.airbnb.android", name = "lottie", version.ref = "lottie"
lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", version.ref = "lottie" }
jsoup = { group = "org.jsoup", name = "jsoup", version.ref = "jsoup" }
kakao-login = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakao-login" }

credentials = { module = "androidx.credentials:credentials", version.ref = "credentials" }
credentials-play-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "credentials" }
google-id = { module = "com.google.android.libraries.identity.googleid:googleid", version.ref = "googleid" }

process-phoenix = { module = "com.jakewharton:process-phoenix", version.ref = "process-pheonix" }
collapsing-toolbar = { group = "me.onebone", name = "toolbar-compose", version.ref = "collapsing-toolbar" }
androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" }
Expand Down