Skip to content

Commit fb1b7dd

Browse files
authored
Merge pull request #11 from YAPP-Github/init/#4-setup-api-library
[init] #4 DataStore, API 응답 핸들링 로직 추가
2 parents 73b2ff8 + 448a732 commit fb1b7dd

21 files changed

Lines changed: 160 additions & 157 deletions

File tree

app/build.gradle.kts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ dependencies {
1919
implementation(projects.core.domain)
2020
implementation(projects.core.model)
2121
implementation(projects.core.navigation)
22-
implementation(projects.feature.sample.impl)
23-
implementation(projects.feature.sample.api)
2422
implementation(projects.feature.pose.api)
2523
implementation(projects.feature.pose.impl)
2624
implementation(projects.feature.archive.api)
@@ -32,4 +30,4 @@ dependencies {
3230

3331
implementation(libs.androidx.activity.compose)
3432
implementation(libs.androidx.navigation3.ui)
35-
}
33+
}

core/common/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ android {
99

1010
dependencies {
1111
api(libs.timber)
12+
implementation(libs.androidx.security.crypto)
1213

1314
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.neki.android.core.common.crypto
2+
3+
import android.annotation.SuppressLint
4+
import android.security.keystore.KeyGenParameterSpec
5+
import android.security.keystore.KeyProperties
6+
import android.util.Base64
7+
import java.security.KeyStore
8+
import javax.crypto.Cipher
9+
import javax.crypto.KeyGenerator
10+
import javax.crypto.SecretKey
11+
import javax.crypto.spec.GCMParameterSpec
12+
13+
object CryptoManager {
14+
private const val ALGORITHM = "AES/GCM/NoPadding"
15+
private const val KEY_ALIAS = "neki_token_encryption_key"
16+
17+
private val keyStore = KeyStore.getInstance("AndroidKeyStore").apply {
18+
load(null)
19+
}
20+
21+
private fun getSecretKey(): SecretKey {
22+
val existingKey = keyStore.getEntry(KEY_ALIAS, null) as? KeyStore.SecretKeyEntry
23+
return existingKey?.secretKey ?: createKey()
24+
}
25+
26+
@SuppressLint("NewApi")
27+
private fun createKey(): SecretKey {
28+
return KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore").apply {
29+
init(
30+
KeyGenParameterSpec.Builder(
31+
KEY_ALIAS,
32+
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT,
33+
)
34+
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
35+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
36+
.build(),
37+
)
38+
}.generateKey()
39+
}
40+
41+
@SuppressLint("NewApi")
42+
fun encrypt(text: String): String {
43+
val cipher = Cipher.getInstance(ALGORITHM)
44+
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey())
45+
46+
val encryptedBytes = cipher.doFinal(text.toByteArray())
47+
val combined = cipher.iv + encryptedBytes
48+
49+
return Base64.encodeToString(combined, Base64.NO_WRAP)
50+
}
51+
52+
@SuppressLint("NewApi")
53+
fun decrypt(encryptedText: String): String {
54+
val combined = Base64.decode(encryptedText, Base64.NO_WRAP)
55+
val encryptedData = combined.sliceArray(12 until combined.size)
56+
57+
val cipher = Cipher.getInstance(ALGORITHM)
58+
val iv = combined.sliceArray(0 until 12)
59+
val spec = GCMParameterSpec(128, iv)
60+
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), spec)
61+
62+
return String(cipher.doFinal(encryptedData))
63+
}
64+
}

core/data-api/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ plugins {
44

55
dependencies {
66
implementation(projects.core.model)
7+
implementation(libs.kotlinx.coroutines.core)
8+
79
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.neki.android.core.dataapi.repository
2+
3+
import kotlinx.coroutines.flow.Flow
4+
5+
interface DataStoreRepository {
6+
suspend fun saveTokens(
7+
accessToken: String,
8+
refreshToken: String,
9+
)
10+
11+
fun getAccessToken(): Flow<String?>
12+
fun getRefreshToken(): Flow<String?>
13+
suspend fun clearTokens()
14+
}

core/data-api/src/main/java/com/neki/android/core/dataapi/repository/SampleRespository.kt

Lines changed: 0 additions & 10 deletions
This file was deleted.

core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreRepository.kt renamed to core/data/src/main/java/com/neki/android/core/data/local/di/DataStoreModule.kt

File renamed without changes.
Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
package com.neki.android.core.data.repository.di
22

3-
import com.neki.android.core.data.repository.impl.SampleRepositoryImpl
4-
import com.neki.android.core.dataapi.repository.SampleRepository
3+
import com.neki.android.core.data.repository.impl.DataStoreRepositoryImpl
4+
import com.neki.android.core.dataapi.repository.DataStoreRepository
55
import dagger.Binds
66
import dagger.Module
77
import dagger.hilt.InstallIn
88
import dagger.hilt.components.SingletonComponent
9+
import javax.inject.Singleton
910

1011
@Module
1112
@InstallIn(SingletonComponent::class)
12-
internal abstract class RepositoryModule {
13+
internal interface RepositoryModule {
1314

1415
@Binds
15-
abstract fun bindSampleRepositoryImpl(
16-
sampleRepositoryImpl: SampleRepositoryImpl,
17-
): SampleRepository
16+
@Singleton
17+
fun bindDataStoreRepositoryImpl(
18+
dataStoreRepositoryImpl: DataStoreRepositoryImpl,
19+
): DataStoreRepository
1820
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.neki.android.core.data.repository.impl
2+
3+
import androidx.datastore.core.DataStore
4+
import androidx.datastore.preferences.core.Preferences
5+
import androidx.datastore.preferences.core.edit
6+
import androidx.datastore.preferences.core.stringPreferencesKey
7+
import com.neki.android.core.common.crypto.CryptoManager
8+
import com.neki.android.core.dataapi.repository.DataStoreRepository
9+
import kotlinx.coroutines.flow.Flow
10+
import kotlinx.coroutines.flow.map
11+
import javax.inject.Inject
12+
13+
class DataStoreRepositoryImpl @Inject constructor(
14+
private val dataStore: DataStore<Preferences>,
15+
) : DataStoreRepository {
16+
companion object {
17+
private val ACCESS_TOKEN = stringPreferencesKey("access_token")
18+
private val REFRESH_TOKEN = stringPreferencesKey("refresh_token")
19+
}
20+
21+
override suspend fun saveTokens(
22+
accessToken: String,
23+
refreshToken: String,
24+
) {
25+
dataStore.edit { preferences ->
26+
preferences[ACCESS_TOKEN] = CryptoManager.encrypt(accessToken)
27+
preferences[REFRESH_TOKEN] = CryptoManager.encrypt(refreshToken)
28+
}
29+
}
30+
31+
override fun getAccessToken(): Flow<String?> {
32+
return dataStore.data.map { preferences ->
33+
preferences[ACCESS_TOKEN]?.let { CryptoManager.decrypt(it) }
34+
}
35+
}
36+
37+
override fun getRefreshToken(): Flow<String?> {
38+
return dataStore.data.map { preferences ->
39+
preferences[REFRESH_TOKEN]?.let { CryptoManager.decrypt(it) }
40+
}
41+
}
42+
43+
override suspend fun clearTokens() {
44+
dataStore.edit { it.clear() }
45+
}
46+
}

core/data/src/main/java/com/neki/android/core/data/repository/impl/SampleRepositoryImpl.kt

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)