Control plane (panel vs admin-web)
One-line: the panel is the company’s control plane (operate all schools); admin-web is tenant-only (operate one school). Keeping them separate keeps operator power out of the tenant app.
Two web apps, two audiences
panel (super-admin-panel) | admin-web (school-admin-panel) | |
|---|---|---|
| Used by | NexSchool operators | A school’s own staff |
| Scope | all tenants | one tenant |
| API surface | /api/platform/* (bypasses tenant resolution) | /api/* (tenant-scoped) |
| Auth | platform-admin JWT (@platform_admin_required) | tenant user JWT + RBAC |
The split is deliberate: operator-only capabilities (create tenants, assign plans, seed schools, impersonate) live only in the panel, so they can’t leak into the app a school admin uses.
What lives in the panel
- Tenant lifecycle — create/suspend/soft-delete tenants, tenant search.
- Plans — assign a plan and its
features_json(which modules a tenant can use). - Onboarding / seeding — white-glove tenant setup; the structured school-setup flow (
/api/school-setup/*) is super-admin-only (the tenant Admin role no longer carriesschool_setup.*). - Add-admin with one-time credential reveal — a temp password shown once, never audited.
- Audit logs and platform settings.
Impersonation: god-login & one-click login link
A platform super-admin can enter any tenant’s admin-web with their own credentials:
- God-login — on the tenant login path, if no tenant user matches,
authenticate_platform_adminis tried (searchesis_platform_adminacross tenants with scoping cleared). On success the admin getspermissions = ["system.manage"], bypassesemail_verified/setup gates, and the entered tenant is reported so subsequent requests scope to it.has_permissionshort-circuitsTruefor platform admins. - One-click “Open admin-web” (panel → tenant) — no password re-entry. Backed by a Redis one-time code (
modules/auth/handoff.py): single-use (atomic GET+DEL), ~90s TTL,sha256-hashed at rest, fail-closed (no Redis → no issue/redeem).POST /api/platform/tenants/<id>/login-linkmints it;POST /api/auth/login-link/redeemconsumes it via the shared_finalize_loginpath (identical response to password login). Possession of a valid code is the credential — only a platform admin can mint one.
Both flows build links from ADMIN_WEB_BASE_URL. Set it in production, or login links point at localhost.
Why this matters
Before the split, onboarding and impersonation lived partly in the tenant app. Concentrating operator power in the panel — and making school-setup super-admin-only — means a compromised or curious school admin can’t reach cross-tenant or structural controls. It also gives operations a single, auditable place to run white-glove onboarding for the Gujarat pilot.