fix(auth): enforce role boundaries on data quality and audit

The data-quality workbench (list, detail, defer, reject) and the audit trail
had no role gate at all beyond authentication -- confirmed live, a Rental
Employee session could list and resolve data-quality issues and read the
full audit trail through both the API and the UI, with only merge-customers
and scan already restricted.

Per the role matrix, both areas are Operations-Manager-only. Gate the
remaining data-quality and audit endpoints with require_operations_manager,
hide their nav items for Rental Employee, show the same restricted-message
pattern Automation.tsx already used for direct URL access, and stop the
dashboard from linking into now-restricted areas for that role.
This commit is contained in:
NuklearRabbit
2026-08-02 04:52:01 +02:00
parent ffc88e33b4
commit 760f3b6ee2
9 changed files with 111 additions and 24 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, Query
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.api.deps import get_current_user, get_db
from app.api.deps import get_db, require_operations_manager
from app.models.audit import AuditEvent
from app.schemas import AuditEventOut, CurrentUser
@@ -19,7 +19,7 @@ def list_audit_events(
correlation_id: str | None = Query(default=None),
limit: int = Query(default=100, le=500),
db: Session = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
_user: CurrentUser = Depends(require_operations_manager),
) -> list[AuditEventOut]:
stmt = select(AuditEvent).order_by(AuditEvent.occurred_at.desc()).limit(limit)
if actor_label:
+5 -5
View File
@@ -4,7 +4,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select
from sqlalchemy.orm import Session
from app.api.deps import get_current_user, get_db, require_operations_manager
from app.api.deps import get_db, require_operations_manager
from app.models.customer import Customer
from app.models.data_quality import DataQualityIssue
from app.models.vehicle import Vehicle
@@ -41,7 +41,7 @@ def list_issues(
rule_type: str | None = Query(default=None),
severity: str | None = Query(default=None),
db: Session = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
_user: CurrentUser = Depends(require_operations_manager),
) -> list[DataQualityIssueOut]:
stmt = select(DataQualityIssue).order_by(DataQualityIssue.detected_at.desc())
if status:
@@ -85,7 +85,7 @@ def _snapshot(entity_type: str, ref: str, db: Session) -> dict | None:
def get_issue(
public_ref: str,
db: Session = Depends(get_db),
_user: CurrentUser = Depends(get_current_user),
_user: CurrentUser = Depends(require_operations_manager),
) -> DataQualityIssueDetailOut:
issue = db.scalar(select(DataQualityIssue).where(DataQualityIssue.public_ref == public_ref))
if issue is None:
@@ -110,7 +110,7 @@ def get_issue(
def defer(
public_ref: str,
db: Session = Depends(get_db),
user: CurrentUser = Depends(get_current_user),
user: CurrentUser = Depends(require_operations_manager),
) -> DataQualityIssueOut:
issue = defer_issue(db, public_ref, user)
return _to_out(issue)
@@ -120,7 +120,7 @@ def defer(
def reject(
public_ref: str,
db: Session = Depends(get_db),
user: CurrentUser = Depends(get_current_user),
user: CurrentUser = Depends(require_operations_manager),
) -> DataQualityIssueOut:
issue = reject_issue(db, public_ref, user)
return _to_out(issue)