Skip to content

DataStore 토큰 저장 기능 구현#58

Closed
chanho0908 wants to merge 3 commits intodevelopfrom
feat/#57-token-datastore
Closed

DataStore 토큰 저장 기능 구현#58
chanho0908 wants to merge 3 commits intodevelopfrom
feat/#57-token-datastore

Conversation

@chanho0908
Copy link
Copy Markdown
Contributor

이슈 번호

#57

리뷰/머지 희망 기한 (선택)

작업내용

  • DataStore에 AccessToken RefreshToken 저장기능 구현

결과물

리뷰어에게 추가로 요구하는 사항 (선택)

같은 core 레벨에 있는 network 모듈과 datastore이 서로 직접 의존하지 않도록 core:token 모듈을 추가하고
인터페이스를 구현해 의존성을 역전시켰어 ! network --> token(interface) <-- datastore (concrete)

@chanho0908 chanho0908 self-assigned this Feb 5, 2026
@chanho0908 chanho0908 linked an issue Feb 5, 2026 that may be closed by this pull request
2 tasks
@chanho0908 chanho0908 added the Feature Extra attention is needed label Feb 5, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

작업 요약

이 변경사항은 두 개의 새로운 코어 모듈(token, datastore)을 프로젝트에 추가합니다. core:token 모듈은 TokenProvider 인터페이스를 정의하고, core:datastore 모듈은 Android DataStore를 활용하여 인증 토큰을 직렬화 및 관리하는 AuthTokenProvider 구현을 제공합니다. 이를 통해 토큰 저장소 추상화 계층을 도입하고, Koin DI를 통해 의존성을 해결합니다.

코드 리뷰 예상 소요 시간

🎯 3 (Moderate) | ⏱️ ~35 minutes


상세 검토 의견

✅ 긍정적인 측면

  1. 명확한 계층 분리 - TokenProvider 인터페이스를 별도 모듈로 분리함으로써 datastore 모듈과의 의존성이 한 방향으로만 흐르도록 설계했습니다. 좋은 아키텍처 결정입니다.

  2. 견고한 직렬화 처리 - AuthConfigure의 직렬화에서 SerializationException 발생 시 기본값으로 폴백하는 로직이 있어 안정성을 고려했습니다.

  3. 적절한 DI 사용 - Koin 모듈을 통해 TokenProvider 바인딩을 중앙 관리하여 의존성 주입이 깔끔합니다.


개선 제안 및 검토 사항

🔍 AuthConfigure.kt 관련

문제점: 기본 JWT 값이 하드코딩되어 있는 부분이 있습니다.

val accessToken: String = "eyJhbGci..." // 실제 값이 길어 보임

개선 방향:

  • 기본값이 실제로 유효한 토큰이어야 하는지, 아니면 빈 문자열로 초기화하는 것이 맞는지 명확히 하는 것이 좋습니다.
  • "기본값이 무엇을 의미하는가?"를 검토해보세요. 초기 앱 실행 시 로그인하지 않은 상태라면 빈 문자열이 더 적절할 수 있습니다.

🔍 AuthTokenProvider.kt 관련

문제점: first()를 사용한 동기식 읽기가 있습니다.

private val dataStore: DataStore<AuthConfigure> = context.authDataStore

suspend fun accessToken(): String {
    return dataStore.data.first().accessToken
}

검토 사항:

  • 이미 suspend 함수이므로 first()를 사용해도 안전합니다. 다만, DataStore 흐름의 최신값이 필요한 경우와 단일 스냅샷이 필요한 경우를 명확히 하고 있는지 확인해주세요.
  • 혹시 여러 번 호출될 때 매번 파일을 읽는 비용이 있을까요? 캐싱 전략이 필요한지 검토하시기 바랍니다.

🔍 DataStore.kt 관련

개선 제안:

  • Context 확장 속성으로 authDataStore를 만든 것은 좋지만, 이것이 여러 곳에서 접근될 수 있습니다.
  • 질문: 이 속성이 싱글톤으로 동작하는지 확인하셨나요? DataStore 문서에 따르면 한 파일당 단일 인스턴스를 유지하는 것이 권장됩니다.

🔍 DataStoreModule.kt 관련

검토 사항:

single<TokenProvider> { AuthTokenProvider(get()) }
  • get()으로 Context를 주입받는 부분이 정상 작동하려면 애플리케이션 컨텍스트가 Koin에 미리 등록되어 있어야 합니다.
  • 질문: 상위 모듈에서 이미 Context를 Koin에 등록했는지 확인이 필요합니다. 없다면 추가 설정이 필요할 수 있습니다.

