web ui: New setting for browser-based login

This is useful in cases where OIDC login is active
which would then redirect the user to a central
login page.

See https://github.com/Kozea/Radicale/issues/2143
This commit is contained in:
Max Berger
2026-05-27 19:36:05 +02:00
parent 10983fc924
commit 19f9d0a8c7
7 changed files with 51 additions and 6 deletions

View File

@@ -1699,6 +1699,15 @@ Available backends are:
Default: `internal`
##### prefer_browser_login _(>= 3.7.4)_
Try to use browser based login rather than the built in login in the web UI. This
can be helpful if the login system you are using uses redirects, such as in
an OIDC setup. For this to work properly, you will also have to make sure CORS
headers are configured correctly.s
Default: `False`
#### [logging]
##### level

3
config
View File

@@ -363,6 +363,9 @@
# Value: none | internal
#type = internal
# Prefer browser login
#prefer_browser_login = False
[logging]

View File

@@ -691,7 +691,11 @@ This is an automated message. Please do not reply.""",
"value": "internal",
"help": "web interface backend",
"type": str_or_callable,
"internal": web.INTERNAL_TYPES})])),
"internal": web.INTERNAL_TYPES}),
("prefer_browser_login", {
"value": "False",
"help": "prefer browser login",
"type": bool})])),
("logging", OrderedDict([
("level", {
"value": "info",

View File

@@ -31,6 +31,11 @@ class TestBaseWebRequests(BaseTest):
_, answer = self.get("/.web/")
assert answer
self.post("/.web", check=405)
_, answer = self.get("/.web/js/config.js")
assert "export const PREFER_BROWSER_LOGIN = false;" in answer
self.configure({"web": {"prefer_browser_login": "True"}})
_, answer = self.get("/.web/js/config.js")
assert "export const PREFER_BROWSER_LOGIN = true;" in answer
def test_none(self) -> None:
self.configure({"web": {"type": "none"}})

View File

@@ -34,7 +34,27 @@ FALLBACK_MIMETYPE = httputils.FALLBACK_MIMETYPE # deprecated
class Web(web.BaseWeb):
def get(self, environ: types.WSGIEnviron, base_prefix: str, path: str,
user: str, request_info: dict) -> types.WSGIResponse:
return httputils.serve_resource("radicale.web", "internal_data",
base_prefix, path)
def get(
self,
environ: types.WSGIEnviron,
base_prefix: str,
path: str,
user: str,
request_info: dict,
) -> types.WSGIResponse:
if path == "/.web/js/config.js":
prefer_browser_login = (
"true"
if self.configuration.get("web", "prefer_browser_login")
else "false"
)
content = (
f"export const PREFER_BROWSER_LOGIN = {prefer_browser_login};\n".encode(
"utf-8"
)
)
headers = {"Content-Type": "application/javascript"}
return 200, headers, content, None
return httputils.serve_resource(
"radicale.web", "internal_data", base_prefix, path
)

View File

@@ -0,0 +1,3 @@
// This file is dynamically overwritten during serving. The contents here are just to enable development an testing.
export const PREFER_BROWSER_LOGIN = false;

View File

@@ -20,6 +20,7 @@
*/
import { get_principal } from "../api/api.js";
import { PREFER_BROWSER_LOGIN } from "../config.js";
import { ROOT_PATH, SERVER } from "../constants.js";
import { Collection } from "../models/collection.js";
import { extract_title, extractUsernameFromPrincipalCollection } from "../utils/collection_utils.js";
@@ -167,7 +168,7 @@ export class LoginScene {
fetch(SERVER + ROOT_PATH, {
method: 'PROPFIND',
headers: { 'Depth': '0' },
credentials: 'omit'
credentials: PREFER_BROWSER_LOGIN ? 'include' : 'omit'
}).then((response) => {
if (response.ok) {
// Authenticated! Now it's safe to call get_principal