Export: allow viewers to download presets, not just admins (#48) #100

Merged
claude-bot merged 1 commit from fix/export-viewer-access into main 2026-09-01 15:02:15 +00:00
Contributor

Closes the last small item on #48.

The spec's rule for the API surface is explicit: "Mutating endpoints require admin role… GET endpoints allow viewer unless noted otherwise." Neither export row carries an admin note the way /audit-log does, and §10's toolbar list marks "Quick push control (admin only)" while leaving "Export: download presets.json" unmarked. Both export GETs were nonetheless gated on require_admin.

  • GET /export/presets.jsonrequire_viewer
  • GET /export/events/{id}.jsonrequire_viewer (same reasoning; leaving it admin-only would have been inconsistent)

Un-gating the API alone would not have been enough. The toolbar's Export button lives inside the {isAdmin && (…)} block, so a viewer had no way to reach the endpoint even once it allowed them. Moved it out — it's now the one always-visible action in that group.

Two tests added to the existing viewer/admin matrix in test_auth.py so the permission can't regress silently.

Verification

  • Backend: 289 passed (2 new), ruff check, ruff format --check, mypy clean.
  • Frontend in a node:22 container: tsc -b, eslint, vitest (70), npm run build all clean.

🤖 Generated with Claude Code

Closes the last small item on #48. The spec's rule for the API surface is explicit: *"Mutating endpoints require `admin` role… **GET endpoints allow `viewer` unless noted otherwise**."* Neither export row carries an admin note the way `/audit-log` does, and §10's toolbar list marks *"Quick push control (admin only)"* while leaving *"Export: download `presets.json`"* unmarked. Both export GETs were nonetheless gated on `require_admin`. - `GET /export/presets.json` → `require_viewer` - `GET /export/events/{id}.json` → `require_viewer` (same reasoning; leaving it admin-only would have been inconsistent) **Un-gating the API alone would not have been enough.** The toolbar's Export button lives inside the `{isAdmin && (…)}` block, so a viewer had no way to reach the endpoint even once it allowed them. Moved it out — it's now the one always-visible action in that group. Two tests added to the existing viewer/admin matrix in `test_auth.py` so the permission can't regress silently. ## Verification - Backend: **289 passed** (2 new), `ruff check`, `ruff format --check`, `mypy` clean. - Frontend in a `node:22` container: `tsc -b`, `eslint`, `vitest` (70), `npm run build` all clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Export: allow viewers to download presets, not just admins (#48)
All checks were successful
CI / Alembic migration check (pull_request) Successful in 44s
CI / Python lint & type-check (pull_request) Successful in 1m10s
CI / Python tests (pull_request) Successful in 2m10s
CI / Frontend lint, test & build (pull_request) Successful in 3m31s
CI / Docker build, health smoke & E2E (pull_request) Successful in 2m5s
9c1e457842
The spec's rule for the API surface is that "GET endpoints allow `viewer`
unless noted otherwise", and neither export row carries an admin note the
way `/audit-log` does. Both export GETs were nonetheless gated on
require_admin.

Un-gating the API alone would not have been enough: the toolbar's Export
button sits inside the `{isAdmin && ...}` block, so a viewer had no way to
reach the endpoint even once it allowed them. Moved it out.

Covered by two tests in the existing viewer/admin matrix so the permission
cannot regress silently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
claude-bot deleted branch fix/export-viewer-access 2026-09-01 15:02:16 +00:00
Sign in to join this conversation.
No description provided.