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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.threegap.bitnagil.di.core
import android.content.Context
import com.threegap.bitnagil.datastore.auth.crypto.TokenCrypto
import com.threegap.bitnagil.datastore.auth.serializer.AuthTokenSerializer
import com.threegap.bitnagil.datastore.auth.serializer.AuthTokenSerializerImpl
import com.threegap.bitnagil.datastore.auth.storage.AuthTokenDataStore
import com.threegap.bitnagil.datastore.auth.storage.AuthTokenStorageFactory
import com.threegap.bitnagil.security.crypto.Crypto
Expand All @@ -26,6 +27,11 @@ object DataStoreModule {
override fun decrypt(bytes: ByteArray): ByteArray = crypto.decrypt(bytes)
}

@Provides
@Singleton
fun provideAuthTokenSerializer(tokenCrypto: TokenCrypto): AuthTokenSerializer =
AuthTokenSerializerImpl(tokenCrypto)

@Provides
@Singleton
fun provideAuthTokenStorage(
Expand Down
21 changes: 11 additions & 10 deletions app/src/main/java/com/threegap/bitnagil/di/core/NetworkModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.serialization.json.Json
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
Expand Down Expand Up @@ -54,17 +54,25 @@ object NetworkModule {
}
}

@Provides
@Singleton
fun provideTokenStore(dataStore: AuthTokenDataStore): TokenProvider =
object : TokenProvider {
override suspend fun getAccessToken(): String? = dataStore.tokenFlow.firstOrNull()?.accessToken
}

@Provides
@Singleton
@Auth
fun provideAuthInterceptor(authInterceptor: AuthInterceptor): Interceptor = authInterceptor
fun provideAuthInterceptor(tokenProvider: TokenProvider): Interceptor =
AuthInterceptor(tokenProvider)

