Per-client ICS calendar feed, replacing the shared ICS_TOKEN (#22)
Test backoffice (smb-crm) / test (push) Has been cancelled
Test backoffice (smb-crm) / test (push) Has been cancelled
Each client now gets their own clients.ics_token (lazily generated on first /owner/settings visit), which both authenticates and scopes /api/bookings.ics -- closing the gap where any shared-token holder could view another client's bookings by swapping the client_id query param. The owner settings page now surfaces a copyable subscribe URL. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+8
-5
@@ -87,8 +87,10 @@ Flask app (`app.py`) + `waitress`, backed by Postgres (`smb-db`, `postgres:16-al
|
||||
- `PATCH /api/<entity>/<id>` — partial update, same auth.
|
||||
- `DELETE /api/<entity>/<id>` — delete, same auth.
|
||||
- `GET /api/bookings.ics` — read-only iCal feed for calendar apps (Apple/Google Calendar can't
|
||||
send custom headers, so this is gated by a separate `?token=$ICS_TOKEN` query param instead of
|
||||
the header token). Supports `?client_id=` to scope to one client.
|
||||
send custom headers, so this is gated by a `?token=` query param instead of the header token).
|
||||
The token is per-client (`clients.ics_token`, lazily generated on first visit to
|
||||
`/owner/settings`), so it both authenticates and scopes the feed to that one client — there's
|
||||
no separate `client_id` param to swap.
|
||||
- `GET /healthz` — DB connectivity check.
|
||||
- `GET /` — the dashboard (`static/index.html`; **Leads**, **Clients** and **Credentials** tabs —
|
||||
read + add (+Neu) + edit (✎) + delete (🗑), all token-gated, all audit-logged. The Credentials
|
||||
@@ -192,9 +194,10 @@ see `deploy/clients/README.md` and each group's own compose file for the exact r
|
||||
- Browser-facing surfaces (`onboard.mivanchenko.de`, dashboards) sit behind Caddy basic-auth.
|
||||
- Machine-to-machine writes (n8n → CRM) are gated by the `CRM_API_TOKEN` header, checked in
|
||||
`backoffice/app/app.py::authed()`.
|
||||
- The iCal feed is gated by a separate query-string token (`ICS_TOKEN`) since calendar clients
|
||||
can't send custom headers — treat that token as effectively public-linkable and rotate it if a
|
||||
feed URL leaks.
|
||||
- The iCal feed is gated by a per-client query-string token (`clients.ics_token`) since calendar
|
||||
clients can't send custom headers — treat that token as effectively public-linkable and rotate
|
||||
it (clear the column, a fresh one is generated on next `/owner/settings` visit) if a feed URL
|
||||
leaks.
|
||||
- Credentials for client-owned accounts are recorded two ways: `clients.vault_ref` points to a
|
||||
Vaultwarden item for anything the operator manually stashes there; the `credentials` table
|
||||
holds secrets the *system itself* generates (currently: the Easy!Appointments provider login
|
||||
|
||||
Reference in New Issue
Block a user