Files
rootnode-academy/platform/api
ced4568 52c17c1907 feat(identity): add persistent Postgres-backed session store
Replaces the in-memory studentSessions/adminSessions Maps (Phase 2B — gone on every
restart) with a sessions table. Browser holds an opaque CSPRNG bearer token; only its
SHA-256 hash is ever stored server-side, same principle as password hashing. Each session
has an absolute expiry (fixed at creation) and a sliding idle expiry (pushed forward on
every authenticated request, capped at the absolute one) — one atomic UPDATE...RETURNING
per resolve, no read-then-write race. Student and instructor sessions stay fully separate
token namespaces, matching Phase 2B's cookie-name separation.

cookies.js centralizes Set-Cookie construction so the Secure attribute (COOKIE_SECURE=1,
for real HTTPS deployments) is applied consistently instead of hand-built per call site.
2026-08-18 13:35:54 -05:00
..

RootNode Academy — Platform API (Phase 2B)

Lightweight service: no k3s, no --privileged, no Lab terminal runtime. See docs/PHASE2B_ADMIN_AND_PLATFORM_DATA.md for the design; this is the operational reference.

Configuration reference

Variable Required Meaning
PLATFORM_DB_URL no (defaults to local dev Postgres) Postgres connection string
PROVISIONER_URL no Provisioner base URL
PROVISIONER_SHARED_SECRET yes Internal Provisioner auth — process refuses to start without it
ADMIN_PASSWORD yes Instructor login credential — process refuses to start without it. Verified via scrypt+timing-safe compare, never stored or logged in plaintext
PLATFORM_API_PORT no Listen port, default 4300
FRONTEND_DIST_DIR no Where the built frontend lives — set by the Dockerfile, override for local non-Docker dev
SCENARIOS_DIR / BUNDLES_DIR / CURRICULUM_PATH_FILE no Override the default scenarios/ locations, mainly for local dev outside Docker

Local dev

cd platform
echo "PROVISIONER_SHARED_SECRET=$(openssl rand -hex 32)" > .env
echo "ADMIN_PASSWORD=$(openssl rand -hex 12)" >> .env
docker compose -f docker-compose.dev.yml build
docker compose -f docker-compose.dev.yml up -d

Everything is reachable through the Gateway at http://localhost:4200 — students at /, instructors at /admin. Platform API itself isn't meant to be hit directly by a browser (same boundary as the Provisioner relative to Platform API), though nothing stops you from curling it directly at :4300 for local debugging.

SQLite migration

One-time cutover, run against the real data volume:

docker run --rm -v <identity-data-volume>:/data \
  -e SQLITE_PATH=/data/progress.db -e PLATFORM_DB_URL=postgres://postgres:postgres@<host>:5433/rootnode_platform \
  <platform-api-image> node migrate-sqlite.js

Always backs up the SQLite file first, is safe to re-run, and writes a JSON report — see docs/PHASE2B_ADMIN_AND_PLATFORM_DATA.md §2/§3 for the full guarantees and rollback procedure.

Tests

platform/test/e2e-phase2a.js (student-flow regression, cookie-based) and platform/test/e2e-phase2b.js (the classroom acceptance workflow: cohort → students → curriculum → Prepare Labs → complete scenarios → reset → destroy-labs → progress survives). Both need the full stack up and ADMIN_PASSWORD set — see platform/README.md.

Known limitations

See docs/PHASE2B_ADMIN_AND_PLATFORM_DATA.md §13 — in-memory sessions (a restart logs everyone out), exam mode's live bookkeeping stays Lab-local, single shared admin credential rather than per-instructor accounts.