Files
bambuddy/backend/app/schemas/api_key.py
T
maziggy ae29a7dcd3 fix(api-keys): expose narrowly-scoped "Update electricity price" toggle (#1356)
Reporter @maziggy followed the Energy Tracking wiki literally - "create a
  key with Write Settings permission, PATCH /api/v1/settings with
  {energy_cost_per_kwh: ...}" - and hit:
  {"detail":"API keys cannot be used for administrative operations"}.

  Triage showed three independent drifts:
  1. Wiki listed nine fictional API-key permissions (Read Printers / Write
     Settings / Admin / ...) but the UI only ever exposed four toggles
     (Read Status, Manage Queue, Control Printer, Allow Cloud Access).
     There was no Write Settings toggle to tick.
  2. Even if it had existed, the backend hard-denies SETTINGS_UPDATE for
     every API key via _APIKEY_DENIED_PERMISSIONS - intentional protection
     because PATCH /settings can rewrite SMTP/LDAP/MQTT credentials and the
     HA access token. Wider surface than any documented use case needs.
  3. So the wiki had been promising a workflow that was never deliverable.

  Fix: introduce a narrowly-scoped door rather than relax the deny list.

    - New column can_update_energy_cost (default FALSE - existing keys
      never silently gain settings-write capability on upgrade).
    - New route POST /api/v1/settings/electricity-price accepting
      {"energy_cost_per_kwh": <float >= 0>}. Field name matches what the
      wiki already documented so the HA rest_command example needs only a
      URL+method change, not a payload change.
    - Custom dependency require_energy_cost_update() bypasses
      _APIKEY_DENIED_PERMISSIONS for this one route for API keys with the
      flag set. JWT users still go through standard SETTINGS_UPDATE.
    - General PATCH /settings remains denied for API keys - flipping the
      narrow flag does NOT widen general settings-write access. Pinned by
      test_patch_settings_still_denied_with_energy_flag.

  Frontend: fifth "Update electricity price" toggle on the create-API-key
  card + amber "Energy" badge on existing keys with the flag set. Three
  new i18n keys across all 8 locales (German translated, English fallbacks
  elsewhere).
2026-05-15 13:12:41 +02:00

59 lines
1.7 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_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_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_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