Files
felhom-controller/controller/internal/web/r709_password_field_test.go
T
admin f037be7a6a
gates / gates (push) Successful in 48s
R-970: the box's hub key opens no route; Data tab leftovers; R-1103: the install form's dead branches
R-970: /api/config, /api/config/hash, /api/config/apply removed (they read and replaced controller.yaml with
every secret, reachable with the box's own hub API key, unsigned; nothing called them). The hub-key Bearer
door on /api/selfupdate/ and /api/geo/ and the matching CSRF exemption are gone; the dashboard keeps using
those with a session. The route table is buildMux, so a test drives the real one.

Data tab: the export password field is autocomplete=new-password with a non-login name; the 2nd copy's
status rows that repeated the form are gone (the no-target reason moves into the form).

R-1103: app_fields.html loses its installed-only branches (no page serves them since R-1095); the tests
that rendered that dead path are retired, their rules asserted on the settings tab (R-694 moved there).

Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
2026-10-16 18:24:00 +02:00

85 lines
3.9 KiB
Go

package web
import (
"bytes"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
)
// R-709 (v0.280.0) — an installed app's `type: password` value is never in its settings page's HTML; it is
// fetched on demand, like a `type: secret` (R-254). The pre-deploy form keeps its generator.
const testAdminPassword = "TESTONLY-admin-pw-Qz72"
func renderDeployWithPassword(t *testing.T, alreadyDeployed bool, restored bool) string {
t.Helper()
s := securityHarness(t)
s.loadTemplates()
data := map[string]interface{}{
"Page": "stacks", "Title": "Telepítés", "Domain": "example.hu",
"Stack": stacks.Stack{Name: "grafana", Deployed: alreadyDeployed},
"Meta": stacks.Metadata{DisplayName: "Grafana", Slug: "grafana"},
"AlreadyDeployed": alreadyDeployed,
"UserFields": []stacks.DeployField{
{EnvVar: "ADMIN_PASSWORD", Label: "Admin jelszó", Type: "password", Generate: "password:24:special"},
},
// The PRE-FIX handler shape: the stored value in DeployedFieldValues. The template must not print it
// even then (the handler now also drops it — TestR709_TheRevealEndpointServesAnInstalledPassword).
"DeployedFieldValues": map[string]string{"ADMIN_PASSWORD": testAdminPassword},
"RestoredLogins": map[string]bool{"ADMIN_PASSWORD": restored},
}
var buf bytes.Buffer
if err := s.tmpl.ExecuteTemplate(&buf, "deploy", data); err != nil {
t.Fatalf("render deploy: %v", err)
}
return buf.String()
}
// v0.322.0 (R-1103): the installed half of this test is retired — the install form no longer renders for an installed
// app (R-1095). „The password is not in the page, an eye fetches it" is asserted on the settings tab
// (TestAppPage_LoginPairPanel), and „no eye for a restore-generated login" too (TestR694_TheSettingsTabDoesNotOfferAGeneratedLogin).
// What stays here is the install form's own rule: it keeps its generator, with the field's spec.
func TestR709_TheInstallFormKeepsItsGenerator(t *testing.T) {
if html := renderDeployWithPassword(t, false, false); !strings.Contains(html, `generatePassword('field-ADMIN_PASSWORD', 'field-confirm-ADMIN_PASSWORD', 'password:24:special')`) {
t.Fatal("the install form lost its generator (or its spec)")
}
}
// The reveal endpoint serves an installed app's password field, refuses a restore-generated one and an unknown
// field. COMPANION RED-PROOF: drop the new password branch in appAutoFieldRevealHandler → the first case 403s.
func TestR709_TheRevealEndpointServesAnInstalledPassword(t *testing.T) {
s := gateHarness(t)
app := filepath.Join(s.cfg.Paths.StacksDir, "gapp")
if err := os.WriteFile(filepath.Join(app, ".felhom.yml"), []byte("display_name: G\nslug: gapp\ndeploy_fields:\n - env_var: ADMIN_PASSWORD\n type: password\n - env_var: OLD_PW\n type: password\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := stacks.SaveAppConfig(app, &stacks.AppConfig{Deployed: true, Env: map[string]string{"ADMIN_PASSWORD": testAdminPassword, "OLD_PW": "x"}, RestoredLogins: []string{"OLD_PW"}}, nil, nil); err != nil {
t.Fatal(err)
}
if err := s.stackMgr.ScanStacks(); err != nil {
t.Fatal(err)
}
ask := func(field string) *httptest.ResponseRecorder {
r := httptest.NewRequest(http.MethodPost, "/stacks/gapp/auto-field/reveal", strings.NewReader("env_var="+field))
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
w := httptest.NewRecorder()
s.appAutoFieldRevealHandler(w, r, "gapp")
return w
}
if w := ask("ADMIN_PASSWORD"); w.Code != http.StatusOK || !strings.Contains(w.Body.String(), testAdminPassword) {
t.Fatalf("installed password: %d %s", w.Code, w.Body.String())
}
if w := ask("OLD_PW"); w.Code != http.StatusForbidden {
t.Fatalf("a restore-generated login was revealed: %d", w.Code)
}
if w := ask("NOPE"); w.Code != http.StatusForbidden {
t.Fatalf("an unknown field: %d", w.Code)
}
}