Permissions & Roles
ProxPanel has a two-axis access control system: a user_type enum + an optional permission_group.
User types
Section titled “User types”| ID | Name | Intended use |
|---|---|---|
| 1 | Subscriber | An end customer logging into the customer portal. Different auth path. |
| 2 | Reseller | A billing partner managing their own subscribers. |
| 3 | Support | Reseller staff handling tickets, lookups, basic diagnostics. |
| 4 | Admin | System-wide administrator. Bypasses all permission checks. |
| 5 | Collector | Field agent / cashier who marks invoices paid. |
| 6 | Readonly | View-only access for auditors, managers, partners. |
Admin bypass
Section titled “Admin bypass”user_type = 4 (Admin) always has every permission. The middleware does:
if user.UserType == models.UserTypeAdmin { return next() // no permission check}Permission groups assigned to an admin are silently ignored.
Backwards-compat default for resellers
Section titled “Backwards-compat default for resellers”If a reseller has permission_group = NULL, they get full access to everything (no permission check applied). This dates from when ProxPanel didn’t have permission groups at all.
To restrict a reseller, you must assign them a permission group with the specific subset of permissions you want them to have.
Non-reseller types with no group
Section titled “Non-reseller types with no group”For Support, Collector, Readonly: permission_group = NULL means no access (everything is denied). You must assign them a permission group.
The 220 permissions
Section titled “The 220 permissions”Permissions are dotted names like subscribers.view, subscribers.edit, nas.delete, invoices.view_all. Categories:
| Prefix | Count | Examples |
|---|---|---|
dashboard.* | 4 | view, stats, view_admin |
subscribers.* | ~50 | view, create, edit, delete, renew, reset_fup, view_all, change_owner, etc. |
services.* | 4 | view, create, edit, delete |
nas.* | 6 | view, create, edit, delete, sync, test |
sessions.* | 4 | view, view_all, disconnect, view_history |
resellers.* | 17 | view, create, edit, add_money, withdraw, etc. |
invoices.* | 7 | view, create, edit, delete, print, email, mark_paid |
prepaid.* | 9 | view, create, generate, delete, etc. |
reports.* | 8 | revenue, subscribers, churn, etc. |
transactions.* | 4 | view, view_all, create, delete |
tickets.* | 7 | view, create, reply, close, assign, etc. |
backups.* | 6 | view, create, restore, delete, download |
bandwidth.*, cdn.*, fup.*, sharing.*, communication.*, audit.*, users.*, permissions.*, settings.* | rest | similar shape |
See the Permission List for the complete table.
*.view_all permissions
Section titled “*.view_all permissions”For data-scoped categories (subscribers, sessions, invoices, transactions, reports, etc.), there’s a paired *.view_all permission that bypasses the reseller hierarchy filter.
Without subscribers.view_all, a reseller’s user only sees subscribers in their own tree. With it, they see everyone — same view as admin. This is the “global read-only partner” pattern.
The pages that honor *.view_all:
subscribers.view_all→ Subscribers, FUP Counters, Reportssessions.view_all→ Sessions listdashboard.*(admin) → Dashboard stats (since v1.0.549)invoices.view_all→ Invoices listtickets.view_all→ Ticketstransactions.view_all→ Transactions
Not all handlers honor view_all yet. Audit ongoing — see Audit history.
How a request is checked
Section titled “How a request is checked”- JWT validated. Claims extracted:
user_id,user_type,reseller_id,permission_group. - If
user_type == Admin→ allow. - If
permission_group == NULLanduser_type == Reseller→ allow (backwards-compat). - Otherwise, query
permission_group_permissions JOIN permissionsto check if the required permission is in the group. - If yes → allow. If no → return 403.
- Data-scope filters apply next: if the handler reads subscriber rows, scope to
WHERE reseller_id IN (own + sub-resellers)unless caller hassubscribers.view_all.
Changing permissions mid-session
Section titled “Changing permissions mid-session”When an admin reassigns a permission group:
- Active user’s JWT keeps working (no logout needed).
- On the next request, the middleware does a fresh permission query → new permissions take effect immediately.
- The frontend permission list refreshes on page reload (
/api/auth/mere-fetched).
So: ask the user to refresh the page (F5), no logout / re-login required.