mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-01 03:31:25 +02:00
API-key permission gates went from a 17-entry admin denylist with the three
documented scope flags (can_read_status / can_queue / can_control_printer)
enforced only inside /api/v1/webhook/* to an explicit per-Permission
allowlist consulted by every dependency:
- core/auth.py: _APIKEY_SCOPE_BY_PERMISSION maps every non-admin
Permission to one scope flag on APIKey; unmapped = 403.
_check_apikey_permissions now takes the api_key and checks the flag.
- require_any_permission_if_auth_enabled + require_ownership_permission
were returning None for any valid key with zero scope check; both now
invoke _check_apikey_permissions and fail closed.
- Two new scope flags on api_keys: can_manage_library (LIBRARY_UPLOAD /
UPDATE_OWN / DELETE_OWN / MAKERWORLD_IMPORT) and can_manage_inventory
(INVENTORY_CREATE / UPDATE / DELETE / FORECAST_WRITE — required by
SpoolBuddy kiosks). Default TRUE, backfilled from can_queue so existing
"queue-only" keys keep working and hardened "read-only" keys do not
silently gain writes.
- CLOUD_AUTH now routed through can_access_cloud for defence-in-depth
alongside the existing _cloud_api_key_gate.
- Migration column-existence check (_api_keys_column_exists) gates the
backfill so user-edited values are never overwritten on restart.
Structural drift backstop: test_every_permission_has_a_classification fails
CI on any new Permission added without an explicit scope mapping —
prevents the denylist-shape regression that grew the prior surface.
Backend 5469 tests green; ruff clean. Frontend build green; i18n parity
green across 9 locales (5005 leaves each, +6 new keys). Wiki permissions
table + allowlist callout + upgrade notes updated.
65 lines
2.1 KiB
Python
65 lines
2.1 KiB
Python
from datetime import datetime
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class APIKeyCreate(BaseModel):
|
|
"""Schema for creating a new API key."""
|
|
|
|
name: str
|
|
can_queue: bool = True
|
|
can_control_printer: bool = False
|
|
can_read_status: bool = True
|
|
can_manage_library: bool = True # Upload / rename / delete own library files + MakerWorld import
|
|
can_manage_inventory: bool = True # Inventory writes — SpoolBuddy NFC/scale/system, manual stock edits via API
|
|
can_access_cloud: bool = False # Read /cloud/* on the creator's behalf — default off (#1182)
|
|
can_update_energy_cost: bool = False # POST /settings/electricity-price only (#1356)
|
|
printer_ids: list[int] | None = None # null = all printers
|
|
expires_at: datetime | None = None
|
|
|
|
|
|
class APIKeyUpdate(BaseModel):
|
|
"""Schema for updating an API key."""
|
|
|
|
name: str | None = None
|
|
can_queue: bool | None = None
|
|
can_control_printer: bool | None = None
|
|
can_read_status: bool | None = None
|
|
can_manage_library: bool | None = None
|
|
can_manage_inventory: bool | None = None
|
|
can_access_cloud: bool | None = None
|
|
can_update_energy_cost: bool | None = None
|
|
printer_ids: list[int] | None = None
|
|
enabled: bool | None = None
|
|
expires_at: datetime | None = None
|
|
|
|
|
|
class APIKeyResponse(BaseModel):
|
|
"""Schema for API key response (without full key)."""
|
|
|
|
id: int
|
|
name: str
|
|
key_prefix: str # First 8 chars for identification
|
|
user_id: int | None # Owner — NULL on legacy keys created before per-user ownership (#1182)
|
|
can_queue: bool
|
|
can_control_printer: bool
|
|
can_read_status: bool
|
|
can_manage_library: bool
|
|
can_manage_inventory: bool
|
|
can_access_cloud: bool
|
|
can_update_energy_cost: bool
|
|
printer_ids: list[int] | None
|
|
enabled: bool
|
|
last_used: datetime | None
|
|
created_at: datetime
|
|
expires_at: datetime | None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class APIKeyCreateResponse(APIKeyResponse):
|
|
"""Response when creating a key - includes full key (shown only once)."""
|
|
|
|
key: str # Full API key, only shown on creation
|