mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-10 06:42:05 +01:00
Initial auth support(ws currently broken, bad login dialog)
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
object Config {
|
object Config {
|
||||||
const val migrationCode = 5
|
const val migrationCode = 6
|
||||||
|
|
||||||
// Suwayomi-Server version
|
// Suwayomi-Server version
|
||||||
const val tachideskVersion = "v2.1.1959"
|
const val tachideskVersion = "v2.1.1959"
|
||||||
|
|||||||
12
data/src/commonMain/graphql/User.graphql
Normal file
12
data/src/commonMain/graphql/User.graphql
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
mutation Login( $username: String!, $password: String!) {
|
||||||
|
login(input: {username: $username, password: $password}) {
|
||||||
|
refreshToken
|
||||||
|
accessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation RefreshAccessToken($refreshToken: String!) {
|
||||||
|
refreshToken(input: {refreshToken: $refreshToken}) {
|
||||||
|
accessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import ca.gosyer.jui.data.manga.MangaRepositoryImpl
|
|||||||
import ca.gosyer.jui.data.settings.SettingsRepositoryImpl
|
import ca.gosyer.jui.data.settings.SettingsRepositoryImpl
|
||||||
import ca.gosyer.jui.data.source.SourceRepositoryImpl
|
import ca.gosyer.jui.data.source.SourceRepositoryImpl
|
||||||
import ca.gosyer.jui.data.updates.UpdatesRepositoryImpl
|
import ca.gosyer.jui.data.updates.UpdatesRepositoryImpl
|
||||||
|
import ca.gosyer.jui.data.user.UserRepositoryImpl
|
||||||
import ca.gosyer.jui.domain.backup.service.BackupRepository
|
import ca.gosyer.jui.domain.backup.service.BackupRepository
|
||||||
import ca.gosyer.jui.domain.category.service.CategoryRepository
|
import ca.gosyer.jui.domain.category.service.CategoryRepository
|
||||||
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
|
import ca.gosyer.jui.domain.chapter.service.ChapterRepository
|
||||||
@@ -27,12 +28,13 @@ import ca.gosyer.jui.domain.global.service.GlobalRepository
|
|||||||
import ca.gosyer.jui.domain.library.service.LibraryRepository
|
import ca.gosyer.jui.domain.library.service.LibraryRepository
|
||||||
import ca.gosyer.jui.domain.manga.service.MangaRepository
|
import ca.gosyer.jui.domain.manga.service.MangaRepository
|
||||||
import ca.gosyer.jui.domain.server.Http
|
import ca.gosyer.jui.domain.server.Http
|
||||||
|
import ca.gosyer.jui.domain.server.HttpNoAuth
|
||||||
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
||||||
import ca.gosyer.jui.domain.settings.service.SettingsRepository
|
import ca.gosyer.jui.domain.settings.service.SettingsRepository
|
||||||
import ca.gosyer.jui.domain.source.service.SourceRepository
|
import ca.gosyer.jui.domain.source.service.SourceRepository
|
||||||
import ca.gosyer.jui.domain.updates.service.UpdatesRepository
|
import ca.gosyer.jui.domain.updates.service.UpdatesRepository
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserRepository
|
||||||
import com.apollographql.apollo.ApolloClient
|
import com.apollographql.apollo.ApolloClient
|
||||||
import com.apollographql.apollo.annotations.ApolloExperimental
|
|
||||||
import com.apollographql.apollo.network.ws.GraphQLWsProtocol
|
import com.apollographql.apollo.network.ws.GraphQLWsProtocol
|
||||||
import com.apollographql.ktor.ktorClient
|
import com.apollographql.ktor.ktorClient
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
@@ -50,6 +52,8 @@ import me.tatarka.inject.annotations.Provides
|
|||||||
|
|
||||||
typealias ApolloAppClient = StateFlow<ApolloClient>
|
typealias ApolloAppClient = StateFlow<ApolloClient>
|
||||||
|
|
||||||
|
typealias ApolloAppClientNoAuth = StateFlow<ApolloClient>
|
||||||
|
|
||||||
private fun getApolloClient(
|
private fun getApolloClient(
|
||||||
httpClient: HttpClient,
|
httpClient: HttpClient,
|
||||||
serverUrl: Url,
|
serverUrl: Url,
|
||||||
@@ -66,7 +70,7 @@ private fun getApolloClient(
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface DataComponent : SharedDataComponent {
|
interface DataComponent : SharedDataComponent {
|
||||||
@OptIn(ApolloExperimental::class)
|
|
||||||
@Provides
|
@Provides
|
||||||
@AppScope
|
@AppScope
|
||||||
fun apolloAppClient(
|
fun apolloAppClient(
|
||||||
@@ -81,6 +85,20 @@ interface DataComponent : SharedDataComponent {
|
|||||||
getApolloClient(http.value, serverPreferences.serverUrl().get()),
|
getApolloClient(http.value, serverPreferences.serverUrl().get()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@AppScope
|
||||||
|
fun apolloAppClientNoAuth(
|
||||||
|
httpNoAuth: HttpNoAuth,
|
||||||
|
serverPreferences: ServerPreferences,
|
||||||
|
): ApolloAppClientNoAuth =
|
||||||
|
httpNoAuth
|
||||||
|
.map { getApolloClient(it, serverPreferences.serverUrl().get()) }
|
||||||
|
.stateIn(
|
||||||
|
GlobalScope,
|
||||||
|
SharingStarted.Eagerly,
|
||||||
|
getApolloClient(httpNoAuth.value, serverPreferences.serverUrl().get()),
|
||||||
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun settingsRepository(apolloAppClient: ApolloAppClient): SettingsRepository = SettingsRepositoryImpl(apolloAppClient)
|
fun settingsRepository(apolloAppClient: ApolloAppClient): SettingsRepository = SettingsRepositoryImpl(apolloAppClient)
|
||||||
|
|
||||||
@@ -147,6 +165,15 @@ interface DataComponent : SharedDataComponent {
|
|||||||
serverPreferences: ServerPreferences,
|
serverPreferences: ServerPreferences,
|
||||||
): UpdatesRepository = UpdatesRepositoryImpl(apolloAppClient, http, serverPreferences.serverUrl().get())
|
): UpdatesRepository = UpdatesRepositoryImpl(apolloAppClient, http, serverPreferences.serverUrl().get())
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun userRepository(
|
||||||
|
apolloAppClient: ApolloAppClient,
|
||||||
|
apolloAppClientNoAuth: ApolloAppClientNoAuth,
|
||||||
|
http: Http,
|
||||||
|
httpNoAuth: HttpNoAuth,
|
||||||
|
serverPreferences: ServerPreferences,
|
||||||
|
): UserRepository = UserRepositoryImpl(apolloAppClient, apolloAppClientNoAuth, http, httpNoAuth,serverPreferences.serverUrl().get())
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun backupRepository(
|
fun backupRepository(
|
||||||
apolloAppClient: ApolloAppClient,
|
apolloAppClient: ApolloAppClient,
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ca.gosyer.jui.data.user
|
||||||
|
|
||||||
|
import ca.gosyer.jui.data.ApolloAppClient
|
||||||
|
import ca.gosyer.jui.data.ApolloAppClientNoAuth
|
||||||
|
import ca.gosyer.jui.data.graphql.LoginMutation
|
||||||
|
import ca.gosyer.jui.data.graphql.RefreshAccessTokenMutation
|
||||||
|
import ca.gosyer.jui.domain.server.Http
|
||||||
|
import ca.gosyer.jui.domain.server.HttpNoAuth
|
||||||
|
import ca.gosyer.jui.domain.user.model.LoginData
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserRepository
|
||||||
|
import com.apollographql.apollo.ApolloClient
|
||||||
|
import io.ktor.client.request.forms.formData
|
||||||
|
import io.ktor.client.request.post
|
||||||
|
import io.ktor.http.Url
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
|
class UserRepositoryImpl(
|
||||||
|
private val apolloAppClient: ApolloAppClient,
|
||||||
|
private val apolloAppClientNoAuth: ApolloAppClientNoAuth,
|
||||||
|
private val http: Http,
|
||||||
|
private val httpNoAuth: HttpNoAuth,
|
||||||
|
private val serverUrl: Url,
|
||||||
|
) : UserRepository {
|
||||||
|
val apolloClient: ApolloClient
|
||||||
|
get() = apolloAppClient.value
|
||||||
|
|
||||||
|
val apolloClientNoAuth: ApolloClient
|
||||||
|
get() = apolloAppClientNoAuth.value
|
||||||
|
|
||||||
|
override fun loginUI(username: String, password: String): Flow<LoginData> {
|
||||||
|
return apolloClientNoAuth.mutation(
|
||||||
|
LoginMutation(username, password),
|
||||||
|
)
|
||||||
|
.toFlow()
|
||||||
|
.map {
|
||||||
|
val data = it.dataAssertNoErrors.login
|
||||||
|
LoginData(data.refreshToken, data.accessToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun refreshUI(refreshToken: String): Flow<String> {
|
||||||
|
return apolloClientNoAuth.mutation(
|
||||||
|
RefreshAccessTokenMutation(refreshToken),
|
||||||
|
)
|
||||||
|
.toFlow()
|
||||||
|
.map {
|
||||||
|
val data = it.dataAssertNoErrors.refreshToken
|
||||||
|
data.accessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loginSimple(username: String, password: String): Flow<String> {
|
||||||
|
return flow {
|
||||||
|
val cookie = httpNoAuth.value.post(
|
||||||
|
"/login.html",
|
||||||
|
) {
|
||||||
|
formData {
|
||||||
|
append("user", username)
|
||||||
|
append("pass", password)
|
||||||
|
}
|
||||||
|
}.headers["Set-Cookie"]
|
||||||
|
|
||||||
|
cookie ?: throw NullPointerException("Missing Set-Cookie header")
|
||||||
|
|
||||||
|
emit(cookie.substringAfter("JSESSIONID=").substringBefore(";"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ abstract class AppComponent(
|
|||||||
@get:AppScope
|
@get:AppScope
|
||||||
@get:Provides
|
@get:Provides
|
||||||
protected val appMigrationsFactory: AppMigrations
|
protected val appMigrationsFactory: AppMigrations
|
||||||
get() = AppMigrations(migrationPreferences, contextWrapper)
|
get() = AppMigrations(migrationPreferences, serverHostPreferences, contextWrapper)
|
||||||
|
|
||||||
val bind: ViewModelComponent
|
val bind: ViewModelComponent
|
||||||
@Provides get() = this
|
@Provides get() = this
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ package ca.gosyer.jui.desktop
|
|||||||
import ca.gosyer.appdirs.AppDirs
|
import ca.gosyer.appdirs.AppDirs
|
||||||
import ca.gosyer.jui.desktop.build.BuildConfig
|
import ca.gosyer.jui.desktop.build.BuildConfig
|
||||||
import ca.gosyer.jui.domain.migration.service.MigrationPreferences
|
import ca.gosyer.jui.domain.migration.service.MigrationPreferences
|
||||||
|
import ca.gosyer.jui.domain.server.service.ServerHostPreferences
|
||||||
|
import ca.gosyer.jui.domain.settings.model.AuthMode
|
||||||
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||||
import com.diamondedge.logging.logging
|
import com.diamondedge.logging.logging
|
||||||
import me.tatarka.inject.annotations.Inject
|
import me.tatarka.inject.annotations.Inject
|
||||||
@@ -18,6 +20,7 @@ import okio.Path.Companion.toPath
|
|||||||
@Inject
|
@Inject
|
||||||
class AppMigrations(
|
class AppMigrations(
|
||||||
private val migrationPreferences: MigrationPreferences,
|
private val migrationPreferences: MigrationPreferences,
|
||||||
|
private val serverHostPreference: ServerHostPreferences,
|
||||||
private val contextWrapper: ContextWrapper,
|
private val contextWrapper: ContextWrapper,
|
||||||
) {
|
) {
|
||||||
@Suppress("KotlinConstantConditions")
|
@Suppress("KotlinConstantConditions")
|
||||||
@@ -32,8 +35,8 @@ class AppMigrations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < 5) {
|
if (oldVersion < 5) {
|
||||||
val oldDir = AppDirs("Tachidesk-JUI").getUserDataDir().toPath()
|
val oldDir = AppDirs { appName = "Tachidesk-JUI" }.getUserDataDir().toPath()
|
||||||
val newDir = AppDirs("Suwayomi-JUI").getUserDataDir().toPath()
|
val newDir = AppDirs { appName = "Suwayomi-JUI" }.getUserDataDir().toPath()
|
||||||
try {
|
try {
|
||||||
FileSystem.SYSTEM.list(oldDir)
|
FileSystem.SYSTEM.list(oldDir)
|
||||||
.filter { FileSystem.SYSTEM.metadata(it).isDirectory }
|
.filter { FileSystem.SYSTEM.metadata(it).isDirectory }
|
||||||
@@ -48,6 +51,14 @@ class AppMigrations(
|
|||||||
log.e(e) { "Failed to run directory migration" }
|
log.e(e) { "Failed to run directory migration" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 6) {
|
||||||
|
val basicAuthEnabled = serverHostPreference.basicAuthEnabled().get()
|
||||||
|
if (basicAuthEnabled) {
|
||||||
|
serverHostPreference.authMode().set(AuthMode.BASIC_AUTH)
|
||||||
|
}
|
||||||
|
serverHostPreference.authUsername().set(serverHostPreference.basicAuthUsername().get())
|
||||||
|
serverHostPreference.authPassword().set(serverHostPreference.basicAuthPassword().get())
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,13 +16,17 @@ import ca.gosyer.jui.domain.migration.interactor.RunMigrations
|
|||||||
import ca.gosyer.jui.domain.migration.service.MigrationPreferences
|
import ca.gosyer.jui.domain.migration.service.MigrationPreferences
|
||||||
import ca.gosyer.jui.domain.reader.service.ReaderPreferences
|
import ca.gosyer.jui.domain.reader.service.ReaderPreferences
|
||||||
import ca.gosyer.jui.domain.server.Http
|
import ca.gosyer.jui.domain.server.Http
|
||||||
|
import ca.gosyer.jui.domain.server.HttpNoAuth
|
||||||
import ca.gosyer.jui.domain.server.httpClient
|
import ca.gosyer.jui.domain.server.httpClient
|
||||||
|
import ca.gosyer.jui.domain.server.httpClientNoAuth
|
||||||
import ca.gosyer.jui.domain.server.service.ServerHostPreferences
|
import ca.gosyer.jui.domain.server.service.ServerHostPreferences
|
||||||
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
||||||
import ca.gosyer.jui.domain.source.service.CatalogPreferences
|
import ca.gosyer.jui.domain.source.service.CatalogPreferences
|
||||||
import ca.gosyer.jui.domain.ui.service.UiPreferences
|
import ca.gosyer.jui.domain.ui.service.UiPreferences
|
||||||
import ca.gosyer.jui.domain.updates.interactor.UpdateChecker
|
import ca.gosyer.jui.domain.updates.interactor.UpdateChecker
|
||||||
import ca.gosyer.jui.domain.updates.service.UpdatePreferences
|
import ca.gosyer.jui.domain.updates.service.UpdatePreferences
|
||||||
|
import ca.gosyer.jui.domain.user.interactor.UserRefreshUI
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserPreferences
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import me.tatarka.inject.annotations.Provides
|
import me.tatarka.inject.annotations.Provides
|
||||||
|
|
||||||
@@ -39,6 +43,8 @@ interface SharedDomainComponent : CoreComponent {
|
|||||||
|
|
||||||
val http: Http
|
val http: Http
|
||||||
|
|
||||||
|
val httpNoAuth: HttpNoAuth
|
||||||
|
|
||||||
val serverPreferences: ServerPreferences
|
val serverPreferences: ServerPreferences
|
||||||
|
|
||||||
val extensionPreferences: ExtensionPreferences
|
val extensionPreferences: ExtensionPreferences
|
||||||
@@ -59,12 +65,30 @@ interface SharedDomainComponent : CoreComponent {
|
|||||||
|
|
||||||
val json: Json
|
val json: Json
|
||||||
|
|
||||||
|
val userRefreshUI: UserRefreshUI
|
||||||
|
|
||||||
|
@get:AppScope
|
||||||
|
@get:Provides
|
||||||
|
val lazyUserRefreshUIFactory: Lazy<UserRefreshUI>
|
||||||
|
get() = lazy { userRefreshUI }
|
||||||
|
|
||||||
@AppScope
|
@AppScope
|
||||||
@Provides
|
@Provides
|
||||||
fun httpFactory(
|
fun httpFactory(
|
||||||
serverPreferences: ServerPreferences,
|
serverPreferences: ServerPreferences,
|
||||||
|
userPreferences: UserPreferences,
|
||||||
|
userRefreshUI: Lazy<UserRefreshUI>,
|
||||||
json: Json,
|
json: Json,
|
||||||
) = httpClient(serverPreferences, json)
|
): Http = httpClient(serverPreferences, userPreferences, userRefreshUI, json)
|
||||||
|
|
||||||
|
@AppScope
|
||||||
|
@Provides
|
||||||
|
fun httpNoAuthFactory(
|
||||||
|
serverPreferences: ServerPreferences,
|
||||||
|
userPreferences: UserPreferences,
|
||||||
|
userRefreshUI: Lazy<UserRefreshUI>,
|
||||||
|
json: Json,
|
||||||
|
): HttpNoAuth = httpClientNoAuth(serverPreferences, userPreferences, userRefreshUI, json)
|
||||||
|
|
||||||
@get:AppScope
|
@get:AppScope
|
||||||
@get:Provides
|
@get:Provides
|
||||||
@@ -108,6 +132,11 @@ interface SharedDomainComponent : CoreComponent {
|
|||||||
val updatePreferencesFactory: UpdatePreferences
|
val updatePreferencesFactory: UpdatePreferences
|
||||||
get() = UpdatePreferences(preferenceFactory.create("update"))
|
get() = UpdatePreferences(preferenceFactory.create("update"))
|
||||||
|
|
||||||
|
@get:AppScope
|
||||||
|
@get:Provides
|
||||||
|
val userPreferencesFactory: UserPreferences
|
||||||
|
get() = UserPreferences(preferenceFactory.create("user"))
|
||||||
|
|
||||||
@get:AppScope
|
@get:AppScope
|
||||||
@get:Provides
|
@get:Provides
|
||||||
val serverHostPreferencesFactory: ServerHostPreferences
|
val serverHostPreferencesFactory: ServerHostPreferences
|
||||||
|
|||||||
@@ -6,10 +6,13 @@
|
|||||||
|
|
||||||
package ca.gosyer.jui.domain.server
|
package ca.gosyer.jui.domain.server
|
||||||
|
|
||||||
|
import ca.gosyer.jui.core.prefs.Preference
|
||||||
import ca.gosyer.jui.domain.build.BuildKonfig
|
import ca.gosyer.jui.domain.build.BuildKonfig
|
||||||
import ca.gosyer.jui.domain.server.model.Auth
|
import ca.gosyer.jui.domain.server.model.Auth
|
||||||
import ca.gosyer.jui.domain.server.model.Proxy
|
import ca.gosyer.jui.domain.server.model.Proxy
|
||||||
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
||||||
|
import ca.gosyer.jui.domain.user.interactor.UserRefreshUI
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserPreferences
|
||||||
import com.diamondedge.logging.logging
|
import com.diamondedge.logging.logging
|
||||||
import io.ktor.client.HttpClient
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.HttpClientConfig
|
import io.ktor.client.HttpClientConfig
|
||||||
@@ -17,6 +20,8 @@ import io.ktor.client.engine.HttpClientEngineConfig
|
|||||||
import io.ktor.client.engine.HttpClientEngineFactory
|
import io.ktor.client.engine.HttpClientEngineFactory
|
||||||
import io.ktor.client.engine.ProxyBuilder
|
import io.ktor.client.engine.ProxyBuilder
|
||||||
import io.ktor.client.plugins.HttpTimeout
|
import io.ktor.client.plugins.HttpTimeout
|
||||||
|
import io.ktor.client.plugins.api.Send
|
||||||
|
import io.ktor.client.plugins.api.createClientPlugin
|
||||||
import io.ktor.client.plugins.auth.providers.BasicAuthCredentials
|
import io.ktor.client.plugins.auth.providers.BasicAuthCredentials
|
||||||
import io.ktor.client.plugins.auth.providers.DigestAuthCredentials
|
import io.ktor.client.plugins.auth.providers.DigestAuthCredentials
|
||||||
import io.ktor.client.plugins.auth.providers.basic
|
import io.ktor.client.plugins.auth.providers.basic
|
||||||
@@ -27,6 +32,7 @@ import io.ktor.client.plugins.logging.LogLevel
|
|||||||
import io.ktor.client.plugins.logging.Logger
|
import io.ktor.client.plugins.logging.Logger
|
||||||
import io.ktor.client.plugins.logging.Logging
|
import io.ktor.client.plugins.logging.Logging
|
||||||
import io.ktor.client.plugins.websocket.WebSockets
|
import io.ktor.client.plugins.websocket.WebSockets
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.http.URLBuilder
|
import io.ktor.http.URLBuilder
|
||||||
import io.ktor.http.URLProtocol
|
import io.ktor.http.URLProtocol
|
||||||
import io.ktor.http.Url
|
import io.ktor.http.Url
|
||||||
@@ -44,10 +50,65 @@ import io.ktor.client.plugins.auth.Auth as AuthPlugin
|
|||||||
|
|
||||||
typealias Http = StateFlow<HttpClient>
|
typealias Http = StateFlow<HttpClient>
|
||||||
|
|
||||||
|
typealias HttpNoAuth = StateFlow<HttpClient>
|
||||||
|
|
||||||
expect val Engine: HttpClientEngineFactory<HttpClientEngineConfig>
|
expect val Engine: HttpClientEngineFactory<HttpClientEngineConfig>
|
||||||
|
|
||||||
expect fun HttpClientConfig<HttpClientEngineConfig>.configurePlatform()
|
expect fun HttpClientConfig<HttpClientEngineConfig>.configurePlatform()
|
||||||
|
|
||||||
|
private val log = logging()
|
||||||
|
|
||||||
|
private class SimpleAuthPluginConfig {
|
||||||
|
var simpleSessionPreference: Preference<String>? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private val SimpleAuthPlugin = createClientPlugin("SimpleAuthPlugin", ::SimpleAuthPluginConfig) {
|
||||||
|
val simpleSessionPreference = pluginConfig.simpleSessionPreference!!
|
||||||
|
|
||||||
|
on(Send) { request ->
|
||||||
|
val session = simpleSessionPreference.get()
|
||||||
|
if (session.isNotEmpty()) {
|
||||||
|
request.headers.append("Cookie", "JSESSIONID=$session")
|
||||||
|
}
|
||||||
|
proceed(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UIAuthPluginConfig {
|
||||||
|
var uiAccessTokenPreference: Preference<String>? = null
|
||||||
|
var userRefreshUI: Lazy<UserRefreshUI>? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private val UIAuthPlugin = createClientPlugin("UIAuthPlugin", ::UIAuthPluginConfig) {
|
||||||
|
val uiAccessTokenPreference = pluginConfig.uiAccessTokenPreference!!
|
||||||
|
val userRefreshUI = pluginConfig.userRefreshUI!!
|
||||||
|
|
||||||
|
on(Send) { request ->
|
||||||
|
val token = uiAccessTokenPreference.get()
|
||||||
|
if (token.isNotEmpty()) {
|
||||||
|
request.headers.append("Authorization", "Bearer $token")
|
||||||
|
val originalCall = proceed(request)
|
||||||
|
if (originalCall.response.status == HttpStatusCode.Unauthorized) {
|
||||||
|
log.warn { "Token expired, refreshing..." }
|
||||||
|
val newToken = userRefreshUI.value.await()
|
||||||
|
if (newToken != null) {
|
||||||
|
request.headers.remove("Authorization")
|
||||||
|
request.headers.append("Authorization", "Bearer $newToken")
|
||||||
|
proceed(request)
|
||||||
|
} else {
|
||||||
|
originalCall
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
originalCall
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proceed(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun getHttpClient(
|
private fun getHttpClient(
|
||||||
serverUrl: Url,
|
serverUrl: Url,
|
||||||
proxy: Proxy,
|
proxy: Proxy,
|
||||||
@@ -58,6 +119,9 @@ private fun getHttpClient(
|
|||||||
auth: Auth,
|
auth: Auth,
|
||||||
authUsername: String,
|
authUsername: String,
|
||||||
authPassword: String,
|
authPassword: String,
|
||||||
|
uiAccessTokenPreference: Preference<String>,
|
||||||
|
simpleSessionPreference: Preference<String>,
|
||||||
|
userRefreshUI: Lazy<UserRefreshUI>,
|
||||||
json: Json
|
json: Json
|
||||||
): HttpClient {
|
): HttpClient {
|
||||||
return HttpClient(Engine) {
|
return HttpClient(Engine) {
|
||||||
@@ -113,6 +177,13 @@ private fun getHttpClient(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Auth.SIMPLE -> install(SimpleAuthPlugin) {
|
||||||
|
this.simpleSessionPreference = simpleSessionPreference
|
||||||
|
}
|
||||||
|
Auth.UI -> install(UIAuthPlugin) {
|
||||||
|
this.uiAccessTokenPreference = uiAccessTokenPreference
|
||||||
|
this.userRefreshUI = userRefreshUI
|
||||||
|
}
|
||||||
}
|
}
|
||||||
install(HttpTimeout) {
|
install(HttpTimeout) {
|
||||||
connectTimeoutMillis = 30.seconds.inWholeMilliseconds
|
connectTimeoutMillis = 30.seconds.inWholeMilliseconds
|
||||||
@@ -143,6 +214,8 @@ private fun getHttpClient(
|
|||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
fun httpClient(
|
fun httpClient(
|
||||||
serverPreferences: ServerPreferences,
|
serverPreferences: ServerPreferences,
|
||||||
|
userPreferences: UserPreferences,
|
||||||
|
userRefreshUI: Lazy<UserRefreshUI>,
|
||||||
json: Json,
|
json: Json,
|
||||||
): Http = combine(
|
): Http = combine(
|
||||||
serverPreferences.serverUrl().stateIn(GlobalScope),
|
serverPreferences.serverUrl().stateIn(GlobalScope),
|
||||||
@@ -156,30 +229,85 @@ fun httpClient(
|
|||||||
serverPreferences.authPassword().stateIn(GlobalScope),
|
serverPreferences.authPassword().stateIn(GlobalScope),
|
||||||
) {
|
) {
|
||||||
getHttpClient(
|
getHttpClient(
|
||||||
it[0] as Url,
|
serverUrl = it[0] as Url,
|
||||||
it[1] as Proxy,
|
proxy = it[1] as Proxy,
|
||||||
it[2] as String,
|
proxyHttpHost = it[2] as String,
|
||||||
it[3] as Int,
|
proxyHttpPort = it[3] as Int,
|
||||||
it[4] as String,
|
proxySocksHost = it[4] as String,
|
||||||
it[5] as Int,
|
proxySocksPort = it[5] as Int,
|
||||||
it[6] as Auth,
|
auth = it[6] as Auth,
|
||||||
it[7] as String,
|
authUsername = it[7] as String,
|
||||||
it[8] as String,
|
authPassword = it[8] as String,
|
||||||
json,
|
uiAccessTokenPreference = userPreferences.uiAccessToken(),
|
||||||
|
simpleSessionPreference = userPreferences.simpleSession(),
|
||||||
|
userRefreshUI = userRefreshUI,
|
||||||
|
json = json,
|
||||||
)
|
)
|
||||||
}.stateIn(
|
}.stateIn(
|
||||||
GlobalScope,
|
GlobalScope,
|
||||||
SharingStarted.Eagerly,
|
SharingStarted.Eagerly,
|
||||||
getHttpClient(
|
getHttpClient(
|
||||||
serverPreferences.serverUrl().get(),
|
serverUrl = serverPreferences.serverUrl().get(),
|
||||||
serverPreferences.proxy().get(),
|
proxy = serverPreferences.proxy().get(),
|
||||||
serverPreferences.proxyHttpHost().get(),
|
proxyHttpHost = serverPreferences.proxyHttpHost().get(),
|
||||||
serverPreferences.proxyHttpPort().get(),
|
proxyHttpPort = serverPreferences.proxyHttpPort().get(),
|
||||||
serverPreferences.proxySocksHost().get(),
|
proxySocksHost = serverPreferences.proxySocksHost().get(),
|
||||||
serverPreferences.proxySocksPort().get(),
|
proxySocksPort = serverPreferences.proxySocksPort().get(),
|
||||||
serverPreferences.auth().get(),
|
auth = serverPreferences.auth().get(),
|
||||||
serverPreferences.authUsername().get(),
|
authUsername = serverPreferences.authUsername().get(),
|
||||||
serverPreferences.authPassword().get(),
|
authPassword = serverPreferences.authPassword().get(),
|
||||||
json,
|
uiAccessTokenPreference = userPreferences.uiAccessToken(),
|
||||||
|
simpleSessionPreference = userPreferences.simpleSession(),
|
||||||
|
userRefreshUI = userRefreshUI,
|
||||||
|
json = json,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
fun httpClientNoAuth(
|
||||||
|
serverPreferences: ServerPreferences,
|
||||||
|
userPreferences: UserPreferences,
|
||||||
|
userRefreshUI: Lazy<UserRefreshUI>,
|
||||||
|
json: Json,
|
||||||
|
): Http = combine(
|
||||||
|
serverPreferences.serverUrl().stateIn(GlobalScope),
|
||||||
|
serverPreferences.proxy().stateIn(GlobalScope),
|
||||||
|
serverPreferences.proxyHttpHost().stateIn(GlobalScope),
|
||||||
|
serverPreferences.proxyHttpPort().stateIn(GlobalScope),
|
||||||
|
serverPreferences.proxySocksHost().stateIn(GlobalScope),
|
||||||
|
serverPreferences.proxySocksPort().stateIn(GlobalScope),
|
||||||
|
) {
|
||||||
|
getHttpClient(
|
||||||
|
serverUrl = it[0] as Url,
|
||||||
|
proxy = it[1] as Proxy,
|
||||||
|
proxyHttpHost = it[2] as String,
|
||||||
|
proxyHttpPort = it[3] as Int,
|
||||||
|
proxySocksHost = it[4] as String,
|
||||||
|
proxySocksPort = it[5] as Int,
|
||||||
|
auth = Auth.NONE,
|
||||||
|
authUsername = "",
|
||||||
|
authPassword = "",
|
||||||
|
uiAccessTokenPreference = userPreferences.uiAccessToken(),
|
||||||
|
simpleSessionPreference = userPreferences.simpleSession(),
|
||||||
|
userRefreshUI = userRefreshUI,
|
||||||
|
json = json,
|
||||||
|
)
|
||||||
|
}.stateIn(
|
||||||
|
GlobalScope,
|
||||||
|
SharingStarted.Eagerly,
|
||||||
|
getHttpClient(
|
||||||
|
serverUrl = serverPreferences.serverUrl().get(),
|
||||||
|
proxy = serverPreferences.proxy().get(),
|
||||||
|
proxyHttpHost = serverPreferences.proxyHttpHost().get(),
|
||||||
|
proxyHttpPort = serverPreferences.proxyHttpPort().get(),
|
||||||
|
proxySocksHost = serverPreferences.proxySocksHost().get(),
|
||||||
|
proxySocksPort = serverPreferences.proxySocksPort().get(),
|
||||||
|
auth = Auth.NONE,
|
||||||
|
authUsername = "",
|
||||||
|
authPassword = "",
|
||||||
|
uiAccessTokenPreference = userPreferences.uiAccessToken(),
|
||||||
|
simpleSessionPreference = userPreferences.simpleSession(),
|
||||||
|
userRefreshUI = userRefreshUI,
|
||||||
|
json = json,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,4 +15,6 @@ enum class Auth {
|
|||||||
NONE,
|
NONE,
|
||||||
BASIC,
|
BASIC,
|
||||||
DIGEST,
|
DIGEST,
|
||||||
|
SIMPLE,
|
||||||
|
UI,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package ca.gosyer.jui.domain.user.interactor
|
||||||
|
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserPreferences
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserRepository
|
||||||
|
import com.diamondedge.logging.logging
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
class UserLoginSimple(
|
||||||
|
private val userRepository: UserRepository,
|
||||||
|
private val userPreferences: UserPreferences,
|
||||||
|
) {
|
||||||
|
suspend fun await(
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
onError: suspend (Throwable) -> Unit = {},
|
||||||
|
) = asFlow(username, password)
|
||||||
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to login with user $username" }
|
||||||
|
}
|
||||||
|
.singleOrNull()
|
||||||
|
|
||||||
|
fun asFlow(
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
) = userRepository.loginSimple(username, password)
|
||||||
|
.onEach {
|
||||||
|
userPreferences.simpleSession().set(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = logging()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package ca.gosyer.jui.domain.user.interactor
|
||||||
|
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserPreferences
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserRepository
|
||||||
|
import com.diamondedge.logging.logging
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
class UserLoginUI(
|
||||||
|
private val userRepository: UserRepository,
|
||||||
|
private val userPreferences: UserPreferences,
|
||||||
|
) {
|
||||||
|
suspend fun await(
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
onError: suspend (Throwable) -> Unit = {},
|
||||||
|
) = asFlow(username, password)
|
||||||
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to login with user $username" }
|
||||||
|
}
|
||||||
|
.singleOrNull()
|
||||||
|
|
||||||
|
fun asFlow(
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
) = userRepository.loginUI(username, password)
|
||||||
|
.onEach {
|
||||||
|
userPreferences.uiRefreshToken().set(it.refreshToken)
|
||||||
|
userPreferences.uiAccessToken().set(it.accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = logging()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package ca.gosyer.jui.domain.user.interactor
|
||||||
|
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserPreferences
|
||||||
|
import com.diamondedge.logging.logging
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
class UserLogout(
|
||||||
|
private val userPreferences: UserPreferences,
|
||||||
|
) {
|
||||||
|
suspend fun await(
|
||||||
|
onError: suspend (Throwable) -> Unit = {},
|
||||||
|
) = asFlow()
|
||||||
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to logout user" }
|
||||||
|
}
|
||||||
|
.singleOrNull()
|
||||||
|
|
||||||
|
fun asFlow() = flow {
|
||||||
|
userPreferences.uiRefreshToken().set("")
|
||||||
|
userPreferences.uiAccessToken().set("")
|
||||||
|
userPreferences.simpleSession().set("")
|
||||||
|
emit(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = logging()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package ca.gosyer.jui.domain.user.interactor
|
||||||
|
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserPreferences
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserRepository
|
||||||
|
import com.diamondedge.logging.logging
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.singleOrNull
|
||||||
|
import me.tatarka.inject.annotations.Inject
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
class UserRefreshUI(
|
||||||
|
private val userRepository: UserRepository,
|
||||||
|
private val userPreferences: UserPreferences,
|
||||||
|
) {
|
||||||
|
suspend fun await(
|
||||||
|
onError: suspend (Throwable) -> Unit = {},
|
||||||
|
) = asFlow()
|
||||||
|
.catch {
|
||||||
|
onError(it)
|
||||||
|
log.warn(it) { "Failed to refresh user" }
|
||||||
|
}
|
||||||
|
.singleOrNull()
|
||||||
|
|
||||||
|
fun asFlow() = userRepository.refreshUI(userPreferences.uiRefreshToken().get())
|
||||||
|
.onEach {
|
||||||
|
userPreferences.uiAccessToken().set(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log = logging()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ca.gosyer.jui.domain.user.model
|
||||||
|
|
||||||
|
data class LoginData(
|
||||||
|
val refreshToken: String,
|
||||||
|
val accessToken: String,
|
||||||
|
)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package ca.gosyer.jui.domain.user.service
|
||||||
|
|
||||||
|
import ca.gosyer.jui.core.prefs.Preference
|
||||||
|
import ca.gosyer.jui.core.prefs.PreferenceStore
|
||||||
|
|
||||||
|
class UserPreferences(
|
||||||
|
private val preferenceStore: PreferenceStore,
|
||||||
|
) {
|
||||||
|
fun uiRefreshToken(): Preference<String> = preferenceStore.getString("ui_refresh_token", "")
|
||||||
|
|
||||||
|
fun uiAccessToken(): Preference<String> = preferenceStore.getString("ui_refresh_token", "")
|
||||||
|
|
||||||
|
fun simpleSession(): Preference<String> = preferenceStore.getString("simple_session", "")
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package ca.gosyer.jui.domain.user.service
|
||||||
|
|
||||||
|
import ca.gosyer.jui.domain.user.model.LoginData
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface UserRepository {
|
||||||
|
|
||||||
|
fun loginUI(username: String, password: String): Flow<LoginData>
|
||||||
|
|
||||||
|
fun refreshUI(refreshToken: String): Flow<String>
|
||||||
|
|
||||||
|
fun loginSimple(username: String, password: String): Flow<String>
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ package ca.gosyer.jui.domain.server.service
|
|||||||
import ca.gosyer.jui.core.prefs.Preference
|
import ca.gosyer.jui.core.prefs.Preference
|
||||||
import ca.gosyer.jui.core.prefs.PreferenceStore
|
import ca.gosyer.jui.core.prefs.PreferenceStore
|
||||||
import ca.gosyer.jui.domain.server.service.host.ServerHostPreference
|
import ca.gosyer.jui.domain.server.service.host.ServerHostPreference
|
||||||
|
import ca.gosyer.jui.domain.settings.model.AuthMode
|
||||||
|
|
||||||
actual class ServerHostPreferences actual constructor(
|
actual class ServerHostPreferences actual constructor(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
@@ -17,46 +18,48 @@ actual class ServerHostPreferences actual constructor(
|
|||||||
|
|
||||||
// IP
|
// IP
|
||||||
private val ip = ServerHostPreference.IP(preferenceStore)
|
private val ip = ServerHostPreference.IP(preferenceStore)
|
||||||
|
|
||||||
fun ip(): Preference<String> = ip.preference()
|
fun ip(): Preference<String> = ip.preference()
|
||||||
|
|
||||||
private val port = ServerHostPreference.Port(preferenceStore)
|
private val port = ServerHostPreference.Port(preferenceStore)
|
||||||
|
|
||||||
fun port(): Preference<Int> = port.preference()
|
fun port(): Preference<Int> = port.preference()
|
||||||
|
|
||||||
// Root
|
// Root
|
||||||
private val rootPath = ServerHostPreference.RootPath(preferenceStore)
|
private val rootPath = ServerHostPreference.RootPath(preferenceStore)
|
||||||
|
|
||||||
fun rootPath(): Preference<String> = rootPath.preference()
|
fun rootPath(): Preference<String> = rootPath.preference()
|
||||||
|
|
||||||
// Downloader
|
// Downloader
|
||||||
private val downloadPath = ServerHostPreference.DownloadPath(preferenceStore)
|
private val downloadPath = ServerHostPreference.DownloadPath(preferenceStore)
|
||||||
|
|
||||||
fun downloadPath(): Preference<String> = downloadPath.preference()
|
fun downloadPath(): Preference<String> = downloadPath.preference()
|
||||||
|
|
||||||
// Backup
|
// Backup
|
||||||
private val backupPath = ServerHostPreference.BackupPath(preferenceStore)
|
private val backupPath = ServerHostPreference.BackupPath(preferenceStore)
|
||||||
|
|
||||||
fun backupPath(): Preference<String> = backupPath.preference()
|
fun backupPath(): Preference<String> = backupPath.preference()
|
||||||
|
|
||||||
// LocalSource
|
// LocalSource
|
||||||
private val localSourcePath = ServerHostPreference.LocalSourcePath(preferenceStore)
|
private val localSourcePath = ServerHostPreference.LocalSourcePath(preferenceStore)
|
||||||
|
|
||||||
fun localSourcePath(): Preference<String> = localSourcePath.preference()
|
fun localSourcePath(): Preference<String> = localSourcePath.preference()
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
private val basicAuthEnabled = ServerHostPreference.BasicAuthEnabled(preferenceStore)
|
private val basicAuthEnabled = ServerHostPreference.BasicAuthEnabled(preferenceStore)
|
||||||
|
@Deprecated("")
|
||||||
fun basicAuthEnabled(): Preference<Boolean> = basicAuthEnabled.preference()
|
fun basicAuthEnabled(): Preference<Boolean> = basicAuthEnabled.preference()
|
||||||
|
|
||||||
private val basicAuthUsername = ServerHostPreference.BasicAuthUsername(preferenceStore)
|
private val basicAuthUsername = ServerHostPreference.BasicAuthUsername(preferenceStore)
|
||||||
|
@Deprecated("")
|
||||||
fun basicAuthUsername(): Preference<String> = basicAuthUsername.preference()
|
fun basicAuthUsername(): Preference<String> = basicAuthUsername.preference()
|
||||||
|
|
||||||
private val basicAuthPassword = ServerHostPreference.BasicAuthPassword(preferenceStore)
|
private val basicAuthPassword = ServerHostPreference.BasicAuthPassword(preferenceStore)
|
||||||
|
@Deprecated("")
|
||||||
fun basicAuthPassword(): Preference<String> = basicAuthPassword.preference()
|
fun basicAuthPassword(): Preference<String> = basicAuthPassword.preference()
|
||||||
|
|
||||||
|
// Authentication
|
||||||
|
private val authMode = ServerHostPreference.AuthMode(preferenceStore)
|
||||||
|
fun authMode(): Preference<AuthMode> = authMode.preference()
|
||||||
|
|
||||||
|
private val authUsername = ServerHostPreference.AuthUsername(preferenceStore)
|
||||||
|
fun authUsername(): Preference<String> = authUsername.preference()
|
||||||
|
|
||||||
|
private val authPassword = ServerHostPreference.AuthPassword(preferenceStore)
|
||||||
|
fun authPassword(): Preference<String> = authPassword.preference()
|
||||||
|
|
||||||
fun properties(): Array<String> =
|
fun properties(): Array<String> =
|
||||||
listOf(
|
listOf(
|
||||||
ip,
|
ip,
|
||||||
@@ -65,9 +68,9 @@ actual class ServerHostPreferences actual constructor(
|
|||||||
downloadPath,
|
downloadPath,
|
||||||
backupPath,
|
backupPath,
|
||||||
localSourcePath,
|
localSourcePath,
|
||||||
basicAuthEnabled,
|
authMode,
|
||||||
basicAuthUsername,
|
authUsername,
|
||||||
basicAuthPassword,
|
authPassword,
|
||||||
).mapNotNull {
|
).mapNotNull {
|
||||||
it.getProperty()
|
it.getProperty()
|
||||||
}.plus(
|
}.plus(
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ package ca.gosyer.jui.domain.server.service.host
|
|||||||
|
|
||||||
import ca.gosyer.jui.core.prefs.Preference
|
import ca.gosyer.jui.core.prefs.Preference
|
||||||
import ca.gosyer.jui.core.prefs.PreferenceStore
|
import ca.gosyer.jui.core.prefs.PreferenceStore
|
||||||
|
import ca.gosyer.jui.core.prefs.getEnum
|
||||||
|
import ca.gosyer.jui.domain.settings.model.AuthMode as ServerAuthMode
|
||||||
|
|
||||||
sealed class ServerHostPreference<T : Any> {
|
sealed class ServerHostPreference<T : Any> {
|
||||||
protected abstract val propertyName: String
|
protected abstract val propertyName: String
|
||||||
@@ -63,6 +65,16 @@ sealed class ServerHostPreference<T : Any> {
|
|||||||
override fun preference(): Preference<Boolean> = preferenceStore.getBoolean(propertyName, defaultValue)
|
override fun preference(): Preference<Boolean> = preferenceStore.getBoolean(propertyName, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class ObjectServerHostPreference<T : Any>(
|
||||||
|
override val preferenceStore: PreferenceStore,
|
||||||
|
override val propertyName: String,
|
||||||
|
override val defaultValue: T,
|
||||||
|
override val serverValue: T = defaultValue,
|
||||||
|
private val getObject: (String, T) -> Preference<T>,
|
||||||
|
) : ServerHostPreference<T>() {
|
||||||
|
override fun preference(): Preference<T> = getObject(propertyName, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
// Root
|
// Root
|
||||||
class RootPath(
|
class RootPath(
|
||||||
preferenceStore: PreferenceStore,
|
preferenceStore: PreferenceStore,
|
||||||
@@ -116,27 +128,43 @@ sealed class ServerHostPreference<T : Any> {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
|
@Deprecated("UseAuthMode")
|
||||||
class BasicAuthEnabled(
|
class BasicAuthEnabled(
|
||||||
preferenceStore: PreferenceStore,
|
preferenceStore: PreferenceStore,
|
||||||
) : BooleanServerHostPreference(
|
) : BooleanServerHostPreference(preferenceStore, "basicAuthEnabled", false)
|
||||||
preferenceStore,
|
@Deprecated("UseAuthUsername")
|
||||||
"basicAuthEnabled",
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
|
|
||||||
class BasicAuthUsername(
|
class BasicAuthUsername(
|
||||||
preferenceStore: PreferenceStore,
|
preferenceStore: PreferenceStore,
|
||||||
) : StringServerHostPreference(
|
) : StringServerHostPreference(preferenceStore, "basicAuthUsername", "")
|
||||||
preferenceStore,
|
@Deprecated("UseAuthPassword")
|
||||||
"basicAuthUsername",
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
|
|
||||||
class BasicAuthPassword(
|
class BasicAuthPassword(
|
||||||
preferenceStore: PreferenceStore,
|
preferenceStore: PreferenceStore,
|
||||||
|
) : StringServerHostPreference(preferenceStore, "basicAuthPassword", "")
|
||||||
|
|
||||||
|
class AuthMode(
|
||||||
|
preferenceStore: PreferenceStore,
|
||||||
|
) : ObjectServerHostPreference<ServerAuthMode>(
|
||||||
|
preferenceStore,
|
||||||
|
"authMode",
|
||||||
|
ServerAuthMode.NONE,
|
||||||
|
getObject = { propertyName, default ->
|
||||||
|
preferenceStore.getEnum(propertyName, default)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
class AuthUsername(
|
||||||
|
preferenceStore: PreferenceStore,
|
||||||
) : StringServerHostPreference(
|
) : StringServerHostPreference(
|
||||||
preferenceStore,
|
preferenceStore,
|
||||||
"basicAuthPassword",
|
"authUsername",
|
||||||
"",
|
"",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class AuthPassword(
|
||||||
|
preferenceStore: PreferenceStore,
|
||||||
|
) : StringServerHostPreference(
|
||||||
|
preferenceStore,
|
||||||
|
"authPassword",
|
||||||
|
"",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,9 +319,10 @@
|
|||||||
<string name="host_webui_sub">Whether the server\'s default WebUI is enabled, makes you able to use Suwayomi in your browser</string>
|
<string name="host_webui_sub">Whether the server\'s default WebUI is enabled, makes you able to use Suwayomi in your browser</string>
|
||||||
<string name="host_open_in_browser">Open Server WebUI on startup</string>
|
<string name="host_open_in_browser">Open Server WebUI on startup</string>
|
||||||
<string name="host_open_in_browser_sub">Open the WebUI inside your browser on server startup. Requires the WebUI be enabled</string>
|
<string name="host_open_in_browser_sub">Open the WebUI inside your browser on server startup. Requires the WebUI be enabled</string>
|
||||||
<string name="host_basic_auth_sub">Use basic auth to protect your library, requires username and password</string>
|
<string name="host_auth">Auth mode</string>
|
||||||
<string name="host_basic_auth_username">Basic auth username</string>
|
<string name="host_auth_sub">Use authentication to protect your library, requires username and password</string>
|
||||||
<string name="host_basic_auth_password">Basic auth password</string>
|
<string name="host_auth_username">Auth username</string>
|
||||||
|
<string name="host_auth_password">Auth password</string>
|
||||||
<string name="server_url">Server URL</string>
|
<string name="server_url">Server URL</string>
|
||||||
<string name="server_port">Server PORT</string>
|
<string name="server_port">Server PORT</string>
|
||||||
<string name="server_path_prefix">Server Path Prefix</string>
|
<string name="server_path_prefix">Server Path Prefix</string>
|
||||||
@@ -338,6 +339,10 @@
|
|||||||
<string name="no_auth">No auth</string>
|
<string name="no_auth">No auth</string>
|
||||||
<string name="basic_auth">Basic auth</string>
|
<string name="basic_auth">Basic auth</string>
|
||||||
<string name="digest_auth">Digest auth</string>
|
<string name="digest_auth">Digest auth</string>
|
||||||
|
<string name="simple_auth">Simple auth</string>
|
||||||
|
<string name="ui_login">UI login</string>
|
||||||
|
<string name="login">Login</string>
|
||||||
|
<string name="logout">Logout</string>
|
||||||
<string name="auth_username">Auth username</string>
|
<string name="auth_username">Auth username</string>
|
||||||
<string name="auth_password">Auth password</string>
|
<string name="auth_password">Auth password</string>
|
||||||
<string name="server_settings_sub">The below settings configure the connected Suwayomi-Server</string>
|
<string name="server_settings_sub">The below settings configure the connected Suwayomi-Server</string>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import androidx.compose.foundation.layout.wrapContentWidth
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.CircularProgressIndicator
|
import androidx.compose.material.CircularProgressIndicator
|
||||||
import androidx.compose.material.Divider
|
import androidx.compose.material.Divider
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
@@ -35,6 +36,8 @@ import androidx.compose.material.OutlinedTextField
|
|||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.rounded.Login
|
||||||
|
import androidx.compose.material.icons.automirrored.rounded.Logout
|
||||||
import androidx.compose.material.icons.rounded.Add
|
import androidx.compose.material.icons.rounded.Add
|
||||||
import androidx.compose.material.icons.rounded.Delete
|
import androidx.compose.material.icons.rounded.Delete
|
||||||
import androidx.compose.material.icons.rounded.Info
|
import androidx.compose.material.icons.rounded.Info
|
||||||
@@ -46,10 +49,15 @@ import androidx.compose.runtime.derivedStateOf
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.autofill.ContentType
|
||||||
|
import androidx.compose.ui.semantics.contentType
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ca.gosyer.jui.core.lang.launchIO
|
import ca.gosyer.jui.core.lang.launchIO
|
||||||
@@ -59,8 +67,13 @@ import ca.gosyer.jui.domain.server.service.ServerHostPreferences
|
|||||||
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
||||||
import ca.gosyer.jui.domain.settings.interactor.GetSettings
|
import ca.gosyer.jui.domain.settings.interactor.GetSettings
|
||||||
import ca.gosyer.jui.domain.settings.interactor.SetSettings
|
import ca.gosyer.jui.domain.settings.interactor.SetSettings
|
||||||
|
import ca.gosyer.jui.domain.settings.model.AuthMode
|
||||||
import ca.gosyer.jui.domain.settings.model.SetSettingsInput
|
import ca.gosyer.jui.domain.settings.model.SetSettingsInput
|
||||||
import ca.gosyer.jui.domain.settings.model.Settings
|
import ca.gosyer.jui.domain.settings.model.Settings
|
||||||
|
import ca.gosyer.jui.domain.user.interactor.UserLoginSimple
|
||||||
|
import ca.gosyer.jui.domain.user.interactor.UserLoginUI
|
||||||
|
import ca.gosyer.jui.domain.user.interactor.UserLogout
|
||||||
|
import ca.gosyer.jui.domain.user.service.UserPreferences
|
||||||
import ca.gosyer.jui.i18n.MR
|
import ca.gosyer.jui.i18n.MR
|
||||||
import ca.gosyer.jui.ui.base.dialog.getMaterialDialogProperties
|
import ca.gosyer.jui.ui.base.dialog.getMaterialDialogProperties
|
||||||
import ca.gosyer.jui.ui.base.navigation.Toolbar
|
import ca.gosyer.jui.ui.base.navigation.Toolbar
|
||||||
@@ -95,10 +108,13 @@ import kotlinx.collections.immutable.toImmutableMap
|
|||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi
|
import kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.LocalTime
|
import kotlinx.datetime.LocalTime
|
||||||
import kotlinx.datetime.format.char
|
import kotlinx.datetime.format.char
|
||||||
@@ -127,8 +143,12 @@ class SettingsServerScreen : Screen {
|
|||||||
authChoices = connectionVM.getAuthChoices(),
|
authChoices = connectionVM.getAuthChoices(),
|
||||||
authUsername = connectionVM.authUsername,
|
authUsername = connectionVM.authUsername,
|
||||||
authPassword = connectionVM.authPassword,
|
authPassword = connectionVM.authPassword,
|
||||||
|
authUILoggedIn = connectionVM.authUILoggedIn.collectAsState().value,
|
||||||
|
authSimpleLoggedIn = connectionVM.authSimpleLoggedIn.collectAsState().value,
|
||||||
serverSettings = connectionVM.serverSettings.collectAsState().value,
|
serverSettings = connectionVM.serverSettings.collectAsState().value,
|
||||||
hosted = connectionVM.host.collectAsState().value,
|
hosted = connectionVM.host.collectAsState().value,
|
||||||
|
onLogin = connectionVM::onLogin,
|
||||||
|
onLogout = connectionVM::onLogout,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,18 +214,18 @@ class ServerSettings(
|
|||||||
getInput = { SetSettingsInput(backupTime = it) },
|
getInput = { SetSettingsInput(backupTime = it) },
|
||||||
)
|
)
|
||||||
|
|
||||||
// val basicAuthEnabled = getServerFlow(
|
val authMode = getServerFlow(
|
||||||
// getSetting = { it.basicAuthEnabled },
|
getSetting = { it.authMode },
|
||||||
// getInput = { SetSettingsInput(basicAuthEnabled = it) },
|
getInput = { SetSettingsInput(authMode = it) },
|
||||||
// )
|
)
|
||||||
// val basicAuthPassword = getServerFlow(
|
val authPassword = getServerFlow(
|
||||||
// getSetting = { it.basicAuthPassword },
|
getSetting = { it.authPassword },
|
||||||
// getInput = { SetSettingsInput(basicAuthPassword = it) },
|
getInput = { SetSettingsInput(authPassword = it) },
|
||||||
// )
|
)
|
||||||
// val basicAuthUsername = getServerFlow(
|
val authUsername = getServerFlow(
|
||||||
// getSetting = { it.basicAuthUsername },
|
getSetting = { it.authUsername },
|
||||||
// getInput = { SetSettingsInput(basicAuthUsername = it) },
|
getInput = { SetSettingsInput(authUsername = it) },
|
||||||
// )
|
)
|
||||||
val debugLogsEnabled = getServerFlow(
|
val debugLogsEnabled = getServerFlow(
|
||||||
getSetting = { it.debugLogsEnabled },
|
getSetting = { it.debugLogsEnabled },
|
||||||
getInput = { SetSettingsInput(debugLogsEnabled = it) },
|
getInput = { SetSettingsInput(debugLogsEnabled = it) },
|
||||||
@@ -370,6 +390,10 @@ class SettingsServerViewModel(
|
|||||||
private val setSettings: SetSettings,
|
private val setSettings: SetSettings,
|
||||||
serverPreferences: ServerPreferences,
|
serverPreferences: ServerPreferences,
|
||||||
serverHostPreferences: ServerHostPreferences,
|
serverHostPreferences: ServerHostPreferences,
|
||||||
|
userPreferences: UserPreferences,
|
||||||
|
private val userLoginSimple: UserLoginSimple,
|
||||||
|
private val userLoginUi: UserLoginUI,
|
||||||
|
private val userLogout: UserLogout,
|
||||||
contextWrapper: ContextWrapper,
|
contextWrapper: ContextWrapper,
|
||||||
) : ViewModel(contextWrapper) {
|
) : ViewModel(contextWrapper) {
|
||||||
val serverUrl = serverPreferences.server().asStateIn(scope)
|
val serverUrl = serverPreferences.server().asStateIn(scope)
|
||||||
@@ -394,6 +418,12 @@ class SettingsServerViewModel(
|
|||||||
val socksPort = serverPreferences.proxySocksPort().asStringStateIn(scope)
|
val socksPort = serverPreferences.proxySocksPort().asStringStateIn(scope)
|
||||||
|
|
||||||
val auth = serverPreferences.auth().asStateIn(scope)
|
val auth = serverPreferences.auth().asStateIn(scope)
|
||||||
|
val authUILoggedIn = userPreferences.uiRefreshToken().changes()
|
||||||
|
.map { it.isNotBlank() }
|
||||||
|
.stateIn(scope, SharingStarted.Eagerly, userPreferences.uiRefreshToken().get().isNotBlank())
|
||||||
|
val authSimpleLoggedIn = userPreferences.simpleSession().changes()
|
||||||
|
.map { it.isNotBlank() }
|
||||||
|
.stateIn(scope, SharingStarted.Eagerly, userPreferences.simpleSession().get().isNotBlank())
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun getAuthChoices(): ImmutableMap<Auth, String> =
|
fun getAuthChoices(): ImmutableMap<Auth, String> =
|
||||||
@@ -401,11 +431,31 @@ class SettingsServerViewModel(
|
|||||||
Auth.NONE to stringResource(MR.strings.no_auth),
|
Auth.NONE to stringResource(MR.strings.no_auth),
|
||||||
Auth.BASIC to stringResource(MR.strings.basic_auth),
|
Auth.BASIC to stringResource(MR.strings.basic_auth),
|
||||||
Auth.DIGEST to stringResource(MR.strings.digest_auth),
|
Auth.DIGEST to stringResource(MR.strings.digest_auth),
|
||||||
|
Auth.SIMPLE to stringResource(MR.strings.simple_auth),
|
||||||
|
Auth.UI to stringResource(MR.strings.ui_login),
|
||||||
)
|
)
|
||||||
|
|
||||||
val authUsername = serverPreferences.authUsername().asStateIn(scope)
|
val authUsername = serverPreferences.authUsername().asStateIn(scope)
|
||||||
val authPassword = serverPreferences.authPassword().asStateIn(scope)
|
val authPassword = serverPreferences.authPassword().asStateIn(scope)
|
||||||
|
|
||||||
|
fun onLogin(username: String, password: String) {
|
||||||
|
when (auth.value) {
|
||||||
|
Auth.SIMPLE -> {
|
||||||
|
userLoginSimple.asFlow(username, password)
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
|
Auth.UI -> {
|
||||||
|
userLoginUi.asFlow(username, password)
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onLogout() {
|
||||||
|
userLogout.asFlow().launchIn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
private val _serverSettings = MutableStateFlow<ServerSettings?>(null)
|
private val _serverSettings = MutableStateFlow<ServerSettings?>(null)
|
||||||
val serverSettings = _serverSettings.asStateFlow()
|
val serverSettings = _serverSettings.asStateFlow()
|
||||||
|
|
||||||
@@ -443,8 +493,12 @@ fun SettingsServerScreenContent(
|
|||||||
authChoices: ImmutableMap<Auth, String>,
|
authChoices: ImmutableMap<Auth, String>,
|
||||||
authUsername: PreferenceMutableStateFlow<String>,
|
authUsername: PreferenceMutableStateFlow<String>,
|
||||||
authPassword: PreferenceMutableStateFlow<String>,
|
authPassword: PreferenceMutableStateFlow<String>,
|
||||||
|
authUILoggedIn: Boolean,
|
||||||
|
authSimpleLoggedIn: Boolean,
|
||||||
hosted: Boolean,
|
hosted: Boolean,
|
||||||
serverSettings: ServerSettings?,
|
serverSettings: ServerSettings?,
|
||||||
|
onLogin: (String, String) -> Unit,
|
||||||
|
onLogout: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.windowInsetsPadding(
|
modifier = Modifier.windowInsetsPadding(
|
||||||
@@ -540,18 +594,95 @@ fun SettingsServerScreenContent(
|
|||||||
item {
|
item {
|
||||||
ChoicePreference(auth, authChoices, stringResource(MR.strings.authentication))
|
ChoicePreference(auth, authChoices, stringResource(MR.strings.authentication))
|
||||||
}
|
}
|
||||||
if (authValue != Auth.NONE) {
|
|
||||||
item {
|
when (authValue) {
|
||||||
EditTextPreference(authUsername, stringResource(MR.strings.auth_username))
|
Auth.NONE -> Unit
|
||||||
}
|
Auth.BASIC, Auth.DIGEST, Auth.UI, Auth.SIMPLE -> {
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(authUsername, stringResource(MR.strings.auth_username))
|
||||||
authPassword,
|
}
|
||||||
stringResource(MR.strings.auth_password),
|
item {
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
EditTextPreference(
|
||||||
)
|
authPassword,
|
||||||
|
stringResource(MR.strings.auth_password),
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authValue == Auth.UI || authValue == Auth.SIMPLE) {
|
||||||
|
if ((authValue == Auth.SIMPLE && authSimpleLoggedIn) || (authValue == Auth.UI && authUILoggedIn)) {
|
||||||
|
item {
|
||||||
|
PreferenceRow(
|
||||||
|
stringResource(MR.strings.logout),
|
||||||
|
icon = Icons.AutoMirrored.Rounded.Logout,
|
||||||
|
onClick = onLogout,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
item {
|
||||||
|
val loginDialogState = rememberMaterialDialogState()
|
||||||
|
PreferenceRow(
|
||||||
|
stringResource(MR.strings.login),
|
||||||
|
icon = Icons.AutoMirrored.Rounded.Login,
|
||||||
|
onClick = {
|
||||||
|
loginDialogState.show()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
var username by rememberSaveable { mutableStateOf("") }
|
||||||
|
var password by rememberSaveable { mutableStateOf("") }
|
||||||
|
|
||||||
|
MaterialDialog(
|
||||||
|
dialogState = loginDialogState,
|
||||||
|
properties = getMaterialDialogProperties(),
|
||||||
|
buttons = {
|
||||||
|
negativeButton(stringResource(MR.strings.action_cancel))
|
||||||
|
positiveButton(
|
||||||
|
stringResource(MR.strings.login),
|
||||||
|
onClick = {
|
||||||
|
onLogin(username, password)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = username,
|
||||||
|
onValueChange = { username = it },
|
||||||
|
modifier = Modifier
|
||||||
|
.semantics { contentType = ContentType.Username }
|
||||||
|
.keyboardHandler(
|
||||||
|
singleLine = true,
|
||||||
|
enterAction = {
|
||||||
|
submit()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Password,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
OutlinedTextField(
|
||||||
|
value = password,
|
||||||
|
onValueChange = { password = it },
|
||||||
|
modifier = Modifier
|
||||||
|
.semantics { contentType = ContentType.Password }
|
||||||
|
.keyboardHandler(
|
||||||
|
singleLine = true,
|
||||||
|
enterAction = {
|
||||||
|
submit()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Password,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
item {
|
item {
|
||||||
Divider()
|
Divider()
|
||||||
}
|
}
|
||||||
@@ -814,32 +945,41 @@ fun LazyListScope.ServerSettingsItems(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// item {
|
item {
|
||||||
// SwitchPreference(
|
ChoicePreference(
|
||||||
// preference = serverSettings.basicAuthEnabled,
|
preference = serverSettings.authMode,
|
||||||
// title = stringResource(MR.strings.basic_auth),
|
title = stringResource(MR.strings.host_auth),
|
||||||
// subtitle = stringResource(MR.strings.host_basic_auth_sub),
|
subtitle = stringResource(MR.strings.host_auth_sub),
|
||||||
// enabled = !hosted,
|
choices = (AuthMode.entries - AuthMode.UNKNOWN__).associateWith {
|
||||||
// )
|
when (it) {
|
||||||
// }
|
AuthMode.NONE -> stringResource(MR.strings.no_auth)
|
||||||
//
|
AuthMode.BASIC_AUTH -> stringResource(MR.strings.basic_auth)
|
||||||
// item {
|
AuthMode.SIMPLE_LOGIN -> stringResource(MR.strings.simple_auth)
|
||||||
// val basicAuthEnabledValue by serverSettings.basicAuthEnabled.collectAsState()
|
AuthMode.UI_LOGIN -> stringResource(MR.strings.ui_login)
|
||||||
// EditTextPreference(
|
AuthMode.UNKNOWN__ -> ""
|
||||||
// preference = serverSettings.basicAuthUsername,
|
}
|
||||||
// title = stringResource(MR.strings.host_basic_auth_username),
|
}.toImmutableMap(),
|
||||||
// enabled = basicAuthEnabledValue && !hosted,
|
enabled = !hosted,
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
// item {
|
|
||||||
// val basicAuthEnabledValue by serverSettings.basicAuthEnabled.collectAsState()
|
item {
|
||||||
// EditTextPreference(
|
val authModeValue by serverSettings.authMode.collectAsState()
|
||||||
// preference = serverSettings.basicAuthPassword,
|
EditTextPreference(
|
||||||
// title = stringResource(MR.strings.host_basic_auth_password),
|
preference = serverSettings.authUsername,
|
||||||
// visualTransformation = PasswordVisualTransformation(),
|
title = stringResource(MR.strings.host_auth_username),
|
||||||
// enabled = basicAuthEnabledValue && !hosted,
|
enabled = authModeValue != AuthMode.NONE && !hosted,
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
|
item {
|
||||||
|
val authModeValue by serverSettings.authMode.collectAsState()
|
||||||
|
EditTextPreference(
|
||||||
|
preference = serverSettings.authPassword,
|
||||||
|
title = stringResource(MR.strings.host_auth_password),
|
||||||
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
|
enabled = authModeValue != AuthMode.NONE && !hosted,
|
||||||
|
)
|
||||||
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
SwitchPreference(
|
||||||
preference = serverSettings.flareSolverrEnabled,
|
preference = serverSettings.flareSolverrEnabled,
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ import ca.gosyer.jui.domain.server.model.Auth
|
|||||||
import ca.gosyer.jui.domain.server.service.ServerHostPreferences
|
import ca.gosyer.jui.domain.server.service.ServerHostPreferences
|
||||||
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
import ca.gosyer.jui.domain.server.service.ServerPreferences
|
||||||
import ca.gosyer.jui.domain.server.service.ServerService
|
import ca.gosyer.jui.domain.server.service.ServerService
|
||||||
|
import ca.gosyer.jui.domain.settings.model.AuthMode
|
||||||
import ca.gosyer.jui.i18n.MR
|
import ca.gosyer.jui.i18n.MR
|
||||||
|
import ca.gosyer.jui.ui.base.prefs.ChoicePreference
|
||||||
import ca.gosyer.jui.ui.base.prefs.EditTextPreference
|
import ca.gosyer.jui.ui.base.prefs.EditTextPreference
|
||||||
import ca.gosyer.jui.ui.base.prefs.PreferenceRow
|
import ca.gosyer.jui.ui.base.prefs.PreferenceRow
|
||||||
import ca.gosyer.jui.ui.base.prefs.SwitchPreference
|
import ca.gosyer.jui.ui.base.prefs.SwitchPreference
|
||||||
@@ -29,6 +31,7 @@ import ca.gosyer.jui.uicore.prefs.asStringStateIn
|
|||||||
import ca.gosyer.jui.uicore.resources.stringResource
|
import ca.gosyer.jui.uicore.resources.stringResource
|
||||||
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
import ca.gosyer.jui.uicore.vm.ContextWrapper
|
||||||
import ca.gosyer.jui.uicore.vm.ViewModel
|
import ca.gosyer.jui.uicore.vm.ViewModel
|
||||||
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
@@ -39,7 +42,7 @@ import me.tatarka.inject.annotations.Inject
|
|||||||
actual fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostViewModel): LazyListScope.() -> Unit {
|
actual fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostViewModel): LazyListScope.() -> Unit {
|
||||||
val serverVm = viewModel()
|
val serverVm = viewModel()
|
||||||
val hostValue by serverVm.host.collectAsState()
|
val hostValue by serverVm.host.collectAsState()
|
||||||
val basicAuthEnabledValue by serverVm.basicAuthEnabled.collectAsState()
|
val authMode by serverVm.hostAuthMode.collectAsState()
|
||||||
|
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
@@ -50,7 +53,7 @@ actual fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostVie
|
|||||||
return {
|
return {
|
||||||
ServerHostItems(
|
ServerHostItems(
|
||||||
hostValue = hostValue,
|
hostValue = hostValue,
|
||||||
basicAuthEnabledValue = basicAuthEnabledValue,
|
authEnabledValue = authMode != AuthMode.NONE,
|
||||||
serverSettingChanged = serverVm::serverSettingChanged,
|
serverSettingChanged = serverVm::serverSettingChanged,
|
||||||
host = serverVm.host,
|
host = serverVm.host,
|
||||||
ip = serverVm.ip,
|
ip = serverVm.ip,
|
||||||
@@ -59,9 +62,9 @@ actual fun getServerHostItems(viewModel: @Composable () -> SettingsServerHostVie
|
|||||||
downloadPath = serverVm.downloadPath,
|
downloadPath = serverVm.downloadPath,
|
||||||
backupPath = serverVm.backupPath,
|
backupPath = serverVm.backupPath,
|
||||||
localSourcePath = serverVm.localSourcePath,
|
localSourcePath = serverVm.localSourcePath,
|
||||||
basicAuthEnabled = serverVm.basicAuthEnabled,
|
authMode = serverVm.hostAuthMode,
|
||||||
basicAuthUsername = serverVm.basicAuthUsername,
|
authUsername = serverVm.hostAuthUsername,
|
||||||
basicAuthPassword = serverVm.basicAuthPassword,
|
authPassword = serverVm.hostAuthPassword,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,9 +95,9 @@ actual class SettingsServerHostViewModel(
|
|||||||
val localSourcePath = serverHostPreferences.localSourcePath().asStateIn(scope)
|
val localSourcePath = serverHostPreferences.localSourcePath().asStateIn(scope)
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
val basicAuthEnabled = serverHostPreferences.basicAuthEnabled().asStateIn(scope)
|
val hostAuthMode = serverHostPreferences.authMode().asStateIn(scope)
|
||||||
val basicAuthUsername = serverHostPreferences.basicAuthUsername().asStateIn(scope)
|
val hostAuthUsername = serverHostPreferences.authUsername().asStateIn(scope)
|
||||||
val basicAuthPassword = serverHostPreferences.basicAuthPassword().asStateIn(scope)
|
val hostAuthPassword = serverHostPreferences.authPassword().asStateIn(scope)
|
||||||
|
|
||||||
private val _serverSettingChanged = MutableStateFlow(false)
|
private val _serverSettingChanged = MutableStateFlow(false)
|
||||||
val serverSettingChanged = _serverSettingChanged.asStateFlow()
|
val serverSettingChanged = _serverSettingChanged.asStateFlow()
|
||||||
@@ -115,16 +118,18 @@ actual class SettingsServerHostViewModel(
|
|||||||
val authPassword = serverPreferences.authPassword().asStateIn(scope)
|
val authPassword = serverPreferences.authPassword().asStateIn(scope)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
combine(host, basicAuthEnabled, basicAuthUsername, basicAuthPassword) { host, enabled, username, password ->
|
combine(host, hostAuthMode, hostAuthUsername, hostAuthPassword) { host, mode, username, password ->
|
||||||
if (host) {
|
if (host) {
|
||||||
if (enabled) {
|
when (mode) {
|
||||||
auth.value = Auth.BASIC
|
AuthMode.NONE -> auth.value = Auth.NONE
|
||||||
authUsername.value = username
|
AuthMode.BASIC_AUTH -> {
|
||||||
authPassword.value = password
|
auth.value = Auth.BASIC
|
||||||
} else {
|
authUsername.value = username
|
||||||
auth.value = Auth.NONE
|
authPassword.value = password
|
||||||
authUsername.value = ""
|
}
|
||||||
authPassword.value = ""
|
AuthMode.SIMPLE_LOGIN -> auth.value = Auth.SIMPLE
|
||||||
|
AuthMode.UI_LOGIN -> auth.value = Auth.UI
|
||||||
|
AuthMode.UNKNOWN__ -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.launchIn(scope)
|
}.launchIn(scope)
|
||||||
@@ -133,7 +138,7 @@ actual class SettingsServerHostViewModel(
|
|||||||
|
|
||||||
fun LazyListScope.ServerHostItems(
|
fun LazyListScope.ServerHostItems(
|
||||||
hostValue: Boolean,
|
hostValue: Boolean,
|
||||||
basicAuthEnabledValue: Boolean,
|
authEnabledValue: Boolean,
|
||||||
serverSettingChanged: () -> Unit,
|
serverSettingChanged: () -> Unit,
|
||||||
host: MutableStateFlow<Boolean>,
|
host: MutableStateFlow<Boolean>,
|
||||||
ip: MutableStateFlow<String>,
|
ip: MutableStateFlow<String>,
|
||||||
@@ -142,9 +147,9 @@ fun LazyListScope.ServerHostItems(
|
|||||||
downloadPath: MutableStateFlow<String>,
|
downloadPath: MutableStateFlow<String>,
|
||||||
backupPath: MutableStateFlow<String>,
|
backupPath: MutableStateFlow<String>,
|
||||||
localSourcePath: MutableStateFlow<String>,
|
localSourcePath: MutableStateFlow<String>,
|
||||||
basicAuthEnabled: MutableStateFlow<Boolean>,
|
authMode: MutableStateFlow<AuthMode>,
|
||||||
basicAuthUsername: MutableStateFlow<String>,
|
authUsername: MutableStateFlow<String>,
|
||||||
basicAuthPassword: MutableStateFlow<String>,
|
authPassword: MutableStateFlow<String>,
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
SwitchPreference(preference = host, title = stringResource(MR.strings.host_server))
|
SwitchPreference(preference = host, title = stringResource(MR.strings.host_server))
|
||||||
@@ -260,28 +265,37 @@ fun LazyListScope.ServerHostItems(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
SwitchPreference(
|
ChoicePreference(
|
||||||
preference = basicAuthEnabled,
|
preference = authMode,
|
||||||
title = stringResource(MR.strings.basic_auth),
|
title = stringResource(MR.strings.host_auth),
|
||||||
subtitle = stringResource(MR.strings.host_basic_auth_sub),
|
subtitle = stringResource(MR.strings.host_auth_sub),
|
||||||
|
choices = (AuthMode.entries - AuthMode.UNKNOWN__).associateWith {
|
||||||
|
when (it) {
|
||||||
|
AuthMode.NONE -> stringResource(MR.strings.no_auth)
|
||||||
|
AuthMode.BASIC_AUTH -> stringResource(MR.strings.basic_auth)
|
||||||
|
AuthMode.SIMPLE_LOGIN -> stringResource(MR.strings.simple_auth)
|
||||||
|
AuthMode.UI_LOGIN -> stringResource(MR.strings.ui_login)
|
||||||
|
AuthMode.UNKNOWN__ -> ""
|
||||||
|
}
|
||||||
|
}.toImmutableMap(),
|
||||||
changeListener = serverSettingChanged,
|
changeListener = serverSettingChanged,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
preference = basicAuthUsername,
|
preference = authUsername,
|
||||||
title = stringResource(MR.strings.host_basic_auth_username),
|
title = stringResource(MR.strings.host_auth_username),
|
||||||
changeListener = serverSettingChanged,
|
changeListener = serverSettingChanged,
|
||||||
enabled = basicAuthEnabledValue,
|
enabled = authEnabledValue,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
EditTextPreference(
|
EditTextPreference(
|
||||||
preference = basicAuthPassword,
|
preference = authPassword,
|
||||||
title = stringResource(MR.strings.host_basic_auth_password),
|
title = stringResource(MR.strings.host_auth_password),
|
||||||
changeListener = serverSettingChanged,
|
changeListener = serverSettingChanged,
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
enabled = basicAuthEnabledValue,
|
enabled = authEnabledValue,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user