OPDS: Allow fallback to Basic Auth (#1613)

* Move API authorization to UserType

We already verify the JWT there, so do the same with cookies. This makes
the next steps easier

* OPDS: Allow basic auth as fallback

* Send 404 for any unmatched API request

Redirecting to the UI is weird and can cause problems with the
SIMPLE_LOGIN check (which ignores API requests)

* Webview: Present Login page in SIMPLE_LOGIN mode

For BASIC_AUTH, the dialog is always presented. With UI_LOGIN, we have a
custom login dialog.
Before, SIMPLE_LOGIN would just say "Unauthorized", as with all API
endpoints. With the last commits, SIMPLE_LOGIN is checked by the
endpoints, which Webview did not, so the page would load, but then the
Websocket would error out, despite showing the login dialog.

* Lint
This commit is contained in:
Constantin Piber
2025-08-24 18:36:11 +02:00
committed by GitHub
parent 9a33e3808a
commit 46e2ef125a
4 changed files with 94 additions and 36 deletions

View File

@@ -9,15 +9,20 @@ package suwayomi.tachidesk.global.controller
import io.javalin.http.ContentType import io.javalin.http.ContentType
import io.javalin.http.HttpStatus import io.javalin.http.HttpStatus
import io.javalin.http.RedirectResponse
import io.javalin.websocket.WsConfig import io.javalin.websocket.WsConfig
import suwayomi.tachidesk.global.impl.WebView import suwayomi.tachidesk.global.impl.WebView
import suwayomi.tachidesk.graphql.types.AuthMode
import suwayomi.tachidesk.i18n.LocalizationHelper import suwayomi.tachidesk.i18n.LocalizationHelper
import suwayomi.tachidesk.server.JavalinSetup.Attribute import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.getAttribute import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.serverConfig
import suwayomi.tachidesk.server.user.UnauthorizedException
import suwayomi.tachidesk.server.user.requireUser import suwayomi.tachidesk.server.user.requireUser
import suwayomi.tachidesk.server.util.handler import suwayomi.tachidesk.server.util.handler
import suwayomi.tachidesk.server.util.queryParam import suwayomi.tachidesk.server.util.queryParam
import suwayomi.tachidesk.server.util.withOperation import suwayomi.tachidesk.server.util.withOperation
import java.net.URLEncoder
import java.util.Locale import java.util.Locale
object WebViewController { object WebViewController {
@@ -31,7 +36,18 @@ object WebViewController {
} }
}, },
behaviorOf = { ctx, lang -> behaviorOf = { ctx, lang ->
// intentionally not user-protected, this pages handles login by itself // intentionally not user-protected, this pages handles login by itself in UI_LOGIN mode
// for SIMPLE_LOGIN, we need to manually redirect to make this work
// for BASIC_AUTH, JavalinSetup already handles this
if (serverConfig.authMode.value == AuthMode.SIMPLE_LOGIN) {
try {
ctx.getAttribute(Attribute.TachideskUser).requireUser()
} catch (_: UnauthorizedException) {
val url = "/login.html?redirect=" + URLEncoder.encode(ctx.fullUrl(), Charsets.UTF_8)
ctx.header("Location", url)
throw RedirectResponse(HttpStatus.SEE_OTHER)
}
}
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.contentType(ContentType.TEXT_HTML) ctx.contentType(ContentType.TEXT_HTML)
ctx.render( ctx.render(

View File

@@ -12,7 +12,7 @@ import suwayomi.tachidesk.opds.impl.OpdsFeedBuilder
import suwayomi.tachidesk.server.JavalinSetup.Attribute import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.future import suwayomi.tachidesk.server.JavalinSetup.future
import suwayomi.tachidesk.server.JavalinSetup.getAttribute import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.user.requireUser import suwayomi.tachidesk.server.user.requireUserWithBasicFallback
import suwayomi.tachidesk.server.util.handler import suwayomi.tachidesk.server.util.handler
import suwayomi.tachidesk.server.util.pathParam import suwayomi.tachidesk.server.util.pathParam
import suwayomi.tachidesk.server.util.queryParam import suwayomi.tachidesk.server.util.queryParam
@@ -67,7 +67,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, lang -> behaviorOf = { ctx, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.contentType(OPDS_MIME).result(OpdsFeedBuilder.getRootFeed(BASE_URL, locale)) ctx.contentType(OPDS_MIME).result(OpdsFeedBuilder.getRootFeed(BASE_URL, locale))
}, },
@@ -90,7 +90,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, pageNumber, lang -> behaviorOf = { ctx, pageNumber, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.future { ctx.future {
future { future {
@@ -116,7 +116,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, lang -> behaviorOf = { ctx, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.contentType("application/opensearchdescription+xml").result( ctx.contentType("application/opensearchdescription+xml").result(
""" """
@@ -144,7 +144,7 @@ object OpdsV1Controller {
handler( handler(
documentWith = { withOperation { summary("OPDS Series in Library Feed") } }, documentWith = { withOperation { summary("OPDS Series in Library Feed") } },
behaviorOf = { ctx -> behaviorOf = { ctx ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val pageNumber = ctx.queryParam("pageNumber")?.toIntOrNull() val pageNumber = ctx.queryParam("pageNumber")?.toIntOrNull()
val query = ctx.queryParam("query") val query = ctx.queryParam("query")
val author = ctx.queryParam("author") val author = ctx.queryParam("author")
@@ -199,7 +199,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, pageNumber, lang -> behaviorOf = { ctx, pageNumber, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.future { ctx.future {
future { future {
@@ -226,7 +226,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, pageNumber, lang -> behaviorOf = { ctx, pageNumber, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.future { ctx.future {
future { future {
@@ -253,7 +253,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, pageNumber, lang -> behaviorOf = { ctx, pageNumber, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.future { ctx.future {
future { future {
@@ -280,7 +280,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, pageNumber, lang -> behaviorOf = { ctx, pageNumber, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.future { ctx.future {
future { future {
@@ -306,7 +306,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, lang -> behaviorOf = { ctx, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.future { ctx.future {
future { future {
@@ -332,7 +332,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, lang -> behaviorOf = { ctx, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.future { ctx.future {
future { future {
@@ -359,7 +359,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, pageNumber, lang -> behaviorOf = { ctx, pageNumber, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.future { ctx.future {
future { future {
@@ -388,7 +388,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, sourceId, pageNumber, sort, lang -> behaviorOf = { ctx, sourceId, pageNumber, sort, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.future { ctx.future {
future { future {
@@ -424,7 +424,7 @@ object OpdsV1Controller {
pathParam<Long>("sourceId"), pathParam<Long>("sourceId"),
documentWith = { withOperation { summary("OPDS Library Source Specific Series Feed") } }, documentWith = { withOperation { summary("OPDS Library Source Specific Series Feed") } },
behaviorOf = { ctx, sourceId -> behaviorOf = { ctx, sourceId ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val criteria = buildCriteriaFromContext(ctx, OpdsMangaFilter(sourceId = sourceId, primaryFilter = PrimaryFilterType.SOURCE)) val criteria = buildCriteriaFromContext(ctx, OpdsMangaFilter(sourceId = sourceId, primaryFilter = PrimaryFilterType.SOURCE))
getLibraryFeed(ctx, ctx.queryParam("pageNumber")?.toIntOrNull(), criteria, isSearch = false) getLibraryFeed(ctx, ctx.queryParam("pageNumber")?.toIntOrNull(), criteria, isSearch = false)
}, },
@@ -442,7 +442,7 @@ object OpdsV1Controller {
pathParam<Int>("categoryId"), pathParam<Int>("categoryId"),
documentWith = { withOperation { summary("OPDS Category Specific Series Feed") } }, documentWith = { withOperation { summary("OPDS Category Specific Series Feed") } },
behaviorOf = { ctx, categoryId -> behaviorOf = { ctx, categoryId ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val criteria = val criteria =
buildCriteriaFromContext(ctx, OpdsMangaFilter(categoryId = categoryId, primaryFilter = PrimaryFilterType.CATEGORY)) buildCriteriaFromContext(ctx, OpdsMangaFilter(categoryId = categoryId, primaryFilter = PrimaryFilterType.CATEGORY))
getLibraryFeed(ctx, ctx.queryParam("pageNumber")?.toIntOrNull(), criteria, isSearch = false) getLibraryFeed(ctx, ctx.queryParam("pageNumber")?.toIntOrNull(), criteria, isSearch = false)
@@ -461,7 +461,7 @@ object OpdsV1Controller {
pathParam<String>("genre"), pathParam<String>("genre"),
documentWith = { withOperation { summary("OPDS Genre Specific Series Feed") } }, documentWith = { withOperation { summary("OPDS Genre Specific Series Feed") } },
behaviorOf = { ctx, genre -> behaviorOf = { ctx, genre ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val criteria = buildCriteriaFromContext(ctx, OpdsMangaFilter(genre = genre, primaryFilter = PrimaryFilterType.GENRE)) val criteria = buildCriteriaFromContext(ctx, OpdsMangaFilter(genre = genre, primaryFilter = PrimaryFilterType.GENRE))
getLibraryFeed(ctx, ctx.queryParam("pageNumber")?.toIntOrNull(), criteria, isSearch = false) getLibraryFeed(ctx, ctx.queryParam("pageNumber")?.toIntOrNull(), criteria, isSearch = false)
}, },
@@ -479,7 +479,7 @@ object OpdsV1Controller {
pathParam<Int>("statusId"), pathParam<Int>("statusId"),
documentWith = { withOperation { summary("OPDS Status Specific Series Feed") } }, documentWith = { withOperation { summary("OPDS Status Specific Series Feed") } },
behaviorOf = { ctx, statusId -> behaviorOf = { ctx, statusId ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val criteria = buildCriteriaFromContext(ctx, OpdsMangaFilter(statusId = statusId, primaryFilter = PrimaryFilterType.STATUS)) val criteria = buildCriteriaFromContext(ctx, OpdsMangaFilter(statusId = statusId, primaryFilter = PrimaryFilterType.STATUS))
getLibraryFeed(ctx, ctx.queryParam("pageNumber")?.toIntOrNull(), criteria, isSearch = false) getLibraryFeed(ctx, ctx.queryParam("pageNumber")?.toIntOrNull(), criteria, isSearch = false)
}, },
@@ -502,7 +502,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, langCode -> behaviorOf = { ctx, langCode ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val criteria = val criteria =
buildCriteriaFromContext(ctx, OpdsMangaFilter(langCode = langCode, primaryFilter = PrimaryFilterType.LANGUAGE)) buildCriteriaFromContext(ctx, OpdsMangaFilter(langCode = langCode, primaryFilter = PrimaryFilterType.LANGUAGE))
getLibraryFeed(ctx, ctx.queryParam("pageNumber")?.toIntOrNull(), criteria, isSearch = false) getLibraryFeed(ctx, ctx.queryParam("pageNumber")?.toIntOrNull(), criteria, isSearch = false)
@@ -530,7 +530,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, seriesId, pageNumber, sort, filter, lang -> behaviorOf = { ctx, seriesId, pageNumber, sort, filter, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.future { ctx.future {
future { future {
@@ -561,7 +561,7 @@ object OpdsV1Controller {
} }
}, },
behaviorOf = { ctx, seriesId, chapterIndex, lang -> behaviorOf = { ctx, seriesId, chapterIndex, lang ->
ctx.getAttribute(Attribute.TachideskUser).requireUser() ctx.getAttribute(Attribute.TachideskUser).requireUserWithBasicFallback(ctx)
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang) val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
ctx.future { ctx.future {
future { future {

View File

@@ -11,10 +11,12 @@ import gg.jte.ContentType
import gg.jte.TemplateEngine import gg.jte.TemplateEngine
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.javalin.Javalin import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.after
import io.javalin.apibuilder.ApiBuilder.path import io.javalin.apibuilder.ApiBuilder.path
import io.javalin.http.Context import io.javalin.http.Context
import io.javalin.http.HandlerType import io.javalin.http.HandlerType
import io.javalin.http.HttpStatus import io.javalin.http.HttpStatus
import io.javalin.http.NotFoundResponse
import io.javalin.http.RedirectResponse import io.javalin.http.RedirectResponse
import io.javalin.http.UnauthorizedResponse import io.javalin.http.UnauthorizedResponse
import io.javalin.http.staticfiles.Location import io.javalin.http.staticfiles.Location
@@ -126,6 +128,14 @@ object JavalinSetup {
OpdsAPI.defineEndpoints() OpdsAPI.defineEndpoints()
GraphQL.defineEndpoints() GraphQL.defineEndpoints()
after { ctx ->
// If not matched, the request was for an invalid endpoint
// Return a 404 instead of redirecting to the UI for usability
if (ctx.endpointHandlerPath() == "*") {
throw NotFoundResponse()
}
}
} }
} }
} }
@@ -180,6 +190,7 @@ object JavalinSetup {
!ctx.path().substring(1).contains('/') && !ctx.path().substring(1).contains('/') &&
listOf(".png", ".jpg", ".ico").any { ctx.path().endsWith(it) } listOf(".png", ".jpg", ".ico").any { ctx.path().endsWith(it) }
val isPreFlight = ctx.method() == HandlerType.OPTIONS val isPreFlight = ctx.method() == HandlerType.OPTIONS
val isApi = ctx.path().startsWith("/api/")
val requiresAuthentication = !isPreFlight && !isPageIcon && !isWebManifest val requiresAuthentication = !isPreFlight && !isPageIcon && !isWebManifest
if (!requiresAuthentication) { if (!requiresAuthentication) {
@@ -200,11 +211,7 @@ object JavalinSetup {
return username == serverConfig.authUsername.value return username == serverConfig.authUsername.value
} }
if (authMode == AuthMode.SIMPLE_LOGIN && !cookieValid() && ctx.path().startsWith("/api")) { if (authMode == AuthMode.SIMPLE_LOGIN && !cookieValid() && !isApi) {
throw UnauthorizedResponse()
}
if (authMode == AuthMode.SIMPLE_LOGIN && !cookieValid()) {
val url = "/login.html?redirect=" + URLEncoder.encode(ctx.fullUrl(), Charsets.UTF_8) val url = "/login.html?redirect=" + URLEncoder.encode(ctx.fullUrl(), Charsets.UTF_8)
ctx.header("Location", url) ctx.header("Location", url)
throw RedirectResponse(HttpStatus.SEE_OTHER) throw RedirectResponse(HttpStatus.SEE_OTHER)
@@ -216,6 +223,7 @@ object JavalinSetup {
} }
ctx.setAttribute(Attribute.TachideskUser, getUserFromContext(ctx)) ctx.setAttribute(Attribute.TachideskUser, getUserFromContext(ctx))
ctx.setAttribute(Attribute.TachideskBasic, credentialsValid())
} }
app.events { event -> app.events { event ->
@@ -294,6 +302,8 @@ object JavalinSetup {
val name: String, val name: String,
) { ) {
data object TachideskUser : Attribute<UserType>("user") data object TachideskUser : Attribute<UserType>("user")
data object TachideskBasic : Attribute<Boolean>("basicAuthValid")
} }
private fun <T : Any> Context.setAttribute( private fun <T : Any> Context.setAttribute(

View File

@@ -5,6 +5,8 @@ import io.javalin.http.Header
import io.javalin.websocket.WsConnectContext import io.javalin.websocket.WsConnectContext
import suwayomi.tachidesk.global.impl.util.Jwt import suwayomi.tachidesk.global.impl.util.Jwt
import suwayomi.tachidesk.graphql.types.AuthMode import suwayomi.tachidesk.graphql.types.AuthMode
import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.getAttribute
import suwayomi.tachidesk.server.serverConfig import suwayomi.tachidesk.server.serverConfig
sealed class UserType { sealed class UserType {
@@ -21,11 +23,17 @@ fun UserType.requireUser(): Int =
UserType.Visitor -> throw UnauthorizedException() UserType.Visitor -> throw UnauthorizedException()
} }
fun getUserFromToken(token: String?): UserType { fun UserType.requireUserWithBasicFallback(ctx: Context): Int =
if (serverConfig.authMode.value != AuthMode.UI_LOGIN) { when (this) {
return UserType.Admin(1) is UserType.Admin -> id
UserType.Visitor if ctx.getAttribute(Attribute.TachideskBasic) -> 1
UserType.Visitor -> {
ctx.header("WWW-Authenticate", "Basic")
throw UnauthorizedException()
}
} }
fun getUserFromToken(token: String?): UserType {
if (token.isNullOrBlank()) { if (token.isNullOrBlank()) {
return UserType.Visitor return UserType.Visitor
} }
@@ -34,18 +42,42 @@ fun getUserFromToken(token: String?): UserType {
} }
fun getUserFromContext(ctx: Context): UserType { fun getUserFromContext(ctx: Context): UserType {
fun cookieValid(): Boolean {
val username = ctx.sessionAttribute<String>("logged-in") ?: return false
return username == serverConfig.authUsername.value
}
return when (serverConfig.authMode.value) {
// NOTE: Basic Auth is expected to have been validated by JavalinSetup
AuthMode.NONE, AuthMode.BASIC_AUTH -> UserType.Admin(1)
AuthMode.SIMPLE_LOGIN -> if (cookieValid()) UserType.Admin(1) else UserType.Visitor
AuthMode.UI_LOGIN -> {
val authentication = ctx.header(Header.AUTHORIZATION) ?: ctx.cookie("suwayomi-server-token") val authentication = ctx.header(Header.AUTHORIZATION) ?: ctx.cookie("suwayomi-server-token")
val token = authentication?.substringAfter("Bearer ") ?: ctx.queryParam("token") val token = authentication?.substringAfter("Bearer ") ?: ctx.queryParam("token")
return getUserFromToken(token) getUserFromToken(token)
}
}
} }
fun getUserFromWsContext(ctx: WsConnectContext): UserType { fun getUserFromWsContext(ctx: WsConnectContext): UserType {
fun cookieValid(): Boolean {
val username = ctx.sessionAttribute<String>("logged-in") ?: return false
return username == serverConfig.authUsername.value
}
return when (serverConfig.authMode.value) {
// NOTE: Basic Auth is expected to have been validated by JavalinSetup
AuthMode.NONE, AuthMode.BASIC_AUTH -> UserType.Admin(1)
AuthMode.SIMPLE_LOGIN -> if (cookieValid()) UserType.Admin(1) else UserType.Visitor
AuthMode.UI_LOGIN -> {
val authentication = val authentication =
ctx.header(Header.AUTHORIZATION) ?: ctx.header("Sec-WebSocket-Protocol") ?: ctx.cookie("suwayomi-server-token") ctx.header(Header.AUTHORIZATION) ?: ctx.header("Sec-WebSocket-Protocol") ?: ctx.cookie("suwayomi-server-token")
val token = authentication?.substringAfter("Bearer ") ?: ctx.queryParam("token") val token = authentication?.substringAfter("Bearer ") ?: ctx.queryParam("token")
return getUserFromToken(token) getUserFromToken(token)
}
}
} }
class UnauthorizedException : IllegalStateException("Unauthorized") class UnauthorizedException : IllegalStateException("Unauthorized")