2.3 KiB
2.3 KiB
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: strcolumn toconfigstable. - 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:
{ "token": "..." }
Auth logic (either is sufficient):
- Valid OIDC
access_tokencookie +owner_idmatch tokenin body matchesconfig.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:
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
ConfigOutgainsdownload_token: str.- New
GenerateRequestPydantic 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_tokencolumn withserver_default=''. - Backfill with
secrets.token_urlsafe(32)for all existing rows via a data migration step.