Users & Permissions
ProxPanel’s authorization model has two layers: user_type (a hard-coded enum on the users table) and permissions (a flexible 220-entry catalog assigned through permission groups). user_type decides the broad role; permission groups decide what fine-grained actions someone can take.
Both layers must agree before an action is allowed. A reseller user_type with the nas.delete permission can delete NAS rows; an admin user_type bypasses permission checks entirely and can do anything.
How to get here
Section titled “How to get here”Sidebar → Users (people-with-gear icon) and Permissions (lock icon) are two related pages.
| Page | Purpose | URL |
|---|---|---|
| Users | Create/edit user accounts (admin, reseller, support, etc.) | /users |
| Permissions | Manage permission groups and assignments | /permissions |
Both require their respective view permissions. In practice these are admin-only pages.
user_type enum
Section titled “user_type enum”Stored in users.user_type. Six values, defined in models/user.go:
| Value | Code (int) | What it represents |
|---|---|---|
subscriber | UserTypeSubscriber (1) | End-customer account for the self-service customer portal. Not a panel/admin role. |
reseller | UserTypeReseller (2) | Owns subscribers. Scoped to own + sub-reseller subscribers unless subscribers.view_all. A sub-reseller is simply a reseller with parent_id set — same enum value, same code paths. |
support | UserTypeSupport (3) | Read-only operator, typically for L1 support. |
admin | UserTypeAdmin (4) | Full system access. Bypasses all permission checks. There is always at least one admin. |
collector | UserTypeCollector (5) | Money-collection role — sees billing and can record payments. |
readonly | UserTypeReadonly (6) | View-only across the panel; cannot mutate anything. |
There is no separate sub_reseller enum value — sub-resellers are reseller rows with a parent_id.
support, collector, and readonly users can be tied to a specific reseller via users.reseller_id. When tied, they only see that reseller’s data — the same scoping logic that applies to resellers (fixed in v1.0.549 to apply to all reseller-tied user types, not just reseller).
The 220-permission catalog
Section titled “The 220-permission catalog”Stored in the permissions table. Each row has name (dotted, e.g. subscribers.view) and description. The full catalog is INSERT’d on first-install from schema.sql. Categories:
| Prefix | Examples |
|---|---|
dashboard.* | dashboard.view_admin |
subscribers.* | subscribers.view, subscribers.view_all, subscribers.create, subscribers.edit, subscribers.delete, subscribers.renew, subscribers.disconnect, subscribers.reset_fup, subscribers.reset_mac, subscribers.inactivate, subscribers.change_service, subscribers.add_days, subscribers.refill_quota, subscribers.rename, subscribers.ping, subscribers.view_graph, subscribers.bandwidth_rules, subscribers.change_bulk, subscribers.view_password |
services.* | services.view, services.create, services.edit, services.delete |
nas.* | nas.view, nas.create, nas.edit, nas.delete |
sessions.* | sessions.view, sessions.view_all |
resellers.* | resellers.view, resellers.create, resellers.edit, resellers.delete, resellers.impersonate |
invoices.* | invoices.view, invoices.create, invoices.edit, invoices.delete |
prepaid.* | prepaid.view, prepaid.create, prepaid.edit |
transactions.* | transactions.view, transactions.view_all |
reports.* | reports.view |
tickets.* | tickets.view, tickets.create, tickets.edit, tickets.delete |
backups.* | backups.view, backups.create, backups.edit, backups.restore, backups.delete |
settings.* | settings.view, settings.edit |
audit.* | audit.view |
bandwidth.* | bandwidth.view, bandwidth.create, bandwidth.edit, bandwidth.delete |
cdn.* | cdn.view, cdn.create, cdn.edit, cdn.delete |
communication.* | communication.view, communication.edit |
fup.* | fup.view, fup.reset |
permissions.* | permissions.view, permissions.create, permissions.edit, permissions.delete |
sharing.* | sharing.view, sharing.edit |
Total is 220 default entries; your install may have added or removed some.
*.view_all semantics
Section titled “*.view_all semantics”A few permissions end in _all and have special meaning: the user sees system-wide data instead of their normal reseller-scoped slice.
| Permission | Effect |
|---|---|
subscribers.view_all | Reseller sees every subscriber, not just own + sub-resellers’. Stat counters reflect global totals. |
transactions.view_all | Reseller sees every transaction across the system. |
sessions.view_all | Reseller sees every active session. |
dashboard.view_admin | Shows admin-only dashboard widgets (System Metrics, Top Resellers). |
These are typically granted to partner-style resellers who report up to the ISP owner but aren’t admins.
Permission groups
Section titled “Permission groups”A permission group is a named bundle of permissions. Resellers and other reseller-tied users are assigned to one group; the group’s permissions are theirs.
| Group field | Notes |
|---|---|
name | E.g. SALES, SUPPORT-L1, ACCOUNTING. |
description | Optional. |
permissions | M2M to permissions table via the permission_group_permissions junction. |
The Permissions page lists groups with a column showing which resellers are assigned to each. The assignment lives on resellers.permission_group.
Admin bypass rule
Section titled “Admin bypass rule”Admins bypass every permission check. This is implemented in middleware/auth.go:
if user.UserType == models.UserTypeAdmin { return c.Next()}before any checkUserPermission() lookup. There is no way to restrict an admin’s actions via the permission system — to restrict an admin, downgrade them to support or another non-admin user_type.
Back-compat: reseller with no group
Section titled “Back-compat: reseller with no group”A reseller whose permission_group is NULL or 0 gets all permissions — the same as admin in practice. This is the back-compat default so installs that haven’t set up groups still work. The moment you assign any group, the reseller’s permissions are limited to that group’s set.
Refreshing permissions
Section titled “Refreshing permissions”When you change a reseller’s permission group, they do not need to log out. The frontend refreshes permissions on every page load via GET /api/auth/me. Steps:
- Admin saves the new group assignment.
- Reseller presses F5 (or just navigates).
- Frontend calls
/api/auth/me. - Backend returns the updated
permissionsarray. authStore.refreshUser()saves to localStorage (so the new perms survive subsequent reloads — fixed in v1.0.165).- UI re-renders with hidden/shown buttons reflecting the new set.
How permission checks work
Section titled “How permission checks work”Backend
Section titled “Backend”Two middleware functions in middleware/auth.go:
RequirePermission("subscribers.view")— caller must have this exact permission. Admins bypass.RequireAnyPermission("subscribers.view", "subscribers.view_all")— caller must have at least one.
Routes are registered like:
sub := api.Group("/subscribers", middleware.RequirePermission("subscribers.view"))sub.Delete("/:id", middleware.RequirePermission("subscribers.delete"))A handler-level inline check is checkUserPermission(user, "...") used for permission gating inside bulk actions and similar mixed flows.
Frontend
Section titled “Frontend”Two layers:
- Route protection —
PermissionRoutecomponent inApp.jsxshows “Access Denied” if the user lacks the required permission, before the page renders. - Button gating —
{hasPermission('...') && <Button />}hides UI when permission is missing.hasPermissionis a selector on the Zustand auth store.
Both layers exist because the backend is the authoritative check; the frontend just hides UI to avoid confusing the user with buttons they can’t use.
Admin-only pages
Section titled “Admin-only pages”These pages bypass the permission system entirely and only check user_type == admin. Resellers see “Access Denied” regardless of permissions:
/settings— system settings (but settings.view permission has been added in many routes)/users— user management/audit— audit log/backups,/permissions,/change-bulk,/communication— historically admin-only, gradually being moved to permission-gated (see v1.0.165 changelog)
Check the source for the latest gating.
Users page
Section titled “Users page”Lists every account in the users table. Columns: username, full name, email, user_type, reseller (if any), is_active, last_login, actions.
Add User opens a form: username, password (with eye-icon show/hide toggle), email, user_type dropdown, reseller association (if non-admin user_type), is_active toggle.
Common workflows
Section titled “Common workflows”Create a support agent who can read everything but change nothing
Section titled “Create a support agent who can read everything but change nothing”- Permissions → Add Group → name
SUPPORT-READ-ONLY. - Tick
*.viewand*.view_allpermissions (subscribers, sessions, transactions, reports). - Save.
- Users → Add User → user_type
support, assign permission groupSUPPORT-READ-ONLY.
Give a senior reseller global visibility
Section titled “Give a senior reseller global visibility”- Open their permission group (or create a new one called
PARTNER). - Tick
subscribers.view_all,transactions.view_all,sessions.view_all. - Save.
- Tell the reseller to press F5. They now see system-wide totals on the dashboard but still can’t modify other resellers’ subscribers.
Lock down a junior reseller to “renew only”
Section titled “Lock down a junior reseller to “renew only””- New permission group
JR-SALES. - Tick:
subscribers.view,subscribers.renew,subscribers.create,transactions.view,dashboard.view_admin. - Assign the reseller to this group.
- They can create + renew but can’t disconnect, delete, or change services.
Permissions
Section titled “Permissions”| Permission | Effect |
|---|---|
permissions.view | Permissions page loads. |
permissions.create | Add Group button. |
permissions.edit | Edit group + assign permissions. |
permissions.delete | Delete group (refused if resellers still assigned). |
These are admin-controlled in practice.
Related pages
Section titled “Related pages”- Resellers — assigns permission groups to reseller accounts.
- Audit — logs every permission check failure (
SECURITY: ...lines). - Settings — admin-only system settings page.
- Subscribers — where the bulk-action permission checks live.
- Dashboard — examples of
*.view_allwidening behaviour.