Skip to content

Scoped admin/operator token minting for CLI admin operations #257

Description

@zachdunn

Context

PR #253 (issue #251) made uploads-cli a managed official OAuth client, but scope semantics are unchanged: workspace API tokens minted via POST /v1/tokens are limited to FILE_SCOPES (files:read/write/delete, apps/api/src/auth-db.ts). CLI-driven admin/operator actions currently have exactly one path — the static ADMIN_TOKEN break-glass secret gating /admin/* (apps/api/src/admin.ts): shared, non-attributable, not revocable per person.

Proposal

Add optional operator scopes to the workspace-token mint path, gated on the minting identity:

  • New scopes (e.g. admin:read, admin:write — naming TBD) accepted by POST /v1/tokens only when the minting session user has the better-auth admin role (requireAdmin, apps/api/src/session-auth.ts). Non-admin mints requesting them fail with invalid_scopes.
  • /admin/* (or a successor surface) accepts these scoped tokens alongside ADMIN_TOKEN, giving per-token revocation, labels, and attribution to the minting admin.
  • Default mint scopes stay files-only; admin scopes are always opt-in ("manually select and add").

This gives attenuation: an admin can mint a files-only token for an agent and a separate short-lived operator token, instead of handing out an all-or-nothing session bearer or the shared break-glass secret.

Guardrails

  • Add operator scopes on the API-worker side only — do NOT add them to the auth worker's OAUTH_SCOPES: anything there is requestable by dynamically-registered MCP clients (DCR is open by design).
  • If AS-side admin scopes are ever wanted, bound them per-client via the managed-registration scopes column (only official clients like uploads-cli), never in the DCR-registerable set.
  • Keep ADMIN_TOKEN as break-glass, consistent with the Better Auth rollout decision.

Alternative considered

CLI admin commands could present the device-flow session token as a bearer against the session+role-gated /admin-ui/* surface with zero scope changes (the bearer() plugin already supports this). Cheaper, but all-or-nothing — no attenuation, no per-token revocation — so scoped minting is the preferred design if agents/automation will perform admin ops.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions