Add CSRF protection and GET /api/v1/me
Independent review Codex gpt-6-sol high: APPROVED (0 findings)
CI / ci (pull_request) Successful in 1m27s
CI / ci (push) Successful in 1m23s

RequireSession now stores the validated session in the request context
(auth.WithSession / auth.SessionFrom); auth.ActorFrom derives the actor
from it, so there is one context value.

Every /api/v1 request other than GET, HEAD and OPTIONS must carry
X-MealQuest-CSRF equal to the session's csrf_token (constant-time
compare), else 403 {"error":{"code":"csrf"}}. The check runs after
RequireSession, so unauthenticated requests still get 401. A session
with no token is a 500, never a match against a missing header.

GET /api/v1/me returns {name, email, household: {label: "Our kitchen"},
csrfToken} with Cache-Control: no-store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 10:32:31 -04:00
co-authored by Claude Opus 5.5
parent b2201c5da1
commit 15cc3c5ce4
9 changed files with 306 additions and 23 deletions
+5 -4
View File
@@ -21,10 +21,11 @@ var (
validSessionCookie = &http.Cookie{Name: auth.SessionCookieName, Value: "valid"}
brokenSessionCookie = &http.Cookie{Name: auth.SessionCookieName, Value: "broken"}
testSession = auth.Session{
Subject: "user-123",
Email: "cook@example.com",
Name: "Home Cook",
Groups: []string{"mealquest-admin"},
Subject: "user-123",
Email: "cook@example.com",
Name: "Home Cook",
Groups: []string{"mealquest-admin"},
CSRFToken: "csrf-token-123",
}
)