Add synchronous refresh token API (#271)

This commit is contained in:
David Perez
2023-11-22 15:52:25 -06:00
committed by Álison Fernandes
parent 8c0c606d72
commit 3a07bbd3da
8 changed files with 200 additions and 3 deletions

View File

@@ -1,6 +1,8 @@
package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
import retrofit2.Call
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.Header
@@ -26,4 +28,16 @@ interface IdentityApi {
@Field(value = "grant_type") grantType: String,
@Field(value = "captchaResponse") captchaResponse: String?,
): Result<GetTokenResponseJson.Success>
/**
* This call needs to be synchronous so we need it to return a [Call] directly. The identity
* service will wrap it up for us.
*/
@POST("/connect/token")
@FormUrlEncoded
fun refreshTokenCall(
@Field(value = "client_id") clientId: String,
@Field(value = "refresh_token") refreshToken: String,
@Field(value = "grant_type") grantType: String,
): Call<RefreshTokenResponseJson>
}

View File

@@ -0,0 +1,27 @@
package com.x8bit.bitwarden.data.auth.datasource.network.model
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* Models the response body from the refresh token request.
*
* @property accessToken The new access token.
* @property expiresIn When the new [accessToken] expires.
* @property refreshToken The new refresh token.
* @property tokenType The type of token the new [accessToken] is.
*/
@Serializable
data class RefreshTokenResponseJson(
@SerialName("access_token")
val accessToken: String,
@SerialName("expires_in")
val expiresIn: Int,
@SerialName("refresh_token")
val refreshToken: String,
@SerialName("token_type")
val tokenType: String,
)

View File

@@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
/**
* Provides an API for querying identity endpoints.
@@ -19,4 +20,11 @@ interface IdentityService {
passwordHash: String,
captchaToken: String?,
): Result<GetTokenResponseJson>
/**
* Synchronously makes a request to get refresh the access token.
*
* @param refreshToken The refresh token needed to obtain a new token.
*/
fun refreshTokenSynchronously(refreshToken: String): Result<RefreshTokenResponseJson>
}

View File

@@ -2,8 +2,10 @@ package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.x8bit.bitwarden.data.auth.datasource.network.api.IdentityApi
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
import com.x8bit.bitwarden.data.platform.datasource.network.util.executeForResult
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
import com.x8bit.bitwarden.data.platform.util.DeviceModelProvider
import kotlinx.serialization.json.Json
@@ -44,4 +46,14 @@ class IdentityServiceImpl constructor(
json = json,
) ?: throw throwable
}
override fun refreshTokenSynchronously(
refreshToken: String,
): Result<RefreshTokenResponseJson> = api
.refreshTokenCall(
clientId = "mobile",
grantType = "refresh_token",
refreshToken = refreshToken,
)
.executeForResult()
}

View File

@@ -1,6 +1,8 @@
package com.x8bit.bitwarden.data.platform.datasource.network.core
import com.x8bit.bitwarden.data.platform.util.asFailure
import okhttp3.Request
import okio.IOException
import okio.Timeout
import retrofit2.Call
import retrofit2.Callback
@@ -48,9 +50,29 @@ class ResultCall<T>(
},
)
override fun execute(): Response<Result<T>> = throw UnsupportedOperationException(
"This call can't be executed synchronously",
)
/**
* Synchronously send the request and return its response as a [Result].
*/
fun executeForResult(): Result<T> = requireNotNull(execute().body())
@Suppress("ReturnCount", "TooGenericExceptionCaught")
override fun execute(): Response<Result<T>> {
val response = try {
backingCall.execute()
} catch (ioException: IOException) {
return success(ioException.asFailure())
} catch (runtimeException: RuntimeException) {
return success(runtimeException.asFailure())
}
return success(
if (!response.isSuccessful) {
HttpException(response).asFailure()
} else {
createResult(response.body())
},
)
}
override fun isCanceled(): Boolean = backingCall.isCanceled

View File

@@ -0,0 +1,21 @@
package com.x8bit.bitwarden.data.platform.datasource.network.util
import com.x8bit.bitwarden.data.platform.datasource.network.core.ResultCall
import retrofit2.Call
/**
* Synchronously executes the [Call] and returns the [Result].
*/
inline fun <reified T : Any> Call<T>.executeForResult(): Result<T> =
this
.toResultCall()
.executeForResult()
/**
* Wraps the existing [Call] in a [ResultCall].
*/
inline fun <reified T : Any> Call<T>.toResultCall(): ResultCall<T> =
ResultCall(
backingCall = this,
successType = T::class.java,
)