From f734ca8945aa1c738fff9480f2bcd33a65eef26c Mon Sep 17 00:00:00 2001 From: Ray Jang <48707913+ajou4095@users.noreply.github.com> Date: Wed, 21 Feb 2024 03:08:09 +0900 Subject: [PATCH] =?UTF-8?q?[System]=20TokenRepository,=20TokenApi=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20(#103)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/proguard-rules.pro | 2 +- .../ac/dnd/mour/android/MourApplication.kt | 7 ++- data/proguard-rules.pro | 2 +- .../mour/android/data/di/RepositoryModule.kt | 40 +++++++----- .../remote/network/api/AuthenticationApi.kt | 10 --- .../data/remote/network/api/TokenApi.kt | 28 +++++++++ .../data/remote/network/di/KtorModule.kt | 15 +++-- .../MockAuthenticationRepository.kt | 36 +---------- .../RealAuthenticationRepository.kt | 59 +++--------------- .../token/MockTokenRepository.kt | 55 ++++++++++++++++ .../token/RealTokenRepository.kt | 62 +++++++++++++++++++ .../repository/AuthenticationRepository.kt | 13 ---- .../domain/repository/TokenRepository.kt | 19 ++++++ .../authentication/UpdateJwtTokenUseCase.kt | 8 +-- 14 files changed, 217 insertions(+), 139 deletions(-) create mode 100644 data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/api/TokenApi.kt create mode 100644 data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/token/MockTokenRepository.kt create mode 100644 data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/token/RealTokenRepository.kt create mode 100644 domain/src/main/kotlin/ac/dnd/mour/android/domain/repository/TokenRepository.kt diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 1aa8aa4b..a62afe5a 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -19,4 +19,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -# TODO : Proguard 설정 +-dontwarn org.slf4j.** diff --git a/app/src/main/kotlin/ac/dnd/mour/android/MourApplication.kt b/app/src/main/kotlin/ac/dnd/mour/android/MourApplication.kt index 55ad0469..aa9f9ce0 100644 --- a/app/src/main/kotlin/ac/dnd/mour/android/MourApplication.kt +++ b/app/src/main/kotlin/ac/dnd/mour/android/MourApplication.kt @@ -1,6 +1,6 @@ package ac.dnd.mour.android -import ac.dnd.mour.android.domain.repository.AuthenticationRepository +import ac.dnd.mour.android.domain.repository.TokenRepository import ac.dnd.mour.android.presentation.common.CHANNEL_1 import ac.dnd.mour.android.presentation.common.CHANNEL_GROUP_1 import ac.dnd.mour.android.presentation.ui.invalid.InvalidJwtTokenActivity @@ -31,7 +31,7 @@ import timber.log.Timber open class MourApplication : Application() { @Inject - lateinit var authenticationRepository: AuthenticationRepository + lateinit var tokenRepository: TokenRepository private val handler = CoroutineExceptionHandler { _, exception -> Timber.d(exception) @@ -84,7 +84,7 @@ open class MourApplication : Application() { with(ProcessLifecycleOwner.get()) { lifecycleScope.launch(handler) { repeatOnLifecycle(Lifecycle.State.STARTED) { - authenticationRepository.isRefreshTokenInvalid.collect { isRefreshTokenInvalid -> + tokenRepository.isRefreshTokenInvalid.collect { isRefreshTokenInvalid -> if (isRefreshTokenInvalid) { val intent = Intent( this@MourApplication, @@ -94,6 +94,7 @@ open class MourApplication : Application() { Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } startActivity(intent) + tokenRepository.resetRefreshTokenInvalidFlag() } } } diff --git a/data/proguard-rules.pro b/data/proguard-rules.pro index 481bb434..f1b42451 100644 --- a/data/proguard-rules.pro +++ b/data/proguard-rules.pro @@ -18,4 +18,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile diff --git a/data/src/main/kotlin/ac/dnd/mour/android/data/di/RepositoryModule.kt b/data/src/main/kotlin/ac/dnd/mour/android/data/di/RepositoryModule.kt index 35b91794..8700bf1a 100644 --- a/data/src/main/kotlin/ac/dnd/mour/android/data/di/RepositoryModule.kt +++ b/data/src/main/kotlin/ac/dnd/mour/android/data/di/RepositoryModule.kt @@ -1,17 +1,18 @@ package ac.dnd.mour.android.data.di import ac.dnd.mour.android.data.remote.local.gallery.GalleryImageRepositoryImpl -import ac.dnd.mour.android.data.repository.authentication.MockAuthenticationRepository +import ac.dnd.mour.android.data.repository.authentication.RealAuthenticationRepository import ac.dnd.mour.android.data.repository.authentication.sociallogin.KakaoLoginRepositoryImpl -import ac.dnd.mour.android.data.repository.feature.group.MockGroupRepository -import ac.dnd.mour.android.data.repository.feature.heart.MockHeartRepository +import ac.dnd.mour.android.data.repository.authentication.token.RealTokenRepository +import ac.dnd.mour.android.data.repository.feature.group.RealGroupRepository +import ac.dnd.mour.android.data.repository.feature.heart.RealHeartRepository import ac.dnd.mour.android.data.repository.feature.relation.KakaoFriendRepositoryImpl -import ac.dnd.mour.android.data.repository.feature.relation.MockRelationRepository -import ac.dnd.mour.android.data.repository.feature.schedule.MockScheduleRepository -import ac.dnd.mour.android.data.repository.feature.statistics.MockStatisticsRepository -import ac.dnd.mour.android.data.repository.file.MockFileRepository +import ac.dnd.mour.android.data.repository.feature.relation.RealRelationRepository +import ac.dnd.mour.android.data.repository.feature.schedule.RealScheduleRepository +import ac.dnd.mour.android.data.repository.feature.statistics.RealStatisticsRepository +import ac.dnd.mour.android.data.repository.file.RealFileRepository import ac.dnd.mour.android.data.repository.gallery.GalleryRepositoryImpl -import ac.dnd.mour.android.data.repository.member.MockMemberRepository +import ac.dnd.mour.android.data.repository.member.RealMemberRepository import ac.dnd.mour.android.domain.repository.AuthenticationRepository import ac.dnd.mour.android.domain.repository.FileRepository import ac.dnd.mour.android.domain.repository.GalleryImageRepository @@ -24,6 +25,7 @@ import ac.dnd.mour.android.domain.repository.MemberRepository import ac.dnd.mour.android.domain.repository.RelationRepository import ac.dnd.mour.android.domain.repository.ScheduleRepository import ac.dnd.mour.android.domain.repository.StatisticsRepository +import ac.dnd.mour.android.domain.repository.TokenRepository import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -34,46 +36,52 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) internal abstract class RepositoryModule { + @Binds + @Singleton + abstract fun bindsTokenRepository( + tokenRepository: RealTokenRepository + ): TokenRepository + @Binds @Singleton abstract fun bindsAuthenticationRepository( - authenticationRepository: MockAuthenticationRepository + authenticationRepository: RealAuthenticationRepository ): AuthenticationRepository @Binds @Singleton abstract fun bindsGroupRepository( - groupRepository: MockGroupRepository + groupRepository: RealGroupRepository ): GroupRepository @Binds @Singleton abstract fun bindsHeartRepository( - heartRepository: MockHeartRepository + heartRepository: RealHeartRepository ): HeartRepository @Binds @Singleton abstract fun bindsMemberRepository( - memberRepository: MockMemberRepository + memberRepository: RealMemberRepository ): MemberRepository @Binds @Singleton abstract fun bindsRelationRepository( - relationRepository: MockRelationRepository + relationRepository: RealRelationRepository ): RelationRepository @Binds @Singleton abstract fun bindsScheduleRepository( - scheduleRepository: MockScheduleRepository + scheduleRepository: RealScheduleRepository ): ScheduleRepository @Binds @Singleton abstract fun bindsStatisticsRepository( - statisticsRepository: MockStatisticsRepository + statisticsRepository: RealStatisticsRepository ): StatisticsRepository @Binds @@ -91,7 +99,7 @@ internal abstract class RepositoryModule { @Binds @Singleton abstract fun bindsFileRepository( - fileRepository: MockFileRepository + fileRepository: RealFileRepository ): FileRepository @Binds diff --git a/data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/api/AuthenticationApi.kt b/data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/api/AuthenticationApi.kt index 58ab9ff9..abfb696c 100644 --- a/data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/api/AuthenticationApi.kt +++ b/data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/api/AuthenticationApi.kt @@ -4,7 +4,6 @@ import ac.dnd.mour.android.data.remote.network.di.AuthHttpClient import ac.dnd.mour.android.data.remote.network.di.NoAuthHttpClient import ac.dnd.mour.android.data.remote.network.environment.BaseUrlProvider import ac.dnd.mour.android.data.remote.network.environment.ErrorMessageMapper -import ac.dnd.mour.android.data.remote.network.model.authentication.GetAccessTokenRes import ac.dnd.mour.android.data.remote.network.model.authentication.LoginReq import ac.dnd.mour.android.data.remote.network.model.authentication.LoginRes import ac.dnd.mour.android.data.remote.network.model.authentication.RegisterReq @@ -12,7 +11,6 @@ import ac.dnd.mour.android.data.remote.network.model.authentication.RegisterRes import ac.dnd.mour.android.data.remote.network.util.convert import io.ktor.client.HttpClient import io.ktor.client.request.delete -import io.ktor.client.request.header import io.ktor.client.request.post import io.ktor.client.request.setBody import javax.inject.Inject @@ -26,14 +24,6 @@ class AuthenticationApi @Inject constructor( private val baseUrl: String get() = baseUrlProvider.get() - suspend fun getAccessToken( - refreshToken: String - ): Result { - return noAuthClient.post("$baseUrl/api/v1/token/reissue") { - header("Token-Refresh", refreshToken) - }.convert(errorMessageMapper::map) - } - suspend fun login( socialId: Long, email: String diff --git a/data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/api/TokenApi.kt b/data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/api/TokenApi.kt new file mode 100644 index 00000000..4b09a78b --- /dev/null +++ b/data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/api/TokenApi.kt @@ -0,0 +1,28 @@ +package ac.dnd.mour.android.data.remote.network.api + +import ac.dnd.mour.android.data.remote.network.di.NoAuthHttpClient +import ac.dnd.mour.android.data.remote.network.environment.BaseUrlProvider +import ac.dnd.mour.android.data.remote.network.environment.ErrorMessageMapper +import ac.dnd.mour.android.data.remote.network.model.authentication.GetAccessTokenRes +import ac.dnd.mour.android.data.remote.network.util.convert +import io.ktor.client.HttpClient +import io.ktor.client.request.header +import io.ktor.client.request.post +import javax.inject.Inject + +class TokenApi @Inject constructor( + @NoAuthHttpClient private val noAuthClient: HttpClient, + private val baseUrlProvider: BaseUrlProvider, + private val errorMessageMapper: ErrorMessageMapper +) { + private val baseUrl: String + get() = baseUrlProvider.get() + + suspend fun getAccessToken( + refreshToken: String + ): Result { + return noAuthClient.post("$baseUrl/api/v1/token/reissue") { + header("Token-Refresh", refreshToken) + }.convert(errorMessageMapper::map) + } +} diff --git a/data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/di/KtorModule.kt b/data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/di/KtorModule.kt index a132caa4..14ad2a55 100644 --- a/data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/di/KtorModule.kt +++ b/data/src/main/kotlin/ac/dnd/mour/android/data/remote/network/di/KtorModule.kt @@ -1,6 +1,6 @@ package ac.dnd.mour.android.data.remote.network.di -import ac.dnd.mour.android.domain.repository.AuthenticationRepository +import ac.dnd.mour.android.domain.repository.TokenRepository import android.content.Context import android.content.pm.ApplicationInfo import dagger.Module @@ -40,7 +40,7 @@ internal object KtorModule { ): HttpClient { return HttpClient(OkHttp) { // default validation to throw exceptions for non-2xx responses - expectSuccess = true + expectSuccess = false engine { if (debugInterceptor.isPresent) { @@ -61,14 +61,13 @@ internal object KtorModule { fun provideAuthHttpClient( @ApplicationContext context: Context, @DebugInterceptor debugInterceptor: Optional, - authenticationRepository: AuthenticationRepository + tokenRepository: TokenRepository ): HttpClient { val isDebug: Boolean = (0 != context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) return HttpClient(OkHttp) { // default validation to throw exceptions for non-2xx responses - // TODO expectSuccess = false engine { @@ -84,8 +83,8 @@ internal object KtorModule { install(Auth) { bearer { loadTokens { - val accessToken = authenticationRepository.accessToken - val refreshToken = authenticationRepository.refreshToken + val accessToken = tokenRepository.accessToken + val refreshToken = tokenRepository.refreshToken if (accessToken.isEmpty() || refreshToken.isEmpty()) { return@loadTokens null } @@ -97,12 +96,12 @@ internal object KtorModule { } refreshTokens { - val refreshToken = authenticationRepository.refreshToken + val refreshToken = tokenRepository.refreshToken if (refreshToken.isEmpty()) { return@refreshTokens null } - authenticationRepository.refreshToken( + tokenRepository.refreshToken( refreshToken ).getOrNull()?.let { token -> BearerTokens( diff --git a/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/MockAuthenticationRepository.kt b/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/MockAuthenticationRepository.kt index ce2c8fd7..5c727eae 100644 --- a/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/MockAuthenticationRepository.kt +++ b/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/MockAuthenticationRepository.kt @@ -1,44 +1,17 @@ package ac.dnd.mour.android.data.repository.authentication -import ac.dnd.mour.android.data.remote.local.SharedPreferencesManager -import ac.dnd.mour.android.domain.model.authentication.JwtToken import ac.dnd.mour.android.domain.model.error.ServerException import ac.dnd.mour.android.domain.model.legacy.Login import ac.dnd.mour.android.domain.model.legacy.Register import ac.dnd.mour.android.domain.repository.AuthenticationRepository +import ac.dnd.mour.android.domain.repository.TokenRepository import javax.inject.Inject import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow class MockAuthenticationRepository @Inject constructor( - private val sharedPreferencesManager: SharedPreferencesManager + private val tokenRepository: TokenRepository ) : AuthenticationRepository { - override var refreshToken: String - set(value) = sharedPreferencesManager.setString(REFRESH_TOKEN, value) - get() = sharedPreferencesManager.getString(REFRESH_TOKEN, "") - - override var accessToken: String - set(value) = sharedPreferencesManager.setString(ACCESS_TOKEN, value) - get() = sharedPreferencesManager.getString(ACCESS_TOKEN, "") - - private val _isRefreshTokenInvalid: MutableStateFlow = MutableStateFlow(false) - override val isRefreshTokenInvalid: StateFlow = _isRefreshTokenInvalid.asStateFlow() - - override suspend fun refreshToken( - refreshToken: String - ): Result { - randomShortDelay() - return Result.success( - JwtToken( - accessToken = "mock_access_token", - refreshToken = "mock_refresh_token" - ) - ) - } - override suspend fun login( socialId: Long, email: String @@ -77,9 +50,4 @@ class MockAuthenticationRepository @Inject constructor( private suspend fun randomLongDelay() { delay(LongRange(500, 2000).random()) } - - companion object { - private const val REFRESH_TOKEN = "mock_refresh_token" - private const val ACCESS_TOKEN = "mock_access_token" - } } diff --git a/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/RealAuthenticationRepository.kt b/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/RealAuthenticationRepository.kt index 6e2554fd..8ce41259 100644 --- a/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/RealAuthenticationRepository.kt +++ b/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/RealAuthenticationRepository.kt @@ -1,51 +1,17 @@ package ac.dnd.mour.android.data.repository.authentication -import ac.dnd.mour.android.data.remote.local.SharedPreferencesManager import ac.dnd.mour.android.data.remote.network.api.AuthenticationApi -import ac.dnd.mour.android.domain.model.authentication.JwtToken import ac.dnd.mour.android.domain.model.legacy.Login import ac.dnd.mour.android.domain.model.legacy.Register import ac.dnd.mour.android.domain.repository.AuthenticationRepository +import ac.dnd.mour.android.domain.repository.TokenRepository import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow class RealAuthenticationRepository @Inject constructor( private val authenticationApi: AuthenticationApi, - private val sharedPreferencesManager: SharedPreferencesManager + private val tokenRepository: TokenRepository ) : AuthenticationRepository { - override var refreshToken: String - set(value) = sharedPreferencesManager.setString(REFRESH_TOKEN, value) - get() = sharedPreferencesManager.getString(REFRESH_TOKEN, "") - - override var accessToken: String - set(value) = sharedPreferencesManager.setString(ACCESS_TOKEN, value) - get() = sharedPreferencesManager.getString(ACCESS_TOKEN, "") - - private val _isRefreshTokenInvalid: MutableStateFlow = MutableStateFlow(false) - override val isRefreshTokenInvalid: StateFlow = _isRefreshTokenInvalid.asStateFlow() - - override suspend fun refreshToken( - refreshToken: String - ): Result { - return authenticationApi.getAccessToken( - refreshToken = refreshToken - ).onSuccess { token -> - this.refreshToken = token.refreshToken - this.accessToken = token.accessToken - _isRefreshTokenInvalid.value = false - }.onFailure { exception -> - _isRefreshTokenInvalid.value = true - }.map { token -> - JwtToken( - accessToken = token.accessToken, - refreshToken = token.refreshToken - ) - } - } - override suspend fun login( socialId: Long, email: String, @@ -54,8 +20,8 @@ class RealAuthenticationRepository @Inject constructor( socialId = socialId, email = email, ).onSuccess { token -> - this.refreshToken = token.refreshToken - this.accessToken = token.accessToken + tokenRepository.refreshToken = token.refreshToken + tokenRepository.accessToken = token.accessToken }.map { login -> Login(id = login.id) } @@ -64,8 +30,8 @@ class RealAuthenticationRepository @Inject constructor( override suspend fun logout(): Result { return authenticationApi.logout() .onSuccess { - this.refreshToken = "" - this.accessToken = "" + tokenRepository.refreshToken = "" + tokenRepository.accessToken = "" } } @@ -87,8 +53,8 @@ class RealAuthenticationRepository @Inject constructor( gender = gender, birth = birth ).onSuccess { register -> - this.refreshToken = register.refreshToken - this.accessToken = register.accessToken + tokenRepository.refreshToken = register.refreshToken + tokenRepository.accessToken = register.accessToken }.map { register -> Register(id = register.id) } @@ -97,13 +63,8 @@ class RealAuthenticationRepository @Inject constructor( override suspend fun withdraw(): Result { return authenticationApi.withdraw() .onSuccess { - this.refreshToken = "" - this.accessToken = "" + tokenRepository.refreshToken = "" + tokenRepository.accessToken = "" } } - - companion object { - private const val REFRESH_TOKEN = "refresh_token" - private const val ACCESS_TOKEN = "access_token" - } } diff --git a/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/token/MockTokenRepository.kt b/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/token/MockTokenRepository.kt new file mode 100644 index 00000000..4b10bbca --- /dev/null +++ b/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/token/MockTokenRepository.kt @@ -0,0 +1,55 @@ +package ac.dnd.mour.android.data.repository.authentication.token + +import ac.dnd.mour.android.data.remote.local.SharedPreferencesManager +import ac.dnd.mour.android.domain.model.authentication.JwtToken +import ac.dnd.mour.android.domain.repository.TokenRepository +import javax.inject.Inject +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class MockTokenRepository @Inject constructor( + private val sharedPreferencesManager: SharedPreferencesManager +) : TokenRepository { + + override var refreshToken: String + set(value) = sharedPreferencesManager.setString(REFRESH_TOKEN, value) + get() = sharedPreferencesManager.getString(REFRESH_TOKEN, "") + + override var accessToken: String + set(value) = sharedPreferencesManager.setString(ACCESS_TOKEN, value) + get() = sharedPreferencesManager.getString(ACCESS_TOKEN, "") + + private val _isRefreshTokenInvalid: MutableStateFlow = MutableStateFlow(false) + override val isRefreshTokenInvalid: StateFlow = _isRefreshTokenInvalid.asStateFlow() + + override suspend fun refreshToken( + refreshToken: String + ): Result { + randomShortDelay() + return Result.success( + JwtToken( + accessToken = "mock_access_token", + refreshToken = "mock_refresh_token" + ) + ) + } + + override suspend fun resetRefreshTokenInvalidFlag() { + _isRefreshTokenInvalid.value = false + } + + private suspend fun randomShortDelay() { + delay(LongRange(100, 500).random()) + } + + private suspend fun randomLongDelay() { + delay(LongRange(500, 2000).random()) + } + + companion object { + private const val REFRESH_TOKEN = "mock_refresh_token" + private const val ACCESS_TOKEN = "mock_access_token" + } +} diff --git a/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/token/RealTokenRepository.kt b/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/token/RealTokenRepository.kt new file mode 100644 index 00000000..9381fc58 --- /dev/null +++ b/data/src/main/kotlin/ac/dnd/mour/android/data/repository/authentication/token/RealTokenRepository.kt @@ -0,0 +1,62 @@ +package ac.dnd.mour.android.data.repository.authentication.token + +import ac.dnd.mour.android.data.remote.local.SharedPreferencesManager +import ac.dnd.mour.android.data.remote.network.api.TokenApi +import ac.dnd.mour.android.domain.model.authentication.JwtToken +import ac.dnd.mour.android.domain.model.error.ServerException +import ac.dnd.mour.android.domain.repository.TokenRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class RealTokenRepository @Inject constructor( + private val tokenApi: TokenApi, + private val sharedPreferencesManager: SharedPreferencesManager +) : TokenRepository { + + override var refreshToken: String + set(value) = sharedPreferencesManager.setString(REFRESH_TOKEN, value) + get() = sharedPreferencesManager.getString(REFRESH_TOKEN, "") + + override var accessToken: String + set(value) = sharedPreferencesManager.setString(ACCESS_TOKEN, value) + get() = sharedPreferencesManager.getString(ACCESS_TOKEN, "") + + private val _isRefreshTokenInvalid: MutableStateFlow = MutableStateFlow(false) + override val isRefreshTokenInvalid: StateFlow = _isRefreshTokenInvalid.asStateFlow() + + override suspend fun refreshToken( + refreshToken: String + ): Result { + return if (refreshToken.isEmpty()) { + Result.failure(ServerException("Client Error", "refreshToken is empty.")) + } else { + tokenApi.getAccessToken( + refreshToken = refreshToken + ).onSuccess { token -> + this.refreshToken = token.refreshToken + this.accessToken = token.accessToken + _isRefreshTokenInvalid.value = false + }.onFailure { exception -> + this.refreshToken = "" + this.accessToken = "" + _isRefreshTokenInvalid.value = true + }.map { token -> + JwtToken( + accessToken = token.accessToken, + refreshToken = token.refreshToken + ) + } + } + } + + override suspend fun resetRefreshTokenInvalidFlag() { + _isRefreshTokenInvalid.value = false + } + + companion object { + private const val REFRESH_TOKEN = "refresh_token" + private const val ACCESS_TOKEN = "access_token" + } +} diff --git a/domain/src/main/kotlin/ac/dnd/mour/android/domain/repository/AuthenticationRepository.kt b/domain/src/main/kotlin/ac/dnd/mour/android/domain/repository/AuthenticationRepository.kt index 86f517a2..8ace9186 100644 --- a/domain/src/main/kotlin/ac/dnd/mour/android/domain/repository/AuthenticationRepository.kt +++ b/domain/src/main/kotlin/ac/dnd/mour/android/domain/repository/AuthenticationRepository.kt @@ -1,22 +1,9 @@ package ac.dnd.mour.android.domain.repository -import ac.dnd.mour.android.domain.model.authentication.JwtToken import ac.dnd.mour.android.domain.model.legacy.Login import ac.dnd.mour.android.domain.model.legacy.Register -import kotlinx.coroutines.flow.StateFlow interface AuthenticationRepository { - - var refreshToken: String - - var accessToken: String - - val isRefreshTokenInvalid: StateFlow - - suspend fun refreshToken( - refreshToken: String - ): Result - suspend fun login( socialId: Long, email: String diff --git a/domain/src/main/kotlin/ac/dnd/mour/android/domain/repository/TokenRepository.kt b/domain/src/main/kotlin/ac/dnd/mour/android/domain/repository/TokenRepository.kt new file mode 100644 index 00000000..26261318 --- /dev/null +++ b/domain/src/main/kotlin/ac/dnd/mour/android/domain/repository/TokenRepository.kt @@ -0,0 +1,19 @@ +package ac.dnd.mour.android.domain.repository + +import ac.dnd.mour.android.domain.model.authentication.JwtToken +import kotlinx.coroutines.flow.StateFlow + +interface TokenRepository { + + var refreshToken: String + + var accessToken: String + + val isRefreshTokenInvalid: StateFlow + + suspend fun refreshToken( + refreshToken: String + ): Result + + suspend fun resetRefreshTokenInvalidFlag() +} diff --git a/domain/src/main/kotlin/ac/dnd/mour/android/domain/usecase/authentication/UpdateJwtTokenUseCase.kt b/domain/src/main/kotlin/ac/dnd/mour/android/domain/usecase/authentication/UpdateJwtTokenUseCase.kt index bb6ca190..87e9c932 100644 --- a/domain/src/main/kotlin/ac/dnd/mour/android/domain/usecase/authentication/UpdateJwtTokenUseCase.kt +++ b/domain/src/main/kotlin/ac/dnd/mour/android/domain/usecase/authentication/UpdateJwtTokenUseCase.kt @@ -1,14 +1,14 @@ package ac.dnd.mour.android.domain.usecase.authentication -import ac.dnd.mour.android.domain.repository.AuthenticationRepository +import ac.dnd.mour.android.domain.repository.TokenRepository import javax.inject.Inject class UpdateJwtTokenUseCase @Inject constructor( - private val authenticationRepository: AuthenticationRepository + private val tokenRepository: TokenRepository ) { suspend operator fun invoke(): Result { - return authenticationRepository.refreshToken( - refreshToken = authenticationRepository.refreshToken + return tokenRepository.refreshToken( + refreshToken = tokenRepository.refreshToken ).map { } } }