Commit Graph
22 Commits
Author SHA1 Message Date
ced4568 7ce435ad32 feat(security): add CSRF double-submit protection and login rate limiting
Non-HttpOnly rn_csrf_s/rn_csrf_i cookies, set on login, echoed back as X-CSRF-Token on every
state-changing request — defense in depth on top of (not instead of) SameSite=Lax. In-memory
sliding-window rate limiting on instructor login (8 attempts / 5 min per ip:email, no new
dependency).

Real bug found while wiring this up: the Gateway's own progress-relay call (triggered when a
student solves a scenario) is a server-to-server POST to /api/progress/record relaying the
browser's session — it needs to forward the CSRF token too, and needs to forward BOTH the
rn_csrf_s cookie and a matching X-CSRF-Token header (forwarding only the header still 403'd,
since the double-submit check needs both on the request Platform API actually receives).

Also tightens the Gateway's session-resolution cache from 5000ms to 2000ms, so a
revoked/expired session stops working at the Gateway within a tighter, tested bound.

Frontend (admin + student API clients) now reads the relevant rn_csrf_* cookie via
document.cookie and attaches it on every POST/PATCH/DELETE.
2026-08-18 13:36:20 -05:00
ced4568 8e4ef9ca53 feat(admin): add cohort and student management portal
New /admin route (frontend/src/admin/), gated by a separate admin
session (scrypt + timingSafeEqual against ADMIN_PASSWORD, no plaintext
storage, no new dependency). Covers cohort CRUD + roster + curriculum
assignment, and student CRUD + cohort assignment + environment/progress/
attempt views, all under /api/admin/* on the Platform API.

main.jsx now does a three-way path split (lab / admin / platform) so the
existing, unmodified Lab UI, the student dashboard, and the new admin
portal all ship in one Vite bundle without a router dependency.
2026-08-18 12:22:58 -05:00
ced4568 ae75bb4cbe feat(auth): add server-controlled student and admin sessions
Replaces the Phase 2A pilot ?sid= query param with an HttpOnly,
SameSite=Lax session cookie. Gateway now derives student identity from
the session (via Platform API's /api/session/whoami) instead of trusting
a client-suppliable id, and checks ownership on every proxied HTTP and
WebSocket Lab request, not just the initial navigation.

Fixes a routing bug found during testing: the fallback route was using
'does this session own a running environment' to decide whether an
unprefixed /api/* call belonged to the Lab UI or the dashboard — true in
both places. Replaced with an explicit LAB_OWNED_PATHS allowlist of the
exact unprefixed paths the existing Lab UI actually calls.

e2e-phase2a.js rewritten to use a cookie jar instead of headers/query
params, with explicit impersonation-rejection tests added. 10/10 passing.
2026-08-18 12:22:58 -05:00
ced4568andClaude Sonnet 5 fb2945069c feat(student): add curriculum dashboard and lab launch/reset/recreate flow
StudentDashboard.jsx — "Up Next" curriculum view + Launch Lab flow, in one
component since they share state (environment status affects what curriculum
actions make sense).

- Renders GET /api/curriculum/path as-is: the existing RootNode path
  (Foundation -> Workloads -> Networking -> Configuration -> Troubleshooting
  -> Real Ops -> Capstone), no new structure invented. Capstone stays the
  locked placeholder from the earlier slice.
- Launch Lab -> POST /api/environment/launch, polls status every 2.5s while
  provisioning/resetting, transitions through none -> provisioning -> running
  -> error with a distinct UI state for each (EnvironmentPanel). On running,
  navigates to launch_path with the student's own id as ?sid= — a
  defense-in-depth ownership check the Gateway verifies on that specific
  navigation (see the gateway commit).
- One "Recreate Lab" control, calling POST /api/environment/reset — no raw
  destroy-without-recreate exposed to students, matching "don't expose raw
  destroy/admin controls beyond what is safe for a student." A visible note
  states progress is saved separately and survives.

Platform.module.css reuses the existing design tokens from index.css
(--green, --surface, --text, etc.) so this reads as the same product as the
Lab UI, not a bolted-on separate app.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-18 11:16:35 -05:00
ced4568andClaude Sonnet 5 5e4f5518fd feat(student): wire roster identity and student session
New Platform Shell entry point (frontend/src/platform/) alongside the
existing, completely unmodified Lab UI (App.jsx and everything under
frontend/src/components/). main.jsx now splits on the current URL path —
/lab/<token>/ renders the existing <App/> exactly as before; anything else
renders the new <PlatformShell/>. Both live in the same built bundle (it's
served both by the persistent identity instance and by every ephemeral Lab
container), split purely at runtime.

- EntryScreen.jsx — cohort dropdown -> name dropdown (existing
  GET /api/roster/cohorts, GET /api/roster?cohort=) -> POST /api/login. No
  password. Visibly labeled pilot identity, not production auth, per the
  governing requirement that this be stated explicitly, not just true in the
  architecture docs.
- api.js — student_id lives in localStorage only, sent as X-Student-Id on
  every authenticated request. Every authenticated call throws if no student
  is stored rather than silently going out anonymously — this is what
  "prevent accidental anonymous progress writes" means in practice, not just
  a comment.
- PlatformShell.jsx — entry screen if no stored session, dashboard otherwise.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-18 11:16:24 -05:00
ced4568andClaude Sonnet 5 60fbf0c385 feat(curriculum): add RootNode learning path, student roster, and scenario isolation metadata
Pre-Phase-1 platform work, approved and tested earlier but not yet committed:

- Terminal banner: "RootNode Academy Lab — node: k8s-lab", upstream KubeKosh
  attribution left untouched in footer/README/LICENSE.
- RootNode curriculum mode: scenarios/rootnode-path.json (Foundation -> Workloads
  -> Networking -> Configuration -> Troubleshooting -> Real Ops -> Capstone),
  built from existing scenario content grouped by category, no new scenarios
  invented. New GET /api/curriculum/path endpoint.
- Student roster + per-student progress: instructor-roster, pick-your-name login
  (no password), students/environments-adjacent progress table migrated to a
  (student_id, scenario_id) composite key so practice progress and exam sessions
  are isolated per student instead of one global bucket. New /api/roster,
  /api/login endpoints.
- scenarios/data/*.json: added isolation_type metadata (shared_namespace vs
  ephemeral_cluster) from an audit of which scenarios execute cluster-scoped or
  node-level commands. This was Tier-1-isolation groundwork that got superseded
  same-day by the full-ephemeral-per-student architecture (see
  docs/LAB_ARCHITECTURE.md) — the field is kept as descriptive metadata only,
  per the note now in scenarios/SCHEMA.md. Not wasted: it's an accurate audit of
  which scenarios touch cluster-scoped resources, useful independent of isolation
  model.
- .gitignore added (node_modules/, frontend/dist/, *.db, /data/, .env) — none
  existed before; without it, npm installs during testing would have landed in
  a commit.

Known limitation still open: no frontend UI for any of this yet (login screen,
roster-add form, curriculum view) — API-only. No auth on the roster-add endpoint.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-08-18 01:21:16 -05:00
ced4568 e31b051f4e feat: update logo.svg for RootNode Academy branding 2026-06-21 03:34:56 -05:00
ced4568 f7cdb48833 feat: rebrand to RootNode Academy + add Real Ops scenario bundle
- Rename app title, header, tagline, footer, terminal tab to RootNode Academy
- Apply RootNode color palette (#39FF14 green, #00D4FF blue, GitHub-style darks)
- Add rootnode-runaway-monitor scenario (homelab NOC incident: oversized memory request)
- Add rootnode-real-ops bundle in terminal-green accent
- Update package name to rootnode-academy-ui
2026-06-21 03:27:46 -05:00
Abhinav Sinha f2d535837c fix: remove reset button from scenario panel in exam mode
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-18 02:00:21 +05:30
Abhinav Sinha ec27b01531 refactor: replace window alerts with modals
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-18 01:49:10 +05:30
Abhinav Sinha 35dfeb7208 feat: add focus mode to maximize scenario panel
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-18 01:30:39 +05:30
Abhinav Sinha 09bd1b20da refactor(exam): flat sidebar list, weight badges, UI cleanup
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-18 01:11:19 +05:30
Abhinav Sinha 7e75c8ae02 fix: hide difficulty from scenario ui in exam mode
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-17 16:33:14 +05:30
Abhinav Sinha 3e77495f4b fix: exam history button hover animation
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-16 16:35:36 +05:30
Abhinav Sinha 4acdb0df40 feat(exam): log time spent per scenario in exam report
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-16 16:29:50 +05:30
Abhinav Sinha 6766d772d1 feat(exam): add exam history and improve scenario timer
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-16 16:03:08 +05:30
Abhinav Sinha 5720b59a5f feat(exam): add timer for each scenario in exam mode
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-16 10:53:14 +05:30
Abhinav Sinha ab2403e914 feat: implement scenario caching and hot-reloading
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-15 15:28:53 +05:30
Abhinav Sinha abcd8592b5 fix: execute teardown on active scenario's progress reset
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-09 16:50:34 +05:30
Abhinav Sinha be1fc296ac fix: Incorrect Scenario Setup State For Completed Scenarios
Signed-off-by: Abhinav Sinha <[email protected]>
2026-06-09 13:28:57 +05:30
Abhinav Sinha e999a8a857 feat: Add Release to Docker Hub GitHub Action
Implemented Docker Release action with GitHub workflows
and added release versioning with docker build args

Signed-off-by: Abhinav Sinha <[email protected]>
2026-05-30 23:08:48 +05:30
Abhinav SinhaandGitHub c26c0122ce Initial commit 2026-05-26 20:18:08 +05:30