perf(auth): avoid duplicate user lookup in require_role

require_role._check previously chained from get_current_user, which
already loaded the user — then looked it up again. Inline the decode +
single user fetch + must_change_password + role check so every
authenticated request costs one SELECT users WHERE uuid=? instead of
two.
This commit is contained in:
2026-04-17 17:48:42 -04:00
parent b5d7bf818f
commit de4b64d857

View File

@@ -105,13 +105,26 @@ async def get_current_user_unchecked(request: Request) -> str:
def require_role(*allowed_roles: str): def require_role(*allowed_roles: str):
"""Factory that returns a FastAPI dependency enforcing role membership. """Factory that returns a FastAPI dependency enforcing role membership.
The returned dependency chains from ``get_current_user`` (JWT + must_change_password) Inlines JWT decode + user lookup + must_change_password + role check so the
then verifies the user's role is in *allowed_roles*. Returns the full user dict so user is only loaded from the DB once per request (not once in
endpoints can inspect ``user["uuid"]``, ``user["role"]``, etc. without a second lookup. ``get_current_user`` and again here). Returns the full user dict so
endpoints can inspect ``user["uuid"]``, ``user["role"]``, etc.
""" """
async def _check(current_user: str = Depends(get_current_user)) -> dict: async def _check(request: Request) -> dict:
user = await repo.get_user_by_uuid(current_user) user_uuid = await _decode_token(request)
if not user or user["role"] not in allowed_roles: user = await repo.get_user_by_uuid(user_uuid)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
if user.get("must_change_password"):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Password change required before accessing this resource",
)
if user["role"] not in allowed_roles:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, status_code=status.HTTP_403_FORBIDDEN,
detail="Insufficient permissions", detail="Insufficient permissions",