docs(02-01): complete auth-substrate plan

This commit is contained in:
Arthur Belleville 2026-05-14 21:58:23 +02:00
parent 2c84f4275b
commit fb9aac30ba
No known key found for this signature in database
4 changed files with 149 additions and 17 deletions

View file

@ -17,8 +17,8 @@ Requirements for the initial Go+HTMX milestone. Each maps to exactly one roadmap
### Authentication
- [ ] **AUTH-01**: User can sign up with email and password (server-side validation, bcrypt/argon2 hash)
- [ ] **AUTH-02**: User can log in with email and password and receives a server-managed session
- [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
- [ ] **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)
- [ ] **AUTH-05**: Protected routes redirect unauthenticated requests to the login page; authenticated users on auth pages are sent to the dashboard

View file

@ -13,7 +13,7 @@
| # | Phase | Goal | Requirements |
|---|-------|------|--------------|
| 1 | Foundation | Fresh `backend/` Go package boots, renders HTMX, talks to Postgres | FOUND-01..05 |
| 2 | Auth | A user can sign up, log in, and stay logged in | AUTH-01..07 |
| 2 | 1/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,9 +58,9 @@ Plans:
**User-in-loop:** Approve the `users` and `sessions` table schemas (columns, indexes, deletion semantics) before sqlc generation. Approve hash algorithm choice.
**Plans:** 7 plans
**Plans:** 1/7 plans executed
Plans:
- [ ] 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)
- [ ] 02-02-PLAN.md — argon2id password hashing (TDD: Hash/Verify with PHC encoding)
- [ ] 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)

View file

@ -2,14 +2,14 @@
gsd_state_version: 1.0
milestone: v1.0
milestone_name: milestone
status: ready_to_plan
last_updated: "2026-05-14T19:44:02.762Z"
status: in_progress
last_updated: "2026-05-14T21:57:00.000Z"
progress:
total_phases: 7
completed_phases: 1
total_plans: 11
completed_plans: 4
percent: 36
total_plans: 18
completed_plans: 5
percent: 28
---
# STATE
@ -23,14 +23,14 @@ progress:
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.
**Current focus:** Phase 01 — foundation
**Current focus:** Phase 02 — authentication, Plan 02 next
## Phase Status
| # | Phase | Status |
|---|-------|--------|
| 1 | Foundation | ○ Pending |
| 2 | Authentication | ○ Pending |
| 1 | Foundation | ✓ Complete |
| 2 | Authentication | ◑ In Progress (1/7 plans done) |
| 3 | Tablos CRUD | ○ Pending |
| 4 | Tasks (Kanban) | ○ Pending |
| 5 | Files | ○ Pending |
@ -39,15 +39,27 @@ See: `.planning/PROJECT.md` (updated 2026-05-14)
## Active Phase
**Phase 1: Foundation** — not started.
**Phase 2: Authentication** — Plan 01 complete. Plan 02 (password hashing) next.
Next: `/gsd-discuss-phase 1` to gather context, or `/gsd-plan-phase 1` to plan directly.
## Decisions
- **Consolidated internal/auth package** (not split with internal/session) — RESEARCH Open Question 3
- **compose Postgres + schema isolation for tests** (not testcontainers-go) — RESEARCH Open Question 1
- **goose.SetTableName per test-schema** prevents public goose_db_version table collision
- **argon2id** over bcrypt for password hashing — D-08, confirmed by user
## Performance Metrics
| Phase | Plan | Duration | Tasks | Files |
|-------|------|----------|-------|-------|
| 02-authentication | 01 | ~10min | 3 | 9 |
## Notes
- Existing `go-backend/` is set aside; new code lives in a fresh `backend/` Go package.
- DB schema is changing from the JS/Supabase version — user is in the loop on every schema decision (Phases 25).
- GSD subagents are not installed in this repo; downstream commands may fail until `npx get-shit-done-cc@latest --global` is run.
- Phase 2 Plan 01 SUMMARY: `.planning/phases/02-authentication/02-01-SUMMARY.md`
- Commits: 513044d (migration), 799c260 (sqlc), 2c84f42 (auth package + test harness)
---
*Last updated: 2026-05-14 after project initialization*
*Last updated: 2026-05-14 after 02-01 execution*

