fix(users): admin-gated /users routes accept api-key auth #23

Merged
hzhang merged 1 commits from fix/users-require-admin-accept-apikey into main 2026-05-29 07:55:45 +00:00
Contributor

Summary

  • The require_admin defined locally in app/api/routers/users.py depended on get_current_user, which is OAuth2 JWT only.
  • That makes every admin-gated /users route (list / get / patch / bind-agent / etc.) reject api-key clients with 401, even when the api-key resolves to an is_admin=True user.
  • Switch to get_current_user_or_apikey (the one already used by schedule_type.py and other admin routes) so X-API-Key and Bearer-as-apikey fallback both work. The admin gate itself still reads User.is_admin — only the auth carrier broadens.

Test plan

  • Reproduce: sherlock (admin role, valid api-key) → hf user list → 401 "Could not validate credentials"
  • Post-fix: same call → 200, returns user list
  • Non-admin api-key user → still 403 "Admin required"
  • OAuth2 JWT users → unaffected (path goes through the same or_apikey branch which tries JWT first when the token comes in via Bearer)

Surfaced during recruitment workflow Step 3 on prod-t2.

## Summary - The `require_admin` defined locally in `app/api/routers/users.py` depended on `get_current_user`, which is OAuth2 JWT only. - That makes every admin-gated /users route (list / get / patch / bind-agent / etc.) reject api-key clients with 401, even when the api-key resolves to an `is_admin=True` user. - Switch to `get_current_user_or_apikey` (the one already used by schedule_type.py and other admin routes) so X-API-Key and Bearer-as-apikey fallback both work. The admin gate itself still reads `User.is_admin` — only the auth carrier broadens. ## Test plan - [x] Reproduce: sherlock (admin role, valid api-key) → `hf user list` → 401 "Could not validate credentials" - [ ] Post-fix: same call → 200, returns user list - [ ] Non-admin api-key user → still 403 "Admin required" - [ ] OAuth2 JWT users → unaffected (path goes through the same `or_apikey` branch which tries JWT first when the token comes in via Bearer) Surfaced during recruitment workflow Step 3 on prod-t2.
hzhang added 1 commit 2026-05-29 07:55:45 +00:00
Local `require_admin` in users.py depended on `get_current_user`, which
is OAuth2 JWT only. That made every admin-gated /users route (list, get,
patch update, bind-agent, etc.) reject api-key clients with 401 even when
the api-key resolves to an is_admin=True user.

Switch to `get_current_user_or_apikey` (the one in deps.py) so X-API-Key
and Bearer-as-apikey fallback both work. The admin gate itself still
reads User.is_admin — only the auth carrier broadens. Matches the auth
pattern schedule_type.py and other admin routes already use.

Surfaced when sherlock (agent-resource-director) tried `hf user list` for
the recruitment workflow Step 3 verify and got 401 "Could not validate
credentials" despite a valid provisioned api-key.
hzhang merged commit 88779d2db0 into main 2026-05-29 07:55:45 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: zhi/HarborForge.Backend#23