Runbook — Dev surface access control (/dev + /dev/docs)

Restricts the developer playground (funday.gg/dev, SvelteKit) and the Quartz docs (funday.gg/dev/docs) to authorized Nakama users. Both were previously public and unauthenticated.

Model

One predicate decides everything: frontend/src/lib/server/auth/devAccess.tshasDevAccess(locals).

A request is granted dev access iff it carries a valid Nakama session token whose account either:

  1. has an id/username in the env allowlist (DEV_ACCESS_USER_IDS / DEV_ACCESS_USERNAMES), or
  2. carries role: "developer" or "admin" (string), a roles[] containing one, or dev/developer/isDev: true in its Nakama account metadata.
  • Security: the funday-identity cookie is plaintext/forgeable, so the decision is derived from getAccount(token) (Nakama validates the token), never from the cookie’s claimed user/role. Any error → deny (fail-closed).
  • Performance: decisions are cached per session token for 60s (Quartz docs fan out into many sub-resources, each triggering an nginx auth_request).

Enforcement points:

SurfaceBackendEnforced by
/dev (+ all sub-tools)SvelteKit :3000frontend/src/routes/dev/+layout.server.ts → non-devs 302 → /
/dev/docsSvelteKit :3000Same dev guard as /dev (FUNDOCS shell)
/dev/docs/wiki/Quartz :8080nginx auth_request/api/auth/dev-access (204 allow / 403 deny)
/remark42Remark42 :8088Not auth_request-gated — embed.js + API must load same-origin for gated wiki pages

Remark42 is intentionally not double-gated: /dev/docs/wiki/ HTML is already dev-only, but gating /remark42/ made embed.js return 403 HTML (mislabeled as JS) so the widget never initialized (“connection denied”).

Granting access

Preferred — Nakama metadata role (no redeploy, effect ≤60s):

  1. Open the Nakama console → Accounts → find the user.
  2. Edit Metadata, add "role": "developer" (or "admin"), save.
  3. The user reaches /dev and /dev/docs within ~60s (cache TTL). They must be signed into that account (not a throwaway guest) in the browser.

Bootstrap / break-glass — env allowlist (needs a frontend restart):

  1. Edit /home/usr/funday/frontend/.env.production, e.g. DEV_ACCESS_USERNAMES=memeblastoise,thunderdragon (case-insensitive; ids via DEV_ACCESS_USER_IDS).
  2. sudo systemctl restart funday-frontend

Revoking: remove the metadata role (effect ≤60s) or the allowlist entry (effect on restart).

First-time cutover (activating the gate)

The nginx gate and the /dev guard only take effect after the frontend is rebuilt. The auth_request is fail-closed: if /api/auth/dev-access is not yet deployed, /dev/docs returns 5xx (denied, never leaked) — so deploy the frontend before reloading nginx.

# 0. Review what will ship — build-atomic builds the current working tree.
cd /home/usr/funday && git status --short
 
# 1. Build + deploy the frontend (brings up /api/auth/dev-access + /dev guard).
bash scripts/build-atomic.sh                 # or: --no-restart to stage, then restart
 
# 2. Verify the endpoint exists (no cookie ⇒ 403, NOT 404).
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/api/auth/dev-access   # → 403
 
# 3. Apply the gated nginx snippet (back up the live one first).
sudo cp /etc/nginx/snippets/funday-traefik-subpaths.conf \
        /etc/nginx/snippets/funday-traefik-subpaths.conf.bak-$(date +%Y%m%d%H%M%S)
sudo cp /home/usr/funday/scripts/nginx/funday-traefik-subpaths.conf /etc/nginx/snippets/
sudo nginx -t && sudo systemctl reload nginx
 
# 4. Verify.
curl -s -o /dev/null -w '%{http_code}\n' https://funday.gg/                # → 200 (site unaffected)
curl -s -o /dev/null -w '%{http_code}\n' https://funday.gg/dev/docs/       # → 403 (no dev cookie)
# As a granted dev (browser, signed in): /dev and /dev/docs → 200.

⚠️ scripts/build-atomic.sh ships the entire current working tree (incl. any uncommitted WIP). Confirm git status is in a deployable state first.

Rollback

# nginx (re-open docs immediately):
sudo cp /etc/nginx/snippets/funday-traefik-subpaths.conf.bak-<TS> /etc/nginx/snippets/funday-traefik-subpaths.conf
sudo nginx -t && sudo systemctl reload nginx
 
# /dev guard: delete frontend/src/routes/dev/+layout.server.ts and redeploy,
# or simply add yourself to DEV_ACCESS_USERNAMES.

Residual security TODOs (tracked separately)

  • Rotate every secret that was previously public on /dev/docs/.../architecture/ (Nakama console password, session/refresh encryption keys, console signing key, socket server key, runtime HTTP key, DB password). Scrubbing the doc does not rotate them.
  • De-hardcode the console password in frontend/src/lib/server/nakama-presence.ts (admin:funday-...). Add NAKAMA_CONSOLE_HOST/PORT/USER/PASS to .env.production and read them via $env/dynamic/private like nakama.ts does.
  • Optional: serve /dev/docs as a static hugo --minify build behind nginx instead of long-running hugo server (hugo-docs.service).

Ask Docs

AI assistant to help answer questions about the documentation. Answers are read-only and cite docs/source.

Hi! How can I help you with the documentation today? Answers are read-only and cite docs/source.

Ctrl+Enter to send