🔍 gradle/libs.versions.toml 관련

개선 사항:

androidx-datastore = "1.2.0"
  • 버전을 명시적으로 지정하신 것은 좋습니다.
  • 제안: 프로젝트의 다른 AndroidX 라이브러리 버전과의 호환성도 함께 검토하시기 바랍니다.

🔍 설정 파일들 관련

긍정적: ProGuard 규칙 파일과 AndroidManifest.xml을 템플릿 형태로 포함한 것은 앞으로의 확장성을 고려한 좋은 결정입니다.


최종 체크리스트

  • Context가 Koin에 올바르게 등록되어 있는지 확인
  • AuthConfigure의 기본값이 실제 사용 사례에 맞는지 재검토
  • DataStore 싱글톤 인스턴스 관리가 올바른지 검증
  • 테스트 코드 (특히 AuthTokenProvider와 DataStore 직렬화) 추가 검토
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 'DataStore 토큰 저장 기능 구현'으로 PR의 핵심 변경사항인 DataStore에 토큰 저장 기능을 구현한 것을 명확하게 요약합니다.
Description check ✅ Passed 설명은 이슈 번호(#57), 작업 내용(DataStore에 AccessToken, RefreshToken 저장), 그리고 아키텍처 개선 방향(의존성 역전)을 포함하여 변경사항과 관련이 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/#57-token-datastore

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
Copy Markdown

@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: 4

🤖 Fix all issues with AI agents
In `@core/datastore/src/main/java/com/twix/datastore/AuthConfigure.kt`:
- Around line 13-21: AuthConfigure currently hardcodes real JWTs in the
accessToken and refreshToken defaults; replace those defaults with safe values
(empty string or null) and remove any embedded tokens, update any call sites
that construct AuthConfigure (or read AuthConfigure.accessToken/refreshToken) to
accept and properly handle empty/null tokens, and ensure tests or local/debug
builds inject test tokens via configuration or environment rather than relying
on AuthConfigure's defaults; reference the AuthConfigure data class and its
accessToken and refreshToken properties when making the change.
- Around line 28-36: The readFrom implementation in AuthConfigure.readFrom
currently swallows SerializationException and returns defaultValue, which
prevents DataStore from detecting corrupted files; instead, catch
SerializationException and rethrow an
androidx.datastore.core.CorruptionException (including the original
SerializationException as the cause and a helpful message) from the
readFrom(InputStream) method so DataStore can delete the corrupted file and
recover using defaultValue; update the catch block in the readFrom function
(where AuthConfigure.serializer(), input.readBytes().decodeToString(), and
defaultValue are referenced) to throw CorruptionException rather than returning
defaultValue.

In `@core/datastore/src/main/java/com/twix/datastore/AuthTokenProvider.kt`:
- Around line 11-30: Auth tokens are being saved in plaintext via the
DataStore/Serializer pipeline (see AuthTokenProvider -> dataStore and
AuthConfigureSerializer readFrom/writeTo writing "auth-configure.json"), so
replace plaintext storage with encryption: either migrate to Jetpack Security's
encrypted solution (create a MasterKey and use
EncryptedDataStore/EncryptedFile-backed DataStore for AuthConfigure so
AuthTokenProvider continues to read/write encrypted data), or implement
encryption inside AuthConfigureSerializer's readFrom/writeTo using a
MasterKey/Cipher to encrypt bytes before writing and decrypt after reading (use
AndroidX Security Crypto APIs or a vetted library like Tink), and update
DataStore usage accordingly so accessToken()/saveToken()/clear() operate on the
encrypted store.

In `@gradle/libs.versions.toml`:
- Line 18: The project declares the wrong DataStore artifact: replace the
preferences-only artifact with the typed DataStore artifact so the
Serializer-based API used in code works; update the dependency entry so the
project depends on androidx.datastore:datastore (which provides Serializer<T>,
DataStore<T>, and the dataStore delegate) instead of the datastore-preferences
artifact referenced for androidx-datastore, so classes/interfaces like
AuthConfigure, Serializer<T>, DataStore<AuthConfigure>, and the dataStore
delegate resolve correctly.
🧹 Nitpick comments (2)
core/datastore/build.gradle.kts (1)

11-15: TokenProvider가 공개 API에 노출될 수 있어 api/가시성 조정이 필요합니다.
AuthTokenProvider가 public이며 TokenProvider를 구현하므로, 소비 모듈에서 TokenProvider 타입이 필요해질 수 있습니다.
api(projects.core.token)으로 전환하거나, AuthTokenProviderinternal로 제한해 구현체를 숨기는 방향을 검토해 주세요.

♻️ 제안 변경
-    implementation(projects.core.token)
+    api(projects.core.token)

As per coding guidelines "implementation / api 구분이 명확한가?".

app/build.gradle.kts (1)

33-34: app에서 core.token 직접 사용 여부를 확인해 주세요.
앱 코드에서 TokenProvider 등을 직접 참조하지 않는다면 의존성이 불필요해 빌드 그래프가 커질 수 있습니다.
직접 사용 여부를 확인한 뒤, 불필요하면 제거하거나 필요 시 core.datastore가 api로 노출하도록 조정하는 방향을 검토해 주세요. 앱에서 직접 사용하는 클래스가 있나요?

As per coding guidelines "불필요한 의존성이 없는가?".

Comment on lines +11 to +30
private val dataStore: DataStore<AuthConfigure>
get() = context.authDataStore

override suspend fun accessToken(): String = dataStore.data.first().accessToken

override suspend fun refreshToken(): String = dataStore.data.first().refreshToken

override suspend fun saveToken(
accessToken: String,
refreshToken: String,
) {
dataStore.updateData {
it.copy(accessToken = accessToken, refreshToken = refreshToken)
}
}

override suspend fun clear() {
dataStore.updateData {
it.copy(accessToken = "", refreshToken = "")
}
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

🏁 Script executed:

fd -t f -e kt | head -20

Repository: YAPP-Github/Twix-Android

Length of output: 1630


🏁 Script executed:

rg "authDataStore" --type kt -A 3 -B 3

Repository: YAPP-Github/Twix-Android

Length of output: 94


🏁 Script executed:

rg "context\.authDataStore\|authDataStore\s*=" --type kt -A 5

Repository: YAPP-Github/Twix-Android

Length of output: 94


🏁 Script executed:

find . -type f -name "*.kt" | xargs grep -l "DataStore\|authDataStore" | head -10

Repository: YAPP-Github/Twix-Android

Length of output: 201


🏁 Script executed:

cat -n core/datastore/src/main/java/com/twix/datastore/DataStore.kt

Repository: YAPP-Github/Twix-Android

Length of output: 431


🏁 Script executed:

cat -n core/datastore/src/main/java/com/twix/datastore/AuthTokenProvider.kt

Repository: YAPP-Github/Twix-Android

Length of output: 1175


🏁 Script executed:

find . -type f -name "*Serializer*" | grep -i auth

Repository: YAPP-Github/Twix-Android

Length of output: 50


🏁 Script executed:

rg "Serializer|serializer" --type-list

Repository: YAPP-Github/Twix-Android

Length of output: 457


🏁 Script executed:

find . -type f \( -name "*.kt" -o -name "*.java" \) | xargs grep -l "AuthConfigureSerializer" | head -5

Repository: YAPP-Github/Twix-Android

Length of output: 197


🏁 Script executed:

rg "object AuthConfigureSerializer|class AuthConfigureSerializer" -A 20

Repository: YAPP-Github/Twix-Android

Length of output: 2099


🏁 Script executed:

find . -type f -name "*.kt" | xargs grep -l "Serializer" | grep -i auth

Repository: YAPP-Github/Twix-Android

Length of output: 134


🏁 Script executed:

cat -n core/datastore/src/main/java/com/twix/datastore/AuthConfigure.kt

Repository: YAPP-Github/Twix-Android

Length of output: 2187


토큰이 평문으로 저장되어 보안 위험이 있습니다.

AuthConfigureSerializer의 readFrom/writeTo에서 Json.decodeFromString/encodeToString를 사용하고 있어, 토큰이 보호 없이 "auth-configure.json" 파일에 평문으로 저장됩니다. 루팅된 기기나 백업 환경에서 토큰이 노출될 수 있습니다.

개선 방안:

  1. 권장: Jetpack Security의 EncryptedDataStore + MasterKey를 사용하는 것이 가장 표준적입니다
  2. 대안: 현재 구조를 유지하면서 Serializer에서 암복호화를 적용할 수도 있습니다 (예: Tink나 BouncyCastle 라이브러리 활용)

EncryptedDataStore 적용이 어렵다면, Serializer에 암호화 로직을 추가하는 예시를 제공할 수 있습니다. 보안 요구사항상 암호화 적용이 필수인지 확인 부탁드립니다.

🤖 Prompt for AI Agents
In `@core/datastore/src/main/java/com/twix/datastore/AuthTokenProvider.kt` around
lines 11 - 30, Auth tokens are being saved in plaintext via the
DataStore/Serializer pipeline (see AuthTokenProvider -> dataStore and
AuthConfigureSerializer readFrom/writeTo writing "auth-configure.json"), so
replace plaintext storage with encryption: either migrate to Jetpack Security's
encrypted solution (create a MasterKey and use
EncryptedDataStore/EncryptedFile-backed DataStore for AuthConfigure so
AuthTokenProvider continues to read/write encrypted data), or implement
encryption inside AuthConfigureSerializer's readFrom/writeTo using a
MasterKey/Cipher to encrypt bytes before writing and decrypt after reading (use
AndroidX Security Crypto APIs or a vetted library like Tink), and update
DataStore usage accordingly so accessToken()/saveToken()/clear() operate on the
encrypted store.

# AndroidX
androidx-core-ktx = "1.17.0"
androidx-lifecycle-runtime-ktx = "2.10.0"
androidx-datastore = "1.2.0"
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

🧩 Analysis chain

🏁 Script executed:

# Find gradle configuration files
git ls-files | grep -E "\.toml$|build\.gradle|settings\.gradle"

Repository: YAPP-Github/Twix-Android

Length of output: 646


🏁 Script executed:

# Check the actual artifact configuration in libs.versions.toml
cat gradle/libs.versions.toml

Repository: YAPP-Github/Twix-Android

Length of output: 9311


🏁 Script executed:

# Search for DataStore imports to understand which library is being used
rg "import.*datastore" --type kotlin -i

Repository: YAPP-Github/Twix-Android

Length of output: 597


🏁 Script executed:

# Look for DataStore API usage
rg "dataStore|DataStore|Preferences\|DataStoreFactory" --type kotlin -B 2 -A 3

Repository: YAPP-Github/Twix-Android

Length of output: 4271


🏁 Script executed:

cat core/datastore/build.gradle.kts

Repository: YAPP-Github/Twix-Android

Length of output: 401


🏁 Script executed:

# Check the full AuthConfigure and AuthConfigureSerializer implementation
cat core/datastore/src/main/java/com/twix/datastore/AuthConfigure.kt

Repository: YAPP-Github/Twix-Android

Length of output: 1823


🏁 Script executed:

cat core/datastore/src/main/java/com/twix/datastore/DataStore.kt

Repository: YAPP-Github/Twix-Android

Length of output: 361


DataStore 의존성을 수정해야 합니다

현재 datastore-preferences artifact를 선언하고 있지만, 코드에서는 typed DataStore API를 사용하고 있습니다. 코드 분석 결과:

  • AuthConfigureSerializer<T>로 직렬화하고 있음 (typed DataStore)
  • DataStore<AuthConfigure>dataStore 위임 함수를 사용 중
  • 이 모든 API는 androidx.datastore:datastore 라이브러리에만 포함됨

문제: datastore-preferences는 key-value 저장소용이며, 커스텀 Serializer<T> 인터페이스와 typed DataStore<T> API를 제공하지 않습니다. 현재 선언된 artifact로는 코드가 정상 동작할 수 없습니다.

🔧 수정 사항
-androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
+androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "androidx-datastore" }
🤖 Prompt for AI Agents
In `@gradle/libs.versions.toml` at line 18, The project declares the wrong
DataStore artifact: replace the preferences-only artifact with the typed
DataStore artifact so the Serializer-based API used in code works; update the
dependency entry so the project depends on androidx.datastore:datastore (which
provides Serializer<T>, DataStore<T>, and the dataStore delegate) instead of the
datastore-preferences artifact referenced for androidx-datastore, so
classes/interfaces like AuthConfigure, Serializer<T>, DataStore<AuthConfigure>,
and the dataStore delegate resolve correctly.

@chanho0908 chanho0908 closed this Feb 5, 2026
@chanho0908 chanho0908 deleted the feat/#57-token-datastore branch February 5, 2026 07:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

토큰 저장을 위한 DataStore 구현

1 participant