Skip to content

Production hardening: RBAC content locking and course ownership #1103

Description

@lgriffin

Context

PR #1102 introduces a client-side RBAC system with course ownership, content locking, and a lecturer management panel. It is intentionally client-side only as a foundation. This issue tracks what needs to change to make it production-grade.

Server-side enforcement

The current implementation relies on client-side checks. A determined user could bypass locks by inspecting network requests or manipulating JavaScript. For production:

  • Supabase Row Level Security (RLS): Add RLS policies to tutors_content_locks so only lecturers listed in the course can write lock state. This requires migrating from the Supabase anon key to authenticated sessions (either Supabase Auth or passing the Auth.js JWT to Supabase).
  • Server-side route guards: Move lock checking from afterNavigate (client-side) to SvelteKit server hooks or +page.server.ts load functions so locked content URLs return 403 rather than relying on client-side redirects.
  • Content withholding: Currently locked content is still fetched in tutors.json — the client just hides it. For truly sensitive content (e.g., quiz answers), consider serving locked content separately and only delivering it when unlocked.

Auth integration

  • Remove dev-only auto-lecturer: The dev flag auto-creates a lecturer identity when no owner/lecturers is configured. This should be removed or gated behind an explicit env var before production deployment.
  • Lecturer identity verification: Currently compares tutorsId.value?.login (from Auth.js GitHub OAuth) against course.properties.owner. This is sound but the comparison is case-insensitive string matching — consider normalizing GitHub logins consistently.
  • Dynamic ownership management: Course ownership is currently baked into properties.yaml (static, requires redeployment to change). Consider a course_owners Supabase table for runtime ownership changes without redeploying course content.

Lecturer panel enhancements

  • Enrollment editing: The enrollment tab is currently read-only (whitelist comes from static tutors.json). Add the ability for lecturers to manage the allowlist at runtime via a Supabase course_enrollment table.
  • Bulk lock/unlock: Add "Lock all" / "Unlock all" controls in the Lecturer Panel, and the ability to lock/unlock an entire topic with all its children.
  • Lock scheduling: Allow lecturers to schedule when content unlocks (e.g., "unlock Topic 3 at 9am Monday") using a timestamp field on the locks table.
  • Access analytics depth: Surface richer analytics — time spent per LO, completion rates, last access timestamps — using the existing learning_records and calendar Supabase tables.

Quiz integration

  • Quiz gating via locks: When a topic or LO is locked, any quizzes within it should also be inaccessible. Wire the lock state into the quiz system so locked quizzes can't be started by students.
  • Lecturer-only quiz controls: The quiz start/reveal actions should check isLecturer to ensure only course owners can manage quiz sessions.

Mobile support

  • LecturerButton on mobile: Currently hidden on mobile (hidden md:block). Add a mobile-friendly trigger — either in the hamburger menu or as a floating action button.
  • Lock toggle touch targets: The inline lock toggles on cards are small. Ensure adequate touch target size (48px minimum) for mobile lecturer use.

Testing

  • Create Supabase table in staging: Run the CREATE TABLE tutors_content_locks SQL in the staging Supabase instance.
  • E2E tests: Add Playwright tests for the lock/unlock flow — lecturer locks content, student sees locked overlay, direct navigation is blocked.
  • Integration tests: Test the RBAC service — loadLocks, toggleLock, localStorage fallback, Supabase sync.

Documentation

  • Course author docs: Document the owner and lecturers properties in the tutors reference manual — how to set them, what they enable, expected format.
  • Deployment guide: Document the Supabase table creation as part of the deployment checklist.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions