feat(backend): enforce admin-only ops endpoints and cover destructive cleanup smoke

restrict ops endpoints to admin-only access

block operator and viewer keys from admin maintenance routes
cover destructive pricing cleanup in smoke execution, not only preview

extend orchestration without regressing existing smoke stages
This commit is contained in:
greebo
2026-03-20 16:02:38 +03:00
parent 210981c953
commit 5aa35b1d04
10 changed files with 1090 additions and 13 deletions

View File

@@ -5,7 +5,7 @@ from app.repositories.scheme_artifacts import artifact_exists, list_scheme_artif
from app.repositories.scheme_versions import get_current_scheme_version
from app.repositories.schemes import get_scheme_record_by_scheme_id, list_scheme_records
from app.repositories.uploads import get_upload_record_by_upload_id
from app.security.auth import require_api_key
from app.security.auth import require_admin_api_key
from app.services.artifact_maintenance import (
cleanup_publish_preview_storage,
inspect_publish_preview_storage,
@@ -19,7 +19,7 @@ router = APIRouter()
@router.get(f"{settings.api_v1_prefix}/admin/schemes/{{scheme_id}}/current/artifacts")
async def list_current_scheme_artifacts(
scheme_id: str,
role: str = Depends(require_api_key),
role: str = Depends(require_admin_api_key),
):
scheme = await get_scheme_record_by_scheme_id(scheme_id)
version = await get_current_scheme_version(
@@ -50,7 +50,7 @@ async def list_current_scheme_artifacts(
@router.get(f"{settings.api_v1_prefix}/admin/schemes/{{scheme_id}}/current/validation")
async def validate_current_scheme(
scheme_id: str,
role: str = Depends(require_api_key),
role: str = Depends(require_admin_api_key),
):
scheme = await get_scheme_record_by_scheme_id(scheme_id)
version = await get_current_scheme_version(
@@ -74,7 +74,7 @@ async def validate_current_scheme(
async def regenerate_current_display(
scheme_id: str,
mode: str = Query(default="passthrough"),
role: str = Depends(require_api_key),
role: str = Depends(require_admin_api_key),
):
scheme = await get_scheme_record_by_scheme_id(scheme_id)
version = await get_current_scheme_version(
@@ -98,7 +98,7 @@ async def bulk_backfill_display_artifacts(
mode: str = Query(default="passthrough"),
limit: int = Query(default=100, ge=1, le=1000),
only_missing: bool = Query(default=True),
role: str = Depends(require_api_key),
role: str = Depends(require_admin_api_key),
):
schemes = await list_scheme_records(limit=limit, offset=0)
@@ -168,7 +168,7 @@ async def bulk_backfill_display_artifacts(
@router.get(f"{settings.api_v1_prefix}/admin/artifacts/publish-preview/audit")
async def audit_publish_preview_storage(
role: str = Depends(require_api_key),
role: str = Depends(require_admin_api_key),
):
return await inspect_publish_preview_storage()
@@ -176,6 +176,6 @@ async def audit_publish_preview_storage(
@router.post(f"{settings.api_v1_prefix}/admin/artifacts/publish-preview/cleanup")
async def cleanup_publish_preview_artifacts_endpoint(
dry_run: bool = Query(default=True),
role: str = Depends(require_api_key),
role: str = Depends(require_admin_api_key),
):
return await cleanup_publish_preview_storage(dry_run=dry_run)

View File

@@ -6,7 +6,7 @@ from app.schemas.admin_cleanup import (
PricingCleanupExecuteResponse,
PricingCleanupPreviewResponse,
)
from app.security.auth import require_api_key
from app.security.auth import require_admin_api_key
from app.services.pricing_cleanup import (
build_pricing_cleanup_preview,
execute_pricing_cleanup,
@@ -25,7 +25,7 @@ async def get_pricing_cleanup_preview(
name_prefix: list[str] = Query(default_factory=list),
pricing_category_id: list[str] = Query(default_factory=list),
delete_only_without_rules: bool = Query(default=True),
role: str = Depends(require_api_key),
role: str = Depends(require_admin_api_key),
):
return await build_pricing_cleanup_preview(
scheme_id=scheme_id,
@@ -43,7 +43,7 @@ async def get_pricing_cleanup_preview(
async def post_pricing_cleanup(
scheme_id: str,
payload: PricingCleanupExecuteRequest,
role: str = Depends(require_api_key),
role: str = Depends(require_admin_api_key),
):
return await execute_pricing_cleanup(
scheme_id=scheme_id,

View File

@@ -1,4 +1,4 @@
from fastapi import Header, HTTPException, status
from fastapi import Depends, Header, HTTPException, status
from app.core.config import settings
from app.domain.roles import UserRole
@@ -31,3 +31,12 @@ async def require_api_key(
)
return role
async def require_admin_api_key(role: str = Depends(require_api_key)) -> str:
if role != UserRole.ADMIN.value:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Admin role required",
)
return role