@Provides
@Singleton
@Auth
fun provideAuthOkHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor,
authInterceptor: Interceptor,
@Auth authInterceptor: Interceptor,
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.addInterceptor(httpLoggingInterceptor)
Expand Down Expand Up @@ -110,11 +118,4 @@ object NetworkModule {
.addConverterFactory(converterFactory)
.client(okHttpClient)
.build()

@Provides
@Singleton
fun provideTokenStore(dataStore: AuthTokenDataStore): TokenProvider =
object : TokenProvider {
override suspend fun getToken(): String? = dataStore.tokenFlow.first().accessToken
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.threegap.bitnagil.di.data

import com.threegap.bitnagil.data.auth.datasource.AuthLocalDataSource
import com.threegap.bitnagil.data.auth.datasource.AuthRemoteDataSource
import com.threegap.bitnagil.data.auth.datasourceimpl.AuthLocalDataSourceImpl
import com.threegap.bitnagil.data.auth.datasourceimpl.AuthRemoteDataSourceImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class DataSourceModule {

@Binds
@Singleton
abstract fun bindAuthDataSource(authDataSourceImpl: AuthRemoteDataSourceImpl): AuthRemoteDataSource

@Binds
@Singleton
abstract fun bindAuthLocalDataSource(authLocalDataSourceImpl: AuthLocalDataSourceImpl): AuthLocalDataSource
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.threegap.bitnagil.di.data

import com.threegap.bitnagil.data.auth.repositoryimpl.AuthRepositoryImpl
import com.threegap.bitnagil.domain.auth.repository.AuthRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {

@Binds
@Singleton
abstract fun bindAuthRepository(authRepositoryImpl: AuthRepositoryImpl): AuthRepository
}
20 changes: 20 additions & 0 deletions app/src/main/java/com/threegap/bitnagil/di/data/ServiceModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.threegap.bitnagil.di.data

import com.threegap.bitnagil.data.auth.service.AuthService
import com.threegap.bitnagil.di.core.Auth
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import retrofit2.Retrofit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object ServiceModule {

@Provides
@Singleton
fun provideAuthService(@Auth retrofit: Retrofit): AuthService =
retrofit.create(AuthService::class.java)
}
2 changes: 1 addition & 1 deletion core/datastore/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ android {
}

dependencies {
implementation(libs.androidx.datastore.preferences)
api(libs.androidx.datastore.preferences)
Comment thread
l5x5l marked this conversation as resolved.
implementation(libs.kotlinx.serialization.json)

testImplementation(libs.androidx.junit)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.threegap.bitnagil.datastore.auth.serializer

import androidx.datastore.core.Serializer
import com.threegap.bitnagil.datastore.auth.crypto.TokenCrypto
import com.threegap.bitnagil.datastore.auth.model.AuthToken
import kotlinx.coroutines.Dispatchers
Expand All @@ -12,7 +11,7 @@ import java.util.Base64

class AuthTokenSerializerImpl(
private val crypto: TokenCrypto,
) : Serializer<AuthToken> {
) : AuthTokenSerializer {
override val defaultValue: AuthToken
get() = AuthToken()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import kotlinx.coroutines.flow.Flow
interface AuthTokenDataStore {
val tokenFlow: Flow<AuthToken>

suspend fun updateAuthToken(authToken: AuthToken): AuthToken
suspend fun updateAuthToken(accessToken: String, refreshToken: String)

suspend fun updateAccessToken(accessToken: String): AuthToken
suspend fun updateAccessToken(accessToken: String)

suspend fun updateRefreshToken(refreshToken: String): AuthToken
suspend fun updateRefreshToken(refreshToken: String)

suspend fun clearAuthToken(): AuthToken
suspend fun clearAuthToken()
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.threegap.bitnagil.datastore.auth.storage

import android.util.Log
import androidx.datastore.core.DataStore
import com.threegap.bitnagil.datastore.auth.model.AuthToken
import kotlinx.coroutines.flow.Flow
Expand All @@ -10,53 +9,43 @@ class AuthTokenDataStoreImpl(
) : AuthTokenDataStore {
override val tokenFlow: Flow<AuthToken> = dataStore.data

override suspend fun updateAuthToken(authToken: AuthToken): AuthToken =
runCatching {
dataStore.updateData { authToken }
}.fold(
onSuccess = { it },
onFailure = {
Log.e(TAG, "updateAuthToken failed:", it)
throw it
},
)
override suspend fun updateAuthToken(accessToken: String, refreshToken: String) {
try {
dataStore.updateData {
AuthToken(accessToken, refreshToken)
}
} catch (e: Exception) {
throw e
}
}

override suspend fun updateAccessToken(accessToken: String): AuthToken =
runCatching {
dataStore.updateData { authToken ->
authToken.copy(accessToken = accessToken)
override suspend fun updateAccessToken(accessToken: String) {
try {
dataStore.updateData { currentToken ->
currentToken.copy(accessToken = accessToken)
}
}.fold(
onSuccess = { it },
onFailure = {
Log.e(TAG, "updateAccessToken failed:", it)
throw it
},
)
} catch (e: Exception) {
throw e
}
}

override suspend fun updateRefreshToken(refreshToken: String): AuthToken =
runCatching {
dataStore.updateData { authToken ->
authToken.copy(refreshToken = refreshToken)
override suspend fun updateRefreshToken(refreshToken: String) {
try {
dataStore.updateData { currentToken ->
currentToken.copy(refreshToken = refreshToken)
}
}.fold(
onSuccess = { it },
onFailure = {
Log.e(TAG, "updateRefreshToken failed:", it)
throw it
},
)
} catch (e: Exception) {
throw e
}
}

override suspend fun clearAuthToken(): AuthToken =
runCatching {
override suspend fun clearAuthToken() {
try {
dataStore.updateData { AuthToken() }
}.fold(
onSuccess = { it },
onFailure = {
Log.e(TAG, "clearAuthToken failed:", it)
throw it
},
)
} catch (e: Exception) {
throw e
}
}

companion object {
private const val TAG = "AuthTokenDataStore"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,19 @@ class AuthTokenDataStoreImplTest {
fun `토큰 전체 업데이트가 성공하면 저장된 토큰을 반환해야 한다`() =
runTest {
// given
val accessToken = "access"
val refreshToken = "refresh"
val token =
AuthToken(
accessToken = "access",
refreshToken = "refresh",
accessToken = accessToken,
refreshToken = refreshToken,
)

// when
val result = authTokenDataStore.updateAuthToken(token)
authTokenDataStore.updateAuthToken(accessToken, refreshToken)

// then
val result = authTokenDataStore.tokenFlow.first()
assertEquals(token, result)
}

Expand All @@ -88,54 +91,51 @@ class AuthTokenDataStoreImplTest {
runTest {
// given
authTokenDataStore.updateAuthToken(
AuthToken(
accessToken = "oldAccess",
refreshToken = "oldRefresh",
),
accessToken = "oldAccess",
refreshToken = "oldRefresh",
)

// when
val updated = authTokenDataStore.updateAccessToken(accessToken = "newAccess")
authTokenDataStore.updateAccessToken(accessToken = "newAccess")

// then
assertEquals("newAccess", updated.accessToken)
assertEquals("oldRefresh", updated.refreshToken)
val result = authTokenDataStore.tokenFlow.first()
assertEquals("newAccess", result.accessToken)
assertEquals("oldRefresh", result.refreshToken)
}

@Test
fun `refreshToken만 업데이트하면 기존 accessToken은 유지되어야 한다`() =
runTest {
// given
authTokenDataStore.updateAuthToken(
AuthToken(
accessToken = "oldAccess",
refreshToken = "oldRefresh",
),
accessToken = "oldAccess",
refreshToken = "oldRefresh",
)

// when
val updated = authTokenDataStore.updateRefreshToken(refreshToken = "newRefresh")
authTokenDataStore.updateRefreshToken(refreshToken = "newRefresh")

// then
assertEquals("oldAccess", updated.accessToken)
assertEquals("newRefresh", updated.refreshToken)
val result = authTokenDataStore.tokenFlow.first()
assertEquals("oldAccess", result.accessToken)
assertEquals("newRefresh", result.refreshToken)
}

@Test
fun `토큰을 클리어하면 기본값이 저장되어야 한다`() =
runTest {
// given
authTokenDataStore.updateAuthToken(
AuthToken(
accessToken = "someAccess",
refreshToken = "someRefresh",
),
accessToken = "someAccess",
refreshToken = "someRefresh",
)

// when
val cleared = authTokenDataStore.clearAuthToken()
authTokenDataStore.clearAuthToken()

// then
val cleared = authTokenDataStore.tokenFlow.first()
assertEquals(AuthToken(), cleared)
}

Expand All @@ -150,7 +150,7 @@ class AuthTokenDataStoreImplTest {
)

// when
authTokenDataStore.updateAuthToken(token)
authTokenDataStore.updateAuthToken("flowAccess", "flowRefresh")

// then
val flowValue = authTokenDataStore.tokenFlow.first()
Expand All @@ -172,7 +172,7 @@ class AuthTokenDataStoreImplTest {
val failingDataStore = AuthTokenDataStoreImpl(brokenStore)

// when & then
failingDataStore.updateAuthToken(AuthToken("access", "refresh"))
failingDataStore.updateAuthToken("access", "refresh")
}

@Test(expected = RuntimeException::class)
Expand Down
Loading