- auth.Mount(env, key) wraps csrf.Protect with locked D-14/D-24 options - auth.LoadKeyFromEnv() reads SESSION_SECRET, hex-decodes, validates 32 bytes; fails fast on error - ui.CSRFField(token) templ component renders hidden _csrf input - Layout, LoginPage/Fragment, SignupPage/Fragment, Index all embed @ui.CSRFField(csrfToken) - Handlers thread csrf.Token(r) into every page/fragment render call - NewRouter mounts auth.Mount after ResolveSession, before all route groups (D-24) - main.go calls auth.LoadKeyFromEnv(); logs.Fatalf on missing/invalid SESSION_SECRET - SESSION_SECRET documented in .env.example with openssl rand -hex 32 instruction - go.mod: gorilla/csrf v1.7.3 (direct); prior tests updated with getCSRFToken helper - All Plan 04/05/06 tests updated to acquire and submit valid _csrf tokens
66 lines
2.1 KiB
Go
66 lines
2.1 KiB
Go
package templates
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestSignupPage_RendersForm verifies the full SignupPage output contains the
|
|
// expected form attributes and that email value round-trips correctly.
|
|
func TestSignupPage_RendersForm(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
err := SignupPage(SignupForm{Email: "x@y.z"}, SignupErrors{}, "testtoken").Render(context.Background(), &buf)
|
|
if err != nil {
|
|
t.Fatalf("SignupPage.Render: %v", err)
|
|
}
|
|
body := buf.String()
|
|
|
|
for _, want := range []string{
|
|
`name="email"`,
|
|
`name="password"`,
|
|
`action="/signup"`,
|
|
`hx-post="/signup"`,
|
|
`value="x@y.z"`,
|
|
`name="_csrf"`,
|
|
} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("SignupPage body missing %q", want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestSignupFormFragment_RendersErrors verifies that SignupFormFragment renders
|
|
// field-specific error messages and does NOT include a full <html> tag (it is
|
|
// a fragment, not a complete page).
|
|
func TestSignupFormFragment_RendersErrors(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
errs := SignupErrors{Password: "Password must be 12-128 characters"}
|
|
err := SignupFormFragment(SignupForm{}, errs, "testtoken").Render(context.Background(), &buf)
|
|
if err != nil {
|
|
t.Fatalf("SignupFormFragment.Render: %v", err)
|
|
}
|
|
body := buf.String()
|
|
|
|
if !strings.Contains(body, "Password must be 12-128 characters") {
|
|
t.Errorf("fragment missing error message; body: %s", body)
|
|
}
|
|
if strings.Contains(body, "<html") {
|
|
t.Errorf("fragment must not contain <html> tag; got full page")
|
|
}
|
|
}
|
|
|
|
// TestSignupPage_DoesNotEchoPassword verifies that the password value is never
|
|
// reflected back into any rendered HTML — even when form.Password is set
|
|
// (security requirement T-2-01, D-25).
|
|
func TestSignupPage_DoesNotEchoPassword(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
err := SignupPage(SignupForm{Email: "a@b.com", Password: "hunter2hunter2"}, SignupErrors{}, "testtoken").Render(context.Background(), &buf)
|
|
if err != nil {
|
|
t.Fatalf("SignupPage.Render: %v", err)
|
|
}
|
|
if strings.Contains(buf.String(), "hunter2") {
|
|
t.Errorf("SignupPage must not echo back the password value")
|
|
}
|
|
}
|