This commit is contained in:
2026-05-10 09:46:07 +12:00
parent cfc193b713
commit 2f2466ecac
81 changed files with 2571 additions and 413 deletions
+10 -8
View File
@@ -16,18 +16,16 @@ from __future__ import annotations
from typing import Iterable
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from fastapi import Depends, HTTPException, Request, status
from sqlalchemy import select
from sqlalchemy.orm import Session, selectinload
from app.core.security import verify_token
from app.core.http import CLIENT_AUTH_COOKIE, get_bearer_or_cookie_token
from app.core.security_logging import log_security_event
from app.db.session import get_db
from app.models.access import Permission, Role, User
bearer_scheme = HTTPBearer(auto_error=False)
# Subject claim used by tokens issued for internal Hunter Stock Feeds users.
# Distinct from the existing client-portal/admin tokens so the two systems
# cannot impersonate each other.
@@ -103,7 +101,7 @@ def _load_user(db: Session, user_id: int) -> User | None:
def get_current_user(
credentials: HTTPAuthorizationCredentials | None = Depends(bearer_scheme),
request: Request,
db: Session = Depends(get_db),
) -> User:
"""Resolve the current internal user from the bearer token.
@@ -111,10 +109,11 @@ def get_current_user(
Raises 401 for missing/invalid tokens or unknown users, 403 for inactive
users.
"""
if credentials is None:
token = get_bearer_or_cookie_token(request, cookie_name=CLIENT_AUTH_COOKIE.name)
if token is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Authentication required")
payload = verify_token(credentials.credentials)
payload = verify_token(token)
if payload.get("sub") != INTERNAL_USER_SUBJECT:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication token")
@@ -136,6 +135,7 @@ def require_permission(permission_key: str):
def dependency(user: User = Depends(get_current_user)) -> User:
if not user_has_permission(user, permission_key):
log_security_event("authz.denied", role=user.role.name if user.role else None, permission=permission_key)
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Missing required permission: {permission_key}",
@@ -152,6 +152,7 @@ def require_any_permission(permission_keys: Iterable[str]):
def dependency(user: User = Depends(get_current_user)) -> User:
granted = get_user_permissions(user)
if not any(key in granted for key in keys):
log_security_event("authz.denied", role=user.role.name if user.role else None, permissions=list(keys))
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Requires any of: {list(keys)}",
@@ -169,6 +170,7 @@ def require_all_permissions(permission_keys: Iterable[str]):
granted = get_user_permissions(user)
missing = [key for key in keys if key not in granted]
if missing:
log_security_event("authz.denied", role=user.role.name if user.role else None, permissions=missing)
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Missing required permissions: {missing}",