From 0f25dbb0b781a4d84763abee3d8db28c46588f19 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Wed, 17 Jul 2024 04:30:49 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[CHORE/#94]=20Interceptor=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/terning/point/di/AuthInterceptor.kt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 app/src/main/java/com/terning/point/di/AuthInterceptor.kt diff --git a/app/src/main/java/com/terning/point/di/AuthInterceptor.kt b/app/src/main/java/com/terning/point/di/AuthInterceptor.kt new file mode 100644 index 000000000..d1ba54ca1 --- /dev/null +++ b/app/src/main/java/com/terning/point/di/AuthInterceptor.kt @@ -0,0 +1,6 @@ +package com.terning.point.di + +import javax.inject.Inject + +class AuthInterceptor @Inject constructor() { +} \ No newline at end of file From b20eadb6a132e307cc4b7d8999006e70d726d5a5 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Wed, 17 Jul 2024 04:45:47 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[CHORE/#96]=20=EC=A4=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/terning/point/di/AuthInterceptor.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/terning/point/di/AuthInterceptor.kt b/app/src/main/java/com/terning/point/di/AuthInterceptor.kt index d1ba54ca1..37d8d3aa9 100644 --- a/app/src/main/java/com/terning/point/di/AuthInterceptor.kt +++ b/app/src/main/java/com/terning/point/di/AuthInterceptor.kt @@ -3,4 +3,5 @@ package com.terning.point.di import javax.inject.Inject class AuthInterceptor @Inject constructor() { + } \ No newline at end of file From 150e30eb09ac461430ee3d41cc3fee68331e5c76 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Wed, 17 Jul 2024 13:14:40 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[FEAT/#96]=20=ED=86=A0=ED=81=B0=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EC=84=9C=EB=B2=84=ED=86=B5=EC=8B=A0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/datasource/TokenReissueDataSource.kt | 11 +++++++++++ .../TokenReissueDataSourceImpl.kt | 16 ++++++++++++++++ .../dto/response/TokenReissueResponseDto.kt | 13 +++++++++++++ .../TokenReissueRepositoryImpl.kt | 19 +++++++++++++++++++ .../data/service/TokenReissueService.kt | 13 +++++++++++++ .../response/TokenReissueResponseModel.kt | 5 +++++ .../repository/TokenReissueRepository.kt | 9 +++++++++ 7 files changed, 86 insertions(+) create mode 100644 data/src/main/java/com/terning/data/datasource/TokenReissueDataSource.kt create mode 100644 data/src/main/java/com/terning/data/datasourceimpl/TokenReissueDataSourceImpl.kt create mode 100644 data/src/main/java/com/terning/data/dto/response/TokenReissueResponseDto.kt create mode 100644 data/src/main/java/com/terning/data/repositoryimpl/TokenReissueRepositoryImpl.kt create mode 100644 data/src/main/java/com/terning/data/service/TokenReissueService.kt create mode 100644 domain/src/main/java/com/terning/domain/entity/response/TokenReissueResponseModel.kt create mode 100644 domain/src/main/java/com/terning/domain/repository/TokenReissueRepository.kt diff --git a/data/src/main/java/com/terning/data/datasource/TokenReissueDataSource.kt b/data/src/main/java/com/terning/data/datasource/TokenReissueDataSource.kt new file mode 100644 index 000000000..746b11752 --- /dev/null +++ b/data/src/main/java/com/terning/data/datasource/TokenReissueDataSource.kt @@ -0,0 +1,11 @@ +package com.terning.data.datasource + +import com.terning.data.dto.BaseResponse +import com.terning.data.dto.response.TokenReissueResponseDto +import com.terning.domain.entity.response.TokenReissueResponseModel + +interface TokenReissueDataSource { + suspend fun postReissueToken( + authorization: String, + ): BaseResponse +} \ No newline at end of file diff --git a/data/src/main/java/com/terning/data/datasourceimpl/TokenReissueDataSourceImpl.kt b/data/src/main/java/com/terning/data/datasourceimpl/TokenReissueDataSourceImpl.kt new file mode 100644 index 000000000..d4bae4bf0 --- /dev/null +++ b/data/src/main/java/com/terning/data/datasourceimpl/TokenReissueDataSourceImpl.kt @@ -0,0 +1,16 @@ +package com.terning.data.datasourceimpl + +import com.terning.data.datasource.TokenReissueDataSource +import com.terning.data.dto.BaseResponse +import com.terning.data.dto.response.TokenReissueResponseDto +import com.terning.data.service.TokenReissueService +import javax.inject.Inject + +class TokenReissueDataSourceImpl @Inject constructor( + private val tokenReissueService: TokenReissueService +) : TokenReissueDataSource { + override suspend fun postReissueToken( + authorization: String + ): BaseResponse = + tokenReissueService.postReissueToken(authorization) +} \ No newline at end of file diff --git a/data/src/main/java/com/terning/data/dto/response/TokenReissueResponseDto.kt b/data/src/main/java/com/terning/data/dto/response/TokenReissueResponseDto.kt new file mode 100644 index 000000000..85d9de8f5 --- /dev/null +++ b/data/src/main/java/com/terning/data/dto/response/TokenReissueResponseDto.kt @@ -0,0 +1,13 @@ +package com.terning.data.dto.response + +import com.terning.domain.entity.response.TokenReissueResponseModel +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class TokenReissueResponseDto( + @SerialName("refreshToken") + val refreshToken: String +) { + fun toTokenReissueResponseModel() = TokenReissueResponseModel(refreshToken = refreshToken) +} \ No newline at end of file diff --git a/data/src/main/java/com/terning/data/repositoryimpl/TokenReissueRepositoryImpl.kt b/data/src/main/java/com/terning/data/repositoryimpl/TokenReissueRepositoryImpl.kt new file mode 100644 index 000000000..3867ffb9e --- /dev/null +++ b/data/src/main/java/com/terning/data/repositoryimpl/TokenReissueRepositoryImpl.kt @@ -0,0 +1,19 @@ +package com.terning.data.repositoryimpl + +import com.terning.data.datasource.TokenReissueDataSource +import com.terning.domain.entity.response.TokenReissueResponseModel +import com.terning.domain.repository.TokenReissueRepository +import javax.inject.Inject + +class TokenReissueRepositoryImpl @Inject constructor( + private val tokenReissueDataSource: TokenReissueDataSource +) : TokenReissueRepository { + override suspend fun postReissueToken( + authorization: String + ): Result = + runCatching { + tokenReissueDataSource.postReissueToken( + authorization = authorization + ).result.toTokenReissueResponseModel() + } +} \ No newline at end of file diff --git a/data/src/main/java/com/terning/data/service/TokenReissueService.kt b/data/src/main/java/com/terning/data/service/TokenReissueService.kt new file mode 100644 index 000000000..10b7eb400 --- /dev/null +++ b/data/src/main/java/com/terning/data/service/TokenReissueService.kt @@ -0,0 +1,13 @@ +package com.terning.data.service + +import com.terning.data.dto.BaseResponse +import com.terning.data.dto.response.TokenReissueResponseDto +import retrofit2.http.Header +import retrofit2.http.POST + +interface TokenReissueService { + @POST("/api/v1/auth/token-reissue") + suspend fun postReissueToken( + @Header("Authorization") authorization: String, + ): BaseResponse +} \ No newline at end of file diff --git a/domain/src/main/java/com/terning/domain/entity/response/TokenReissueResponseModel.kt b/domain/src/main/java/com/terning/domain/entity/response/TokenReissueResponseModel.kt new file mode 100644 index 000000000..b4d09c7e9 --- /dev/null +++ b/domain/src/main/java/com/terning/domain/entity/response/TokenReissueResponseModel.kt @@ -0,0 +1,5 @@ +package com.terning.domain.entity.response + +data class TokenReissueResponseModel ( + val refreshToken : String +) \ No newline at end of file diff --git a/domain/src/main/java/com/terning/domain/repository/TokenReissueRepository.kt b/domain/src/main/java/com/terning/domain/repository/TokenReissueRepository.kt new file mode 100644 index 000000000..ea302936c --- /dev/null +++ b/domain/src/main/java/com/terning/domain/repository/TokenReissueRepository.kt @@ -0,0 +1,9 @@ +package com.terning.domain.repository + +import com.terning.domain.entity.response.TokenReissueResponseModel + +interface TokenReissueRepository { + suspend fun postReissueToken( + authorization: String, + ): Result +} \ No newline at end of file From 6c65c85a1e803e9b9e88c0d40c6479c285cf46ce Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Wed, 17 Jul 2024 13:28:27 +0900 Subject: [PATCH 4/6] [FEAT/#96] RetrofitModule --- .../com/terning/point/di/AuthInterceptor.kt | 16 +++++++- .../com/terning/point/di/RetrofitModule.kt | 38 +++++++++++++++++-- .../point/di/qualifier/RetrofitQualifier.kt | 6 ++- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/terning/point/di/AuthInterceptor.kt b/app/src/main/java/com/terning/point/di/AuthInterceptor.kt index 37d8d3aa9..c7ce4d233 100644 --- a/app/src/main/java/com/terning/point/di/AuthInterceptor.kt +++ b/app/src/main/java/com/terning/point/di/AuthInterceptor.kt @@ -1,7 +1,21 @@ package com.terning.point.di +import android.content.Context +import com.terning.data.local.TerningDataStore +import com.terning.domain.repository.TokenReissueRepository +import dagger.hilt.android.qualifiers.ApplicationContext +import okhttp3.Interceptor +import okhttp3.Response import javax.inject.Inject -class AuthInterceptor @Inject constructor() { +class AuthInterceptor @Inject constructor( + private val tokenReissueRepository : TokenReissueRepository, + private val terningDataStore : TerningDataStore, + @ApplicationContext private val context : Context +) : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/app/src/main/java/com/terning/point/di/RetrofitModule.kt b/app/src/main/java/com/terning/point/di/RetrofitModule.kt index c5840f081..05165b7cf 100644 --- a/app/src/main/java/com/terning/point/di/RetrofitModule.kt +++ b/app/src/main/java/com/terning/point/di/RetrofitModule.kt @@ -4,6 +4,7 @@ import com.terning.core.extension.isJsonArray import com.terning.core.extension.isJsonObject import com.terning.point.BuildConfig.BASE_URL import com.terning.point.di.qualifier.JWT +import com.terning.point.di.qualifier.REISSUE import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -57,8 +58,25 @@ object RetrofitModule { @Provides @Singleton - fun provideOkHttpClient( - loggingInterceptor: Interceptor + @JWT + fun provideAuthInterceptor(authInterceptor: AuthInterceptor): Interceptor = authInterceptor + + @Provides + @Singleton + @JWT + fun provideJWTOkHttpClient( + loggingInterceptor: Interceptor, + @JWT authInterceptor: Interceptor, + ): OkHttpClient = OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .addInterceptor(authInterceptor) + .build() + + @Provides + @Singleton + @REISSUE + fun provideReissueOkHttpClient( + loggingInterceptor: Interceptor, ): OkHttpClient = OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .build() @@ -67,12 +85,24 @@ object RetrofitModule { @Singleton @JWT fun provideJWTRetrofit( - client: OkHttpClient, - factory: Converter.Factory + @JWT client: OkHttpClient, + factory: Converter.Factory, ): Retrofit = Retrofit.Builder() .baseUrl(BASE_URL) + .client(client) .addConverterFactory(factory) + .build() + + @Provides + @Singleton + @REISSUE + fun provideReissueRetrofit( + @REISSUE client: OkHttpClient, + factory: Converter.Factory, + ): Retrofit = Retrofit.Builder() + .baseUrl(BASE_URL) .client(client) + .addConverterFactory(factory) .build() } \ No newline at end of file diff --git a/app/src/main/java/com/terning/point/di/qualifier/RetrofitQualifier.kt b/app/src/main/java/com/terning/point/di/qualifier/RetrofitQualifier.kt index 11956ab89..543f704d3 100644 --- a/app/src/main/java/com/terning/point/di/qualifier/RetrofitQualifier.kt +++ b/app/src/main/java/com/terning/point/di/qualifier/RetrofitQualifier.kt @@ -5,4 +5,8 @@ import javax.inject.Qualifier @Qualifier @Retention(AnnotationRetention.BINARY) -annotation class JWT \ No newline at end of file +annotation class JWT + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class REISSUE \ No newline at end of file From 5636cfa198ee743c434586507e2459a9f64c15f6 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Wed, 17 Jul 2024 14:06:06 +0900 Subject: [PATCH 5/6] [FEAT/#96] AuthInterceptor --- app/build.gradle.kts | 1 + .../com/terning/point/di/AuthInterceptor.kt | 76 ++++++++++++++++++- .../com/terning/core/extension/ContextExt.kt | 4 + gradle/libs.versions.toml | 5 ++ 4 files changed, 82 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 93facf65f..c8d5fa787 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -109,6 +109,7 @@ dependencies { implementation(libs.retrofit2.kotlinx.serialization.converter) implementation(libs.timber) implementation(libs.ossLicense) + implementation(libs.process.phoenix) debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.test.manifest) diff --git a/app/src/main/java/com/terning/point/di/AuthInterceptor.kt b/app/src/main/java/com/terning/point/di/AuthInterceptor.kt index c7ce4d233..d54fffc06 100644 --- a/app/src/main/java/com/terning/point/di/AuthInterceptor.kt +++ b/app/src/main/java/com/terning/point/di/AuthInterceptor.kt @@ -1,21 +1,89 @@ package com.terning.point.di import android.content.Context +import android.content.Intent +import android.os.Handler +import android.os.Looper +import com.jakewharton.processphoenix.ProcessPhoenix +import com.terning.core.extension.stringToast import com.terning.data.local.TerningDataStore import com.terning.domain.repository.TokenReissueRepository +import com.terning.feature.main.MainActivity import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.runBlocking import okhttp3.Interceptor +import okhttp3.Request import okhttp3.Response +import timber.log.Timber import javax.inject.Inject class AuthInterceptor @Inject constructor( - private val tokenReissueRepository : TokenReissueRepository, - private val terningDataStore : TerningDataStore, - @ApplicationContext private val context : Context + private val tokenReissueRepository: TokenReissueRepository, + private val terningDataStore: TerningDataStore, + @ApplicationContext private val context: Context ) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { - TODO("Not yet implemented") + val originalRequest = chain.request() + + Timber.d("GET ACCESS TOKEN : ${terningDataStore.accessToken}") + + val authRequest = if (terningDataStore.accessToken.isNotBlank()) { + originalRequest.newBuilder().newAuthBuilder().build() + } else { + originalRequest + } + + val response = chain.proceed(authRequest) + + when (response.code) { + CODE_TOKEN_EXPIRED -> { + try { + runBlocking { + tokenReissueRepository.postReissueToken( + terningDataStore.refreshToken + ) + }.onSuccess { data -> + terningDataStore.apply { + refreshToken = data.refreshToken + } + + response.close() + + val newRequest = + authRequest.newBuilder().removeHeader(AUTHORIZATION).newAuthBuilder() + .build() + + return chain.proceed(newRequest) + } + } catch (t: Throwable) { + Timber.d(t.message) + } + + terningDataStore.clearInfo() + + Handler(Looper.getMainLooper()).post { + context.stringToast(TOKEN_EXPIRED_ERROR) + Handler(Looper.getMainLooper()).post { + ProcessPhoenix.triggerRebirth( + context, + Intent(context, MainActivity::class.java) + ) + } + } + } + } + return response + } + + private fun Request.Builder.newAuthBuilder() = + this.addHeader(AUTHORIZATION, "$BEARER ${terningDataStore.accessToken}") + + companion object { + private const val CODE_TOKEN_EXPIRED = 401 + private const val TOKEN_EXPIRED_ERROR = "토큰이 만료되었어요\n다시 로그인 해주세요" + private const val BEARER = "Bearer" + private const val AUTHORIZATION = "Authorization" } } \ No newline at end of file diff --git a/core/src/main/java/com/terning/core/extension/ContextExt.kt b/core/src/main/java/com/terning/core/extension/ContextExt.kt index 370fb9f06..dd79389b8 100644 --- a/core/src/main/java/com/terning/core/extension/ContextExt.kt +++ b/core/src/main/java/com/terning/core/extension/ContextExt.kt @@ -8,6 +8,10 @@ fun Context.toast(@StringRes message: Int) { Toast.makeText(this, getString(message), Toast.LENGTH_SHORT).show() } +fun Context.stringToast(message: String) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() +} + fun Context.longToast(@StringRes message: Int) { Toast.makeText(this, getString(message), Toast.LENGTH_SHORT).show() } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1606804dd..5951d4c44 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -82,6 +82,9 @@ lifecycleRuntimeComposeAndroid = "2.8.2" ## Kakao kakaoVersion = "2.20.1" +## ProcessPhoenix +processPhoenix = "2.0.0" + [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" } @@ -159,6 +162,8 @@ lottie = {group = "com.airbnb.android", name = "lottie", version.ref = "lottieVe kakao-user = {group = "com.kakao.sdk", name = "v2-user", version.ref = "kakaoVersion"} +process-phoenix = {group = "com.jakewharton", name = "process-phoenix", version.ref = "processPhoenix"} + [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } From 7d148ab7b3447e021d8b24ef5ae3fdbd544c9d32 Mon Sep 17 00:00:00 2001 From: LEE YOU BIN Date: Wed, 17 Jul 2024 14:17:58 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[FEAT/#96]=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/terning/point/di/DataSourceModule.kt | 6 ++++++ app/src/main/java/com/terning/point/di/RepositoryModule.kt | 6 ++++++ app/src/main/java/com/terning/point/di/ServiceModule.kt | 7 +++++++ 3 files changed, 19 insertions(+) diff --git a/app/src/main/java/com/terning/point/di/DataSourceModule.kt b/app/src/main/java/com/terning/point/di/DataSourceModule.kt index b7f3ae9a8..7e00cbb4c 100644 --- a/app/src/main/java/com/terning/point/di/DataSourceModule.kt +++ b/app/src/main/java/com/terning/point/di/DataSourceModule.kt @@ -2,8 +2,10 @@ package com.terning.point.di import com.terning.data.datasource.AuthDataSource import com.terning.data.datasource.SearchDataSource +import com.terning.data.datasource.TokenReissueDataSource import com.terning.data.datasourceimpl.AuthDataSourceImpl import com.terning.data.datasourceimpl.SearchDataSourceImpl +import com.terning.data.datasourceimpl.TokenReissueDataSourceImpl import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -22,4 +24,8 @@ abstract class DataSourceModule { @Singleton abstract fun bindSearchViewsDataSource(searchViewsDataSourceImpl: SearchDataSourceImpl): SearchDataSource + + @Binds + @Singleton + abstract fun bindTokenReissueDataSource(tokenReissueDataSourceImpl: TokenReissueDataSourceImpl): TokenReissueDataSource } \ No newline at end of file diff --git a/app/src/main/java/com/terning/point/di/RepositoryModule.kt b/app/src/main/java/com/terning/point/di/RepositoryModule.kt index 82eca6c3f..2377e8ef7 100644 --- a/app/src/main/java/com/terning/point/di/RepositoryModule.kt +++ b/app/src/main/java/com/terning/point/di/RepositoryModule.kt @@ -2,9 +2,11 @@ package com.terning.point.di import com.terning.data.repositoryimpl.AuthRepositoryImpl import com.terning.data.repositoryimpl.SearchViewsRepositoryImpl +import com.terning.data.repositoryimpl.TokenReissueRepositoryImpl import com.terning.data.repositoryimpl.TokenRepositoryImpl import com.terning.domain.repository.AuthRepository import com.terning.domain.repository.SearchRepository +import com.terning.domain.repository.TokenReissueRepository import com.terning.domain.repository.TokenRepository import dagger.Binds import dagger.Module @@ -27,4 +29,8 @@ abstract class RepositoryModule { @Binds @Singleton abstract fun bindSearchViewsRepository(searchViewsRepositoryImpl: SearchViewsRepositoryImpl): SearchRepository + + @Binds + @Singleton + abstract fun bindTokenReissueRepository(tokenReissueRepositoryImpl: TokenReissueRepositoryImpl): TokenReissueRepository } \ No newline at end of file diff --git a/app/src/main/java/com/terning/point/di/ServiceModule.kt b/app/src/main/java/com/terning/point/di/ServiceModule.kt index c8733f01e..92c4765db 100644 --- a/app/src/main/java/com/terning/point/di/ServiceModule.kt +++ b/app/src/main/java/com/terning/point/di/ServiceModule.kt @@ -2,7 +2,9 @@ package com.terning.point.di import com.terning.data.service.AuthService import com.terning.data.service.SearchService +import com.terning.data.service.TokenReissueService import com.terning.point.di.qualifier.JWT +import com.terning.point.di.qualifier.REISSUE import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -23,4 +25,9 @@ object ServiceModule { @Singleton fun provideSearchService(@JWT retrofit: Retrofit): SearchService = retrofit.create(SearchService::class.java) + + @Provides + @Singleton + fun provideTokenReissueService(@REISSUE retrofit: Retrofit): TokenReissueService = + retrofit.create(TokenReissueService::class.java) } \ No newline at end of file