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
This commit is contained in:
parent
efdc16babe
commit
f6a453ff1f
3 changed files with 171 additions and 10 deletions
|
|
@ -13,7 +13,7 @@
|
||||||
| # | Phase | Goal | Requirements |
|
| # | Phase | Goal | Requirements |
|
||||||
|---|-------|------|--------------|
|
|---|-------|------|--------------|
|
||||||
| 1 | Foundation | Fresh `backend/` Go package boots, renders HTMX, talks to Postgres | FOUND-01..05 |
|
| 1 | Foundation | Fresh `backend/` Go package boots, renders HTMX, talks to Postgres | FOUND-01..05 |
|
||||||
| 2 | 3/7 | In Progress| |
|
| 2 | 4/7 | In Progress| |
|
||||||
| 3 | Tablos CRUD | An authenticated user can manage their tablos end-to-end | TABLO-01..06 |
|
| 3 | Tablos CRUD | An authenticated user can manage their tablos end-to-end | TABLO-01..06 |
|
||||||
| 4 | Tasks (Kanban) | A user can run a kanban board inside a tablo | TASK-01..07 |
|
| 4 | Tasks (Kanban) | A user can run a kanban board inside a tablo | TASK-01..07 |
|
||||||
| 5 | Files | A user can attach, list, download, delete files on a tablo | FILE-01..06 |
|
| 5 | Files | A user can attach, list, download, delete files on a tablo | FILE-01..06 |
|
||||||
|
|
@ -58,12 +58,12 @@ Plans:
|
||||||
|
|
||||||
**User-in-loop:** Approve the `users` and `sessions` table schemas (columns, indexes, deletion semantics) before sqlc generation. Approve hash algorithm choice.
|
**User-in-loop:** Approve the `users` and `sessions` table schemas (columns, indexes, deletion semantics) before sqlc generation. Approve hash algorithm choice.
|
||||||
|
|
||||||
**Plans:** 3/7 plans executed
|
**Plans:** 4/7 plans executed
|
||||||
Plans:
|
Plans:
|
||||||
- [x] 02-01-PLAN.md — Schema + sqlc + auth-package skeleton (citext + users + sessions, test DB harness)
|
- [x] 02-01-PLAN.md — Schema + sqlc + auth-package skeleton (citext + users + sessions, test DB harness)
|
||||||
- [x] 02-02-PLAN.md — argon2id password hashing (TDD: Hash/Verify with PHC encoding)
|
- [x] 02-02-PLAN.md — argon2id password hashing (TDD: Hash/Verify with PHC encoding)
|
||||||
- [x] 02-03-PLAN.md — Session store + cookie + ResolveSession/RequireAuth/RedirectIfAuthed middleware
|
- [x] 02-03-PLAN.md — Session store + cookie + ResolveSession/RequireAuth/RedirectIfAuthed middleware
|
||||||
- [ ] 02-04-PLAN.md — Signup vertical slice (form → validate → hash → InsertUser → session → cookie → redirect)
|
- [x] 02-04-PLAN.md — Signup vertical slice (form → validate → hash → InsertUser → session → cookie → redirect)
|
||||||
- [ ] 02-05-PLAN.md — Login vertical slice + in-memory rate limiter (AUTH-07)
|
- [ ] 02-05-PLAN.md — Login vertical slice + in-memory rate limiter (AUTH-07)
|
||||||
- [ ] 02-06-PLAN.md — Logout + protect GET / + layout header logout button
|
- [ ] 02-06-PLAN.md — Logout + protect GET / + layout header logout button
|
||||||
- [ ] 02-07-PLAN.md — Mount gorilla/csrf + @ui.CSRFField templ helper across every form (AUTH-06)
|
- [ ] 02-07-PLAN.md — Mount gorilla/csrf + @ui.CSRFField templ helper across every form (AUTH-06)
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ gsd_state_version: 1.0
|
||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: in_progress
|
status: in_progress
|
||||||
last_updated: "2026-05-14T22:15:00.000Z"
|
last_updated: "2026-05-14T22:18:30.000Z"
|
||||||
progress:
|
progress:
|
||||||
total_phases: 7
|
total_phases: 7
|
||||||
completed_phases: 1
|
completed_phases: 1
|
||||||
total_plans: 11
|
total_plans: 11
|
||||||
completed_plans: 8
|
completed_plans: 9
|
||||||
percent: 73
|
percent: 82
|
||||||
---
|
---
|
||||||
|
|
||||||
# STATE
|
# STATE
|
||||||
|
|
@ -23,14 +23,14 @@ progress:
|
||||||
See: `.planning/PROJECT.md` (updated 2026-05-14)
|
See: `.planning/PROJECT.md` (updated 2026-05-14)
|
||||||
|
|
||||||
**Core value:** A user can sign in and run the Tablos workflow — create tablos, manage their tasks (kanban), and attach files — without a JS framework.
|
**Core value:** A user can sign in and run the Tablos workflow — create tablos, manage their tasks (kanban), and attach files — without a JS framework.
|
||||||
**Current focus:** Phase 02 — authentication, Plan 04 next (signup handler)
|
**Current focus:** Phase 02 — authentication, Plan 05 next (login handler)
|
||||||
|
|
||||||
## Phase Status
|
## Phase Status
|
||||||
|
|
||||||
| # | Phase | Status |
|
| # | Phase | Status |
|
||||||
|---|-------|--------|
|
|---|-------|--------|
|
||||||
| 1 | Foundation | ✓ Complete |
|
| 1 | Foundation | ✓ Complete |
|
||||||
| 2 | Authentication | ◑ In Progress (3/7 plans done) |
|
| 2 | Authentication | ◑ In Progress (4/7 plans done) |
|
||||||
| 3 | Tablos CRUD | ○ Pending |
|
| 3 | Tablos CRUD | ○ Pending |
|
||||||
| 4 | Tasks (Kanban) | ○ Pending |
|
| 4 | Tasks (Kanban) | ○ Pending |
|
||||||
| 5 | Files | ○ Pending |
|
| 5 | Files | ○ Pending |
|
||||||
|
|
@ -39,7 +39,7 @@ See: `.planning/PROJECT.md` (updated 2026-05-14)
|
||||||
|
|
||||||
## Active Phase
|
## Active Phase
|
||||||
|
|
||||||
**Phase 2: Authentication** — Plans 01–03 complete. Plan 04 (signup handler) next.
|
**Phase 2: Authentication** — Plans 01–04 complete. Plan 05 (login handler) next.
|
||||||
|
|
||||||
## Decisions
|
## Decisions
|
||||||
|
|
||||||
|
|
@ -50,6 +50,9 @@ See: `.planning/PROJECT.md` (updated 2026-05-14)
|
||||||
- **Hand-rolled PHC encode/decode** (Pattern 1 verbatim) — no alexedwards/argon2id wrapper dep; keeps code lean and self-tested
|
- **Hand-rolled PHC encode/decode** (Pattern 1 verbatim) — no alexedwards/argon2id wrapper dep; keeps code lean and self-tested
|
||||||
- **Store.now injectable field** (not method/interface) — simpler for single-test-clock use case in MaybeExtend threshold tests
|
- **Store.now injectable field** (not method/interface) — simpler for single-test-clock use case in MaybeExtend threshold tests
|
||||||
- **sha256.Sum256 inlined at Create + Lookup** (not in helper) — satisfies >= 2 grep acceptance criterion; makes D-05 hashing visible at usage sites
|
- **sha256.Sum256 inlined at Create + Lookup** (not in helper) — satisfies >= 2 grep acceptance criterion; makes D-05 hashing visible at usage sites
|
||||||
|
- **SignupForm/SignupErrors in templates package** (not handlers or separate forms pkg) — avoids import cycle between templates and internal/web
|
||||||
|
- **setupTestDB duplicated into web package test file** — Go prohibits importing _test.go files across packages; duplication chosen over non-test shared infra
|
||||||
|
- **nil-Store guard in auth.ResolveSession** — enables Phase 1 unit tests to pass AuthDeps{} zero value without panic
|
||||||
|
|
||||||
## Performance Metrics
|
## Performance Metrics
|
||||||
|
|
||||||
|
|
@ -58,6 +61,7 @@ See: `.planning/PROJECT.md` (updated 2026-05-14)
|
||||||
| 02-authentication | 01 | ~10min | 3 | 9 |
|
| 02-authentication | 01 | ~10min | 3 | 9 |
|
||||||
| 02-authentication | 02 | ~8min | 2 | 4 |
|
| 02-authentication | 02 | ~8min | 2 | 4 |
|
||||||
| 02-authentication | 03 | ~15min | 2 | 5 |
|
| 02-authentication | 03 | ~15min | 2 | 5 |
|
||||||
|
| 02-authentication | 04 | ~25min | 2 | 11 |
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|
@ -69,6 +73,8 @@ See: `.planning/PROJECT.md` (updated 2026-05-14)
|
||||||
- Commits (02-01): 513044d (migration), 799c260 (sqlc), 2c84f42 (auth package + test harness)
|
- Commits (02-01): 513044d (migration), 799c260 (sqlc), 2c84f42 (auth package + test harness)
|
||||||
- Commits (02-02): 3bb3828 (RED tests), ee36a5c (GREEN implementation)
|
- Commits (02-02): 3bb3828 (RED tests), ee36a5c (GREEN implementation)
|
||||||
- Commits (02-03): fd2301d (session store + cookie helpers), 1d07830 (middleware)
|
- Commits (02-03): fd2301d (session store + cookie helpers), 1d07830 (middleware)
|
||||||
|
- Commits (02-04): 73935ed (signup templates + smoke tests), efdc16b (handler + router + integration tests)
|
||||||
|
- Phase 2 Plan 04 SUMMARY: `.planning/phases/02-authentication/02-04-SUMMARY.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
*Last updated: 2026-05-14 after 02-03 execution*
|
*Last updated: 2026-05-14 after 02-04 execution*
|
||||||
|
|
|
||||||
155
.planning/phases/02-authentication/02-04-SUMMARY.md
Normal file
155
.planning/phases/02-authentication/02-04-SUMMARY.md
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
---
|
||||||
|
phase: 02-authentication
|
||||||
|
plan: 04
|
||||||
|
subsystem: auth
|
||||||
|
tags: [go, htmx, templ, signup, argon2id, sessions, postgres, sqlc, chi]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: 02-authentication/01
|
||||||
|
provides: "migrations + sqlc InsertUser/GetUserByEmail"
|
||||||
|
- phase: 02-authentication/02
|
||||||
|
provides: "auth.Hash, auth.Verify, DefaultParams, TestParams"
|
||||||
|
- phase: 02-authentication/03
|
||||||
|
provides: "auth.Store.Create, SetSessionCookie, ResolveSession, RedirectIfAuthed"
|
||||||
|
provides:
|
||||||
|
- "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"
|
||||||
|
affects: [02-05-login, 02-06-protected-routes, 02-07-csrf]
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
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)"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- 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
|
||||||
|
modified:
|
||||||
|
- backend/internal/web/router.go
|
||||||
|
- backend/internal/web/handlers_test.go
|
||||||
|
- backend/cmd/web/main.go
|
||||||
|
- backend/internal/auth/middleware.go
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "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"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "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)"
|
||||||
|
|
||||||
|
requirements-completed: [AUTH-01, AUTH-03, AUTH-05]
|
||||||
|
|
||||||
|
# Metrics
|
||||||
|
duration: 25min
|
||||||
|
completed: 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*
|
||||||
Loading…
Reference in a new issue