rewrite authentication flow
This commit is contained in:
@@ -7,7 +7,7 @@ plugins {
|
||||
alias(libs.plugins.convention.ktor.client)
|
||||
}
|
||||
|
||||
val appVersion = "1.25.4"
|
||||
val appVersion = "1.25.5"
|
||||
|
||||
android {
|
||||
buildFeatures { buildConfig = true }
|
||||
|
||||
+4
-4
@@ -3,20 +3,20 @@ package ru.shadowsparky.vbox.shared.data
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.get
|
||||
import org.koin.core.annotation.Factory
|
||||
import ru.shadowsparky.vbox.shared.domain.HeathCheck
|
||||
import ru.shadowsparky.vbox.shared.domain.HealthCheck
|
||||
import ru.shadowsparky.vbox.shared.domain.ServerConfigurationRepository
|
||||
import ru.shadowsparky.vbox.shared.domain.getServerConfiguration
|
||||
|
||||
@Factory
|
||||
class HeathCheckImpl(
|
||||
class HealthCheckImpl(
|
||||
private val httpClient: HttpClient,
|
||||
private val serverConfigurationProvider: ServerConfigurationRepository,
|
||||
) : HeathCheck {
|
||||
) : HealthCheck {
|
||||
|
||||
override suspend fun check() {
|
||||
httpClient.get {
|
||||
serverConfigurationProvider.getServerConfiguration()
|
||||
.apply(this, HeathCheck.PATH)
|
||||
.apply(this, HealthCheck.PATH)
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
-5
@@ -9,18 +9,24 @@ import io.ktor.websocket.readText
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import kotlinx.coroutines.channels.SendChannel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.retryWhen
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.koin.core.annotation.Single
|
||||
import ru.shadowsparky.domain.Log
|
||||
import ru.shadowsparky.http.domain.TokenStorage
|
||||
import ru.shadowsparky.vbox.shared.domain.AuthRequest
|
||||
import ru.shadowsparky.vbox.shared.domain.AuthResponse
|
||||
import ru.shadowsparky.vbox.shared.domain.HealthCheck
|
||||
import ru.shadowsparky.vbox.shared.domain.RemoteEvent
|
||||
import ru.shadowsparky.vbox.shared.domain.RemoteEventHandler
|
||||
import ru.shadowsparky.vbox.shared.domain.RemoteEventListener
|
||||
@@ -34,21 +40,22 @@ class RemoteEventListenerImpl(
|
||||
private val httpClient: HttpClient,
|
||||
private val serverConfigurationRepository: ServerConfigurationRepository,
|
||||
private val authTokenCache: TokenStorage,
|
||||
private val healthCheck: HealthCheck,
|
||||
private val json: Json,
|
||||
private val log: Log
|
||||
) : RemoteEventListener {
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
|
||||
override val event = flow {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val event = authTokenCache.token.filterNotNull().transformLatest { token ->
|
||||
val config = serverConfigurationRepository.getServerConfiguration()
|
||||
val token = authTokenCache.token.first() ?: return@flow
|
||||
log.d(TAG, "listener started")
|
||||
try {
|
||||
httpClient.webSocket(
|
||||
method = HttpMethod.Get,
|
||||
request = { url(config.asWebSocketStr() + "/${RemoteEventHandler.ON_EVENT}") }
|
||||
) {
|
||||
outgoing.send(Frame.Text(token.token))
|
||||
authV2(token.token, incoming, outgoing)
|
||||
for (frame in incoming) {
|
||||
if (frame is Frame.Text) {
|
||||
val text = frame.readText()
|
||||
@@ -70,6 +77,20 @@ class RemoteEventListenerImpl(
|
||||
}
|
||||
}.retryExponential { true }.shareIn(scope, SharingStarted.WhileSubscribed(500), 0)
|
||||
|
||||
private suspend fun authV2(
|
||||
token: String,
|
||||
input: ReceiveChannel<Frame>,
|
||||
output: SendChannel<Frame>
|
||||
) {
|
||||
output.send(Frame.Text(json.encodeToString(AuthRequest(token))))
|
||||
val rawFrame = (input.receive() as Frame.Text).readText()
|
||||
val response = json.decodeFromString<AuthResponse>(rawFrame)
|
||||
if (!response.ok) {
|
||||
healthCheck.check()
|
||||
error("Authentication failed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> Flow<T>.retryExponential(
|
||||
maxRetries: Int = Int.MAX_VALUE,
|
||||
initialDelay: Long = 5000L,
|
||||
|
||||
+4
-4
@@ -19,7 +19,7 @@ import ru.shadowsparky.http.domain.TokenStorage
|
||||
import ru.shadowsparky.ui.nav.RootComponent
|
||||
import ru.shadowsparky.updater.presentation.UpdateComponentFactory
|
||||
import ru.shadowsparky.vbox.shared.di.factory.ImageLoaderProvider
|
||||
import ru.shadowsparky.vbox.shared.domain.HeathCheck
|
||||
import ru.shadowsparky.vbox.shared.domain.HealthCheck
|
||||
import ru.shadowsparky.vbox.shared.domain.RemoteEventListener
|
||||
import ru.shadowsparky.vbox.shared.presentation.nav.Route
|
||||
|
||||
@@ -28,7 +28,7 @@ class VBoxRootComponent(
|
||||
componentContext: ComponentContext,
|
||||
tokenStorage: TokenStorage,
|
||||
private val initRoute: Route,
|
||||
private val healthCheck: HeathCheck,
|
||||
private val healthCheck: HealthCheck,
|
||||
val imageLoaderProvider: ImageLoaderProvider,
|
||||
updateComponentFactory: UpdateComponentFactory,
|
||||
private val remoteEventListener: RemoteEventListener,
|
||||
@@ -91,7 +91,7 @@ class VBoxRootComponent(
|
||||
@Factory
|
||||
class RootFactory(
|
||||
private val tokenStorage: TokenStorage,
|
||||
private val heathCheck: HeathCheck,
|
||||
private val healthCheck: HealthCheck,
|
||||
private val imageLoaderProvider: ImageLoaderProvider,
|
||||
private val updateComponentFactory: UpdateComponentFactory,
|
||||
private val remoteEventListener: RemoteEventListener
|
||||
@@ -111,7 +111,7 @@ class RootFactory(
|
||||
context,
|
||||
tokenStorage,
|
||||
Route.Loading(next = initStack.toSet()),
|
||||
heathCheck,
|
||||
healthCheck,
|
||||
imageLoaderProvider,
|
||||
updateComponentFactory,
|
||||
remoteEventListener
|
||||
|
||||
Reference in New Issue
Block a user