Files
classroomio/prd/secure-student-invites [DONE]/README.md
T

142 lines
5.5 KiB
Markdown
Raw Normal View History

2026-03-09 11:18:22 +01:00
# Secure Student Invite Flow
## Purpose
Document the current secure student invite architecture, what is implemented right now, and what should be implemented next to make the invite system excellent for a modern course platform.
## Scope
- Student invitations into a course
- Security controls around link generation and acceptance
- Teacher/admin management surface for invite links
## Current Flow (Implemented)
### 0. Public landing page enroll button (free courses)
1. User clicks Enroll on a free course landing page.
2. Frontend calls `POST /invite/student/public-link` with `courseId`.
3. Backend validates course is active, published, and accepting students.
4. Backend creates a one-time secure invite token (24h expiry).
5. Frontend redirects user to `/invite/s/:token`.
### 1. Teacher creates invite link
1. Teacher opens the invite modal in the course people page.
2. Frontend calls `POST /course/:courseId/invites`.
3. Backend creates a cryptographically random token.
4. Backend stores only `sha256(token)` in `course_invite.token_hash`.
5. Backend returns a single usable invite URL: `/invite/s/:token`.
### 2. Student opens invite link
1. Student hits invite page with token in URL.
2. Frontend calls `GET /invite/student/:token/preview`.
3. Backend hashes token and looks up invite by `token_hash`.
4. Backend returns invite + course + organization preview data and computed status.
### 3. Student accepts invite
1. Student clicks Join.
2. Frontend calls `POST /invite/student/:token/accept` (authenticated route).
3. Backend performs checks in a transaction:
- invite exists
- invite role is student
- invite is active (not expired/revoked/used up)
- course is active and published
- course allows new students
- optional email/domain restrictions pass
- if restricted invite, verified email is required
4. Backend idempotently handles already-joined students.
5. Backend inserts missing org membership + group membership.
6. Backend atomically increments invite usage with race-safe condition.
7. Backend sends welcome email to student and notification emails to teachers.
### 4. Teacher can revoke
1. Teacher calls `POST /course/:courseId/invites/:inviteId/revoke`.
2. Invite is marked revoked and immediately blocked from future joins.
## Functionalities Implemented
### Database
- New `course_invite` table in `packages/db/src/schema.ts`.
- Fields include:
- `tokenHash`, `expiresAt`, `maxUses`, `usedCount`, `isRevoked`
- `allowedEmails`, `allowedDomains`, `metadata`
- `createdByProfileId`, `lastUsedAt`, timestamps
- Indexes and uniqueness on token hash.
### API and Service Layer
- Course-level invite management routes:
- `GET /course/:courseId/invites`
- `POST /course/:courseId/invites`
- `POST /course/:courseId/invites/:inviteId/revoke`
- Public/auth invite routes:
- `POST /invite/student/public-link`
- `GET /invite/student/:token/preview`
- `POST /invite/student/:token/accept`
- Invite validation schemas added for params and payloads.
- Legacy invite fallback removed; only secure tokenized invites are supported (including landing-page enroll flow).
### Security Controls
- High-entropy token generation (`crypto.randomBytes`).
- Token hashing at rest (no raw token stored in DB).
- Expiry, max-use, and revocation enforcement.
- Public free-course enroll flow issues one-time, short-lived secure links instead of payload-derived links.
- Optional allowlist restrictions (emails/domains).
- Verified-email enforcement for restricted invites.
- Transactional acceptance with optimistic concurrency guard on `usedCount`.
- Idempotent join behavior to avoid duplicate consumption.
### Dashboard / UX
- Invite modal now requests server-generated secure links.
- Copy-link and QR features use secure link output.
- Invite join page uses backend preview + accept endpoints.
- Invite join page shows clear blocked states for expired/revoked/used-up/unavailable course.
## What I Recommend Next (Priority Order)
### P0: Must-have next
1. Invite management UI in course people page:
- list invites
- show status and uses left
- revoke from UI
- copy existing invite link metadata (without exposing raw token again)
2. Per-invite presets:
- one-time link
- multi-use link
- short expiry presets (24h, 7d, 30d)
3. Full audit trail:
- who created/revoked invite
- who joined with invite
- timestamped acceptance history
### P1: High impact
1. Direct email invites (personalized):
- issue invite per recipient
- send one-click email invites
- track delivery/open/accept
2. Bulk invite import:
- CSV of emails
- domain validation and duplicate detection
3. Abuse protection:
- rate limits on preview/accept
- IP/device anomaly detection
### P2: Advanced platform features
1. SSO/domain auto-enrollment rules:
- trusted domains auto-map to org/course policies
2. Invite analytics dashboard:
- conversion funnel (created -> opened -> accepted)
- per-course acceptance rates
3. Expiry automation:
- automatic cleanup/archive
- reminders before expiration
## Suggested Product Standard for an Outstanding Course Platform
- Secure by default (hashed tokens, expiry, revocation, least privilege).
- Clear invite lifecycle (active, expired, used up, revoked) visible to staff.
- Excellent failure UX for students (explicit reason + next action).
- Strong governance (audit logs and admin controls).
- Scalable operations (bulk invite, email campaigns, analytics).
## Validation Status
- `pnpm --filter @cio/db build` passed.
- `pnpm --filter @cio/utils build` passed.
- `pnpm --filter @cio/api build` passed.
- `pnpm --filter @cio/dashboard build` passed (with unrelated pre-existing Svelte warnings).