v0.332.0: one sign-in for the file manager — a dashboard pass, a host-only files cookie, a per-box secret header (R-1100, operator D16)
gates / gates (push) Successful in 47s

This commit is contained in:
2026-10-19 04:56:56 +02:00
parent ba4a6f060b
commit 9bdd8f8fe8
21 changed files with 1078 additions and 54 deletions
+49 -14
View File
@@ -264,23 +264,35 @@ networks:
// v0.327.1). The name resolves only while FileBrowser is on traefik-public, which the box allows only after this run's
// default login was checked — so the route can stay in place and still never reach an unchecked FileBrowser (502 then).
// TLS as the old Docker label had it (`tls: {}`, the default certificate store / the wildcard).
func RenderFileBrowserRoute(domain string) string {
//
// R-1100: with ssoHeader set, every request to files.<domain> first asks the controller (forwardAuth, FilesAuthURL).
// traefik removes ssoHeader from the client's request whatever the answer (it is in authResponseHeaders — measured,
// any case, twice: audits/proxyauth-spike-2026-10-18) and sets it only from the controller's 200, which carries it only
// for a browser holding the dashboard's files pass. The same answer handles /_felhom/sso (the pass → the files cookie).
// ssoHeader "" = the route as before, byte for byte. Pinned by TestFileBrowserRoute_R1100_*.
func RenderFileBrowserRoute(domain, ssoHeader string) string {
mw, mwDef := "", ""
if ssoHeader != "" {
mw = " middlewares:\n - " + FilesSSOMiddleware + "\n"
mwDef = fmt.Sprintf(" middlewares:\n %s:\n forwardAuth:\n address: %q\n authResponseHeaders:\n - %q\n",
FilesSSOMiddleware, FilesAuthURL, ssoHeader)
}
return fmt.Sprintf(`# Traefik dynamic route for FileBrowser (files.<domain>) — managed by felhom-controller (R-1126).
# WARNING: auto-generated at base-infra bring-up. Manual edits are overwritten.
http:
routers:
%s routers:
filebrowser:
rule: "Host(`+"`"+`files.%s`+"`"+`)"
entryPoints:
- websecure
service: filebrowser
tls: {}
%s tls: {}
services:
filebrowser:
loadBalancer:
servers:
- url: "http://filebrowser:80"
`, domain)
`, mwDef, domain, mw)
}
// RenderControllerRoute returns a traefik file-provider dynamic config routing the controller's own
@@ -420,12 +432,13 @@ http:
// storage path (each a named sidebar entry). Empty paths → a single default /srv source. Ported
// verbatim from internal/web/handlers.go.
func RenderFileBrowserConfig(paths []settings.StoragePath, importSource bool) string {
return RenderFileBrowserConfigKept(paths, importSource, "")
return RenderFileBrowserConfigKept(paths, importSource, "", FileBrowserSSO{})
}
// RenderFileBrowserConfigKept is RenderFileBrowserConfig plus the read-only kept-data source, LAST,
// named keptLabel ("" = no such source: nothing is kept, or the caller predates it).
func RenderFileBrowserConfigKept(paths []settings.StoragePath, importSource bool, keptLabel string) string {
// named keptLabel ("" = no such source: nothing is kept, or the caller predates it), and the household's header
// sign-in (R-1100; a zero sso = none, the config byte for byte as before).
func RenderFileBrowserConfigKept(paths []settings.StoragePath, importSource bool, keptLabel string, sso FileBrowserSSO) string {
var sources string
// The canonical drop-zone (R-75) is FIRST and is NOT a registered storage path — it is a separate
// bind of <system namespace>/userdata/import. Separate rather than nested inside a drive source:
@@ -471,7 +484,7 @@ server:
logging:
- levels: "info|warning|error"
sources:
%suserDefaults:
%s%suserDefaults:
stickySidebar: true
darkMode: true
viewMode: "normal"
@@ -490,7 +503,23 @@ server:
autoplayMedia: true
folder: true
permissions:
api: false
%sfrontend:
# No „Install app" (PWA) prompt in the sidebar: the file manager is opened from the dashboard (v0.321.0;
# structs.go:294, sent to the page at backend/http/static.go:300).
disablePWAInstall: true
`, sources, renderFileBrowserAuth(sso), fileBrowserDefaultPermissions(sso))
}
// fileBrowserDefaultPermissions is userDefaults.permissions — what FileBrowser gives a user it creates. With the header
// sign-in, the one user it creates on its own is the household's header user (FileBrowserSSOUser, on its first sign-in),
// and that user must be able to do with the files what the household does as `admin`: change, add, delete, share,
// download — but not run FileBrowser itself (admin) or its API keys (api). FileBrowser has no per-user setting for a
// header user; the defaults are the only place the box can set its rights (R-1100). A user the household adds by hand
// in FileBrowser starts from the same ticks and the household can change them there. Without the header sign-in: the
// old download-only defaults, unchanged. Pinned by TestFileBrowserConfig_R1100_HeaderUserGetsTheHouseholdsRights.
func fileBrowserDefaultPermissions(sso FileBrowserSSO) string {
if sso.Header == "" {
return ` api: false
admin: false
modify: false
share: false
@@ -498,9 +527,15 @@ server:
delete: false
create: false
download: true
frontend:
# No „Install app" (PWA) prompt in the sidebar: the file manager is opened from the dashboard (v0.321.0;
# structs.go:294, sent to the page at backend/http/static.go:300).
disablePWAInstall: true
`, sources)
`
}
return ` api: false
admin: false
modify: true
share: true
realtime: true
delete: true
create: true
download: true
`
}