Three roles (owner > admin > instructor), enforced server-side on every /api/admin/* route via reusable middleware (requireOwner/requireAdminOrOwner/requireAnyInstructor) — the frontend hides controls a role can't use, but that's UX, never the actual boundary. A plain 'instructor' is further scoped to cohorts assigned via the new cohort_instructors table (requireCohortAccess/requireStudentCohortAccess); owner/admin bypass that check entirely. Owner-only: instructor account lifecycle, audit log. Admin/owner: cohort/student CRUD, curriculum assignment. Any instructor role (cohort-scoped for 'instructor'): dashboard, cohort/student views, Prepare/Destroy Labs, provisioning. Deliberately NOT cohort-scoped: the raw /api/admin/environments/:id/* routes (no cohort context in a bare environment id without an extra Provisioner round trip on every call) — a plain instructor still has full Lab control via the cohort/student-scoped routes instead, which already carry the check. server.js wires the new persistent-session middleware (studentSession/instructorSession — re-fetches the instructor row from Postgres every request, so a role change or deactivation takes effect on the very next request with no separate revoke needed) and adds a last-resort error handler so a DB error in any async route returns 500 instead of crashing the process or leaking a stack trace.
14 lines
696 B
SQL
14 lines
696 B
SQL
-- cohort_instructors — scopes the 'instructor' role to specific cohorts. 'owner' and 'admin'
|
|
-- are never checked against this table (they have global teaching visibility by role alone,
|
|
-- see src/rbac.js) — this only matters for the plain 'instructor' role.
|
|
|
|
CREATE TABLE IF NOT EXISTS cohort_instructors (
|
|
cohort_id TEXT NOT NULL REFERENCES cohorts(cohort_id),
|
|
instructor_id UUID NOT NULL REFERENCES instructors(instructor_id),
|
|
role_in_cohort TEXT NOT NULL DEFAULT 'teacher',
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (cohort_id, instructor_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_cohort_instructors_instructor ON cohort_instructors(instructor_id);
|