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.
This commit is contained in:
@@ -1,14 +1,28 @@
|
||||
import { useState } from 'react'
|
||||
import { getStoredStudent } from './api'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { api, getCachedDisplay } from './api'
|
||||
import EntryScreen from './EntryScreen'
|
||||
import StudentDashboard from './StudentDashboard'
|
||||
|
||||
// Top-level Platform Shell: entry screen if no student session, dashboard otherwise. This is
|
||||
// everything OUTSIDE the /lab/<token>/ Lab experience (which stays the existing, unmodified
|
||||
// <App/> — see main.jsx).
|
||||
// Top-level Platform Shell: entry screen if no valid session, dashboard otherwise. Session
|
||||
// validity is decided by the server (GET /api/session/whoami against the rn_session cookie),
|
||||
// not by anything read from localStorage/sessionStorage — those only cache the display name
|
||||
// for a snappier first paint, per api.js.
|
||||
export default function PlatformShell() {
|
||||
const [student, setStudent] = useState(getStoredStudent)
|
||||
const [student, setStudent] = useState(null)
|
||||
const [checking, setChecking] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const cached = getCachedDisplay()
|
||||
if (cached) setStudent(cached) // optimistic paint while we confirm with the server
|
||||
api.whoami()
|
||||
.then(({ student_id }) => {
|
||||
if (!cached || cached.student_id !== student_id) setStudent({ student_id, display_name: cached?.display_name || student_id })
|
||||
})
|
||||
.catch(() => setStudent(null))
|
||||
.finally(() => setChecking(false))
|
||||
}, [])
|
||||
|
||||
if (checking) return null
|
||||
if (!student) return <EntryScreen onLogin={setStudent} />
|
||||
return <StudentDashboard student={student} onLogout={() => setStudent(null)} />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user