xtablo-source/.planning/phases/02-authentication/02-04-SUMMARY.md
Arthur Belleville f6a453ff1f
docs(02-04): complete signup vertical slice plan
- Add 02-04-SUMMARY.md: signup form + handler + integration tests
- Update STATE.md: plan 04 complete (4/7 in phase 2), plan 05 next
- Update ROADMAP.md: 02-04 checked off, phase 2 progress 4/7
2026-05-14 22:20:09 +02:00

8.2 KiB

phase plan subsystem tags requires provides affects tech-stack key-files key-decisions patterns-established requirements-completed duration completed
02-authentication 04 auth
go
htmx
templ
signup
argon2id
sessions
postgres
sqlc
chi
phase provides
02-authentication/01 migrations + sqlc InsertUser/GetUserByEmail
phase provides
02-authentication/02 auth.Hash, auth.Verify, DefaultParams, TestParams
phase provides
02-authentication/03 auth.Store.Create, SetSessionCookie, ResolveSession, RedirectIfAuthed
GET /signup renders SignupPage with email+password form
POST /signup: validate -> argon2id hash -> InsertUser -> Store.Create -> set cookie -> 303 redirect
POST /signup HTMX path: 200 + HX-Redirect: /
POST /signup validation errors render inline (HTMX) or full page (no-JS)
Duplicate email detected via pgconn.PgError code 23505
Authed users GET /signup -> 303 Location: / (RedirectIfAuthed)
ResolveSession wired into chi middleware stack (D-24 order)
SignupForm + SignupErrors types in templates package
FieldError + GeneralError templ error primitives
02-05-login
02-06-protected-routes
02-07-csrf
added patterns
SignupForm/SignupErrors defined in templates package (not handlers) to avoid import cycle between templates and internal/web
Test helper (setupTestDB) duplicated into web package test file; no cross-package import of _test.go files
Pre-seed test users via InsertUser + auth.TestParams (not handler POST) to avoid 250ms argon2 DefaultParams cost in setup
nil-Store guard in ResolveSession for Phase 1 unit-test compatibility with AuthDeps{} zero value
renderSignupError helper: HTMX path renders fragment, non-HTMX renders full page (D-19)
created modified
backend/templates/auth_forms.go
backend/templates/auth_form_errors.templ
backend/templates/auth_signup.templ
backend/templates/auth_signup_test.go
backend/internal/web/handlers_auth.go
backend/internal/web/handlers_auth_test.go
backend/internal/web/testdb_test.go
backend/internal/web/router.go
backend/internal/web/handlers_test.go
backend/cmd/web/main.go
backend/internal/auth/middleware.go
SignupForm/SignupErrors defined in templates package (auth_forms.go) — avoids import cycle; handlers import templates package directly
setupTestDB duplicated into web package test file (~100 LOC) — Go does not allow importing _test.go files across packages; duplication chosen over moving infra into production code
POST /signup outside RedirectIfAuthed group — GET guard handles common case; authed POST gets a response rather than silent 303
nil-Store guard added to auth.ResolveSession — enables Phase 1 unit tests to pass AuthDeps{} zero value without panic
Pattern: templ form types in templates package sibling .go file
Pattern: renderXxxError helper selects fragment vs full page by HX-Request header
Pattern: test pre-seed with TestParams hash via direct sqlc call (not handler POST)
AUTH-01
AUTH-03
AUTH-05
25min 2026-05-14

Phase 2 Plan 04: Signup Vertical Slice Summary

Signup flow end-to-end: templ form + POST handler + argon2id hash + InsertUser + Store.Create + session cookie + 303 redirect wired into chi with ResolveSession (D-24)

Performance

  • Duration: ~25 min
  • Started: 2026-05-14T22:16:00Z
  • Completed: 2026-05-14T22:18:30Z
  • Tasks: 2
  • Files modified: 11

Accomplishments

  • Signup form (full page + HTMX fragment) renders with email/password fields; email round-trips; password never echoed
  • POST /signup handler validates input, hashes with argon2id DefaultParams, inserts user, creates session, sets cookie, redirects
  • HTMX fast-path returns 200 + HX-Redirect:/; non-JS path returns 303 See Other
  • Duplicate email (pgconn 23505) renders "already in use" inline; no second row inserted
  • Authed users hitting GET /signup bounce to / (RedirectIfAuthed)
  • ResolveSession wired at position 5 in middleware stack per D-24
  • 8 integration tests all pass against real Postgres; Phase 1 unit tests unaffected

