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