View file

@ -0,0 +1,120 @@
---
phase: 02-authentication
plan: "01"
subsystem: backend/auth-substrate
tags: [go, postgres, sqlc, migration, auth-foundation, goose, citext, uuid]
dependency_graph:
requires: [01-foundation]
provides: [auth-migration, sqlc-bindings, auth-package-skeleton, test-harness]
affects: [02-02, 02-03, 02-04, 02-05, 02-06, 02-07]
tech_stack:
added:
- github.com/pressly/goose/v3 v3.27.1 (test harness — per-schema migration isolation)
patterns:
- per-test isolated schema via unique schemaName + schema-scoped DSN
- goose.SetTableName for per-test version table isolation (no public-schema collision)
- sqlc citext→string + uuid→uuid.UUID overrides (Pattern 10)
key_files:
created:
- backend/migrations/0002_auth.sql
- backend/internal/db/queries/users.sql
- backend/internal/db/queries/sessions.sql
- backend/internal/auth/doc.go
- backend/internal/auth/types.go
- backend/internal/auth/testdb_test.go
modified:
- backend/sqlc.yaml (added overrides block)
- backend/.env.example (added TEST_DATABASE_URL + SESSION_SECRET)
- backend/go.mod + go.sum (added pressly/goose/v3)
decisions:
- "Consolidated internal/auth package (not split with internal/session) — RESEARCH Open Question 3 resolved"
- "Test harness uses compose Postgres + schema isolation (not testcontainers-go) — RESEARCH Open Question 1 resolved"
- "goose.SetTableName per test-schema prevents collision with public goose_db_version table"
- "goose v3.27.1 pinned to match justfile CLI version"
- "schemaName uses UUID[:12] with hyphens replaced by underscores for valid SQL identifiers"
metrics:
duration: "~10 minutes"
completed_date: "2026-05-14"
tasks_completed: 3
tasks_total: 3
files_created: 6
files_modified: 3
---
# Phase 2 Plan 01: Auth Substrate (Migration + sqlc + Package Skeleton) Summary
**One-liner:** Postgres migration with citext users+sessions tables, sqlc type-safe bindings with citext→string and uuid→uuid.UUID overrides, and internal/auth package with types/constants/sentinel-errors plus a per-test-schema Postgres harness.
## What Was Built
### Task 1: Migration 0002_auth.sql
Created `backend/migrations/0002_auth.sql` with goose Up/Down annotations:
- `CREATE EXTENSION IF NOT EXISTS citext` + `pgcrypto` (defensive for gen_random_uuid)
- `users` table: id uuid PK, email citext UNIQUE, password_hash text, created_at/updated_at timestamptz (D-01)
- `sessions` table: id text PK (SHA-256 hex per D-05), user_id FK ON DELETE CASCADE, created_at/expires_at (D-04)
- Indexes: `sessions_user_id_idx` and `sessions_expires_at_idx`
- No deleted_at, email_verified_at, user_agent, ip_address, last_seen_at (D-02, D-03, D-04)
- Up/Down round-trip verified against compose Postgres (goose Up → Down → Up exits 0)
### Task 2: sqlc.yaml overrides + query files + generated bindings
- `sqlc.yaml`: added `overrides` block mapping `citext→string` and `uuid→uuid.UUID` (Pattern 10, Pitfall 3)
- `users.sql`: InsertUser :one + GetUserByEmail :one
- `sessions.sql`: InsertSession :exec, GetSessionWithUser :one (with `expires_at > now()` per D-07), DeleteSession :exec, DeleteSessionsByUser :exec, ExtendSession :exec
- `sqlc generate` produces `Email string` (not pgtype.Text) and `uuid.UUID` (not pgtype.UUID) — citext and uuid overrides verified
- Generated files are gitignored (per Phase 1 .gitignore) and regenerated by `sqlc generate`
### Task 3: internal/auth package skeleton + test harness + env docs
- `auth/doc.go`: package comment — consolidated layout decision documented
- `auth/types.go`: User struct, Session struct, SessionCookieName (D-12), SessionTTL 30d (D-09), SessionExtendThreshold 7d (D-09), ErrSessionNotFound, ErrInvalidHash, ErrIncompatibleVersion
- `auth/testdb_test.go`: `setupTestDB(t)` creates unique schema (test_\<uuid12\>), runs goose Up with schema-specific version table via `goose.SetTableName`, returns pool + cleanup. `TestSetupTestDB_Roundtrip` pings pool and verifies users table visibility. Test skips when neither TEST_DATABASE_URL nor DATABASE_URL is set.
- `.env.example`: added `TEST_DATABASE_URL` and `SESSION_SECRET` with generation instructions
- `go.mod`: added `github.com/pressly/goose/v3 v3.27.1` as direct dependency
## Decisions Made
1. **Consolidated internal/auth package** (not split with internal/session): all auth capabilities (password, session, ratelimit, cookie, csrf) live in one package. Phase 1's `internal/session/doc.go` placeholder is kept as-is (one-liner pointing at `internal/auth`). See RESEARCH Open Question 3.
2. **compose Postgres + schema isolation** (not testcontainers-go): reuses the existing compose Postgres via `TEST_DATABASE_URL`. Per-test isolation via unique schemas avoids testcontainers podman friction (RESEARCH Open Question 1, Assumption A7).
3. **goose.SetTableName per test**: each test schema gets its own goose version table (`test_<uuid>_goose_version`) so the public `goose_db_version` (tracking production migration state) is never read/modified during test runs.
4. **UUID hyphens stripped from schema/table names**: `uuid.New().String()[:12]` can contain hyphens which are invalid in unquoted SQL identifiers; replaced with underscores.
5. **goose v3.27.1 pinned**: matches the `goose_version` in `backend/justfile` CLI installation.
## Deviations from Plan
None — plan executed exactly as written. All acceptance criteria verified.
## Threat Flags
No new security-relevant surface introduced. This plan only creates:
- Schema migration (DDL only — no network endpoints)
- sqlc query files and generated bindings (parameterized queries prevent SQL injection — T-2-06 defense)
- Internal Go package with types and test helpers
T-2-06 mitigated: sessions.id column is designed for SHA-256(token) hex per D-05; sqlc query `GetSessionWithUser` includes `AND expires_at > now()` gate (D-07 lazy expiry).
T-2-11 mitigated: goose Up/Down round-trip exercised and exits 0.
T-2-12 mitigated: `.env.example` SESSION_SECRET has empty placeholder value only; real value comes from `.env` (gitignored since Phase 1).
## Commits
| Task | Commit | Description |
|------|--------|-------------|
| 1 | 513044d | feat(02-01): add 0002_auth.sql migration |
| 2 | 799c260 | feat(02-01): add sqlc queries + citext/uuid overrides |
| 3 | 2c84f42 | feat(02-01): create internal/auth package skeleton, test DB harness, env docs |
## Self-Check: PASSED
Files verified:
- backend/migrations/0002_auth.sql ✓
- backend/internal/db/queries/users.sql ✓
- backend/internal/db/queries/sessions.sql ✓
- backend/internal/auth/doc.go ✓
- backend/internal/auth/types.go ✓
- backend/internal/auth/testdb_test.go ✓
- backend/sqlc.yaml ✓
- backend/.env.example ✓
Commits verified: 513044d, 799c260, 2c84f42 all present in git log.