Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c56e4fe
[fix]: BuildConfig 오류 해결을 위한 android.defaults.buildfeatures.buildconf…
JJUYAAA Aug 11, 2025
95ca8c8
[feat]: 카카오 - 신규유저 여부 확인 api 요청, 응답 dto(#68)
JJUYAAA Aug 12, 2025
e80c3a5
[feat]: 카카오 - AuthService 생성(#68)
JJUYAAA Aug 12, 2025
a4c5c28
[feat]: 카카오 - AuthRepository 생성(#68)
JJUYAAA Aug 12, 2025
48014b0
[feat]: 카카오 로그인 관련 의존성 추가(#68)
JJUYAAA Aug 12, 2025
e00e585
[feat]: loginscreen - content와 screen(뷰모델 주입받음) 분리 (#68)
JJUYAAA Aug 12, 2025
cdff7a0
[feat]: 카카오 로그인에 필요한 maven 설정 (#68)
JJUYAAA Aug 12, 2025
9a67f61
[feat]: lifecycle 라이브러리 재설치 (#68)
JJUYAAA Aug 12, 2025
56144a0
[feat]: 카카오로그인 뷰모델 생성(#68)
JJUYAAA Aug 12, 2025
e78a120
[feat]:common routes에 로그인 회원가입 관련 스크린 루트 추가(#68)
JJUYAAA Aug 12, 2025
9833a07
[feat]: 토큰매니절 생성(#68)
JJUYAAA Aug 12, 2025
5b5b7e3
[feat]: 신규유저인 경우 피드화면으로 이동하는 로직 테스트 후 주석처리(#68)
JJUYAAA Aug 12, 2025
9b4c045
[feat]: 신규유저 여부에 따라 카카오로그인 후 화면이동 설정(#68)
JJUYAAA Aug 12, 2025
d846745
[feat]: 네크워크모듈 - auth 서비스 추가(#68)
JJUYAAA Aug 12, 2025
fe5f3bb
[feat]: 인터넷 권한 추가(#68)
JJUYAAA Aug 12, 2025
7befae5
[feat]: 네이티브앱 주입하는 코드들 (#68)
JJUYAAA Aug 12, 2025
f39127a
[refactor]: provideAuthService 네트워크 모듈-> 서비스 모듈 이동(#68)
JJUYAAA Aug 12, 2025
5a5c765
[refactor]: NATIVE_APP_KEY 관련 수정사항 반영(#68)
JJUYAAA Aug 12, 2025
d8310aa
[refactor]: NATIVE_APP_KEY 관련 수정사항 반영(#68)
JJUYAAA Aug 12, 2025
fafe4d2
[refactor]: inject import문 변경(#68)
JJUYAAA Aug 12, 2025
8638197
[refactor]: 카카오 sdk 초기화 -> 예외처리 추가(#68)
JJUYAAA Aug 12, 2025
b609cc2
[feat]: google-services.json 추가(#77)
JJUYAAA Aug 12, 2025
7f6cee5
[feat]: 구글 로그인 위한 의존성 추가(#77)
JJUYAAA Aug 12, 2025
09a9f98
[feat]: 구글 로그인 메서드 추가(#77)
JJUYAAA Aug 12, 2025
b116fd1
[feat]: client_id 텍스트 직접 추가(#77)
JJUYAAA Aug 12, 2025
305bb85
[feat]: 구글 로그인 메서드 추가(#77)
JJUYAAA Aug 12, 2025
7c199d5
[feat]: loginscreen -> 구글 로직 추가(#77)
JJUYAAA Aug 12, 2025
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
2 changes: 1 addition & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ plugins {
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.serialization)
id("com.google.dagger.hilt.android")
id("com.google.gms.google-services")
kotlin("kapt")
}

Expand All @@ -27,6 +28,10 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

buildConfigField("String", "BASE_URL", "\"${properties["BASE_URL"]}\"")
buildConfigField("String", "NATIVE_APP_KEY", "\"${properties["NATIVE_APP_KEY"]}\"")
manifestPlaceholders += mapOf(
"NATIVE_APP_KEY" to properties["NATIVE_APP_KEY"] as String
)
}

buildTypes {
Expand Down Expand Up @@ -68,6 +73,7 @@ dependencies {
implementation(libs.kotlinx.serialization.json)
implementation(libs.coil.compose)
implementation(libs.foundation)
implementation(libs.androidx.lifecycle.runtime.compose)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand All @@ -90,6 +96,17 @@ dependencies {
// OkHttp
implementation(libs.okhttp)
implementation(libs.logging.interceptor)

// 카카오 로그인 SDK
implementation("com.kakao.sdk:v2-all:2.21.6")

// 토큰 저장을 위한 DataStore
implementation ("androidx.datastore:datastore-preferences:1.1.1")

// 구글 로그인
implementation(platform("com.google.firebase:firebase-bom:34.1.0"))
implementation("com.google.firebase:firebase-auth")
implementation("com.google.android.gms:play-services-auth:21.2.0")
Comment on lines +107 to +109

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Firebase BOM / Play Services Auth 최신화 여부 확인

보안/호환성 차원에서 최신 안정 버전 사용을 권장합니다. 현재 지정된 버전이 최신인지 확인해 주세요.


🌐 Web query:

What is the latest stable version of "com.google.firebase:firebase-bom" and "com.google.android.gms:play-services-auth"?

💡 Result:

  • Firebase BoM latest stable: 34.1.0.[3][6][7]
  • Google Play services Auth latest stable: 21.4.0.[1][3]

Details and sources:

  • Google’s Firebase Auth docs updated Aug 8, 2025 show the Gradle snippet using implementation(platform("com.google.firebase:firebase-bom:34.1.0")).[3][6] The Firebase release notes confirm the major BoM bump to 34.0.0 on July 21, 2025, with the docs reflecting the current 34.1.0 point update.[7][3]
  • Google Play services release notes list play-services-auth version 21.4.0 released July 23, 2025.[1] The same version is referenced in current Firebase Auth integration docs.[3]

Citations:


Play Services Auth 라이브러리 버전 업데이트 필요

  • 파일: app/build.gradle.kts (라인 107–109)
  • Firebase BoM(34.1.0)은 최신 안정 버전이지만,
    com.google.android.gms:play-services-auth는 현재 21.2.0으로 설정되어 있습니다.
  • 최신 안정 버전인 21.4.0으로 올려주세요.

수정 예시:

- implementation("com.google.android.gms:play-services-auth:21.2.0")
+ implementation("com.google.android.gms:play-services-auth:21.4.0")
📝 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
implementation(platform("com.google.firebase:firebase-bom:34.1.0"))
implementation("com.google.firebase:firebase-auth")
implementation("com.google.android.gms:play-services-auth:21.2.0")
implementation(platform("com.google.firebase:firebase-bom:34.1.0"))
implementation("com.google.firebase:firebase-auth")
implementation("com.google.android.gms:play-services-auth:21.4.0")
🤖 Prompt for AI Agents
In app/build.gradle.kts around lines 107 to 109, the dependency
com.google.android.gms:play-services-auth is pinned to 21.2.0; update that
artifact to the latest stable 21.4.0 so it matches the current Play Services
stable release and works with the Firebase BOM; modify the implementation line
to reference version 21.4.0.

}

kapt {
Expand Down
47 changes: 47 additions & 0 deletions app/google-services.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"project_info": {
"project_number": "353417813537",
"project_id": "thip-3a698",
"storage_bucket": "thip-3a698.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:353417813537:android:69029c2bc957df7fab4d60",
"android_client_info": {
"package_name": "com.texthip.thip"
}
},
"oauth_client": [
{
"client_id": "353417813537-lovs0p2tb9kjnjlp493a7098ov0cb2bu.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.texthip.thip",
"certificate_hash": "99315de9382f8b2903131eb4ae926a4739803ef1"
}
},
{
"client_id": "353417813537-ck9g1v0qprlb5nf4dvasinim403eng0f.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyCFzpbeT-8JQCGxiSKuVXMvcNNYFtl6Fuo"
}
],
Comment on lines +29 to +33

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

❓ Verification inconclusive

공개 저장소에 Firebase API Key가 그대로 커밋됨 — 키 제한 및 회수(rotate) 필요

이 파일은 보통 커밋되기도 하지만, 레포가 공개(public)라면 남용 위험이 있습니다. 최소한 다음 조치가 필요합니다:

  • GCP 콘솔에서 해당 API Key에 Android 앱 제한(패키지명 + SHA-1/256) 강제.
  • 불필요한 API 사용권한 제거(키 제한).
  • 이미 공개됐다면 키 회수(rotate) 후 새 키로 교체.
  • release용 google-services.json은 CI에서만 주입하고, 레포에는 debug용만 커밋(예: src/debug/google-services.json, src/release는 CI 시크릿 사용).

원하시면 CI에서 flavor별 google-services.json을 주입하도록 스크립트/가이드를 드리겠습니다.


긴급: 공개 저장소에 노출된 Firebase API Key 제한 및 회수 필요

app/google-services.json (29-33행)에 아래와 같은 API Key가 그대로 커밋되어 있습니다. 노출된 키는 악용될 위험이 있으므로 즉시 조치해 주세요:

  • GCP 콘솔에서 해당 API Key에 Android 앱 제한(패키지명 + SHA-1/256) 설정
  • 사용하지 않는 API 권한 제거
  • 이미 공개된 키는 회수(rotate) 후 신규 키로 교체
  • release용 google-services.json은 CI에서만 주입하고, 저장소에는 debug용만 커밋 (예: src/debug/google-services.json, release는 CI 시크릿 사용)

필요하시면 CI에서 flavor별 json을 주입하는 스크립트/가이드를 제공해 드리겠습니다.

🤖 Prompt for AI Agents
In app/google-services.json around lines 29-33 an exposed Firebase/GCP API key
was committed; restrict and rotate it immediately: in GCP Console add Android
app restrictions (package name + SHA-1/256) and remove any unnecessary API
permissions, then revoke (rotate) the compromised key and replace it with the
new key in your local config; update the repo to remove the release
google-services.json (keep only a debug/test file, e.g., move release json out
of repo to src/debug/google-services.json if needed), and change CI to inject
the release google-services.json from secrets during build (ensure CI uses the
new rotated key and that no production keys remain in commits).

"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "353417813537-ck9g1v0qprlb5nf4dvasinim403eng0f.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}
22 changes: 21 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".ThipApplication"
android:allowBackup="true"
Expand All @@ -13,6 +15,25 @@
android:supportsRtl="true"
android:theme="@style/Theme.Thip"
tools:targetApi="31">

<meta-data
android:name="com.kakao.sdk.AppKey"
android:value="${NATIVE_APP_KEY}" />

<activity
android:name="com.kakao.sdk.auth.AuthCodeHandlerActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:host="oauth"
android:scheme="kakao${NATIVE_APP_KEY}" />

</intent-filter>
</activity>

<activity
android:name=".MainActivity"
android:exported="true"
Expand All @@ -25,5 +46,4 @@
</intent-filter>
</activity>
</application>

</manifest>
95 changes: 39 additions & 56 deletions app/src/main/java/com/texthip/thip/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.texthip.thip.ui.navigator.navigations.commonNavigation
import com.texthip.thip.ui.navigator.navigations.feedNavigation
import com.texthip.thip.ui.navigator.navigations.groupNavigation
import com.texthip.thip.ui.navigator.navigations.myPageNavigation
import com.texthip.thip.ui.navigator.navigations.searchNavigation
import com.texthip.thip.ui.navigator.routes.CommonRoutes
import com.texthip.thip.ui.signin.screen.LoginScreen
import com.texthip.thip.ui.signin.screen.SigninNicknameScreen
import com.texthip.thip.ui.signin.screen.SplashScreen
import com.texthip.thip.ui.theme.ThipTheme
import com.texthip.thip.ui.theme.ThipTheme.colors
import com.texthip.thip.ui.theme.ThipTheme.typography
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
Expand All @@ -21,63 +27,40 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
setContent {
ThipTheme {
/*Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}*/
MainScreen()
RootNavHost()
}
}
}
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Column {
Text(
text = "Hello $name!",
modifier = modifier,
style = typography.bigtitle_b700_s22_h24,
color = colors.Purple,
)

Text(
text = "Hello $name!",
modifier = modifier,
style = typography.smalltitle_sb600_s16_h20,
color = colors.NeonGreen,
)

Text(
text = "Hello $name!",
modifier = modifier,
style = typography.menu_sb600_s12,
color = colors.Red,
)
fun RootNavHost() {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = CommonRoutes.Splash
) {
// --- 인증 관련 화면들 ---
composable<CommonRoutes.Splash> {
SplashScreen(navController = navController)
}
composable<CommonRoutes.Login> {
LoginScreen(navController = navController)
}
composable<CommonRoutes.Signup> {
SigninNicknameScreen(navController = navController)
}

Text(
text = "Hello $name!",
modifier = modifier,
style = typography.navi_m500_s10,
color = colors.DarkGrey,
// --- 메인 관련 화면들 ---
feedNavigation(navController)
groupNavigation(
navController = navController,
navigateBack = navController::popBackStack
)

Text(
text = "Hello $name!",
modifier = modifier,
style = typography.view_r400_s11_h20,
color = colors.Black,
searchNavigation(navController)
myPageNavigation(navController)
commonNavigation(
navController = navController,
navigateBack = navController::popBackStack
)
}

}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
ThipTheme {
Greeting("Android")
}
}
16 changes: 15 additions & 1 deletion app/src/main/java/com/texthip/thip/ThipApplication.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
package com.texthip.thip

import com.kakao.sdk.common.KakaoSdk
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import com.texthip.thip.BuildConfig


@HiltAndroidApp
class ThipApplication : Application()
class ThipApplication : Application(){
override fun onCreate() {
super.onCreate()

// 카카오 SDK 초기화
try {
KakaoSdk.init(this, BuildConfig.NATIVE_APP_KEY)
}catch (e: Exception){
e.printStackTrace()
}
}
}
Comment on lines +10 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Kakao SDK 초기화: 광범위한 예외 삼키기 대신 키 검증 + 명확한 로깅

현재 try/catch(Exception)으로 초기화 실패를 삼키면 설정 오류(예: 빈 NATIVE_APP_KEY)가 런타임에서 조용히 무시됩니다. 초기화 전 키를 검증하고, 문제 시 명확히 로깅한 뒤 초기화를 건너뛰는 쪽이 디버깅과 운영 안정성에 유리합니다.

적용 제안(diff):

-        // 카카오 SDK 초기화
-        try {
-            KakaoSdk.init(this, BuildConfig.NATIVE_APP_KEY)
-        }catch (e: Exception){
-            e.printStackTrace()
-        }
+        // 카카오 SDK 초기화
+        if (BuildConfig.NATIVE_APP_KEY.isBlank()) {
+            android.util.Log.e("ThipApplication", "KAKAO NATIVE_APP_KEY is missing. Kakao SDK initialization skipped.")
+        } else {
+            KakaoSdk.init(this, BuildConfig.NATIVE_APP_KEY)
+        }

검증 포인트:

  • CI/릴리즈 빌드에서도 BuildConfig.NATIVE_APP_KEY가 빈 문자열이 아닌지 확인(로컬 프로퍼티 미설정 시 대체 값 주입 여부).
  • Manifest의 com.kakao.sdk.AppKey 메타데이터와 프로그램적 초기화 키가 동일하게 유지되는지 확인.

아래 스크립트를 실행해 NATIVE_APP_KEY 설정 위치와 메타데이터를 확인해 주세요.
이 정보를 바탕으로 검증 후 최종 리뷰 코멘트를 작성하겠습니다.


🏁 Script executed:

#!/bin/bash
# 1. build.gradle에서 BuildConfig 필드로 선언된 NATIVE_APP_KEY 위치 확인
rg "buildConfigField.*NATIVE_APP_KEY" -n

# 2. 프로젝트 전반에서 NATIVE_APP_KEY 사용 위치 확인 (예: KakaoSdk.init 호출 외)
rg "NATIVE_APP_KEY" -n

# 3. AndroidManifest.xml에서 com.kakao.sdk.AppKey 메타데이터 선언 확인
rg "com.kakao.sdk.AppKey" -n

Length of output: 806


카카오 SDK 초기화: 키 검증 후 명확한 로깅으로 예외 처리 개선
BuildConfig.NATIVE_APP_KEYapp/build.gradle.kts에서 properties["NATIVE_APP_KEY"]로 정의되고, AndroidManifest.xml의 com.kakao.sdk.AppKey 메타데이터 및 앱 스킴(kakao${NATIVE_APP_KEY})에도 동일한 값으로 주입되는 것을 확인했습니다. 따라서 초기화 전 빈 값 검증 + 오류 로깅 후 초기화를 건너뛰는 방식으로 변경하는 것이 적절합니다.

대상 파일

  • app/src/main/java/com/texthip/thip/ThipApplication.kt

제안하는 diff:

@@ -13,9 +13,12 @@ class ThipApplication : Application(){
-        // 카카오 SDK 초기화
-        try {
-            KakaoSdk.init(this, BuildConfig.NATIVE_APP_KEY)
-        } catch (e: Exception) {
-            e.printStackTrace()
-        }
+        // 카카오 SDK 초기화: 키 검증 후 초기화
+        val kakaoKey = BuildConfig.NATIVE_APP_KEY
+        if (kakaoKey.isBlank()) {
+            Log.e("ThipApplication", "KAKAO NATIVE_APP_KEY가 설정되지 않았습니다. SDK 초기화를 건너뜁니다.")
+        } else {
+            KakaoSdk.init(this, kakaoKey)
+        }

이렇게 변경하면 설정 누락 시 Silent Fail을 방지하고, 운영 환경에서 문제 원인을 빠르게 파악할 수 있습니다.

🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/ThipApplication.kt around lines 10 to 21,
the KakaoSdk.init call should guard against an empty or missing
BuildConfig.NATIVE_APP_KEY and log a clear error instead of silently failing;
change the onCreate to check if BuildConfig.NATIVE_APP_KEY.isNullOrBlank() and
if so log an error (including the key state and guidance to check
app/build.gradle.kts and AndroidManifest meta-data) and skip initialization,
otherwise proceed to call KakaoSdk.init inside a try/catch that logs the
exception with context (use Log.e or your app logger) rather than only printing
the stack trace.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.texthip.thip.data.di

import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.texthip.thip.BuildConfig
import com.texthip.thip.data.service.AuthService
import com.texthip.thip.utils.auth.AuthInterceptor
import dagger.Module
import dagger.Provides
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/java/com/texthip/thip/data/di/ServiceModule.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.texthip.thip.data.di

import com.texthip.thip.data.service.AuthService
import com.texthip.thip.data.service.BookService
import com.texthip.thip.data.service.GroupService
import com.texthip.thip.data.service.RoomsService
Expand All @@ -25,8 +26,15 @@ object ServiceModule {
fun provideBookService(retrofit: Retrofit): BookService {
return retrofit.create(BookService::class.java)
}

@Provides
@Singleton
fun providesRoomsService(retrofit: Retrofit): RoomsService =
retrofit.create(RoomsService::class.java)

@Provides
@Singleton
fun provideAuthService(retrofit: Retrofit): AuthService {
return retrofit.create(AuthService::class.java)
}
}
46 changes: 46 additions & 0 deletions app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.texthip.thip.data.manager

import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class TokenManager @Inject constructor(
@ApplicationContext private val context: Context
) {
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "thip_auth_tokens")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

DataStore 위임 프로퍼티는 파일 top-level로 옮기세요 (싱글 인스턴스 보장 및 메모리 누수 방지).

Jetpack DataStore 권장사항은 Context 확장 위임 프로퍼티를 파일 최상위(top-level)에 두는 것입니다. 현재 클래스 내부에 선언되어 있어도 싱글톤일 때는 동작하지만, 유지보수성과 안전성 측면에서 top-level로 이동하는 것이 좋습니다.

아래 diff로 클래스 내부 사용처를 context.authDataStore로 변경하고, 내부 위임 프로퍼티를 제거해 주세요:

-    private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "thip_auth_tokens")
+    // moved to top-level: use `context.authDataStore` instead

@@
-        context.dataStore.edit { prefs ->
+        context.authDataStore.edit { prefs ->
             prefs[APP_TOKEN_KEY] = token
         }
@@
-        return context.dataStore.data.map { prefs ->
+        return context.authDataStore.data.map { prefs ->
             prefs[APP_TOKEN_KEY]
         }
@@
-        context.dataStore.edit { prefs ->
+        context.authDataStore.edit { prefs ->
             prefs.remove(APP_TOKEN_KEY)
         }

클래스 외부(파일 상단)에 아래 top-level 확장 프로퍼티를 추가하세요:

// 파일 상단 (클래스 외부)
val Context.authDataStore: DataStore<Preferences> by preferencesDataStore(name = "thip_auth_tokens")

Also applies to: 28-31, 35-37, 42-44

🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt around lines
19 (and also apply same change for usages at 28-31, 35-37, 42-44), the
Context.dataStore delegate is declared inside the class; move this DataStore
extension delegate to the file top-level to guarantee a single instance and
avoid leaks. Remove the in-class delegated property and add a top-level
extension val Context.authDataStore: DataStore<Preferences> by
preferencesDataStore(name = "thip_auth_tokens") at the file head, then update
all in-class usages to reference context.authDataStore instead of the removed
property.


companion object {
//토큰저장에 사용되는 키
private val APP_TOKEN_KEY = stringPreferencesKey("app_token")
}

//토큰 저장
suspend fun saveToken(token: String) {
context.dataStore.edit { prefs ->
prefs[APP_TOKEN_KEY] = token
}
}

//저장된 토큰을 Flow 형태로 불러옴
fun getToken(): Flow<String?> {
return context.dataStore.data.map { prefs ->
prefs[APP_TOKEN_KEY]
}
}
Comment on lines +33 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

DataStore 읽기 시 IOException 처리를 추가해 앱 크래시를 방지하세요.

DataStore의 data Flow는 I/O 오류 시 예외를 내보낼 수 있습니다. catchIOException을 처리해 빈 Preferences를 emit하도록 하는 것이 권장됩니다.

-    fun getToken(): Flow<String?> {
-        return context.dataStore.data.map { prefs ->
-            prefs[APP_TOKEN_KEY]
-        }
-    }
+    fun getToken(): Flow<String?> {
+        return context.authDataStore.data
+            .catch { e ->
+                if (e is IOException) emit(emptyPreferences()) else throw e
+            }
+            .map { prefs -> prefs[APP_TOKEN_KEY] }
+    }

추가 import가 필요합니다(파일 import 섹션에 추가):

import androidx.datastore.preferences.core.emptyPreferences
import kotlinx.coroutines.flow.catch
import java.io.IOException
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt around lines
33 to 38, the DataStore data Flow isn't handling IOExceptions which can crash
the app; wrap the data Flow with catch to intercept IOException and emit
emptyPreferences(), then continue mapping to APP_TOKEN_KEY; also add the
suggested imports: androidx.datastore.preferences.core.emptyPreferences,
kotlinx.coroutines.flow.catch, and java.io.IOException.

Comment on lines +33 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

DataStore 접근 시 예외 처리(catch) 추가 권장

Preferences DataStore는 I/O 예외로 플로우가 취소될 수 있습니다. 예외를 처리해 기본값을 반환하도록 하면 안정성이 올라갑니다.

-    fun getToken(): Flow<String?> {
-        return context.dataStore.data.map { prefs ->
-            prefs[APP_TOKEN_KEY]
-        }
-    }
+    fun getToken(): Flow<String?> {
+        return context.dataStore.data
+            .catch { e ->
+                if (e is IOException) emit(emptyPreferences()) else throw e
+            }
+            .map { prefs -> prefs[APP_TOKEN_KEY] }
+    }

추가로 필요한 import:

  • import java.io.IOException
  • import androidx.datastore.preferences.core.emptyPreferences
  • import kotlinx.coroutines.flow.catch
🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/manager/TokenManager.kt around lines
33 to 38, wrap the DataStore flow with a catch that handles IOException by
emitting emptyPreferences and rethrow other exceptions, then continue mapping
prefs[APP_TOKEN_KEY] so a default value is returned on I/O errors; add the
necessary imports: java.io.IOException,
androidx.datastore.preferences.core.emptyPreferences, and
kotlinx.coroutines.flow.catch.


//저장된 토큰 삭제 (로그아웃 시?)
suspend fun deleteToken() {
context.dataStore.edit { prefs ->
prefs.remove(APP_TOKEN_KEY)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.texthip.thip.data.model.auth.request
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class AuthRequest(
@SerialName("oauth2Id") val oauth2Id: String
)
Comment on lines +5 to +8

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

UID 단독 전송은 위변조 위험 — 서버 검증용 토큰(idToken/serverAuthCode) 포함 권장

현재 요청 스키마가 oauth2Id(예: “google_”)만 담습니다. 클라이언트가 임의 UID를 조작해 전송할 수 있어, 서버는 사용자 신원을 검증할 수 없습니다. 최소 한 가지 검증 가능한 증거를 추가하세요:

  • Google: ID Token(JWT) 또는 Server Auth Code 전달 → 서버에서 구글/파이어베이스로 검증
  • Kakao: Access Token 또는 ID Token 전달 → 서버 검증

예시 스키마 제안:

 @Serializable
 data class AuthRequest(
-    @SerialName("oauth2Id") val oauth2Id: String
+    @SerialName("oauth2Id") val oauth2Id: String,
+    @SerialName("provider") val provider: String, // "google" | "kakao" 등
+    @SerialName("idToken") val idToken: String? = null,
+    @SerialName("serverAuthCode") val serverAuthCode: String? = null
 )

서버가 검증을 수행하지 않으면 인증 우회 취약점이 됩니다.

🤖 Prompt for AI Agents
In app/src/main/java/com/texthip/thip/data/model/auth/request/AuthRequest.kt
around lines 5-8, the DTO currently only contains oauth2Id which allows
client-side UID spoofing; add one or more verifiable token fields (e.g.,
idToken: String? and/or serverAuthCode: String? and/or accessToken: String?) to
the data class and annotate them for serialization so the server can receive
provider-issued proof, make at least one token required for authentication flows
(or validate presence at deserialization/validation layer), and update any
calling code/serialization tests to send and handle the new token field(s) so
the backend can verify the token with the provider before trusting oauth2Id.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.texthip.thip.data.model.auth.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class AuthResponse(
@SerialName("token") val token: String,
@SerialName("isNewUser") val isNewUser: Boolean
)
Loading