docs(02-06): complete logout + protected routes + layout plan
- Create 02-06-SUMMARY.md with TDD gate compliance, router middleware snapshot - Update STATE.md: plans 01-06 complete, plan 07 (CSRF) next - Update ROADMAP.md: Phase 2 at 6/7 plans, 02-06 checked - Mark AUTH-04 complete in REQUIREMENTS.md (AUTH-05 was already checked)
This commit is contained in:
parent
8b54ff4bec
commit
00a9388c32
4 changed files with 177 additions and 10 deletions
|
|
@ -20,7 +20,7 @@ Requirements for the initial Go+HTMX milestone. Each maps to exactly one roadmap
|
|||
- [x] **AUTH-01**: User can sign up with email and password (server-side validation, bcrypt/argon2 hash)
|
||||
- [x] **AUTH-02**: User can log in with email and password and receives a server-managed session
|
||||
- [x] **AUTH-03**: Sessions persist via HTTP-only, signed cookies (Secure + SameSite=Lax) and survive browser refresh
|
||||
- [ ] **AUTH-04**: User can log out from any authenticated page (server invalidates session)
|
||||
- [x] **AUTH-04**: User can log out from any authenticated page (server invalidates session)
|
||||
- [x] **AUTH-05**: Protected routes redirect unauthenticated requests to the login page; authenticated users on auth pages are sent to the dashboard
|
||||
- [ ] **AUTH-06**: CSRF protection on all state-changing requests
|
||||
- [x] **AUTH-07**: Rate-limited login attempts per email + IP to discourage credential stuffing
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
| # | Phase | Goal | Requirements |
|
||||
|---|-------|------|--------------|
|
||||
| 1 | Foundation | Fresh `backend/` Go package boots, renders HTMX, talks to Postgres | FOUND-01..05 |
|
||||
| 2 | 5/7 | In Progress| |
|
||||
| 2 | 6/7 | In Progress| |
|
||||
| 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,14 +58,14 @@ Plans:
|
|||
|
||||
**User-in-loop:** Approve the `users` and `sessions` table schemas (columns, indexes, deletion semantics) before sqlc generation. Approve hash algorithm choice.
|
||||
|
||||
**Plans:** 5/7 plans executed
|
||||
**Plans:** 6/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)
|
||||
- [x] 02-03-PLAN.md — Session store + cookie + ResolveSession/RequireAuth/RedirectIfAuthed middleware
|
||||
- [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)
|
||||
- [ ] 02-06-PLAN.md — Logout + protect GET / + layout header logout button
|
||||
- [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)
|
||||
|
||||
### Phase 3: Tablos CRUD
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@ gsd_state_version: 1.0
|
|||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: in_progress
|
||||
last_updated: "2026-05-14T20:34:00.000Z"
|
||||
last_updated: "2026-05-14T20:41:09.893Z"
|
||||
progress:
|
||||
total_phases: 7
|
||||
completed_phases: 1
|
||||
total_plans: 11
|
||||
completed_plans: 10
|
||||
percent: 91
|
||||
completed_plans: 11
|
||||
percent: 92
|
||||
---
|
||||
|
||||
# STATE
|
||||
|
|
@ -30,7 +30,7 @@ See: `.planning/PROJECT.md` (updated 2026-05-14)
|
|||
| # | Phase | Status |
|
||||
|---|-------|--------|
|
||||
| 1 | Foundation | ✓ Complete |
|
||||
| 2 | Authentication | ◑ In Progress (5/7 plans done) |
|
||||
| 2 | Authentication | ◑ In Progress (6/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–05 complete. Plan 06 (logout handler) next.
|
||||
**Phase 2: Authentication** — Plans 01–06 complete. Plan 07 (CSRF) next.
|
||||
|
||||
## Decisions
|
||||
|
||||
|
|
@ -56,6 +56,8 @@ See: `.planning/PROJECT.md` (updated 2026-05-14)
|
|||
- **errInvalidCreds const** (not inline string) — satisfies D-20 single-source-of-truth grep gate; both credential failure paths use the constant
|
||||
- **NewLimiterStoreWithClock exported** — cross-package integration tests inject a frozen clock without a test-helper shim
|
||||
- **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
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
|
|
@ -66,6 +68,7 @@ See: `.planning/PROJECT.md` (updated 2026-05-14)
|
|||
| 02-authentication | 03 | ~15min | 2 | 5 |
|
||||
| 02-authentication | 04 | ~25min | 2 | 11 |
|
||||
| 02-authentication | 05 | ~7min | 2 | 9 |
|
||||
| 02-authentication | 06 | ~12min | 1 | 9 |
|
||||
|
||||
## Notes
|
||||
|
||||
|
|
@ -81,6 +84,8 @@ See: `.planning/PROJECT.md` (updated 2026-05-14)
|
|||
- Phase 2 Plan 04 SUMMARY: `.planning/phases/02-authentication/02-04-SUMMARY.md`
|
||||
- Commits (02-05): b5c20c7 (LimiterStore + tests), 7d8c498 (login handler + integration tests)
|
||||
- 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`
|
||||
|
||||
---
|
||||
*Last updated: 2026-05-14 after 02-05 execution*
|
||||
*Last updated: 2026-05-14 after 02-06 execution*
|
||||
|
|
|
|||
162
.planning/phases/02-authentication/02-06-SUMMARY.md
Normal file
162
.planning/phases/02-authentication/02-06-SUMMARY.md
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
---
|
||||
phase: 02-authentication
|
||||
plan: 06
|
||||
subsystem: auth
|
||||
tags: [go, htmx, templ, logout, protected-routes, layout, sessions]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 02-authentication/01-05
|
||||
provides: "Session store (Create/Delete/Lookup), RequireAuth middleware, ResolveSession middleware, cookie helpers (ClearSessionCookie)"
|
||||
provides:
|
||||
- "POST /logout: hard-deletes session row, clears cookie, redirects to /login (D-06, T-2-07)"
|
||||
- "GET / protected by RequireAuth group — unauth GET redirects to /login (AUTH-05, D-23)"
|
||||
- "Layout(title, *auth.User) — renders logout form + email in header when authed, nothing when nil"
|
||||
- "Index(user *auth.User) — renders home page with user email visible (smoke test for ctx user)"
|
||||
- "AUTH-04 and AUTH-05 closed"
|
||||
affects: [02-authentication/07-csrf, 03-tablos-crud]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Protected route group pattern: chi Group + r.Use(auth.RequireAuth) wraps / and /logout together (D-23)"
|
||||
- "HTMX-aware logout: LogoutHandler checks HX-Request header, sends HX-Redirect vs 303 See Other"
|
||||
- "Layout receives *auth.User as explicit parameter — no template-context magic, no hidden coupling"
|
||||
- "Defense-in-depth: LogoutHandler checks Authed(ctx) even though RequireAuth already gates"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- backend/templates/layout_test.go
|
||||
modified:
|
||||
- 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/internal/web/router.go
|
||||
- backend/templates/layout.templ
|
||||
- backend/templates/index.templ
|
||||
- backend/templates/auth_signup.templ
|
||||
- backend/templates/auth_login.templ
|
||||
|
||||
key-decisions:
|
||||
- "Layout takes *auth.User as explicit parameter (not thread-through-ctx) — easier to test, no magic"
|
||||
- "Index(user *auth.User) takes user directly — IndexHandler pulls from auth.Authed(ctx) and passes it"
|
||||
- "TestIndex_RendersHxGet rewritten to TestIndex_UnauthRedirects — GET / is now protected, not public"
|
||||
|
||||
patterns-established:
|
||||
- "Route group pattern: Group(func(r chi.Router){ r.Use(auth.RequireAuth); r.Get... }) for protected routes"
|
||||
- "LogoutHandler shape: check Authed (defense-in-depth), Store.Delete, ClearSessionCookie, HTMX-aware redirect"
|
||||
|
||||
requirements-completed: [AUTH-04, AUTH-05]
|
||||
|
||||
# Metrics
|
||||
duration: ~12min
|
||||
completed: 2026-05-14
|
||||
---
|
||||
|
||||
# Phase 02 Plan 06: Logout + Protected Routes + Layout Summary
|
||||
|
||||
**POST /logout hard-deletes the session via Store.Delete, clears the cookie, and redirects to /login; GET / is moved into a RequireAuth-guarded route group; Layout header adapts to auth state via an explicit *auth.User parameter.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** ~12 min
|
||||
- **Started:** 2026-05-14T20:40:00Z
|
||||
- **Completed:** 2026-05-14T20:52:00Z
|
||||
- **Tasks:** 1 (TDD: RED + GREEN)
|
||||
- **Files modified:** 9
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- AUTH-04 closed: `LogoutHandler` hard-deletes the session row (`Store.Delete`), clears the browser cookie (`ClearSessionCookie`), and redirects to `/login` (303 or HX-Redirect for HTMX).
|
||||
- AUTH-05 closed: `GET /` is now inside a `chi.Group` wrapped with `auth.RequireAuth`; unauthenticated requests get 303 → `/login` (or HX-Redirect for HTMX).
|
||||
- `Layout(title string, user *auth.User)` — header renders logout POST form + email when `user != nil`; nothing when `nil` (D-22 anti-GET-logout enforced by template structure).
|
||||
- `Index(user *auth.User)` — home page shows `Signed in as {email}` smoke check.
|
||||
- Phase 1 test `TestIndex_RendersHxGet` rewritten to `TestIndex_UnauthRedirects` — the route is now protected and the old test would have been a false positive.
|
||||
- `TestLogout_AfterLogoutSubsequentRequestUnauth` verifies that a captured cookie is invalidated server-side after logout (T-2-07).
|
||||
|
||||
## Task Commits
|
||||
|
||||
1. **Task 1 (RED): Failing tests for logout, protected routes, layout** - `b5c3fc4` (test)
|
||||
2. **Task 1 (GREEN): Full implementation** - `8b54ff4` (feat)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `backend/templates/layout.templ` - Updated signature: `Layout(title string, user *auth.User)`; header conditionally renders logout form
|
||||
- `backend/templates/index.templ` - Updated signature: `Index(user *auth.User)`; shows "Signed in as {email}"
|
||||
- `backend/templates/auth_signup.templ` - Updated `@Layout("Sign up", nil)` call
|
||||
- `backend/templates/auth_login.templ` - Updated `@Layout("Sign in", nil)` call
|
||||
- `backend/templates/layout_test.go` - New: `TestLayout_LogoutFormVisibleWhenAuthed`, `TestLayout_LogoutFormHiddenWhenUnauthed`
|
||||
- `backend/internal/web/handlers_auth.go` - Added `LogoutHandler(deps AuthDeps)`, added `log/slog` import
|
||||
- `backend/internal/web/handlers_auth_test.go` - Added 7 new tests: `TestLogout_*`, `TestProtected_*`
|
||||
- `backend/internal/web/handlers.go` - `IndexHandler` pulls `auth.Authed(ctx)` and passes user to template
|
||||
- `backend/internal/web/router.go` - Added protected group `Group(func(r chi.Router){ r.Use(auth.RequireAuth); r.Get("/"); r.Post("/logout") })`; removed old top-level `r.Get("/")`
|
||||
- `backend/internal/web/handlers_test.go` - `TestIndex_RendersHxGet` → `TestIndex_UnauthRedirects`
|
||||
|
||||
## Router Middleware Order (Plan 07 diff base)
|
||||
|
||||
Current full middleware stack in `NewRouter` as of this plan:
|
||||
|
||||
```
|
||||
RequestIDMiddleware
|
||||
chimw.RealIP
|
||||
SlogLoggerMiddleware
|
||||
chimw.Recoverer
|
||||
auth.ResolveSession(deps.Store)
|
||||
--- route groups below ---
|
||||
Group: auth.RedirectIfAuthed → GET /signup, GET /login
|
||||
r.Post("/signup", ...), r.Post("/login", ...) [no group middleware]
|
||||
Group: auth.RequireAuth → GET /, POST /logout
|
||||
r.Get("/healthz", ...), r.Get("/demo/time", ...), r.Get("/static/*", ...)
|
||||
```
|
||||
|
||||
Plan 07 will insert `csrf.Protect(...)` after `auth.ResolveSession` and before the route groups. The comment in `router.go` already notes this insertion point.
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- **Layout takes `*auth.User` explicitly** (not threaded via context) — simpler to unit-test templates in isolation; templ components receive data through parameters, not magic context values.
|
||||
- **IndexHandler() stays a no-arg constructor** — it pulls the user from `auth.Authed(r.Context())` internally. The `user` can be `nil` if called without RequireAuth (though in practice the group gate ensures it's always set). The `templates.Index(user)` accepts nil gracefully (not called — only reached through RequireAuth).
|
||||
- **TestIndex_RendersHxGet rewritten** — the old test assumed GET / is public and returns 200 with HTMX demo content. After this plan, GET / is protected. The new `TestIndex_UnauthRedirects` correctly tests the new behavior. The HTMX demo rendering with a valid session is covered by `TestProtected_HomeAuthRendersUserEmail` in `handlers_auth_test.go` (DB-backed, skips without TEST_DATABASE_URL).
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None — plan executed exactly as written.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None. All tests pass; build is clean; templ generate succeeds.
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None — no placeholder data, hardcoded empty values, or TODO content in any file created/modified by this plan.
|
||||
|
||||
## Threat Flags
|
||||
|
||||
None — no new security-relevant surfaces beyond what the plan's threat model covers.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None — no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- Auth loop is functionally complete: signup → GET / (authed) → logout → GET /login → login → GET / (authed).
|
||||
- Plan 07 (CSRF) is the final Phase 2 plan. The router comment at the csrf.Protect insertion point is already in place.
|
||||
- Downstream phases (03-tablos-crud) can use the `auth.Authed(ctx)` pattern to access the user in any protected handler.
|
||||
|
||||
## Self-Check
|
||||
|
||||
- [x] `backend/templates/layout_test.go` exists
|
||||
- [x] `backend/templates/layout.templ` updated with user parameter
|
||||
- [x] `backend/internal/web/handlers_auth.go` contains `func LogoutHandler`
|
||||
- [x] `backend/internal/web/router.go` contains `auth.RequireAuth` in protected group
|
||||
- [x] Commits b5c3fc4 (RED) and 8b54ff4 (GREEN) exist
|
||||
- [x] `go build ./...` exits 0
|
||||
- [x] `go test ./internal/web/ ./internal/auth/ ./templates/` all pass
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
---
|
||||
*Phase: 02-authentication*
|
||||
*Completed: 2026-05-14*
|
||||
Loading…
Reference in a new issue