mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-06 02:40:58 +01:00
Add option to require server login for user profile (#1367)
## Description This PR adds a new method of protecting a user profile when switching to it: server login. When enabled, switching to the user requires authenticating with the server again via Quick Connect or username & password. This is basically the same as the official client. ### Related issues Closes #321 Related to #1330 - might cause merge conflicts Related to #1287 - definitely causes merge conflicts ### Testing Emulator with various uses cases including auto sign-in, switching between server login & PIN protection, and removing the protection ## Screenshots N/A, the login looks identical as adding a new user ## AI or LLM usage None
This commit is contained in:
parent
1463480e26
commit
45d466d76f
12 changed files with 813 additions and 35 deletions
|
|
@ -19,11 +19,19 @@ import com.github.damontecres.wholphin.data.model.PlaybackLanguageChoice
|
|||
import com.github.damontecres.wholphin.data.model.SeerrServer
|
||||
import com.github.damontecres.wholphin.data.model.SeerrUser
|
||||
import com.github.damontecres.wholphin.ui.components.ViewOptions
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.SortOrder
|
||||
import org.jellyfin.sdk.model.serializer.toUUID
|
||||
import timber.log.Timber
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.UUID
|
||||
|
||||
@Database(
|
||||
|
|
@ -40,7 +48,7 @@ import java.util.UUID
|
|||
SeerrUser::class,
|
||||
|
||||
],
|
||||
version = 31,
|
||||
version = 32,
|
||||
exportSchema = true,
|
||||
autoMigrations = [
|
||||
AutoMigration(3, 4),
|
||||
|
|
@ -55,6 +63,7 @@ import java.util.UUID
|
|||
AutoMigration(12, 20),
|
||||
AutoMigration(20, 30),
|
||||
AutoMigration(30, 31),
|
||||
AutoMigration(31, 32),
|
||||
],
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
|
@ -116,6 +125,27 @@ class Converters {
|
|||
Timber.e(ex, "Error parsing view options")
|
||||
null
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun convertToString(dateTime: ZonedDateTime): String = DateTimeFormatter.ISO_ZONED_DATE_TIME.format(dateTime)
|
||||
|
||||
@TypeConverter
|
||||
fun convertToLocalDateTime(dateTime: String): ZonedDateTime = ZonedDateTime.parse(dateTime, DateTimeFormatter.ISO_ZONED_DATE_TIME)
|
||||
}
|
||||
|
||||
class ZonedDateTimeSerializer : KSerializer<ZonedDateTime> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor(ZonedDateTime::class.qualifiedName!!, PrimitiveKind.STRING)
|
||||
|
||||
override fun deserialize(decoder: Decoder): ZonedDateTime =
|
||||
ZonedDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_ZONED_DATE_TIME)
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: ZonedDateTime,
|
||||
) {
|
||||
encoder.encodeString(DateTimeFormatter.ISO_ZONED_DATE_TIME.format(value))
|
||||
}
|
||||
}
|
||||
|
||||
object Migrations {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import org.jellyfin.sdk.model.api.AuthenticationResult
|
|||
import org.jellyfin.sdk.model.api.UserDto
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import timber.log.Timber
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -167,6 +168,7 @@ class ServerRepository
|
|||
suspend fun changeUser(
|
||||
serverUrl: String,
|
||||
authenticationResult: AuthenticationResult,
|
||||
existingUser: JellyfinUser?,
|
||||
) = withContext(ioDispatcher) {
|
||||
val accessToken = authenticationResult.accessToken
|
||||
if (accessToken != null) {
|
||||
|
|
@ -183,12 +185,24 @@ class ServerRepository
|
|||
if (server != null) {
|
||||
val user =
|
||||
authedUser?.let {
|
||||
JellyfinUser(
|
||||
id = it.id,
|
||||
name = it.name,
|
||||
serverId = server.id,
|
||||
accessToken = accessToken,
|
||||
)
|
||||
if (existingUser != null) {
|
||||
Timber.d("Re-using existing user")
|
||||
existingUser.copy(
|
||||
// If the server authenticated via the server, always remove the PIN
|
||||
pin = null,
|
||||
accessToken = accessToken,
|
||||
lastUsed = ZonedDateTime.now(),
|
||||
)
|
||||
} else {
|
||||
Timber.d("Creating new user")
|
||||
JellyfinUser(
|
||||
id = it.id,
|
||||
name = it.name,
|
||||
serverId = server.id,
|
||||
accessToken = accessToken,
|
||||
lastUsed = ZonedDateTime.now(),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (user != null) {
|
||||
return@withContext changeUser(server, user)
|
||||
|
|
@ -253,11 +267,12 @@ class ServerRepository
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun setUserPin(
|
||||
suspend fun updateUserAuth(
|
||||
user: JellyfinUser,
|
||||
pin: String?,
|
||||
) = withContext(ioDispatcher) {
|
||||
val newUser = user.copy(pin = pin)
|
||||
requireLogin: Boolean,
|
||||
) {
|
||||
val newUser = user.copy(pin = pin, requireLogin = requireLogin)
|
||||
val updatedUser = serverDao.addOrUpdateUser(newUser)
|
||||
if (currentUser.value?.id == updatedUser.id && currentServer.value?.id == user.serverId) {
|
||||
// Updating current user, so push out the change
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@file:UseSerializers(UUIDSerializer::class)
|
||||
@file:UseSerializers(UUIDSerializer::class, ZonedDateTimeSerializer::class)
|
||||
|
||||
package com.github.damontecres.wholphin.data.model
|
||||
|
||||
|
|
@ -10,11 +10,13 @@ import androidx.room.Ignore
|
|||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Relation
|
||||
import com.github.damontecres.wholphin.data.ZonedDateTimeSerializer
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.sdk.model.ServerVersion
|
||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
|
|
@ -27,6 +29,7 @@ data class JellyfinServer(
|
|||
val name: String?,
|
||||
val url: String,
|
||||
val version: String?,
|
||||
val lastUsed: ZonedDateTime? = null,
|
||||
) {
|
||||
@get:Ignore
|
||||
val serverVersion: ServerVersion? by lazy { version?.let(ServerVersion::fromString) }
|
||||
|
|
@ -58,11 +61,14 @@ data class JellyfinUser(
|
|||
val serverId: UUID,
|
||||
val accessToken: String?,
|
||||
val pin: String? = null,
|
||||
@ColumnInfo(defaultValue = "false")
|
||||
val requireLogin: Boolean = false,
|
||||
val lastUsed: ZonedDateTime? = null,
|
||||
) {
|
||||
val hasPin: Boolean get() = pin.isNotNullOrBlank()
|
||||
|
||||
override fun toString(): String =
|
||||
"JellyfinUser(rowId=$rowId, id=$id, name=$name, serverId=$serverId, accessToken?=${accessToken.isNotNullOrBlank()}, pin?=${pin.isNotNullOrBlank()})"
|
||||
"JellyfinUser(rowId=$rowId, id=$id, name=$name, serverId=$serverId, lastUsed=$lastUsed, accessToken?=${accessToken.isNotNullOrBlank()}, pin?=${pin.isNotNullOrBlank()})"
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue