diff --git a/data/src/main/kotlin/us/wedemy/eggeum/android/data/datasource/token/TokenDataSource.kt b/data/src/main/kotlin/us/wedemy/eggeum/android/data/datasource/token/TokenDataSource.kt new file mode 100644 index 00000000..05d48115 --- /dev/null +++ b/data/src/main/kotlin/us/wedemy/eggeum/android/data/datasource/token/TokenDataSource.kt @@ -0,0 +1,15 @@ +/* + * Designed and developed by Wedemy 2023. + * + * Licensed under the MIT. + * Please see full license: https://github.com/Wedemy/eggeum-android/blob/main/LICENSE + */ + +package us.wedemy.eggeum.android.data.datasource.token + +import us.wedemy.eggeum.android.data.model.token.TokenRequest +import us.wedemy.eggeum.android.data.model.token.TokenResponse + +public interface TokenDataSource { + public suspend fun getRefreshToken(tokenRequest: TokenRequest): TokenResponse? +} diff --git a/data/src/main/kotlin/us/wedemy/eggeum/android/data/datasource/token/TokenDataSourceImpl.kt b/data/src/main/kotlin/us/wedemy/eggeum/android/data/datasource/token/TokenDataSourceImpl.kt new file mode 100644 index 00000000..c84d4c26 --- /dev/null +++ b/data/src/main/kotlin/us/wedemy/eggeum/android/data/datasource/token/TokenDataSourceImpl.kt @@ -0,0 +1,25 @@ +/* + * Designed and developed by Wedemy 2023. + * + * Licensed under the MIT. + * Please see full license: https://github.com/Wedemy/eggeum-android/blob/main/LICENSE + */ + +package us.wedemy.eggeum.android.data.datasource.token + +import javax.inject.Inject +import us.wedemy.eggeum.android.data.model.token.TokenRequest +import us.wedemy.eggeum.android.data.model.token.TokenResponse +import us.wedemy.eggeum.android.data.service.TokenService +import us.wedemy.eggeum.android.data.util.safeRequest + +public class TokenDataSourceImpl @Inject constructor( + private val service: TokenService, +) : TokenDataSource { + + override suspend fun getRefreshToken(tokenRequest: TokenRequest): TokenResponse? { + return safeRequest { + service.getRefreshToken(tokenRequest) + } + } +} diff --git a/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/DataSourceModule.kt b/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/DataSourceModule.kt index 49838b60..80b845fa 100644 --- a/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/DataSourceModule.kt +++ b/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/DataSourceModule.kt @@ -26,6 +26,8 @@ import us.wedemy.eggeum.android.data.datasource.place.remote.PlaceRemoteDataSour import us.wedemy.eggeum.android.data.datasource.place.remote.PlaceRemoteDataSourceImpl import us.wedemy.eggeum.android.data.datasource.report.ReportDataSource import us.wedemy.eggeum.android.data.datasource.report.ReportDataSourceImpl +import us.wedemy.eggeum.android.data.datasource.token.TokenDataSource +import us.wedemy.eggeum.android.data.datasource.token.TokenDataSourceImpl import us.wedemy.eggeum.android.data.datasource.user.UserDataSource import us.wedemy.eggeum.android.data.datasource.user.UserDataSourceImpl @@ -63,4 +65,8 @@ internal abstract class DataSourceModule { @Binds @Singleton abstract fun bindFileDataSource(fileDataSourceImpl: FileDataSourceImpl): FileDataSource + + @Binds + @Singleton + abstract fun bindTokenDataSource(tokenDataSourceImpl: TokenDataSourceImpl): TokenDataSource } diff --git a/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/NetworkModule.kt b/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/NetworkModule.kt index 6b27af15..47a3c7f6 100644 --- a/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/NetworkModule.kt +++ b/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/NetworkModule.kt @@ -35,6 +35,7 @@ import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit import timber.log.Timber import us.wedemy.eggeum.android.data.BuildConfig +import us.wedemy.eggeum.android.data.datasource.token.TokenDataSource import us.wedemy.eggeum.android.data.datastore.TokenDataStoreProvider import us.wedemy.eggeum.android.data.service.TokenAuthenticator import us.wedemy.eggeum.android.data.service.TokenInterceptor @@ -129,8 +130,9 @@ internal object NetworkModule { @Provides internal fun provideTokenAuthenticator( dataStoreProvider: TokenDataStoreProvider, + tokenDataSource: TokenDataSource, ): TokenAuthenticator { - return TokenAuthenticator(dataStoreProvider) + return TokenAuthenticator(dataStoreProvider, tokenDataSource) } @Singleton diff --git a/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/ServiceModule.kt b/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/ServiceModule.kt index 8329a7f2..b180e9fd 100644 --- a/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/ServiceModule.kt +++ b/data/src/main/kotlin/us/wedemy/eggeum/android/data/di/ServiceModule.kt @@ -19,6 +19,7 @@ import us.wedemy.eggeum.android.data.service.LoginService import us.wedemy.eggeum.android.data.service.NoticeService import us.wedemy.eggeum.android.data.service.PlaceService import us.wedemy.eggeum.android.data.service.ReportService +import us.wedemy.eggeum.android.data.service.TokenService import us.wedemy.eggeum.android.data.service.UserService @Module @@ -78,4 +79,13 @@ internal object ServiceModule { ): FileService { return retrofit.create(FileService::class.java) } + + @Singleton + @Provides + internal fun provideTokenService( + @Named("RetrofitAuthHttpClient") + retrofit: Retrofit, + ): TokenService { + return retrofit.create(TokenService::class.java) + } } diff --git a/data/src/main/kotlin/us/wedemy/eggeum/android/data/model/token/TokenRequest.kt b/data/src/main/kotlin/us/wedemy/eggeum/android/data/model/token/TokenRequest.kt new file mode 100644 index 00000000..bcf416df --- /dev/null +++ b/data/src/main/kotlin/us/wedemy/eggeum/android/data/model/token/TokenRequest.kt @@ -0,0 +1,17 @@ +/* + * Designed and developed by Wedemy 2023. + * + * Licensed under the MIT. + * Please see full license: https://github.com/Wedemy/eggeum-android/blob/main/LICENSE + */ + +package us.wedemy.eggeum.android.data.model.token + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +public data class TokenRequest( + @SerialName("refreshToken") + val refreshToken: String, +) diff --git a/data/src/main/kotlin/us/wedemy/eggeum/android/data/model/token/TokenResponse.kt b/data/src/main/kotlin/us/wedemy/eggeum/android/data/model/token/TokenResponse.kt new file mode 100644 index 00000000..63e97e9e --- /dev/null +++ b/data/src/main/kotlin/us/wedemy/eggeum/android/data/model/token/TokenResponse.kt @@ -0,0 +1,29 @@ +/* + * Designed and developed by Wedemy 2023. + * + * Licensed under the MIT. + * Please see full license: https://github.com/Wedemy/eggeum-android/blob/main/LICENSE + */ + +package us.wedemy.eggeum.android.data.model.token + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +public data class TokenResponse( + @SerialName("accessToken") + val accessToken: String, + + @SerialName("expiresIn") + val expiresIn: Long, + + @SerialName("refreshToken") + val refreshToken: String, + + @SerialName("refreshExpiresIn") + val refreshExpiresIn: Long, + + @SerialName("userRoles") + val userRoles: List, +) diff --git a/data/src/main/kotlin/us/wedemy/eggeum/android/data/service/TokenAuthenticator.kt b/data/src/main/kotlin/us/wedemy/eggeum/android/data/service/TokenAuthenticator.kt index ee0a998b..7ad5f191 100644 --- a/data/src/main/kotlin/us/wedemy/eggeum/android/data/service/TokenAuthenticator.kt +++ b/data/src/main/kotlin/us/wedemy/eggeum/android/data/service/TokenAuthenticator.kt @@ -7,32 +7,32 @@ import okhttp3.Request import okhttp3.Response import okhttp3.Route import timber.log.Timber +import us.wedemy.eggeum.android.data.datasource.token.TokenDataSource import us.wedemy.eggeum.android.data.datastore.TokenDataStoreProvider +import us.wedemy.eggeum.android.data.model.token.TokenRequest import us.wedemy.eggeum.android.domain.util.RefreshTokenExpiredException @Suppress("TooGenericExceptionCaught") public class TokenAuthenticator @Inject constructor( private val dataStoreProvider: TokenDataStoreProvider, + private val tokenDataSource: TokenDataSource, ) : Authenticator { override fun authenticate(route: Route?, response: Response): Request? { Timber.d("authenticate 호출") return runBlocking { - val currentAccessToken = dataStoreProvider.getAccessToken() - - // 현재 액세스 토큰이 요청 헤더의 토큰과 다르면 이미 갱신된 것으로 간주 - if (response.request.header("Authorization") != "Bearer $currentAccessToken") { - Timber.d("RefreshToken is Expired") - // 로그인 토큰 제거(로그아웃) - // dataStoreProvider.clear() - throw RefreshTokenExpiredException - } - try { - val newAccessToken = runBlocking { dataStoreProvider.getRefreshToken() } - newRequestWithAccessToken(response.request, newAccessToken) + val refreshToken = dataStoreProvider.getRefreshToken() + val tokenResponse = tokenDataSource.getRefreshToken(TokenRequest(refreshToken)) + if (tokenResponse != null) { + dataStoreProvider.setAccessToken(tokenResponse.accessToken) + dataStoreProvider.setRefreshToken(tokenResponse.refreshToken) + newRequestWithAccessToken(response.request, tokenResponse.accessToken) + } else { + null + } } catch (e: Exception) { Timber.e("TokenAuthenticator Error :: ${e.message}") - null + throw RefreshTokenExpiredException } } } diff --git a/data/src/main/kotlin/us/wedemy/eggeum/android/data/service/TokenService.kt b/data/src/main/kotlin/us/wedemy/eggeum/android/data/service/TokenService.kt new file mode 100644 index 00000000..0a87c325 --- /dev/null +++ b/data/src/main/kotlin/us/wedemy/eggeum/android/data/service/TokenService.kt @@ -0,0 +1,22 @@ +/* + * Designed and developed by Wedemy 2023. + * + * Licensed under the MIT. + * Please see full license: https://github.com/Wedemy/eggeum-android/blob/main/LICENSE + */ + +package us.wedemy.eggeum.android.data.service + +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.POST +import us.wedemy.eggeum.android.data.model.token.TokenRequest +import us.wedemy.eggeum.android.data.model.token.TokenResponse + +public interface TokenService { + + @POST("app/token/refresh") + public suspend fun getRefreshToken( + @Body tokenRequest: TokenRequest, + ): Response +}