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() } // COMPANION RED-PROOF: put `value="{{index $.DeployedFieldValues .EnvVar}}"` back on the deployed password input // → the first assertion fails with the password in the body. func TestR709_AnInstalledAppsPasswordIsNotInThePage(t *testing.T) { html := renderDeployWithPassword(t, true, false) if strings.Contains(html, testAdminPassword) { t.Fatal("R-709: the installed app's admin password is in the HTML of its settings page") } if !strings.Contains(html, `onclick="revealPasswordField('grafana', 'ADMIN_PASSWORD', this)"`) { t.Fatal("no reveal control: the household cannot see its own password") } // A login a restore generated is not the app's login (R-694): no reveal for it. if html := renderDeployWithPassword(t, true, true); strings.Contains(html, `revealPasswordField('grafana'`) || strings.Contains(html, testAdminPassword) { t.Fatal("a restore-generated login is offered for reveal") } // The pre-deploy form keeps its generator, with the field's own spec. 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) } }