Files
classroomio/apps/tenant-router/README.md
T

70 lines
2.8 KiB
Markdown

# @cio/tenant-router
Cloudflare Worker that fronts every browser-facing ClassroomIO host:
- `*.classroomio.school/*` — free-tier tenant sites
- `classroomio.school/*` — apex (301 → `classroomio.com`)
- `app.classroomio.com/*` — admin dashboard
- BYOD customer domains via Cloudflare for SaaS
The Worker forwards each request to one of two Render services based on path:
| Path | Upstream |
|---|---|
| `/api/*` | API service (`/api` prefix stripped) |
| anything else | Dashboard service |
It preserves the original host as `X-Forwarded-Host` so:
- The dashboard's SvelteKit adapter-node reads it via `HOST_HEADER=x-forwarded-host` and `event.url.host` becomes the tenant/admin/BYOD host.
- Better Auth on the API emits session cookies without a `Domain` attribute (see `packages/db/src/auth.ts` — `crossSubDomainCookies: { enabled: false }`), so the browser scopes each session to the exact host it called. Tenants on `acme.classroomio.school`, the admin app on `app.classroomio.com`, and BYOD `learn.acme.com` each get host-only sessions.
## Deploy
```bash
pnpm --filter @cio/tenant-router deploy
```
Live tail:
```bash
pnpm --filter @cio/tenant-router tail
```
## Env vars (wrangler.toml `[vars]`)
| Var | Value |
|---|---|
| `DASHBOARD_UPSTREAM_HOST` | `.onrender.com` host of the dashboard service (no scheme) |
| `API_UPSTREAM_HOST` | `.onrender.com` host of the API service |
| `APEX_REDIRECT_TARGET` | URL to redirect `classroomio.school` apex to (default `https://classroomio.com`) |
Update the values in `wrangler.toml` after looking them up in the Render dashboard.
## Cloudflare DNS expected setup
`classroomio.school` zone (new):
- `*` CNAME → any valid target, **Proxied**
- `@` CNAME → any valid target, **Proxied** (Worker serves the 301)
- `cname` CNAME → any valid target, **Proxied** — this is the value BYOD customers point their CNAME at
`classroomio.com` zone:
- `app` CNAME → **Proxied** so the route binds
- `api` CNAME → **DNS only** (server-to-server callers and webhooks bypass the Worker)
- The old wildcard `*` CNAME on `classroomio.com` should be deleted — tenant traffic now belongs on `classroomio.school`.
## Cloudflare for SaaS (BYOD)
On the `classroomio.school` zone:
- **SSL/TLS → Custom Hostnames**: enabled
- **Fallback Origin**: `cname.classroomio.school` — BYOD traffic lands on the Worker route and is forwarded through the same code path as `*.classroomio.school`.
The API uses these env vars (`apps/api/src/services/org/domain.ts`):
- `CLOUDFLARE_CUSTOM_HOSTNAMES_API_TOKEN` — Cloudflare API token scoped to the `classroomio.school` zone with `Zone → SSL and Certificates → Edit` + `Zone → Zone → Read`.
- `CLOUDFLARE_CUSTOM_HOSTNAMES_ZONE_ID` — Zone ID for `classroomio.school`.
- `CLOUDFLARE_CUSTOM_HOSTNAME_CNAME_TARGET=cname.classroomio.school`.