diff --git a/app/src/main/java/com/lateinit/rightweight/data/api/AuthApiService.kt b/app/src/main/java/com/lateinit/rightweight/data/api/AuthApiService.kt index 2db0e004..924675af 100644 --- a/app/src/main/java/com/lateinit/rightweight/data/api/AuthApiService.kt +++ b/app/src/main/java/com/lateinit/rightweight/data/api/AuthApiService.kt @@ -1,7 +1,9 @@ package com.lateinit.rightweight.data.api +import com.lateinit.rightweight.data.model.remote.DeleteAccountResponse import com.lateinit.rightweight.data.model.remote.LoginRequestBody import com.lateinit.rightweight.data.model.remote.LoginResponse +import retrofit2.Response import retrofit2.http.Body import retrofit2.http.Field import retrofit2.http.FormUrlEncoded @@ -21,5 +23,6 @@ interface AuthApiService { suspend fun deleteAccount( @Query("key") key: String, @Field("idToken") idToken: String - ) -} \ No newline at end of file + ): Response + +} diff --git a/app/src/main/java/com/lateinit/rightweight/data/api/TokenApiService.kt b/app/src/main/java/com/lateinit/rightweight/data/api/TokenApiService.kt new file mode 100644 index 00000000..464bc456 --- /dev/null +++ b/app/src/main/java/com/lateinit/rightweight/data/api/TokenApiService.kt @@ -0,0 +1,17 @@ +package com.lateinit.rightweight.data.api + +import com.lateinit.rightweight.data.model.remote.RefreshTokenResponse +import retrofit2.http.Field +import retrofit2.http.FormUrlEncoded +import retrofit2.http.POST +import retrofit2.http.Query + +interface TokenApiService { + @FormUrlEncoded + @POST("token") + suspend fun refreshIdToken( + @Query("key") key: String, + @Field("refresh_token") refreshToken: String, + @Field("grant_type") grantType: String = "refresh_token" + ): RefreshTokenResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/lateinit/rightweight/data/datasource/remote/LoginDataSource.kt b/app/src/main/java/com/lateinit/rightweight/data/datasource/remote/LoginDataSource.kt index 77f7485f..87767663 100644 --- a/app/src/main/java/com/lateinit/rightweight/data/datasource/remote/LoginDataSource.kt +++ b/app/src/main/java/com/lateinit/rightweight/data/datasource/remote/LoginDataSource.kt @@ -1,10 +1,13 @@ package com.lateinit.rightweight.data.datasource.remote import com.lateinit.rightweight.data.model.remote.LoginResponse +import com.lateinit.rightweight.data.model.remote.RefreshTokenResponse interface LoginDataSource { suspend fun login(key: String, token: String): LoginResponse suspend fun deleteAccount(key: String, idToken: String) + + suspend fun refreshIdToken(key: String, refreshToken: String): RefreshTokenResponse } \ No newline at end of file diff --git a/app/src/main/java/com/lateinit/rightweight/data/datasource/remote/impl/LoginDataSourceImpl.kt b/app/src/main/java/com/lateinit/rightweight/data/datasource/remote/impl/LoginDataSourceImpl.kt index 272f579e..552523f2 100644 --- a/app/src/main/java/com/lateinit/rightweight/data/datasource/remote/impl/LoginDataSourceImpl.kt +++ b/app/src/main/java/com/lateinit/rightweight/data/datasource/remote/impl/LoginDataSourceImpl.kt @@ -1,19 +1,23 @@ package com.lateinit.rightweight.data.datasource.remote.impl import com.lateinit.rightweight.data.api.AuthApiService -import com.lateinit.rightweight.data.model.remote.LoginResponse +import com.lateinit.rightweight.data.api.TokenApiService import com.lateinit.rightweight.data.datasource.remote.LoginDataSource import com.lateinit.rightweight.data.model.remote.LoginRequestBody +import com.lateinit.rightweight.data.model.remote.LoginResponse import com.lateinit.rightweight.data.model.remote.PostBody +import com.lateinit.rightweight.data.model.remote.RefreshTokenResponse +import com.lateinit.rightweight.ui.MainViewModel import javax.inject.Inject class LoginDataSourceImpl @Inject constructor( - val api: AuthApiService + private val authApi: AuthApiService, + private val tokenApi: TokenApiService ) : LoginDataSource { override suspend fun login(key: String, token: String): LoginResponse { val postBody = PostBody(token, "google.com").toString() - return api.loginToFirebase( + return authApi.loginToFirebase( key, LoginRequestBody( postBody, "http://localhost", returnIdpCredential = true, @@ -23,6 +27,14 @@ class LoginDataSourceImpl @Inject constructor( } override suspend fun deleteAccount(key: String, idToken: String) { - api.deleteAccount(key, idToken) + val deleteAccount = authApi.deleteAccount(key, idToken) + if (deleteAccount.isSuccessful.not()) { + throw MainViewModel.IdTokenExpiredException("IdToken expired.") + } } -} \ No newline at end of file + + override suspend fun refreshIdToken(key: String, refreshToken: String): RefreshTokenResponse { + return tokenApi.refreshIdToken(key, refreshToken) + } +} + diff --git a/app/src/main/java/com/lateinit/rightweight/data/model/local/User.kt b/app/src/main/java/com/lateinit/rightweight/data/model/local/User.kt index a15aae94..c6ef1867 100644 --- a/app/src/main/java/com/lateinit/rightweight/data/model/local/User.kt +++ b/app/src/main/java/com/lateinit/rightweight/data/model/local/User.kt @@ -8,5 +8,6 @@ data class User( val email: String, val displayName: String, val photoUrl: String, - val idToken: String + val idToken: String, + val oauthIdToken: String ) diff --git a/app/src/main/java/com/lateinit/rightweight/data/model/remote/DeleteAccountResponse.kt b/app/src/main/java/com/lateinit/rightweight/data/model/remote/DeleteAccountResponse.kt new file mode 100644 index 00000000..80c3348f --- /dev/null +++ b/app/src/main/java/com/lateinit/rightweight/data/model/remote/DeleteAccountResponse.kt @@ -0,0 +1,3 @@ +package com.lateinit.rightweight.data.model.remote + +data class DeleteAccountResponse(val kind: String) diff --git a/app/src/main/java/com/lateinit/rightweight/data/model/remote/RefreshTokenResponse.kt b/app/src/main/java/com/lateinit/rightweight/data/model/remote/RefreshTokenResponse.kt new file mode 100644 index 00000000..b7c1924e --- /dev/null +++ b/app/src/main/java/com/lateinit/rightweight/data/model/remote/RefreshTokenResponse.kt @@ -0,0 +1,10 @@ +package com.lateinit.rightweight.data.model.remote + +import com.google.gson.annotations.SerializedName + +data class RefreshTokenResponse( + @SerializedName("refresh_token") + val refreshToken: String, + @SerializedName("id_token") + val idToken: String +) \ No newline at end of file diff --git a/app/src/main/java/com/lateinit/rightweight/data/repository/LoginRepository.kt b/app/src/main/java/com/lateinit/rightweight/data/repository/LoginRepository.kt index bb3c0662..ad0c99c1 100644 --- a/app/src/main/java/com/lateinit/rightweight/data/repository/LoginRepository.kt +++ b/app/src/main/java/com/lateinit/rightweight/data/repository/LoginRepository.kt @@ -1,10 +1,13 @@ package com.lateinit.rightweight.data.repository import com.lateinit.rightweight.data.model.remote.LoginResponse +import com.lateinit.rightweight.data.model.remote.RefreshTokenResponse interface LoginRepository { suspend fun login(key: String, token: String): LoginResponse suspend fun deleteAccount(key: String, idToken: String) + + suspend fun refreshIdToken(key: String, refreshToken: String): RefreshTokenResponse } \ No newline at end of file diff --git a/app/src/main/java/com/lateinit/rightweight/data/repository/impl/LoginRepositoryImpl.kt b/app/src/main/java/com/lateinit/rightweight/data/repository/impl/LoginRepositoryImpl.kt index 4882d5d9..c60819f2 100644 --- a/app/src/main/java/com/lateinit/rightweight/data/repository/impl/LoginRepositoryImpl.kt +++ b/app/src/main/java/com/lateinit/rightweight/data/repository/impl/LoginRepositoryImpl.kt @@ -1,7 +1,8 @@ package com.lateinit.rightweight.data.repository.impl -import com.lateinit.rightweight.data.model.remote.LoginResponse import com.lateinit.rightweight.data.datasource.remote.LoginDataSource +import com.lateinit.rightweight.data.model.remote.LoginResponse +import com.lateinit.rightweight.data.model.remote.RefreshTokenResponse import com.lateinit.rightweight.data.repository.LoginRepository import javax.inject.Inject @@ -16,4 +17,8 @@ class LoginRepositoryImpl @Inject constructor( override suspend fun deleteAccount(key: String, idToken: String) { loginDataSource.deleteAccount(key, idToken) } + + override suspend fun refreshIdToken(key: String, refreshToken: String): RefreshTokenResponse { + return loginDataSource.refreshIdToken(key, refreshToken) + } } \ No newline at end of file diff --git a/app/src/main/java/com/lateinit/rightweight/di/ApiModule.kt b/app/src/main/java/com/lateinit/rightweight/di/ApiModule.kt index f9f8cc33..9d0cef71 100644 --- a/app/src/main/java/com/lateinit/rightweight/di/ApiModule.kt +++ b/app/src/main/java/com/lateinit/rightweight/di/ApiModule.kt @@ -3,6 +3,7 @@ package com.lateinit.rightweight.di import com.lateinit.rightweight.BuildConfig import com.lateinit.rightweight.data.api.AuthApiService import com.lateinit.rightweight.data.api.RoutineApiService +import com.lateinit.rightweight.data.api.TokenApiService import com.lateinit.rightweight.data.api.UserApiService import dagger.Module import dagger.Provides @@ -39,6 +40,12 @@ class ApiModule { return retrofit.create(UserApiService::class.java) } + @Provides + @Singleton + fun provideTokenApiService(@Named("Token") retrofit: Retrofit): TokenApiService { + return retrofit.create(TokenApiService::class.java) + } + @Provides @Singleton @Named("Auth") @@ -67,6 +74,20 @@ class ApiModule { .build() } + @Provides + @Singleton + @Named("Token") + fun provideTokenRetrofit( + okHttpClient: OkHttpClient, + gsonConverterFactory: GsonConverterFactory + ): Retrofit { + return Retrofit.Builder() + .baseUrl("https://securetoken.googleapis.com/v1/") + .addConverterFactory(gsonConverterFactory) + .client(okHttpClient) + .build() + } + @Provides @Singleton fun provideGsonConvertFactory(): GsonConverterFactory { diff --git a/app/src/main/java/com/lateinit/rightweight/di/DataSourceModule.kt b/app/src/main/java/com/lateinit/rightweight/di/DataSourceModule.kt index 27217eb7..5aa505e1 100644 --- a/app/src/main/java/com/lateinit/rightweight/di/DataSourceModule.kt +++ b/app/src/main/java/com/lateinit/rightweight/di/DataSourceModule.kt @@ -2,6 +2,7 @@ package com.lateinit.rightweight.di import com.lateinit.rightweight.data.api.AuthApiService import com.lateinit.rightweight.data.api.RoutineApiService +import com.lateinit.rightweight.data.api.TokenApiService import com.lateinit.rightweight.data.api.UserApiService import com.lateinit.rightweight.data.dataStore.AppPreferencesDataStore import com.lateinit.rightweight.data.database.AppDatabase @@ -40,9 +41,10 @@ class DataSourceModule { @Provides @Singleton fun provideLoginDataSource( - api: AuthApiService + authApi: AuthApiService, + tokenApi: TokenApiService ): LoginDataSource { - return LoginDataSourceImpl(api) + return LoginDataSourceImpl(authApi, tokenApi) } @Provides diff --git a/app/src/main/java/com/lateinit/rightweight/ui/MainActivity.kt b/app/src/main/java/com/lateinit/rightweight/ui/MainActivity.kt index c4eccb36..4aef80c3 100644 --- a/app/src/main/java/com/lateinit/rightweight/ui/MainActivity.kt +++ b/app/src/main/java/com/lateinit/rightweight/ui/MainActivity.kt @@ -211,13 +211,23 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte viewModel.deleteAccount(getString(R.string.google_api_key)) collectOnLifecycle { viewModel.networkState.collect { - if (it == NetworkState.SUCCESS) { - logout() - } else { - Snackbar.make(binding.root, R.string.wrong_connection, Snackbar.LENGTH_LONG) - .apply { - anchorView = binding.bottomNavigation - }.show() + when (it) { + NetworkState.SUCCESS -> { + logout() + } + NetworkState.TOKEN_EXPIRED -> { + viewModel.refreshIdToken(getString(R.string.google_api_key)) + Snackbar.make(binding.root, R.string.retry_message, Snackbar.LENGTH_LONG) + .apply { + anchorView = binding.bottomNavigation + }.show() + } + else -> { + Snackbar.make(binding.root, R.string.wrong_connection, Snackbar.LENGTH_LONG) + .apply { + anchorView = binding.bottomNavigation + }.show() + } } } } diff --git a/app/src/main/java/com/lateinit/rightweight/ui/MainViewModel.kt b/app/src/main/java/com/lateinit/rightweight/ui/MainViewModel.kt index 248afb75..181a0a28 100644 --- a/app/src/main/java/com/lateinit/rightweight/ui/MainViewModel.kt +++ b/app/src/main/java/com/lateinit/rightweight/ui/MainViewModel.kt @@ -65,6 +65,7 @@ class MainViewModel @Inject constructor( is SocketException -> sendNetworkResultEvent(NetworkState.BAD_INTERNET) is HttpException -> sendNetworkResultEvent(NetworkState.PARSE_ERROR) is UnknownHostException -> sendNetworkResultEvent(NetworkState.WRONG_CONNECTION) + is IdTokenExpiredException -> sendNetworkResultEvent(NetworkState.TOKEN_EXPIRED) else -> sendNetworkResultEvent(NetworkState.OTHER_ERROR) } } @@ -80,9 +81,9 @@ class MainViewModel @Inject constructor( } fun deleteAccount(key: String) { - val userInfo = userInfo.value?.idToken ?: return + val idToken = userInfo.value?.idToken ?: return viewModelScope.launch(networkExceptionHandler) { - loginRepository.deleteAccount(key, userInfo) + loginRepository.deleteAccount(key, idToken) sendNetworkResultEvent(NetworkState.SUCCESS) } } @@ -94,6 +95,20 @@ class MainViewModel @Inject constructor( } } + fun refreshIdToken(key: String) { + val user = userInfo.value ?: return + + viewModelScope.launch { + val loginResponse = loginRepository.login(key, user.oauthIdToken) + userRepository.saveUser( + user.copy( + idToken = loginResponse.idToken, + oauthIdToken = loginResponse.oauthIdToken + ) + ) + } + } + fun backup() { viewModelScope.launch(networkExceptionHandler) { _loadingState.emit(LoadingState.BACKUP) @@ -321,4 +336,5 @@ class MainViewModel @Inject constructor( } } + class IdTokenExpiredException(message: String) : Exception(message) } \ No newline at end of file diff --git a/app/src/main/java/com/lateinit/rightweight/ui/login/LoginActivity.kt b/app/src/main/java/com/lateinit/rightweight/ui/login/LoginActivity.kt index 967c5b73..dd80cc55 100644 --- a/app/src/main/java/com/lateinit/rightweight/ui/login/LoginActivity.kt +++ b/app/src/main/java/com/lateinit/rightweight/ui/login/LoginActivity.kt @@ -129,6 +129,7 @@ class LoginActivity : AppCompatActivity() { NetworkState.SUCCESS -> { moveToHomeActivity(false) } + NetworkState.TOKEN_EXPIRED -> {} } } } diff --git a/app/src/main/java/com/lateinit/rightweight/ui/login/LoginViewModel.kt b/app/src/main/java/com/lateinit/rightweight/ui/login/LoginViewModel.kt index 8617bb5e..7eaa71fd 100644 --- a/app/src/main/java/com/lateinit/rightweight/ui/login/LoginViewModel.kt +++ b/app/src/main/java/com/lateinit/rightweight/ui/login/LoginViewModel.kt @@ -44,12 +44,22 @@ class LoginViewModel @Inject constructor( private suspend fun saveUser(loginResponse: LoginResponse) { with(loginResponse) { userRepository.saveUser( - User(localId, "", "", "", email, displayName, photoUrl, idToken) + User( + localId, + "", + "", + "", + email, + displayName, + photoUrl, + idToken, + oauthIdToken + ) ) } } } enum class NetworkState { - NO_ERROR, BAD_INTERNET, PARSE_ERROR, WRONG_CONNECTION, OTHER_ERROR, SUCCESS + NO_ERROR, BAD_INTERNET, PARSE_ERROR, WRONG_CONNECTION, OTHER_ERROR, SUCCESS, TOKEN_EXPIRED } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 358d5ffd..a71ae144 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -90,4 +90,5 @@ 데이터를 복원하는 중 입니다. 데이터를 백업하는 중 입니다. 데이터를 가져오는 중 입니다 + 다시 시도해주세요. \ No newline at end of file