2 Commits
Author SHA1 Message Date
maziggy d81040607e fix(api-keys): slice + slicer-presets routes resolve cloud token via key owner (#1182 follow-up)
turulix's headless slicing pipeline got cloud preset IDs from
  /api/v1/cloud/settings (the /cloud/* gate from #1182 worked), but
  slicing those IDs via POST /library/files/{id}/slice failed with
  "no Bambu Cloud session is stored" — the slice route lives on a
  different router, never saw the api_key_owner stash, and
  _resolve_cloud fell through to the empty auth-disabled global
  Settings token.

  Add a permissive route-level dep that returns the API key's owner
  when the key has the cloud scope and None otherwise (never raises),
  so non-/cloud/* routes can opt in without breaking the local-preset
  path. Wire it into POST /library/files/{id}/slice and
  GET /slicer/presets (same root cause, would hit any UI proxied
  through an API key). The route picks current_user or
  api_key_cloud_owner before deriving user_id.

  Auth gate's None-return for API keys is unchanged — keeping the
  owner-resolution scoped to the routes that actually need a cloud
  token prevents scope creep into routes that fence on
  ``current_user is None``.
2026-05-02 08:10:47 +02:00
maziggy 133ec72527 feat(api-keys): per-user ownership + opt-in cloud access scope (#1182)
Tim (@turulix) is building a fully automated headless slicing pipeline
  against Bambuddy's API and hit the wall flagged in #665: /cloud/* routes
  resolve cloud_token per-user from User.cloud_token, but the auth gate
  returned None for API-keyed requests, so the route fell back to the
  global Settings-table token, which only carries a value in auth-disabled
  deployments. Net effect on auth-enabled deployments: API keys reached
  the gate just fine, then /cloud/filaments always saw user=None and
  returned 401 / empty results — no path to read slicer presets or the
  filament catalogue that a CLI workflow needs.

  Make API keys carry an owner and route /cloud/* lookups through that
  owner; gate the new capability behind an explicit opt-in scope so
  existing automation doesn't gain cloud-read access on upgrade.

  - APIKey gains user_id (FK to users.id, ON DELETE CASCADE) and
    can_access_cloud (BOOLEAN DEFAULT 0). User-delete route also runs an
    explicit DELETE FROM api_keys WHERE user_id = ? since SQLite ships
    FK enforcement off — same pattern as the existing created_by_id
    cleanup blocks.

  - New cloud_caller dep on /cloud/* routes resolves to the JWT user OR
    the API-key owner stashed by a router-level gate. The auth gate itself
    continues to return None for API keys so #1182's surface stays bounded
    to /cloud/* — without that bound, any route that fences API keys via
    `if current_user is None: raise 403` (e.g. long-lived-token
    management) would silently start accepting them.

  - The /cloud/* router-level dep enforces three independent fences for
    API-keyed callers: user_id IS NOT NULL (legacy keys → 401 with
    recreate copy), can_access_cloud=True (otherwise 403), and owner has
    cloud_token (existing fence, unchanged). Two extra one-shot fence
    errors at create/update time refuse can_access_cloud=True when auth
    is disabled or the key is ownerless.

  - Frontend: APIKey list shows "Cloud" badge on cloud-enabled keys and
    "Legacy" badge on ownerless rows; create form gains an "Allow cloud
    access" toggle, default off. New i18n keys in all 8 locales (en + de
    fully translated, others seeded with English fallbacks pending native
    translation — matches the project's flow for newly-added features).

  Migration: two idempotent ALTER TABLE statements + an index on user_id
  for the auth gate's owner→keys lookup. Postgres-safe.

  Tests: 9 backend integration tests in test_api_key_cloud_access.py
  covering creation flags, the three /cloud/* fences, JWT no-op, and
  deletion CASCADE; 2 frontend SettingsPage tests pinning the badge
  matrix and the create-form contract; 5 daemon unit tests for the
  related SpoolBuddy ssh-key sync work that landed in the same branch.
  Full backend suite: 3578 passed; full frontend suite: 1597 passed; no
  regressions.

  Permission semantics for existing keys: keys created before this
  release become "legacy" and are rejected at /cloud/* with the recreate
  message. Every other endpoint they were used against — queue, status,
  control — is untouched.
2026-05-01 11:43:55 +02:00