JWT Authorization

This commit is contained in:
Koitharu
2022-05-03 17:47:27 +03:00
parent 00e1aac984
commit fbc86f6d3b
5 changed files with 78 additions and 26 deletions

View File

@@ -3,10 +3,13 @@ package org.koitharu.kotatsu.sync
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module import org.koin.dsl.module
import org.koitharu.kotatsu.sync.data.SyncAuthApi
import org.koitharu.kotatsu.sync.ui.SyncAuthViewModel import org.koitharu.kotatsu.sync.ui.SyncAuthViewModel
val syncModule val syncModule
get() = module { get() = module {
viewModel { SyncAuthViewModel(androidContext(), get()) } factory { SyncAuthApi(androidContext(), get()) }
viewModel { SyncAuthViewModel(get()) }
} }

View File

@@ -0,0 +1,38 @@
package org.koitharu.kotatsu.sync.data
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context
import kotlinx.coroutines.runBlocking
import okhttp3.Authenticator
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import org.koitharu.kotatsu.R
class AccountAuthenticator(
context: Context,
private val account: Account,
private val authApi: SyncAuthApi,
) : Authenticator {
private val accountManager = AccountManager.get(context)
private val tokenType = context.getString(R.string.account_type_sync)
override fun authenticate(route: Route?, response: Response): Request? {
val newToken = tryRefreshToken() ?: return null
accountManager.setAuthToken(account, tokenType, newToken)
return response.request.newBuilder()
.header("Authorization", "Bearer $newToken")
.build()
}
private fun tryRefreshToken() = runCatching {
runBlocking {
authApi.authenticate(
account.name,
accountManager.getPassword(account),
)
}
}.getOrNull()
}

View File

@@ -0,0 +1,30 @@
package org.koitharu.kotatsu.sync.data
import android.content.Context
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.parsers.util.parseJson
import org.koitharu.kotatsu.utils.ext.toRequestBody
class SyncAuthApi(
context: Context,
private val okHttpClient: OkHttpClient,
) {
private val baseUrl = context.getString(R.string.url_sync_server)
suspend fun authenticate(email: String, password: String): String {
val body = JSONObject(
mapOf("email" to email, "password" to password)
).toRequestBody()
val request = Request.Builder()
.url("$baseUrl/auth")
.post(body)
.build()
val response = okHttpClient.newCall(request).await().parseJson()
return response.getString("token")
}
}

View File

@@ -18,7 +18,9 @@ import org.koitharu.kotatsu.core.db.MangaDatabase.Companion.TABLE_MANGA_TAGS
import org.koitharu.kotatsu.core.db.MangaDatabase.Companion.TABLE_TAGS import org.koitharu.kotatsu.core.db.MangaDatabase.Companion.TABLE_TAGS
import org.koitharu.kotatsu.parsers.util.json.mapJSONTo import org.koitharu.kotatsu.parsers.util.json.mapJSONTo
import org.koitharu.kotatsu.parsers.util.parseJson import org.koitharu.kotatsu.parsers.util.parseJson
import org.koitharu.kotatsu.sync.data.AccountAuthenticator
import org.koitharu.kotatsu.sync.data.AccountInterceptor import org.koitharu.kotatsu.sync.data.AccountInterceptor
import org.koitharu.kotatsu.sync.data.SyncAuthApi
import org.koitharu.kotatsu.utils.GZipInterceptor import org.koitharu.kotatsu.utils.GZipInterceptor
import org.koitharu.kotatsu.utils.ext.toContentValues import org.koitharu.kotatsu.utils.ext.toContentValues
import org.koitharu.kotatsu.utils.ext.toJson import org.koitharu.kotatsu.utils.ext.toJson
@@ -40,6 +42,7 @@ class SyncHelper(
) { ) {
private val httpClient = OkHttpClient.Builder() private val httpClient = OkHttpClient.Builder()
.authenticator(AccountAuthenticator(context, account, SyncAuthApi(context, OkHttpClient())))
.addInterceptor(AccountInterceptor(context, account)) .addInterceptor(AccountInterceptor(context, account))
.addInterceptor(GZipInterceptor()) .addInterceptor(GZipInterceptor())
.build() .build()

View File

@@ -1,44 +1,22 @@
package org.koitharu.kotatsu.sync.ui package org.koitharu.kotatsu.sync.ui
import android.content.Context
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONObject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.parsers.util.await import org.koitharu.kotatsu.sync.data.SyncAuthApi
import org.koitharu.kotatsu.parsers.util.parseJson
import org.koitharu.kotatsu.sync.domain.SyncAuthResult import org.koitharu.kotatsu.sync.domain.SyncAuthResult
import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.toRequestBody
import java.util.*
class SyncAuthViewModel( class SyncAuthViewModel(
context: Context, private val api: SyncAuthApi,
private val okHttpClient: OkHttpClient,
) : BaseViewModel() { ) : BaseViewModel() {
private val baseUrl = context.getString(R.string.url_sync_server)
val onTokenObtained = SingleLiveEvent<SyncAuthResult>() val onTokenObtained = SingleLiveEvent<SyncAuthResult>()
fun obtainToken(email: String, password: String) { fun obtainToken(email: String, password: String) {
launchLoadingJob(Dispatchers.Default) { launchLoadingJob(Dispatchers.Default) {
authenticate(email, password) val token = api.authenticate(email, password)
val token = UUID.randomUUID().toString()
val result = SyncAuthResult(email, password, token) val result = SyncAuthResult(email, password, token)
onTokenObtained.postCall(result) onTokenObtained.postCall(result)
} }
} }
private suspend fun authenticate(email: String, password: String) {
val body = JSONObject(
mapOf("email" to email, "password" to password)
).toRequestBody()
val request = Request.Builder()
.url("$baseUrl/register")
.post(body)
.build()
val response = okHttpClient.newCall(request).await().parseJson()
}
} }