# 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:///healthz` → `{"ok":true,"service":"coop-checkin"}` - `https:///api/health?token=` → all checks should be green - Visit `https:///?cid=&cs=` → 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**.