Give /api/preview-link its own Bearer secret; constant-time token checks

preview-link mints working form links for any contact, but was gated by
HEALTH_TOKEN (the diagnostics secret) passed in the query string, where
it lands in access logs. README already documented PREVIEW_ADMIN_TOKEN
as a Bearer header; make the code match.

- PREVIEW_ADMIN_TOKEN, when set, is required as Authorization: Bearer
  and is never accepted from the URL.
- Until it is set, HEALTH_TOKEN still works (Bearer or legacy ?token=)
  so the support runbook in EMAIL_DELIVERY.md keeps working.
- preview-link and /api/health compare tokens in constant time.
- redact() also scrubs PREVIEW_ADMIN_TOKEN and STAFF_REPORT_KEY.
This commit is contained in:
Joel Brock
2026-10-30 11:20:30 -07:00
parent df471e0e88
commit 526422abed
5 changed files with 43 additions and 19 deletions
+1 -1
View File
@@ -88,7 +88,7 @@ no live CRM calls. Production startup will refuse to boot in stub mode.
| `CIVI_HTTP_AUTH_USER` | optional | Username, if CiviCRM sits behind HTTP Basic Auth at the webserver layer |
| `CIVI_HTTP_AUTH_PASS` | optional | Password for the above |
| `HEALTH_TOKEN` | optional | If set, `/api/health` requires `?token=…` in production |
| `PREVIEW_ADMIN_TOKEN` | optional | If set, `/api/preview-link` requires a matching `Authorization: Bearer …` header (admin convenience endpoint for minting form links) |
| `PREVIEW_ADMIN_TOKEN` | recommended | `/api/preview-link` requires a matching `Authorization: Bearer …` header (admin endpoint for minting form links). Until set, `HEALTH_TOKEN` is accepted instead |
| `NODE_ENV` | — | Set to `production` for the production build |
Copy `.env.example` to `.env.local` for local development.