xtablo-source/backend/templates/auth_signup.templ
Arthur Belleville 389e1bc8b4
feat(02-07): gorilla/csrf integration — mount middleware, wire all forms, env-driven key
- 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
2026-05-14 22:59:06 +02:00

72 lines
2.2 KiB
Text

package templates
import "backend/internal/web/ui"
// SignupPage renders the full /signup page wrapped in the base Layout.
// It delegates the form section to SignupFormFragment so HTMX can swap just the
// form on validation errors without re-rendering the surrounding shell.
templ SignupPage(form SignupForm, errs SignupErrors, csrfToken string) {
@Layout("Sign up", nil, csrfToken) {
<div class="flex min-h-[60vh] items-start justify-center pt-16">
@ui.Card(nil) {
<div class="w-full max-w-sm px-6 py-8">
<h1 class="mb-6 text-2xl font-semibold">Create your account</h1>
@SignupFormFragment(form, errs, csrfToken)
</div>
}
</div>
}
}
// SignupFormFragment is the bare form used for HTMX swaps.
// hx-post targets this component itself so the form can be replaced inline
// on validation failure (D-19, D-25).
// The outer id="signup-form" must match the hx-target on this element.
templ SignupFormFragment(form SignupForm, errs SignupErrors, csrfToken string) {
<form
id="signup-form"
method="POST"
action="/signup"
hx-post="/signup"
hx-target="#signup-form"
hx-swap="outerHTML"
class="space-y-5"
>
@ui.CSRFField(csrfToken)
@GeneralError(errs.General)
<div>
<label for="email" class="block text-sm font-medium text-slate-700">Email address</label>
<input
id="email"
type="email"
name="email"
value={ form.Email }
required
autocomplete="email"
class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm placeholder-slate-400 focus:border-slate-500 focus:outline-none"
placeholder="you@example.com"
/>
@FieldError(errs.Email)
</div>
<div>
<label for="password" class="block text-sm font-medium text-slate-700">Password</label>
<input
id="password"
type="password"
name="password"
required
autocomplete="new-password"
class="mt-1 block w-full rounded border border-slate-300 px-3 py-2 text-sm placeholder-slate-400 focus:border-slate-500 focus:outline-none"
placeholder="12 characters minimum"
/>
@FieldError(errs.Password)
</div>
@ui.Button(ui.ButtonProps{
Label: "Create account",
Variant: ui.ButtonVariantDefault,
Tone: ui.ButtonToneSolid,
Size: ui.SizeMD,
Type: "submit",
})
</form>
}