diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 098f389..e54be6f 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -13,7 +13,7 @@ | # | Phase | Goal | Requirements | |---|-------|------|--------------| | 1 | Foundation | Fresh `backend/` Go package boots, renders HTMX, talks to Postgres | FOUND-01..05 | -| 2 | 6/7 | In Progress| | +| 2 | Authentication | Complete (7/7) | AUTH-01..07 | | 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 | | 5 | Files | A user can attach, list, download, delete files on a tablo | FILE-01..06 | @@ -58,7 +58,7 @@ Plans: **User-in-loop:** Approve the `users` and `sessions` table schemas (columns, indexes, deletion semantics) before sqlc generation. Approve hash algorithm choice. -**Plans:** 6/7 plans executed +**Plans:** 7/7 plans executed Plans: - [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) @@ -66,7 +66,7 @@ Plans: - [x] 02-04-PLAN.md — Signup vertical slice (form → validate → hash → InsertUser → session → cookie → redirect) - [x] 02-05-PLAN.md — Login vertical slice + in-memory rate limiter (AUTH-07) - [x] 02-06-PLAN.md — Logout + protect GET / + layout header logout button (AUTH-04, AUTH-05) -- [ ] 02-07-PLAN.md — Mount gorilla/csrf + @ui.CSRFField templ helper across every form (AUTH-06) +- [x] 02-07-PLAN.md — Mount gorilla/csrf + @ui.CSRFField templ helper across every form (AUTH-06) ### Phase 3: Tablos CRUD **Goal:** A logged-in user can list, create, view, edit, and delete their tablos end-to-end through HTMX-driven flows. diff --git a/.planning/STATE.md b/.planning/STATE.md index 8437c4f..ef94b43 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,13 +3,13 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: in_progress -last_updated: "2026-05-14T20:41:09.893Z" +last_updated: "2026-05-14T21:00:30.036Z" progress: total_phases: 7 - completed_phases: 1 - total_plans: 11 - completed_plans: 11 - percent: 92 + completed_phases: 2 + total_plans: 12 + completed_plans: 12 + percent: 100 --- # STATE @@ -30,7 +30,7 @@ See: `.planning/PROJECT.md` (updated 2026-05-14) | # | Phase | Status | |---|-------|--------| | 1 | Foundation | ✓ Complete | -| 2 | Authentication | ◑ In Progress (6/7 plans done) | +| 2 | Authentication | ✓ Complete (7/7 plans done) | | 3 | Tablos CRUD | ○ Pending | | 4 | Tasks (Kanban) | ○ Pending | | 5 | Files | ○ Pending | @@ -39,7 +39,7 @@ See: `.planning/PROJECT.md` (updated 2026-05-14) ## Active Phase -**Phase 2: Authentication** — Plans 01–06 complete. Plan 07 (CSRF) next. +**Phase 2: Authentication** — All 7 plans complete (AUTH-06 closed). Phase 3: Tablos CRUD next. ## Decisions @@ -58,6 +58,8 @@ See: `.planning/PROJECT.md` (updated 2026-05-14) - **Status 401 for non-HTMX credential failures** — consistent with HTTP semantics; HTMX path returns 200+fragment - **Layout takes *auth.User explicitly** (not thread-through-ctx) — easier to test templates in isolation; no magic context values - **TestIndex_RendersHxGet rewritten to TestIndex_UnauthRedirects** — GET / is now protected; Phase 1 test was a false positive after this plan +- **csrf.PlaintextHTTPRequest(r) in dev mode** — skips TLS Referer check in plain-HTTP local dev/httptest; production unaffected +- **trustedOrigins variadic arg in auth.Mount + NewRouter** — test routers pass "localhost"; production passes none ## Performance Metrics @@ -69,6 +71,7 @@ See: `.planning/PROJECT.md` (updated 2026-05-14) | 02-authentication | 04 | ~25min | 2 | 11 | | 02-authentication | 05 | ~7min | 2 | 9 | | 02-authentication | 06 | ~12min | 1 | 9 | +| 02-authentication | 07 | ~25min | 1 | 18 | ## Notes @@ -86,6 +89,8 @@ See: `.planning/PROJECT.md` (updated 2026-05-14) - Phase 2 Plan 05 SUMMARY: `.planning/phases/02-authentication/02-05-SUMMARY.md` - Commits (02-06): b5c3fc4 (RED tests), 8b54ff4 (logout + protected routes + layout) - Phase 2 Plan 06 SUMMARY: `.planning/phases/02-authentication/02-06-SUMMARY.md` +- Commits (02-07): ae2d356 (RED tests), 389e1bc (GREEN csrf implementation) +- Phase 2 Plan 07 SUMMARY: `.planning/phases/02-authentication/02-07-SUMMARY.md` --- -*Last updated: 2026-05-14 after 02-06 execution* +*Last updated: 2026-05-14 after 02-07 execution (Phase 2 complete)* diff --git a/.planning/phases/02-authentication/02-07-SUMMARY.md b/.planning/phases/02-authentication/02-07-SUMMARY.md new file mode 100644 index 0000000..322e006 --- /dev/null +++ b/.planning/phases/02-authentication/02-07-SUMMARY.md @@ -0,0 +1,218 @@ +--- +phase: 02-authentication +plan: "07" +subsystem: auth/csrf +tags: [go, csrf, gorilla, htmx, templ, security-hardening] +requirements: [AUTH-06] +dependency_graph: + requires: [02-01, 02-03, 02-04, 02-05, 02-06] + provides: [gorilla/csrf middleware, ui.CSRFField component, env-driven CSRF key] + affects: [all state-changing POST routes, all form-rendering templates] +tech_stack: + added: [github.com/gorilla/csrf v1.7.3] + patterns: [double-submit CSRF protection, templ component composition, env-driven secrets] +key_files: + created: + - backend/internal/auth/csrf.go + - backend/internal/web/ui/csrf_field.templ + - backend/internal/web/csrf_test.go + - backend/internal/web/csrf_debug_test.go + - backend/internal/auth/csrf_test.go + modified: + - backend/internal/web/router.go + - backend/internal/web/handlers_auth.go + - backend/internal/web/handlers_auth_test.go + - backend/internal/web/handlers.go + - backend/internal/web/handlers_test.go + - backend/templates/layout.templ + - backend/templates/auth_login.templ + - backend/templates/auth_signup.templ + - backend/templates/index.templ + - backend/templates/layout_test.go + - backend/templates/auth_signup_test.go + - backend/cmd/web/main.go + - backend/go.mod + - backend/go.sum + - backend/.env.example +decisions: + - gorilla/csrf v1.7.3 in dev mode uses csrf.PlaintextHTTPRequest to skip TLS Referer check — local HTTP development + integration tests work without https + - trustedOrigins variadic arg added to NewRouter + auth.Mount — "localhost" is passed from test routers; production passes none + - csrf_debug_test.go retained as diagnostic aid (not removed after GREEN) +metrics: + duration: ~25min + completed: 2026-05-14 + tasks: 1 + files: 18 +--- + +# Phase 2 Plan 07: gorilla/csrf Integration Summary + +**One-liner:** gorilla/csrf v1.7.3 mounted on the chi stack with env-driven 32-byte CSRF key, reusable ui.CSRFField component, and all POST routes enforcing 403 on missing token. + +## What Was Built + +AUTH-06 is closed. Every state-changing POST route (POST /signup, POST /login, POST /logout) now requires a valid CSRF token. Missing or invalid tokens return 403 Forbidden from gorilla/csrf before any handler logic runs. + +### auth.Mount and auth.LoadKeyFromEnv (backend/internal/auth/csrf.go) + +`Mount(env string, key []byte, trustedOrigins ...string)` builds `csrf.Protect` with the D-14 locked options: + +- `csrf.Secure(env != "dev")` — Secure cookie flag disabled only in local dev +- `csrf.SameSite(csrf.SameSiteLaxMode)` — interim SameSite=Lax defense +- `csrf.Path("/")` — CSRF cookie scoped to entire site +- `csrf.FieldName("_csrf")` — hidden form field name +- `csrf.RequestHeader("X-CSRF-Token")` — accepted header for HTMX hx-headers future usage + +In dev mode, `csrf.PlaintextHTTPRequest(r)` is applied before the CSRF middleware to skip the TLS Referer check. This allows local plain-HTTP development and httptest integration tests to function correctly without HTTPS. + +`LoadKeyFromEnv()` reads `SESSION_SECRET` from env, hex-decodes it, and validates `len == 32`. Returns `ErrCSRFKeyInvalid` for missing, non-hex, or wrong-length input. + +### ui.CSRFField Component (backend/internal/web/ui/csrf_field.templ) + +``` +templ CSRFField(token string) { + +} +``` + +The canonical, reusable component for embedding CSRF tokens in templ forms. Future forms must use `@ui.CSRFField(token)` as the first child of every `