# Config Download Token Design **Date:** 2026-03-01 **Status:** Approved ## Problem Downloading a generated config ZIP from the command line requires extracting an httpOnly OIDC session cookie from the browser, which is fragile and not scriptable. Users need a stable, per-config token they can embed in automation scripts. ## Solution Add a `download_token` field to each `Config`. The existing generate endpoint accepts this token in the POST body as an alternative to OIDC cookie auth, allowing unauthenticated-but-authorized downloads. ## Data Model - Add `download_token: str` column to `configs` table. - Value: `secrets.token_urlsafe(32)` — 32 bytes of URL-safe random data (43 characters). - Generated automatically on config creation. - Stored as plaintext (the token is low-value; it only grants read access to a single config's generated output). - Alembic migration backfills existing configs with auto-generated tokens. ## API Changes ### Modified: `POST /api/configs/{id}/generate` Accepts an optional JSON body: ```json { "token": "..." } ``` Auth logic (either is sufficient): 1. Valid OIDC `access_token` cookie + `owner_id` match 2. `token` in body matches `config.download_token` (no owner filter needed) Error responses: - No cookie and no/wrong token → 401 - Valid token but wrong config ID → 404 Example curl usage: ```bash curl -X POST "https://host/api/configs/1/generate?format=zip" \ -H 'Content-Type: application/json' \ -d '{"token": "abc..."}' -o shorewall.zip ``` ### New: `POST /api/configs/{id}/regenerate-token` - OIDC-protected, owner-only. - Generates a new `secrets.token_urlsafe(32)`, saves it to the config, returns it. - Response: `{ "download_token": "..." }` - Non-owner → 403. ## Schema Changes - `ConfigOut` gains `download_token: str`. - New `GenerateRequest` Pydantic model: `token: Optional[str] = None`. ## Frontend Changes On the Config Detail page header area (above the tabs): - Read-only text field showing `download_token`. - Copy-to-clipboard icon button. - "Regenerate" button that calls the new endpoint and updates the displayed value. ## Migration New Alembic migration `0012_config_add_download_token.py`: - Add `download_token` column with `server_default=''`. - Backfill with `secrets.token_urlsafe(32)` for all existing rows via a data migration step.