Commit graph

20 commits

Author SHA1 Message Date
Arthur Belleville
8b54ff4bec
feat(02-06): implement logout, protect GET /, and update layout with auth state
- Add LogoutHandler: deletes session row (D-06), clears cookie, redirects to /login
- Protect GET / inside RequireAuth group; remove old top-level registration
- Add POST /logout inside same RequireAuth group (D-22: POST-only logout)
- Update Layout signature to accept *auth.User; render logout form + email when authed
- Update Index template to accept *auth.User and show "Signed in as {email}"
- Update SignupPage/LoginPage to pass nil to Layout (auth pages are unauthed)
- Update IndexHandler to pull user from auth.Authed(ctx) and pass to template
- Update TestIndex_RendersHxGet -> TestIndex_UnauthRedirects (GET / now protected)
- AUTH-04 (logout) and AUTH-05 (protected /) are now closed
2026-05-14 22:40:10 +02:00
Arthur Belleville
b5c3fc4d48
test(02-06): add failing tests for logout, protected routes, and layout auth
- TestLogout_Success: POST /logout with valid cookie -> 303, cookie cleared, session deleted
- TestLogout_UnauthRedirectsToLogin: POST /logout without cookie -> 303 from RequireAuth
- TestLogout_HXRedirect: HTMX logout -> 200 + HX-Redirect: /login
- TestLogout_AfterLogoutSubsequentRequestUnauth: stale cookie blocked after logout
- TestProtected_HomeUnauthRedirects: GET / without session -> 303 /login
- TestProtected_HomeUnauthHXRedirect: HTMX GET / without session -> 200 + HX-Redirect
- TestProtected_HomeAuthRendersUserEmail: authed GET / -> 200 with user email
- TestLayout_LogoutFormVisibleWhenAuthed: Layout with user shows logout form
- TestLayout_LogoutFormHiddenWhenUnauthed: Layout with nil user hides logout form
2026-05-14 22:32:33 +02:00
Arthur Belleville
7d8c498980
feat(02-05): login vertical slice with rate limiting
- auth_login.templ: LoginPage + LoginFormFragment (mirrors signup shape)
- LoginForm + LoginErrors types added to templates/auth_forms.go
- LoginPageHandler + LoginPostHandler in handlers_auth.go
  - Rate-limit check before user lookup (D-16, T-2-14)
  - Single errInvalidCreds constant for D-20 enumeration defense
  - Session rotation via Store.Rotate on success (D-10, T-2-04)
  - HTMX-aware redirect and fragment responses (D-19, D-21)
- AuthDeps extended with Limiter *auth.LimiterStore field
- router.go: GET /login in RedirectIfAuthed group (D-23)
- main.go: LimiterStore created with janitor goroutine (D-16)
- Export NewLimiterStoreWithClock + SetLimiterClock for cross-package tests
- 12 TestLogin_* integration tests all pass with real DB
2026-05-14 22:27:54 +02:00
Arthur Belleville
b5c20c7892
feat(02-05): implement LimiterStore with injectable clock and janitor
- Token-bucket rate limiter keyed per (email+IP) using golang.org/x/time/rate
- rate.Every(12s), burst=5, idleTTL=10min (D-16)
- AllowN(t, 1) with injectable clock for deterministic tests (Pattern 8)
- Janitor goroutine evicts entries idle > 10min via cleanupNow()
- No .Allow() without args (Pitfall 8 avoided)
- Five tests pass with -race: burst, refill, isolation, janitor, concurrent
- golang.org/x/time v0.15.0 added to go.mod
2026-05-14 22:22:24 +02:00
Arthur Belleville
efdc16babe
feat(02-04): signup handler, router wiring, and integration tests
- Add handlers_auth.go: SignupPageHandler + SignupPostHandler (validate -> hash -> insert -> session -> redirect)
- Add AuthDeps struct; wire argon2id hash, InsertUser, Store.Create, SetSessionCookie
- Update router.go: NewRouter accepts AuthDeps; mount ResolveSession (D-24); wire /signup routes behind RedirectIfAuthed
- Update cmd/web/main.go: build AuthDeps (sqlc.Queries + auth.Store + secure flag) and pass to NewRouter
- Add nil-Store guard to auth.ResolveSession for Phase 1 unit-test compatibility
- Update handlers_test.go: pass AuthDeps{} zero value to NewRouter (Phase 1 routes unaffected)
- Add testdb_test.go: isolated-schema test helper for web package integration tests
- Add handlers_auth_test.go: 8 TestSignup_* integration tests (all pass against real Postgres)
2026-05-14 22:17:50 +02:00
Arthur Belleville
1d07830954
feat(02-03): ResolveSession + RequireAuth + RedirectIfAuthed middleware
- ResolveSession: reads cookie, SHA-256 lookup, MaybeExtend best-effort, attaches Session+User to ctx
- RequireAuth: 303 /login for plain requests; HX-Redirect: /login for HTMX (D-23, Pattern 5)
- RedirectIfAuthed: bounces authed users to / from login/signup pages
- Authed(ctx): typed context accessor for session + user
- redirect helper centralizes 303 vs HX-Redirect logic (Pitfall 9: no 302)
- 9 tests: 3 real-DB (ResolveSession) + 6 pure ctx/routing (RequireAuth, RedirectIfAuthed)
2026-05-14 22:09:58 +02:00
Arthur Belleville
fd2301decf
feat(02-03): session store + cookie helpers (real-DB TDD)
- Store.Create: 32-byte crypto/rand token, SHA-256 hex as DB id (D-05)
- Store.Lookup: hashes cookie, maps pgx.ErrNoRows to ErrSessionNotFound (D-07)
- Store.Delete: hard-deletes session row (D-06)
- Store.Rotate: deletes old row before creating new one (D-10, T-2-04)
- Store.MaybeExtend: extends only when remaining < 7 days (D-09)
- SetSessionCookie: HttpOnly + Secure (env-gated) + SameSite=Lax (D-12)
- ClearSessionCookie: MaxAge=-1 not 0 (RESEARCH Pattern 3 / D-06)
- 10 tests: 7 real-DB (skip without TEST_DATABASE_URL) + 3 cookie unit tests
2026-05-14 22:08:04 +02:00
Arthur Belleville
ee36a5c78b
feat(02): GREEN — argon2id Hash + Verify + self-test
- Add Params struct with Memory/Iterations/Parallelism/SaltLength/KeyLength
- DefaultParams: OWASP 2024 baseline (m=64KiB, t=1, p=4, salt=16B, key=32B) — D-08
- TestParams: reduced cost (m=8KiB) so go test stays under 5s — D-26/Pitfall 4
- Hash(): crypto/rand salt per call, argon2.IDKey, PHC format $argon2id$v=19$...
- Verify(): PHC split/parse, ErrInvalidHash on malformed, ErrIncompatibleVersion on v!=19
- subtle.ConstantTimeCompare for timing-attack resistance (T-2-13)
- init() self-test: hash/verify round-trip panics on regression (D-08/T-2-15)
- Add golang.org/x/crypto v0.51.0 as direct dependency
2026-05-14 22:00:55 +02:00
Arthur Belleville
3bb3828cdc
test(02): RED — failing argon2id password tests
- Six tests: HashVerify, VerifyWrong, VerifyMalformed, VerifyVersion, DistinctSaltsPerCall, DefaultParamsShape
- Tests use auth.TestParams for fast wall time (Pitfall 4 guard)
- Guards ErrInvalidHash + ErrIncompatibleVersion sentinel errors
- All tests fail with undefined: auth.Hash / auth.TestParams / auth.Verify (implementation missing)
2026-05-14 21:59:38 +02:00
Arthur Belleville
2c84f4275b
feat(02-01): create internal/auth package skeleton, test DB harness, env docs
- auth/doc.go: package comment explaining consolidated layout (Open Question 3 resolved)
- auth/types.go: User + Session structs, SessionCookieName (D-12), SessionTTL (D-09),
  SessionExtendThreshold (D-09), ErrSessionNotFound, ErrInvalidHash, ErrIncompatibleVersion
- auth/testdb_test.go: setupTestDB creates isolated per-test schema (test_<uuid>),
  runs goose Up with unique version table, drops schema on cleanup (D-26)
  TestSetupTestDB_Roundtrip smoke test verifies users table visible
- go.mod: added github.com/pressly/goose/v3 v3.27.1 as direct dependency
- .env.example: added TEST_DATABASE_URL and SESSION_SECRET with comments (D-14, D-26)
2026-05-14 21:56:45 +02:00
Arthur Belleville
799c26099e
feat(02-01): add sqlc queries + citext/uuid overrides; generate bindings
- sqlc.yaml: overrides citext→string and uuid→uuid.UUID (Pattern 10, Pitfall 3)
- users.sql: InsertUser :one and GetUserByEmail :one
- sessions.sql: InsertSession :exec, GetSessionWithUser :one (expires_at > now() per D-07),
  DeleteSession :exec, DeleteSessionsByUser :exec, ExtendSession :exec
- sqlc generate produces Email string (not pgtype.Text) and uuid.UUID in bindings
- go build ./internal/db/... exits 0
2026-05-14 21:52:48 +02:00
Arthur Belleville
3a12f8f47d
feat(01-03): templ layout/index/fragments + handlers + chi router
- templates/layout.templ: base HTML shell per UI-SPEC §Base Layout Contract
  (max-w-5xl container, slate-50 header, slate-200 borders, footer copy,
  /static/tailwind.css in <head>, /static/htmx.min.js deferred at body end —
  D-10: HTMX never loaded from a CDN)
- templates/index.templ: root page consuming @ui.Card and @ui.Button for the
  canonical HTMX demo (UI-SPEC §Component Library Contract canonical block)
- templates/fragments.templ: TimeFragment renders <span> with RFC3339 UTC
  timestamp; templ auto-escapes interpolation (T-01-13)
- internal/web/handlers.go: HealthzHandler (200 ok / 503 degraded per D-20,
  2s Ping timeout), IndexHandler, DemoTimeHandler with injected clock
- internal/web/router.go: Pinger interface; NewRouter wires
  RequestIDMiddleware → RealIP → SlogLoggerMiddleware → Recoverer (D-08
  + Pitfall 6 — chi middleware.Logger deliberately NOT registered) and
  routes /, /healthz, /demo/time, /static/* via http.FileServer (T-01-08
  path traversal blocked by http.Dir)

All six handler tests + ui package tests are GREEN under default go test.
2026-05-14 19:25:43 +02:00
Arthur Belleville
36e96015f5
feat(01-03): pgxpool wrapper, RequestID/slog middleware, slog handler switch
- Remove //go:build red_gate tag from internal/web/handlers_test.go and
  internal/db/pool_test.go now that consumer symbols are about to exist
- go mod tidy after real importers land (deferred from Plan 01-01 per
  Codex concern #1) — chi/v5, templ, pgx/v5, google/uuid now in require list
- internal/db/pool.go: NewPool(ctx, dsn) builds a pgxpool.Pool with
  MaxConns=10, MinConns=1; no eager Ping (RESEARCH Pitfall 2)
- internal/web/slog.go: NewSlogHandler returns JSON when env='production',
  text otherwise; pure helper, no slog.SetDefault side effect
- internal/web/middleware.go: RequestIDMiddleware (UUIDv4 → ctx +
  X-Request-ID header), LoggerFromContext helper, SlogLoggerMiddleware
  factory using chi WrapResponseWriter; field allowlist per V7/T-01-09
2026-05-14 19:24:16 +02:00
Arthur Belleville
37d19a3314 test(01-02): add red-gated handler and pool tests
- handlers_test.go (//go:build red_gate): TestHealthz_OK, TestHealthz_Down,
  TestIndex_RendersHxGet, TestDemoTime_Fragment, TestRequestID_HeaderSet,
  TestSlog_HandlerSwitch — reference web.HealthzHandler / NewRouter /
  NewSlogHandler / Pinger to be implemented in Plan 01-03
- pool_test.go (//go:build red_gate): TestPool_Connects with t.Skip
  fallback when DATABASE_URL is unset
- Build tag isolates the RED state from default 'go test ./...' (Codex #3)
2026-05-14 18:48:26 +02:00
Arthur Belleville
75cbd29d44 test(01-02): add ui package smoke tests
- TestButton_DefaultSolidMD: asserts root class and label
- TestButton_PassesThroughAttrs: asserts hx-* attribute spread
- TestButton_ExplicitTypeSubmit: type override
- TestCard_RendersChildren: templ.WithChildren child injection
- TestBadge_{Info,Success}Variant + zero-value normalization
- TestButtonClass_String / TestBadgeClass_String: class contract
- TestNormalizers_ZeroValueDefaults: every Normalized* returns safe default
2026-05-14 18:47:31 +02:00
Arthur Belleville
d056b33241 feat(01-02): add Button, Card, Badge templ components + CSS
- button.templ: Button(ButtonProps) renders <button type=...> with class
  from ui.ButtonClass(); Attrs spread for hx-* pass-through
- button.css: .ui-button base + .ui-button-solid-default-md variant
  with non-nested :hover and :focus-visible (Codex concern #7)
- card.templ: Card(attrs) accepts children via templ child syntax
- card.css: slate-50 panel, slate-200 border
- badge.templ: Badge(BadgeProps) renders <span class=...>
- badge.css: info / success / danger variants (warning deferred)
2026-05-14 18:46:42 +02:00
Arthur Belleville
1ff8e681da feat(01-02): add ui package enums, helpers, base CSS
- tokens.go: semantic token constants
- variants.go: Size/ButtonVariant/ButtonTone/BadgeVariant enums + Normalized*
- helpers.go: mergeAttrs for templ.Attributes
- base.css: resets, :focus-visible ring (no nesting)
2026-05-14 18:45:15 +02:00
Arthur Belleville
4de96854b5
feat(01-01): create directory skeleton and per-package doc.go placeholders
- internal/{db,session,tablos,tasks,files}/doc.go (D-02 placeholder packages)
- internal/db/{queries,sqlc}, templates/, migrations/, bin/, static/ via .gitkeep
- 'go build ./internal/...' compiles cleanly
- cmd/web, cmd/worker, internal/web are deliberately deferred to Plans 01-02 / 01-03
2026-05-14 17:53:55 +02:00
Arthur Belleville
50ba9b340b
Modify backend 2025-03-15 08:46:51 +01:00
Arthur Belleville
684b057b27
Initial commit 2025-01-18 22:40:32 +01:00