Files
WebForm-mw/DEPLOYMENT.md

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**.