Files
felhom-controller/controller/internal/web/filebrowser_login_test.go
T

97 lines
3.3 KiB
Go

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)
}
}