0f2afb29-1136-4b93-b088-48d451bdbbbe — Admin auth: nestquest-admins group gate and service-token rejection (Feature 16 — NestQuest API Service)
Done-condition (verbatim)
Admin routes require membership in the nestquest-admins group: a valid JWT whose groups claim does NOT include nestquest-admins returns 403, one that does returns 200. The panel service token is rejected on admin routes OUTRIGHT (not merely treated as a non-admin). The check is ONE reusable dependency (e.g. require_admin) that every admin route uses. Tests prove: no token 401; panel service token on an admin route rejected; non-admin JWT 403; admin JWT 200.
Changes
api/auth.py — adds the ONE reusable require_admin dependency: rejects the configured panel service token outright (403, distinct credential type) before any JWT validation, then verifies the JWT via the existing path and requires the groups claim to be a list containing nestquest-admins (403 otherwise), returning the verified claims. require_admin_jwt remains for plain JWT validation.
api/routes_admin.py — the admin router and probe route now use require_admin.
tests/test_api_admin_auth.py — proves no token 401; panel service token rejected on an admin route; non-admin JWT 403; admin JWT 200; missing/malformed groups 403; and the router wiring.
Verification
TZ=UTC uv run pytest -q → 1652 passed
TZ=UTC uv run pytest -q tests/test_api_admin_auth.py → 40 passed
Baseline on the feature branch: 1645 passed.
Opened by the controller on the developer's behalf after the developer's PR step was permission-denied in its headless session; the branch and commit are the developer's (4d697dc025).
## Maestro task
`0f2afb29-1136-4b93-b088-48d451bdbbbe` — Admin auth: nestquest-admins group gate and service-token rejection (Feature 16 — NestQuest API Service)
## Done-condition (verbatim)
Admin routes require membership in the nestquest-admins group: a valid JWT whose groups claim does NOT include nestquest-admins returns 403, one that does returns 200. The panel service token is rejected on admin routes OUTRIGHT (not merely treated as a non-admin). The check is ONE reusable dependency (e.g. require_admin) that every admin route uses. Tests prove: no token 401; panel service token on an admin route rejected; non-admin JWT 403; admin JWT 200.
## Changes
- **api/auth.py** — adds the ONE reusable `require_admin` dependency: rejects the configured panel service token outright (403, distinct credential type) before any JWT validation, then verifies the JWT via the existing path and requires the `groups` claim to be a list containing `nestquest-admins` (403 otherwise), returning the verified claims. `require_admin_jwt` remains for plain JWT validation.
- **api/routes_admin.py** — the admin router and probe route now use `require_admin`.
- **tests/test_api_admin_auth.py** — proves no token 401; panel service token rejected on an admin route; non-admin JWT 403; admin JWT 200; missing/malformed groups 403; and the router wiring.
## Verification
- `TZ=UTC uv run pytest -q` → 1652 passed
- `TZ=UTC uv run pytest -q tests/test_api_admin_auth.py` → 40 passed
- Baseline on the feature branch: 1645 passed.
Opened by the controller on the developer's behalf after the developer's PR step was permission-denied in its headless session; the branch and commit are the developer's (4d697dc025eec98db4ea34ae94b00ec14efb3aea).
WHY: the admin plane was authenticated (any valid JWT passed) but not
authorized, and the panel service token would have been run through
JWT verification like any other credential. require_admin is now the
ONE dependency every admin route uses: it refuses the panel service
token outright with 403 BEFORE any JWT parsing (a refused credential
type, not an invalid one — a 401 challenge would imply a retry could
ever succeed), then verifies the JWT via the existing require_admin_jwt
path (uniform 401 unchanged) and requires the groups claim to name
nestquest-admins (403 for a missing/empty/non-list claim or one
without the group).
Fixes review finding P2: require_admin compared the Bearer credential
against config.panel_token with Python ==, which short-circuits and is
not constant-time. Compare with secrets.compare_digest on UTF-8 bytes
instead — the same pattern require_panel_token already uses for this
exact secret — keeping the truthiness guard on config.panel_token and
the same 403 rejection detail and behaviour.
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.
Maestro task
0f2afb29-1136-4b93-b088-48d451bdbbbe— Admin auth: nestquest-admins group gate and service-token rejection (Feature 16 — NestQuest API Service)Done-condition (verbatim)
Admin routes require membership in the nestquest-admins group: a valid JWT whose groups claim does NOT include nestquest-admins returns 403, one that does returns 200. The panel service token is rejected on admin routes OUTRIGHT (not merely treated as a non-admin). The check is ONE reusable dependency (e.g. require_admin) that every admin route uses. Tests prove: no token 401; panel service token on an admin route rejected; non-admin JWT 403; admin JWT 200.
Changes
require_admindependency: rejects the configured panel service token outright (403, distinct credential type) before any JWT validation, then verifies the JWT via the existing path and requires thegroupsclaim to be a list containingnestquest-admins(403 otherwise), returning the verified claims.require_admin_jwtremains for plain JWT validation.require_admin.Verification
TZ=UTC uv run pytest -q→ 1652 passedTZ=UTC uv run pytest -q tests/test_api_admin_auth.py→ 40 passedOpened by the controller on the developer's behalf after the developer's PR step was permission-denied in its headless session; the branch and commit are the developer's (
4d697dc025).Independent Claude Sonnet 5 review completed for this exact commit.