package web import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/crypto" "gitea.dooplex.hu/admin/felhom-controller/internal/settings" "gitea.dooplex.hu/admin/felhom-controller/internal/stacks" ) // R-513 — the file manager's login is shown where app logins are, under the R-254 rule: the value is // never in the page, only behind the reveal act. const fbTestPW = "TESTONLYfbPw7Kq2" func renderFBPage(t *testing.T, operatorSet bool) string { t.Helper() s := securityHarness(t) s.loadTemplates() data := map[string]interface{}{ "Page": "stacks", "Title": "FileBrowser", "Domain": "example.hu", "Stack": stacks.Stack{Name: "filebrowser", Deployed: true, State: "running"}, "Meta": stacks.Metadata{DisplayName: "FileBrowser", Slug: "filebrowser"}, "HasAppInfo": true, "InitialCreds": &stacks.ExtractedCreds{Available: true, Username: "admin"}, } if operatorSet { data["InitialCredsOperatorSet"] = true } else { data["InitialCredsHasPassword"] = true } var buf bytes.Buffer if err := s.tmpl.ExecuteTemplate(&buf, "app_info", data); err != nil { t.Fatalf("render: %v", err) } return buf.String() } func TestFileBrowserLoginCard_Generated(t *testing.T) { html := renderFBPage(t, false) if !strings.Contains(html, "initial-credentials/reveal") || !strings.Contains(html, "admin") { t.Fatal("generated state: the login card has no reveal call or no username") } if strings.Contains(html, "zemeltet") { // „üzemeltető" — ASCII fragment t.Fatal("generated state shows the operator-set text") } } // RED-PROOF (run 2026-09-15, recorded in REPORT.md): with the {{if .InitialCredsOperatorSet}} branch // removed from app_info.html, the operator page rendered the reveal button and this failed at // "operator state still offers a reveal". func TestFileBrowserLoginCard_OperatorSet(t *testing.T) { html := renderFBPage(t, true) if !strings.Contains(html, "zemeltet") { t.Fatal("operator state does not say the operator set the password") } if strings.Contains(html, `id="initcred-reveal"`) { t.Fatal("operator state still offers a reveal for a password the Felhom does not hold") } } func revealFB(t *testing.T, s *Server) (int, map[string]any) { t.Helper() w := httptest.NewRecorder() s.fileBrowserPasswordReveal(w, httptest.NewRequest(http.MethodPost, "/apps/filebrowser/initial-credentials/reveal", nil)) var body map[string]any _ = json.Unmarshal(w.Body.Bytes(), &body) return w.Code, body } func TestFileBrowserPasswordReveal(t *testing.T) { s := securityHarness(t) s.encKey = bytes.Repeat([]byte{7}, 32) if code, _ := revealFB(t, s); code != http.StatusNotFound { t.Fatalf("undecided: want 404, got %d", code) } _ = s.settings.SetFileBrowserAdmin(settings.FileBrowserAdminOperator, "", "2026-09-15T00:00:00Z") if code, _ := revealFB(t, s); code != http.StatusNotFound { t.Fatalf("operator-set: want 404, got %d", code) } enc, err := crypto.Encrypt(s.encKey, fbTestPW) if err != nil { t.Fatal(err) } _ = s.settings.SetFileBrowserAdmin(settings.FileBrowserAdminGenerated, enc, "2026-09-15T00:00:00Z") code, body := revealFB(t, s) data, _ := body["data"].(map[string]any) if code != http.StatusOK || data["password"] != fbTestPW { t.Fatalf("generated: want 200 + the decrypted password, got %d %v", code, body) } }