Safe-by-default dashboard exposure: localhost-only unless you explicitly opt in
to a public bind with a token.
- src/cli.ts: host default 0.0.0.0 → 127.0.0.1 (localhost-only; --host 0.0.0.0 to
expose, which then requires a token when the auth flag is on).
- src/server.ts startServer guards (before listen): SELF-LOCKOUT (flag on + no
token → throw — you'd otherwise brick the dashboard with no recovery short of
editing .env) + PUBLIC-EXPOSURE gate (flag on + 0.0.0.0 + no token → throw).
Both use __authState (mutable seam) so they're testable. __authState.bound set
from cfg.host. When the flag is off, the guards are skipped (back-compat
escape hatch — the bind still defaults to 127.0.0.1).
- src/server.ts authenticate: E7.2 bind-gating — enforce only on a PUBLIC bind
(0.0.0.0); 127.0.0.1 is localhost-trusted (requireAuth routes stay open even
with the flag on). The refuse-to-start guard ensures a 0.0.0.0 bind has a token.
- src/server.ts routes: GET /api/auth/status (open; {authRequired, bound,
relayConfigured, foundryConfigured} — booleans only, no secret values),
POST /api/auth/login (open; validates token constant-time, sets an HttpOnly
SameSite=Strict cookie; 401 invalid credentials on mismatch, no leak; empty
token = unset; no token configured → 401), POST /api/auth/logout (clears
cookie, Max-Age=0).
- src/dashboard.html: first-run login card (token input, shown when authRequired
&& no stored token), a shared apiFetch wrapper (attaches the stored token as a
Bearer header, on 401 → show login), checkAuth gating init; bare fetch('/api/')
calls migrated to apiFetch (auth endpoints stay plain fetch).
- .env.example: documents DASHBOARD_AUTH_TOKEN + ENABLE_AUTH_MIDDLEWARE.
- tests: e7-1-auth/dispatch updated for bind-gating (enforcement tests set
bound=0.0.0.0; + a 127.0.0.1 no-enforcement test). e7-2-auth.test.ts (13
tests): auth-status (off/localhost/public/no-secret-leak), login
(valid-cookie/invalid-401/empty/unset), logout (clears), refuse-to-start
(self-lockout / 0.0.0.0-no-token / token-set-passes / flag-off-no-guard).
tsc clean; 194 passing project-wide (18 pre-existing fixture-missing unchanged).
Co-Authored-By: Claude <noreply@anthropic.com>
76 lines
4.2 KiB
Plaintext
76 lines
4.2 KiB
Plaintext
# ─────────────────────────────────────────────────────────────────────────────
|
|
# foundry-obsidian-sync — environment template.
|
|
# Copy to .env and fill in: cp .env.example .env
|
|
# (Or just run the /setup skill, which fills most of this in for you.)
|
|
# .env is gitignored — never commit real keys or passwords.
|
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
# === Relay (ThreeHats foundryvtt-rest-api-relay) ===
|
|
# Host port the relay publishes (its web UI + REST API + WS endpoint live here).
|
|
RELAY_PORT=3010
|
|
RELAY_CONTAINER=foundry-rest-api-relay
|
|
# URL the relay advertises to itself (email links, etc.). Usually http://localhost:<RELAY_PORT>.
|
|
RELAY_FRONTEND_URL=http://localhost:3010
|
|
# URL the sync TOOL calls the relay at. For a local compose this is
|
|
# http://localhost:<RELAY_PORT>. If Foundry is remote and you front the relay with
|
|
# a public/tailnet domain, set this to that URL instead.
|
|
RELAY_URL=http://localhost:3010
|
|
# x-api-key for /get, /update, /search. Created via the relay's web UI on first run
|
|
# (sign up at http://localhost:<RELAY_PORT>, copy the key from the dashboard).
|
|
RELAY_API_KEY=
|
|
# Optional: pin a specific connected Foundry client (leave blank to auto-resolve
|
|
# when exactly one client is connected to the key).
|
|
RELAY_CLIENT_ID=
|
|
|
|
# === Headless Foundry session (the relay drives a headless browser at Foundry) ===
|
|
# Your Foundry server URL. The relay logs a headless user into THIS Foundry.
|
|
FOUNDRY_URL=https://your-foundry.example.com
|
|
# Foundry player credentials the headless session uses to log in.
|
|
RELAY_USER=
|
|
RELAY_PASSWORD=
|
|
# Foundry world id/title to launch in the headless session.
|
|
FOUNDRY_WORLD=
|
|
# Seconds before an idle headless session is reaped (relay env).
|
|
HEADLESS_SESSION_TIMEOUT=3600
|
|
|
|
# === Dashboard auth (E7) ===
|
|
# The dashboard binds 127.0.0.1 by default (localhost-only, no auth needed).
|
|
# To expose it on your tailnet, pass --host 0.0.0.0 AND set a token below, then
|
|
# flip ENABLE_AUTH_MIDDLEWARE=on. With the flag on, a 0.0.0.0 bind WITHOUT a
|
|
# token is refused at boot (safe-by-default); the flag on without a token is
|
|
# also refused (self-lockout guard — you'd otherwise brick the dashboard).
|
|
# (Optional; off-by-default — the dashboard is localhost-only without these.)
|
|
DASHBOARD_AUTH_TOKEN=
|
|
ENABLE_AUTH_MIDDLEWARE=false
|
|
|
|
# IMPORTANT — networking: Foundry's rest-api module connects OUT to the relay over
|
|
# WebSocket. So the relay must be REACHABLE FROM your Foundry host. If Foundry runs
|
|
# elsewhere, expose RELAY_PORT (port-forward / tailnet / public domain) and point the
|
|
# module's relay URL at ws(s)://<that-reachable-host>:<RELAY_PORT>.
|
|
|
|
# === Vault (the sync tool / dashboard reads & writes this directly on the host) ===
|
|
# Absolute path on the HOST to your Obsidian vault root (the folder containing
|
|
# .obsidian). You edit these notes in your own Obsidian desktop app, pointed at this
|
|
# vault; the host-run dashboard reads/writes the same files.
|
|
# QUOTE any path with spaces — sync.sh sources this file in bash, so an unquoted
|
|
# "VAULT=/home/me/My Vault" tries to run "Vault" as a command and VAULT stays unset.
|
|
VAULT="/home/me/My Vault"
|
|
# The refined-notes subdirectory the dashboard's --vault points at. Defaults to
|
|
# ${VAULT}/Refined if you leave this blank.
|
|
REFINED="${VAULT}/Refined"
|
|
# Optional Campaign Codex export dir (omit to let the dashboard auto-build stubs).
|
|
CC=
|
|
|
|
# === Sync tool ===
|
|
# Optional: a Foundry journal LevelDB snapshot, for offline dashboard indexing
|
|
# (to-foundry / ui). Leave blank if you only ever push live via the relay.
|
|
JOURNAL=
|
|
# Sandbox output dir for the tool (name-uuid.json, index.json, converted notes).
|
|
OUT=./out
|
|
|
|
# === Foundry host control (OPTIONAL — only for `refresh --full-index`) ===
|
|
# Only needed if you run `./sync.sh refresh --full-index` to stop/start a LOCAL
|
|
# Foundry Docker container and read its live journal LevelDB. Live `push` and plain
|
|
# `refresh` go through the relay and never touch these.
|
|
FOUNDRY_CONTAINER=
|
|
FOUNDRY_DATA_DIR= |