74 lines
4.8 KiB
Markdown
74 lines
4.8 KiB
Markdown
# Deployment
|
|
|
|
This app is built to deploy to **[Render](https://render.com)** as a Web
|
|
Service via the included `render.yaml` blueprint.
|
|
|
|
## Pre-flight checklist
|
|
|
|
Before deploying, confirm:
|
|
|
|
- [ ] CiviCRM has a working API key for a service user with sufficient privileges to read/write `Contact`, `Activity`, `Relationship`, and `OptionValue`.
|
|
- [ ] The `Primary Contact` (or whatever value `FORM_CONTACT_RELATIONSHIP` resolves to in `config/form.ts`) relationship type exists, with Individual on side A and Organization on side B.
|
|
- [ ] The `Check-in (organizing)` activity type exists with the six custom field groups attached (`Check_in_data__organizing_`, `Stage_1`, `Stage_2`, `Stage_3`, `Stage_4`, `Stage_5`).
|
|
- [ ] The Framework Stage option group on the Organization contact contains all six in-scope values: `Inquiry`, `Organizing`, `Feasibility`, `Business feasibility`, `Store Implementation`, `Stabilize newly opened co-op`.
|
|
|
|
Verify all of the above against your live CiviCRM by hitting `/api/health` (in development; production requires a token — see below).
|
|
|
|
## First-time Render setup
|
|
|
|
1. **Create a new Web Service** from this repo on Render. Render will detect `render.yaml` and configure the service automatically.
|
|
|
|
2. **Set the secrets** in the Render dashboard → Environment. The `render.yaml` declares them but marks them `sync: false` so they're never echoed in the committed file:
|
|
|
|
| Variable | Required? | Purpose |
|
|
|---|---|---|
|
|
| `CIVI_BASE_URL` | yes | e.g. `https://crm.fci.coop` |
|
|
| `CIVI_API_KEY` | yes | Per-user API key. Generate via CiviCRM Contact → API Key field. |
|
|
| `CIVI_SITE_KEY` | yes | From `civicrm.settings.php` (`CIVICRM_SITE_KEY`). |
|
|
| `CIVI_HTTP_AUTH_USER` | only if Civi sits behind webserver-level Basic Auth | HTTP Basic Auth username. |
|
|
| `CIVI_HTTP_AUTH_PASS` | only if CIVI_HTTP_AUTH_USER is set | HTTP Basic Auth password. |
|
|
| `HEALTH_TOKEN` | recommended | Long random string (e.g. `openssl rand -hex 32`). Required to access `/api/health` in production. If unset, that route returns 404. |
|
|
| `PUBLIC_ORIGIN` | optional | e.g. `https://check-in.fci.coop` — used in absolute self-links if needed later. |
|
|
|
|
3. **Trigger the first deploy.** Render will run `npm ci && npm run build` then `npm run start`. The platform health check hits `/healthz` (lightweight, no Civi dependency).
|
|
|
|
4. **Verify**:
|
|
- `https://<your-render-url>/healthz` → `{"ok":true,"service":"coop-checkin"}`
|
|
- `https://<your-render-url>/api/health?token=<HEALTH_TOKEN>` → all checks should be green
|
|
- Visit `https://<your-render-url>/?cid=<test-individual>&cs=<their-checksum>` → form loads with prefill
|
|
|
|
5. **Custom domain (optional)** — Render dashboard → Custom Domain → add `check-in.fci.coop`. Update DNS to the provided CNAME. Render auto-issues a Let's Encrypt cert.
|
|
|
|
## Security defaults
|
|
|
|
The app ships with these defaults; review and adjust per your threat model.
|
|
|
|
| Concern | Default |
|
|
|---|---|
|
|
| HTTPS | Required. Render terminates TLS for you. HSTS header is set with a 2-year max-age + preload. |
|
|
| CSP | Tight: same-origin scripts/connections; Google Fonts allowed for next/font; `frame-ancestors 'none'`; no third-party scripts. |
|
|
| Frame embedding | Denied (X-Frame-Options + CSP frame-ancestors). |
|
|
| Referrer | `same-origin` so the cid+cs query string never leaks via Referer to other origins. |
|
|
| Health endpoint | Production: gated behind `HEALTH_TOKEN`. Dev: unauthenticated. |
|
|
| Submit rate limit | 10 submissions per IP per minute (in-memory; resets on restart). |
|
|
| Error messages | Production responses say "Could not save" without details; full error logged server-side with secrets redacted. |
|
|
| Stub mode | Refuses to start if `NODE_ENV=production` and any of `CIVI_BASE_URL`/`CIVI_API_KEY`/`CIVI_SITE_KEY` is missing. |
|
|
|
|
## What's intentionally NOT done
|
|
|
|
- **CSRF tokens.** The submit endpoint is gated by the per-contact CiviCRM checksum, which is not browser-cookie-based, so traditional CSRF doesn't apply. Add CSRF tokens if you ever introduce a session-cookie auth path.
|
|
- **Multi-instance rate limiting.** The in-memory token bucket is per-process. Acceptable for a single Render instance; switch to Redis or Render's edge rate-limit if you scale horizontally.
|
|
- **WAF.** None configured. Render's platform provides basic DDoS protection; add a WAF (Cloudflare in front of Render, or similar) if your threat model warrants it.
|
|
- **i18n.** English-only.
|
|
|
|
## Rolling out updates
|
|
|
|
1. Push to your default branch.
|
|
2. Render auto-builds and deploys (per `autoDeploy: true` in `render.yaml`).
|
|
3. Watch deploy logs for any health-check failures.
|
|
4. After deploy: hit `/api/health?token=…` to confirm all checks still pass against the live CRM.
|
|
|
|
## Rollback
|
|
|
|
Render keeps the previous deploy available. From the Render dashboard → **Deploys** → previous deploy → **Rollback to this deploy**.
|