Task Commits

  1. Task 1: Signup templates + render smoke tests - 73935ed (feat)
  2. Task 2: SignupPostHandler + router wiring + integration tests - efdc16b (feat)

Files Created/Modified

  • backend/templates/auth_forms.go - SignupForm and SignupErrors types (in templates pkg to avoid import cycle)
  • backend/templates/auth_form_errors.templ - FieldError and GeneralError templ primitives
  • backend/templates/auth_signup.templ - SignupPage (full) + SignupFormFragment (HTMX swap target)
  • backend/templates/auth_signup_test.go - 3 render smoke tests (form renders, error renders, password not echoed)
  • backend/internal/web/handlers_auth.go - SignupPageHandler, SignupPostHandler, AuthDeps, renderSignupError
  • backend/internal/web/handlers_auth_test.go - 8 TestSignup_* integration tests
  • backend/internal/web/testdb_test.go - setupTestDB helper (duplicated from auth package)
  • backend/internal/web/router.go - NewRouter updated: accepts AuthDeps, mounts ResolveSession + signup routes
  • backend/internal/web/handlers_test.go - Pass AuthDeps{} to NewRouter (Phase 1 tests updated)
  • backend/cmd/web/main.go - Build AuthDeps (sqlc.Queries + auth.Store + secure flag), pass to NewRouter
  • backend/internal/auth/middleware.go - nil-Store guard in ResolveSession for Phase 1 test compatibility

Decisions Made

Where SignupForm/SignupErrors live: Templates package (backend/templates/auth_forms.go). The handler file imports the templates package; defining types in templates avoids a circular dependency (templates importing internal/web). Handlers reference these types as templates.SignupForm, templates.SignupErrors.

Test helper strategy: Duplicated setupTestDB (~100 LOC) into backend/internal/web/testdb_test.go. Go does not allow importing _test.go files across packages. The duplication is minimal and keeps test infra out of production code paths. Alternative (shared internal/testhelpers package) would put infra in a non-test file.

POST /signup outside RedirectIfAuthed group: The GET /signup route is inside the RedirectIfAuthed group. POST /signup is intentionally outside because an authed user directly submitting the form should get a proper error response rather than a silent 303 that could confuse tooling.

nil-Store guard in ResolveSession: Phase 1 unit tests in handlers_test.go call NewRouter(stubPinger{}, "./static", AuthDeps{}) with a zero-value AuthDeps (nil Store). Without the nil guard, ResolveSession would panic on cookie lookup. The guard is a no-op pass-through when Store == nil.

Deviations from Plan

Auto-fixed Issues

1. [Rule 2 - Missing Critical] nil-Store guard added to auth.ResolveSession

  • Found during: Task 2 (updating handlers_test.go to pass AuthDeps{})
  • Issue: Phase 1 tests pass AuthDeps{} zero value; ResolveSession would panic on store.Lookup() call if any cookie was present (or if future tests hit that path)
  • Fix: Added if store == nil { next.ServeHTTP(w, r); return } at the top of the returned handler closure
  • Files modified: backend/internal/auth/middleware.go
  • Verification: Phase 1 tests pass with AuthDeps{} zero value; all Phase 2 signup tests pass with real store
  • Committed in: efdc16b

Total deviations: 1 auto-fixed (Rule 2 — missing critical nil guard) Impact on plan: Essential for backward compatibility with Phase 1 unit tests. No scope creep.

Issues Encountered

None — plan executed cleanly.

Known Stubs

None — the signup form is fully wired end-to-end with real DB operations.

Next Phase Readiness

  • Plan 05 (login handler) can now build on the same AuthDeps pattern and templ primitives
  • SignupFormFragment and FieldError/GeneralError primitives are ready for reuse in the login form
  • ResolveSession is live in the middleware stack; RequireAuth is implemented (Plan 03) and ready for Plan 06 protected routes
  • auth.TestParams + direct InsertUser pre-seeding pattern is established for future tests

Phase: 02-authentication Completed: 2026-05-14