Задать вопрос
@empty_project

Github API OAuth token validation с помощью Retrofit 2.9.0?

Я реализую в своем приложении проверку действительности токена OAuth. Сначала я прочитал документацию по нужному мне для этого запросу и проверил работу в cmd с помощью cURL:
curl -X POST https://CLIENT_ID:CLIENT_SECRET@api.github.com/applications/CLIENT_ID/token -d {\"access_token\":\"ACCESS_TOKEN\"}

данный запрос работает корректно, сервер отвечает 200 кодом.

Однако тот же запрос, с тем же токеном доступа, только реализованный с помощью retrofit, выдает мне следующую ошибку:
{"message":"Not Found","documentation_url":"https://docs.github.com/rest"}

Но я читал логи от HttpLoggingInterceptor и не увидел там какой-то ошибки, запрос выглядит аналогично тому, что я писал в cmd. Я пробовал делать запрос через cURL с неправильным токеном доступа и в этом случае сервер мне отвечал таким образом:
{
  "message": "Not Found",
  "documentation_url": "https://docs.github.com/rest/reference/apps#check-a-token"
}

Я понимаю, что можно сделать проверку токена на валидность иным способом, просто выполнив какой-нибудь запрос к серверу с хедером "Authorization: token <TOKEN>", но это, как мне кажется, не лучший вариант, т.к. в этом случае уменьшается кол-во доступных запросов в час, да и для проверки токена все-таки есть отдельная конечная точка (обращение к которой не уменьшает кол-во доступных запросов).

Вот какой код у меня получился.

Сущность для POST запроса:
data class AccessToken(
    @SerializedName("access_token")
    val value: String,
)

Интерфейсы GitHub API:
interface GitHubApi {
    companion object {
        const val DEFAULT_REQUEST_LIMIT = 60
        const val AUTHORIZED_USER_REQUEST_LIMIT = 5000
    }
}

interface TokenValidityApi : GitHubApi {

    @POST("applications/$CLIENT_ID_PATH_SEGMENT/token")
    suspend fun checkAccessTokenValidity(
        @Body accessToken: AccessToken,
    ): Response<AccessToken>

    companion object {
        const val CLIENT_ID_PATH_SEGMENT = "{client_id}"
    }
}

API providers:
abstract class GitHubApiProvider {

    abstract val api: GitHubApi

    protected abstract val acceptHeaderValue: String

    protected abstract val baseUrl: String

    protected abstract val clientBuilder: OkHttpClient.Builder

    protected val clientId = ClientId(value = CLIENT_ID)

    protected val clientSecret = ClientSecret(value = CLIENT_SECRET)

    protected val retrofit: Retrofit
        get() {
            val client = clientBuilder
                .addInterceptor(headerInterceptor)
                .addInterceptor(loggingInterceptor)
                .build()

            return Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .client(client)
                .build()
        }

    private val headerInterceptor: Interceptor
        get() = Interceptor { chain ->
            val acceptHeader = Accept(acceptHeaderValue)

            val newRequest = chain.request().newBuilder()
                .addHeader(acceptHeader.name, acceptHeader.value)
                .build()

            return@Interceptor chain.proceed(newRequest)
        }

    private val loggingInterceptor: Interceptor
        get() = HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        }
}

object TokenValidityApiProvider : GitHubApiProvider() {

    override val acceptHeaderValue = "application/vnd.github+json"

    override val baseUrl = "https://api.github.com/"

    override val clientBuilder: OkHttpClient.Builder
        get() = OkHttpClient.Builder()
            .addInterceptor(UrlInterceptor)

    override val api: TokenValidityApi
        get() = retrofit.create(TokenValidityApi::class.java)

    private val UrlInterceptor: Interceptor
        get() = Interceptor { chain ->
            val originalRequest = chain.request()
            val originalUrl = originalRequest.url

            val newUrl = originalUrl.newBuilder()
                .setPathSegment(
                    index = originalUrl.pathSegments.indexOf(TokenValidityApi.CLIENT_ID_PATH_SEGMENT),
                    pathSegment = clientId.value,
                )
                .username(clientId.value)
                .password(clientSecret.value)
                .build()

            val newRequest = originalRequest.newBuilder()
                .url(newUrl)
                .build()

            return@Interceptor chain.proceed(newRequest)
        }
}

Мой метод проверки на наличие и валидность токена:
suspend fun isAccessTokenAvailableAndValid(): Boolean {
    dataStore.getAccessTokenSync()?.let {
        Log.d(TAG, "access_token = $it")
        val accessToken = AccessToken(value = it)
        val response = tokenValidityApi.checkAccessTokenValidity(accessToken)
        return response.isSuccessful
    }
    return false
}

Всякие вспомогательные классы, которые есть в коде выше:
open class Query(
    val name: String,
    val value: String,
) {
    override fun toString(): String {
        return "$name=$value"
    }
}

class ClientId(value: String) : Query("client_id", value)

class ClientSecret(value: String) : Query("client_secret", value)

open class Header(
    val name: String,
    val value: String,
) {
    override fun toString(): String {
        return "$name: $value"
    }
}

class Accept(value: String) : Header("Accept", value)

Может я что-то делаю не так?
  • Вопрос задан
  • 140 просмотров
Подписаться 1 Средний Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы