Sub-Admin & Access Control
Scholiphi's admin panel has two effective tiers: super admins (platform-wide) and admins (scoped to one or more schools). What we casually call a "sub-admin" is just an
adminuser assigned to a subset of schools. There are no per-feature permission flags — access is gated by role and school assignment. This page covers creating these admins, the invite/password-set flow, the recently-shipped admin password reset, and account deactivation.
At a glance
| Who can do this | Super admin creates/edits/resets/deletes admin users · Admins can only see admins assigned to schools they share |
| Where it lives | Admin panel → /dashboard/admins (super-admin sidebar) |
| Recently shipped | Reset Password modal — super admin can set a new password for any admin without going through the email invite (commit feat: feature to reset sub admin password) |
| Related features | School Creation · Admin Profile · Admin Panel Overview |
Roles in the admin panel
| Role key | Who they are | Sidebar | Schools they see |
|---|---|---|---|
super_admin | Platform owner | Full sidebar: Dashboard, Admins, Schools, Classes, Icons, Payments, Syllabus KB, Vector Search, Categories, AI Assistant, AI Usage, Profile, Settings | All schools |
admin | School-scoped admin (a.k.a. "sub-admin") | Trimmed sidebar: Dashboard, Schools, Syllabus KB, Vector Search, AI Assistant, Profile, Settings | Only schools where the admin's assignedSchoolIds contains the school |
There is no separate sub_admin role key. The "sub-admin" idea is enforced purely through the multi-select Assigned Schools field on the admin user.
How it flows
Step-by-step
1. Create an admin
- Route:
/dashboard/admins→ Create Admin button (top-right). - Modal title: Create Admin.
- Fields:
- Email (required, validated as email; debounced live check warns "This email is already registered" before submit)
- First Name (optional; letters/spaces only)
- Last Name (optional; letters/spaces only)
- Assigned Schools (optional, multi-select; empty = unassigned admin who sees no schools)
- On submit, a set-password email is sent to the address. The row appears in the table tagged PENDING INVITE until the admin sets their password.
2. The admin sets their password
- They open the email and click the link →
/setup-password?token=.... - Form: Password + Confirm Password (min 6 chars, must match).
- On success, they're redirected to
/loginafter a 2-second confirmation message.
3. The admin logs in
/loginaccepts email + password (zod validation: valid email format + min 6 chars).- After login they land on
/dashboard. Their sidebar is the trimmed admin variant. The Schools page shows only schools whose IDs are in their assignment list.
4. Resend the invitation (if they lost the email)
- In the row's kebab menu → Resend Invitation.
- Only shown for admins where
isPasswordSet === false. - A new tokenised email is dispatched.
5. Reset an admin's password (super-admin only)
- In the row's kebab menu → Reset Password.
- This action only appears when the logged-in user is
super_admin. - Modal: Reset Admin Password with a yellow warning banner naming the target admin.
- Fields: New Password (min 6 chars) + Confirm Password (must match).
- On success, toast: "Password reset for
<email>". Super admin must share the new password through a secure out-of-band channel — there is no email notification for this path.
6. Edit profile / re-assign schools
- Kebab menu → Edit Profile.
- Modal: Edit Admin with email, first/last name, Assigned Schools multi-select, and Active Status toggle.
- Saving an empty assigned-schools list effectively un-scopes the admin (they see no schools but can still log in).
7. Deactivate (revoke access)
- Two ways:
- Quick toggle: the System Access switch in the table row (optimistic UI; reverts on backend error).
- Full edit: Active Status switch inside the Edit Admin modal.
- An inactive admin cannot log in.
8. Delete an admin
- Kebab menu → Remove Admin → confirm modal "Access will be revoked immediately".
- Hard-deletes the admin record. School assignments are detached.
Permissions matrix
There are no fine-grained permission flags. Access is determined entirely by role and assigned schools. The matrix below documents what each tier actually does.
| Capability | Super admin | Admin (a.k.a. sub-admin) |
|---|---|---|
| Create / edit / delete other admins | ✅ | ❌ (no Admins entry in sidebar) |
| Reset another admin's password | ✅ | ❌ |
| Resend admin invitation | ✅ | ❌ |
| Toggle admin active/inactive | ✅ | ❌ |
| Create new schools | ✅ | ❌ (button hidden on /dashboard/schools) |
| Edit / delete schools | ✅ | ❌ (only View Dashboard action available) |
| See all schools | ✅ | Only assigned schools |
| Manage classes (platform-wide library) | ✅ | ❌ (sidebar entry hidden) |
| Manage icons / Categories / Payments | ✅ | ❌ (sidebar entries hidden) |
| AI Usage dashboard | ✅ | ❌ (sidebar entry hidden) |
| Inside an assigned school: students, teachers, sections, transport, yearly plans, moderation, token approvals | ✅ | ✅ |
| AI Assistant + Vector Search + Syllabus KB | ✅ | ✅ |
If you need granular flags (e.g. "this admin can edit students but not delete them"), the panel does not currently support that. It's role + school scope, full stop.
Edge cases & things to test
- Email already in use: enter an email used by a teacher/student/parent — Create Admin should reject with "This email is already registered" (debounced live check).
- Invalid name characters: paste "John123" into First Name — should fail "Only letters allowed" validation.
- Empty assigned schools: create an admin with zero schools selected. Confirm they can log in but their Schools list is empty (no crash, just empty state).
- Reset password for self: as super admin, try to open Reset Password on your own row — confirm what happens (the action is gated by role, not by self/other).
- Reset password while admin is logged in: reset the password of an admin who is currently in an active session. Their existing tokens — do they get invalidated? (Verify by trying their old session after reset.)
- Resend invitation after password set: confirm the Resend Invitation action is hidden once
isPasswordSet === true. - Pending invite + active toggle: try toggling System Access on/off for an admin who hasn't set their password yet. Confirm both states behave (active+pending vs disabled+pending).
- Deleting a school assigned to an admin: super admin deletes a school that's in an admin's
assignedSchoolIds. Confirm the admin's list updates on next refresh and they don't get a crash trying to navigate to the deleted school. - Filter by school: in
/dashboard/adminsuse the school filter dropdown — confirm it correctly narrows to admins who include that school in their assignments. - Permissions UI vs route-level: a non-super admin manually navigates to
/dashboard/admins(typed URL). ConfirmProtectedRouteeither redirects or 404s — the sidebar hides it, but the URL must be guarded too. - Password reset min length: try setting a 5-char password from the reset modal — should fail "Password must be at least 6 characters". Same rule applies on
/setup-password. - Sidebar role detection lag: after super admin promotes/demotes someone, the affected admin's sidebar is computed from
useUserStore. Confirm a hard refresh shows the correct sidebar (or that login refresh picks up the new role). - Search debounce: type rapidly in the admin search box — confirm only one request fires per ~500ms idle window.
Related
- School Creation — schools you assign here come from this flow
- Admin Profile — what each admin sees on
/dashboard/profile - Admin Panel Overview — high-level role tiers