package infra import ( "strings" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/settings" ) const testSSOHeader = "X-Felhom-User-0123456789abcdef0123456789abcdef" // With the header sign-in FileBrowser trusts THIS box's secret header name (never the old public X-Felhom-User), keeps // password sign-in on (admin: the fallback and the operator's way), and sends a header user's logout to the dashboard. // Without it the config is the pre-R-1100 one. // // COMPANION RED-PROOF (observed 2026-10-19): render renderFileBrowserAuth's header as "X-Felhom-User" → this fails with // `the auth block lacks "header: \"X-Felhom-User-0123…\""`. Restored. func TestFileBrowserConfig_R1100_TrustsOnlyTheBoxSecret(t *testing.T) { sso := FileBrowserSSO{Header: testSSOHeader, LogoutURL: "https://felhom.example.hu/"} c := RenderFileBrowserConfigKept([]settings.StoragePath{{Path: "/mnt/felhom-drives/hdd_1", Label: "HDD"}}, true, "", sso) block := c[strings.Index(c, "\nauth:\n"):] block = block[:strings.Index(block, "\nuserDefaults:")] for _, want := range []string{" password:\n enabled: true", " proxy:\n enabled: true", `header: "` + testSSOHeader + `"`, `logoutRedirectUrl: "https://felhom.example.hu/"`} { if !strings.Contains(block, want) { t.Fatalf("the auth block lacks %q:\n%s", want, block) } } if strings.Count(c, "header:") != 1 || strings.Contains(c, `header: "X-Felhom-User"`) { t.Fatal("FileBrowser trusts a header name other than the box's secret") } plain := RenderFileBrowserConfigKept([]settings.StoragePath{{Path: "/mnt/felhom-drives/hdd_1", Label: "HDD"}}, true, "", FileBrowserSSO{}) if strings.Contains(plain, "auth:") || strings.Contains(plain, "X-Felhom-User") { t.Fatal("without the header sign-in the config must carry no auth block") } } // The household's header user is created by FileBrowser from userDefaults, so with the header sign-in the defaults are // the household's file rights (not FileBrowser's admin or API); without it, the old download-only defaults. // // COMPANION RED-PROOF (observed 2026-10-19): return the download-only block for both cases → this fails with "the // header user could not change files: modify false". Restored. func TestFileBrowserConfig_R1100_HeaderUserGetsTheHouseholdsRights(t *testing.T) { with := RenderFileBrowserConfigKept(nil, false, "", FileBrowserSSO{Header: testSSOHeader, LogoutURL: "https://felhom.example.hu/"}) perms := with[strings.Index(with, " permissions:\n"):strings.Index(with, "frontend:")] for _, k := range []string{"modify", "share", "delete", "create", "download", "realtime"} { if !strings.Contains(perms, " "+k+": true\n") { t.Fatalf("the header user could not change files: %s false\n%s", k, perms) } } for _, k := range []string{"admin", "api"} { if !strings.Contains(perms, " "+k+": false\n") { t.Fatalf("the header user must not get %s", k) } } without := RenderFileBrowserConfigKept(nil, false, "", FileBrowserSSO{}) if !strings.Contains(without, " modify: false\n") || !strings.Contains(without, " download: true\n") { t.Fatal("without the header sign-in the old download-only defaults must stay") } } // The route asks the controller before FileBrowser and names the header in authResponseHeaders — which is what makes // traefik remove a client's copy of it (measured, audits/proxyauth-spike-2026-10-18). Without the header sign-in: the // old route, no middleware. // // COMPANION RED-PROOF (observed 2026-10-19): drop the router's `middlewares:` line in RenderFileBrowserRoute → this fails // with "the files router does not ask the controller first". Restored. func TestFileBrowserRoute_R1100_AsksTheControllerAndStripsTheHeader(t *testing.T) { r := RenderFileBrowserRoute("example.hu", testSSOHeader) router := r[strings.Index(r, " filebrowser:\n rule:"):strings.Index(r, " services:")] if !strings.Contains(router, " middlewares:\n - "+FilesSSOMiddleware+"\n") { t.Fatalf("the files router does not ask the controller first:\n%s", router) } for _, want := range []string{" " + FilesSSOMiddleware + ":\n forwardAuth:\n", `address: "` + FilesAuthURL + `"`, "authResponseHeaders:\n - \"" + testSSOHeader + "\""} { if !strings.Contains(r, want) { t.Fatalf("the route lacks %q:\n%s", want, r) } } if plain := RenderFileBrowserRoute("example.hu", ""); strings.Contains(plain, "middlewares") || strings.Contains(plain, "X-Felhom-User") { t.Fatalf("without the header sign-in the route must be the old one:\n%s", plain) } }