Admin Panel¶
Audience: Tenant admins, course admins, and super-admins.
Scope: A tour of the admin surfaces that ship today. Detailed RBAC and the consumed-token impersonation contract live in
development/auth.mdandreference/user-management.md.
All /admin/* routes call requireRole(...) server-side. Tenant scoping flows from the JWT claim. super_admin accesses cross-tenant data via withTenantOverride().
Routes¶
| Route | Roles | Purpose |
|---|---|---|
/admin |
tenant_admin, course_admin, super_admin | Landing dashboard with counts + recent audit. |
/admin/users |
tenant_admin, super_admin | Paginated user list with search / role / status filters. |
/admin/users/\[id\] |
tenant_admin, super_admin | User detail + actions (role change, deactivate, force sign-out, resend verification, password-reset link, impersonate). |
/admin/users/invite |
tenant_admin, super_admin | Send a tenant-scoped invitation. |
/admin/users/invitations |
tenant_admin, super_admin | Search invitations and resend or revoke invitations that have not been accepted. |
/admin/courses |
tenant_admin, course_admin, super_admin | Read-only catalogue + links to course-admin manager. |
/admin/courses/\[slug\]/dashboard |
tenant_admin, course_admin (assigned), super_admin | Per-course roster + analytics. |
/admin/courses/\[slug\]/admins |
tenant_admin, super_admin | Assign / revoke course_admin_assignments. |
/admin/audit |
tenant_admin, super_admin | Audit log viewer; tenant-scoped or global. |
/admin/tenants |
super_admin | Create + suspend tenants. |
Mutating Actions¶
Every mutating action follows the same contract:
- Validates input with Zod at the boundary.
- Re-checks role / target ownership inside the action.
- Rate-limits via
RATE_LIMIT_POLICIES.adminMutation(seesecurity-and-rate-limiting.md). - Writes an
audit_logrow. - Bumps target
users.token_versionon role change / deactivation / force-sign-out so live JWTs are invalidated on the next request.
Common Tasks¶
Invite and manage users¶
Tenant admins and super-admins can invite users from a tenant host. Invitations always belong to the current tenant; an invitation link cannot be accepted on a different tenant.
Send an invitation¶
- Open Invitations in the admin navigation.
- Select Invite a user.
- Enter the recipient's email address.
- Choose the role the recipient should receive:
- Student — course selection is optional. Selecting a course sends the learner to that course's landing page after acceptance, but does not enrol them. Enrolment remains a separate administrator or learner action.
- Course admin — a published course is required. Acceptance grants the course-admin role and access to that course, then opens its admin dashboard.
- Tenant admin — no course is selected. Acceptance grants tenant-wide administration and opens the admin dashboard.
- Select Send invitation.
The course picker searches published courses by title or slug. If the CMS course list is temporarily unavailable, enter the exact course slug manually. The server still verifies that the course exists before sending.
Each invitation is valid for seven days. The system refuses a second active, unaccepted invitation for the same email and tenant; use Resend invitation on the existing invitation instead.
What the recipient sees¶
The email contains a tenant-specific, one-time invitation link:
- A new user enters their name and creates a password. The account is created with the invited role.
- An existing user signs in with the invited email address, returns to the invitation, and confirms acceptance.
- If a different account is signed in, the recipient must sign out and continue with the invited account.
After acceptance, the invitation becomes Accepted and cannot be resent or revoked. The recipient is sent to the destination determined by the role and optional course selection described above.
Find and manage invitations¶
Open /admin/users/invitations from Invitations in the admin navigation.
The list is scoped to the current tenant. Search matches email, role, or course
slug.
| Status | Meaning |
|---|---|
Pending |
The invitation is unaccepted and its link is still valid. |
Accepted |
The recipient accepted the invitation; no row actions are available. |
Expired |
The invitation was not accepted within its seven-day validity window. |
Use the row actions menu for a pending or expired invitation:
- Resend invitation creates a new one-time token, resets expiry to seven days, and emails a new link. The previous link stops working immediately.
- Revoke invitation permanently invalidates and removes the invitation after confirmation. The audit history remains available.
Creating, resending, accepting, granting a role from, and revoking invitations
write audit events. Use /admin/audit to investigate who performed an action
and which invitation was affected.
Troubleshooting invitations¶
| Problem | Administrator action |
|---|---|
| Course cannot be found | Confirm the course is published and select it from the picker, or verify the exact slug when using manual entry. |
| Course list is unavailable | Enter the exact slug manually or retry after CMS connectivity is restored. |
| An active invitation already exists | Find the email on Invitations and resend or revoke that invitation instead of creating another. |
| Too many invite attempts | Wait briefly before retrying; invitation creation, resend, and revoke actions are rate-limited. |
| Link has expired | Open the expired invitation's row actions and select Resend invitation. |
| Recipient did not receive the email | Resend once, then check delivery configuration and provider logs in Email Delivery. |
| Recipient sees an account-mismatch message | Ask them to sign out and accept the link while signed in with the invited email address. |
| Recipient needs course access as a student | Complete enrolment separately; a course selected on a student invitation only controls the post-accept landing page. |
Promote a user to course admin for one course¶
- Open
/admin/courses/\[slug\]/admins. - Search for the target user.
- Assign — this writes a
course_admin_assignmentsrow scoped to(user_id, tenant_id, course_slug). - The user is signed out everywhere on next request (token-version bump) and signs back in with the new claim.
Force a user to re-authenticate¶
Open /admin/users/\[id\] and use Force sign out everywhere. The action bumps users.token_version and writes an audit row.
Impersonate a user for support¶
- Sign in as
super_adminand browse the target tenant host. - Open
/admin/users/\[id\]for the target user. - Use Impersonate to switch into their session, then complete the support check.
- Use the admin impersonation banner to Stop impersonation when done.
See Impersonation for prerequisites, limitations, and audit guidance.
Investigate suspicious activity¶
/admin/audit shows tenant-scoped audit events; super_admin can switch to a global view. Filter by actor, action, target, or tenant.
Related References¶
- Impersonation — super-admin support workflow.
- Authentication — provider behaviour, session shape, impersonation contract.
- Multi-Tenant Administration —
super_admintenant lifecycle. - Security And Rate Limiting — auth and admin rate-limit policies.
- User Management & Access — full RBAC contract and persona flows.