Implements Feature 03 task 3 (1f240a09). Done-condition: the callback refuses a session unless the ID token or userinfo groups claim contains a group in MEALQUEST_OIDC_ALLOWED_GROUPS, showing an HTML 403 ("This kitchen is for the household only" + sign out); RequireSession protects /api/v1/* and /media/* (JSON 401 {"error":{"code":"unauthenticated"}}) while /health, /auth/* and static assets stay public; a removed user is refused at next login; tests cover member, non-member, missing groups claim, API 401 without a cookie and /health without a cookie; quality floor passes.
Evidence (developer-run): gofmt empty; go vet clean; go test -race all packages pass; CGO_ENABLED=0 build ok; make test/build ok; live run showed 401 JSON for /api/v1 and /media without a cookie, 200 /health, 302 /auth/login. Adds the canonical actor-in-context helper.
Implements Feature 03 task 3 (1f240a09). Done-condition: the callback refuses a session unless the ID token or userinfo groups claim contains a group in MEALQUEST_OIDC_ALLOWED_GROUPS, showing an HTML 403 ("This kitchen is for the household only" + sign out); RequireSession protects /api/v1/* and /media/* (JSON 401 {"error":{"code":"unauthenticated"}}) while /health, /auth/* and static assets stay public; a removed user is refused at next login; tests cover member, non-member, missing groups claim, API 401 without a cookie and /health without a cookie; quality floor passes.
Evidence (developer-run): gofmt empty; go vet clean; go test -race all packages pass; CGO_ENABLED=0 build ok; make test/build ok; live run showed 401 JSON for /api/v1 and /media without a cookie, 200 /health, 302 /auth/login. Adds the canonical actor-in-context helper.
The callback now starts a session only when the verified groups claim
contains a group from MEALQUEST_OIDC_ALLOWED_GROUPS. When the ID token has
no groups claim, groups come from the userinfo endpoint, whose subject must
match the ID token. A refused sign-in gets a 403 HTML page ("This kitchen is
for the household only") with a sign-out button, and no session. The check
runs on every login, so an account removed from the group is refused at its
next one. auth.New requires at least one allowed group and a discovered
userinfo endpoint that passes the endpoint rule.
The RequireSession middleware wraps the /api/v1 and /media subrouters: no
live session gets 401 {"error":{"code":"unauthenticated",...}}, a lookup
failure 500, and a live session reaches the handler with the actor in the
context. It wraps the subrouters instead of using chi's Use because chi
skips middleware on a subrouter with no routes. /health, /auth and the web
build stay public.
auth.WithActor and auth.ActorFrom are the one way to read the signed-in
household member; handlers record Actor.Subject as updated_by.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A browser that still holds a session and signs in as an account outside
the household's group got the 403 page but kept its old session. The
refusal now ends that session and clears its cookie first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
{"verdict":"APPROVED","reviewed_sha":"c824942484eceb492a88a382c8455a5f2f84aad3","summary":"The group gate, refusal page, session middleware, public routes, and actor context meet the stated done-condition. Tests cover the required cases. gofmt, go vet, and go test -race -count=1 ./... passed.","findings":[]}
Posted unaltered by review_agent. This is transport, not a second review.
## Independent review — Codex `gpt-6-sol`, reasoning high
Verdict: **APPROVED**
Reviewed SHA: `c824942484eceb492a88a382c8455a5f2f84aad3`
```json
{
"verdict": "APPROVED",
"reviewed_sha": "c824942484eceb492a88a382c8455a5f2f84aad3",
"summary": "The group gate, refusal page, session middleware, public routes, and actor context meet the stated done-condition. Tests cover the required cases. gofmt, go vet, and go test -race -count=1 ./... passed.",
"findings": []
}
```
---
Posted unaltered by `review_agent`. This is transport, not a second review.
approver_agent
merged commit b2201c5da1 into feature/9c4eba48-9a49-4b73-a85f-a4bc19eb4062-authentik-oidc-sessions2026-10-03 10:30:08 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements Feature 03 task 3 (1f240a09). Done-condition: the callback refuses a session unless the ID token or userinfo groups claim contains a group in MEALQUEST_OIDC_ALLOWED_GROUPS, showing an HTML 403 ("This kitchen is for the household only" + sign out); RequireSession protects /api/v1/* and /media/* (JSON 401 {"error":{"code":"unauthenticated"}}) while /health, /auth/* and static assets stay public; a removed user is refused at next login; tests cover member, non-member, missing groups claim, API 401 without a cookie and /health without a cookie; quality floor passes.
Evidence (developer-run): gofmt empty; go vet clean; go test -race all packages pass; CGO_ENABLED=0 build ok; make test/build ok; live run showed 401 JSON for /api/v1 and /media without a cookie, 200 /health, 302 /auth/login. Adds the canonical actor-in-context helper.
The callback now starts a session only when the verified groups claim contains a group from MEALQUEST_OIDC_ALLOWED_GROUPS. When the ID token has no groups claim, groups come from the userinfo endpoint, whose subject must match the ID token. A refused sign-in gets a 403 HTML page ("This kitchen is for the household only") with a sign-out button, and no session. The check runs on every login, so an account removed from the group is refused at its next one. auth.New requires at least one allowed group and a discovered userinfo endpoint that passes the endpoint rule. The RequireSession middleware wraps the /api/v1 and /media subrouters: no live session gets 401 {"error":{"code":"unauthenticated",...}}, a lookup failure 500, and a live session reaches the handler with the actor in the context. It wraps the subrouters instead of using chi's Use because chi skips middleware on a subrouter with no routes. /health, /auth and the web build stay public. auth.WithActor and auth.ActorFrom are the one way to read the signed-in household member; handlers record Actor.Subject as updated_by. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Independent review — Codex
gpt-6-sol, reasoning highVerdict: APPROVED
Reviewed SHA:
c824942484eceb492a88a382c8455a5f2f84aad3Posted unaltered by
review_agent. This is transport, not a second review.