From cacb1d265224714476084df93d931c88e55bca6f Mon Sep 17 00:00:00 2001 From: hzhang Date: Fri, 29 May 2026 08:55:28 +0100 Subject: [PATCH] fix(users): admin-gated /users routes accept api-key auth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- app/api/routers/users.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/api/routers/users.py b/app/api/routers/users.py index 58906c6..7c9d128 100644 --- a/app/api/routers/users.py +++ b/app/api/routers/users.py @@ -39,7 +39,11 @@ def _user_response(user: models.User) -> dict: return data -def require_admin(current_user: models.User = Depends(get_current_user)): +def require_admin(current_user: models.User = Depends(get_current_user_or_apikey)): + # Accept either OAuth2 JWT or X-API-Key (incl. Bearer-as-apikey fallback) + # so CLI clients using their provisioned api-key can hit admin-gated user + # routes (list / get / update / patch). The admin gate still reads + # User.is_admin — only the auth carrier broadens. if not current_user.is_admin: raise HTTPException(status_code=403, detail="Admin required") return current_user