Vito uses two overlapping role systems:
System roles (UserRole enum) — stored in the users.role column. Controls access to the Filament admin panel and super-admin features.
Tenant roles (Role enum + Spatie Permission) — scoped to a tenant membership. Controls what actions a staff member can perform within a business.
System roles
Defined in App\Domain\Identity\Enums\UserRole:
Enum value DB value Description SUPER_ADMINsuper_adminFull platform access, bypasses all tenant checks TENANT_OWNERsellerOwner of one or more tenants (legacy DB value) STAFFstaffEmployee member of a tenant CUSTOMERuserEnd consumer (legacy DB value)
The super_admin role can also be granted by listing an email in the APP_SUPER_ADMINS environment variable (comma-separated). This takes precedence over the database role column. // User::isSuperAdmin()
$adminEmail = config ( 'auth.super_admin_email' );
if ( $adminEmail && strcasecmp ( $this -> email , $adminEmail ) === 0 ) {
return true ;
}
return $this -> role -> value === UserRole :: SUPER_ADMIN -> value ;
Tenant roles and permissions
Tenant-scoped roles are managed by Spatie Laravel Permission and defined in App\Domain\Access\Enums\Role:
Role Value Typical use OWNERownerFull control of the tenant, all permissions ADMINadminManage catalog, orders, staff, analytics MANAGERmanagerManage catalog, orders, and fulfillment OPERATORoperatorView catalog and orders, fulfill orders VIEWERviewerRead-only access across most resources
Permission list
Permissions are defined in App\Domain\Access\Enums\Permission and assigned to roles in config/rbac.php:
// config/rbac.php — example for the 'admin' role
Role :: ADMIN -> value => [
Permission :: TENANT_VIEW -> value ,
Permission :: TENANT_UPDATE -> value ,
Permission :: CATALOG_VIEW -> value ,
Permission :: CATALOG_CREATE -> value ,
// ...
Permission :: AI_USE -> value ,
],
Permission Value Description TENANT_VIEWtenant.viewView tenant profile TENANT_UPDATEtenant.updateUpdate tenant profile SETTINGS_VIEWsettings.viewView settings SETTINGS_UPDATEsettings.updateUpdate settings BILLING_VIEWbilling.viewView billing information BILLING_MANAGEbilling.manageManage billing and subscriptions CATALOG_VIEWcatalog.viewView products CATALOG_CREATEcatalog.createCreate products CATALOG_UPDATEcatalog.updateUpdate products CATALOG_DELETEcatalog.deleteDelete products CATALOG_PUBLISHcatalog.publishPublish / unpublish products MENU_VIEWmenu.viewView menu sections MENU_MANAGEmenu.manageManage menu sections INVENTORY_VIEWinventory.viewView stock levels INVENTORY_ADJUSTinventory.adjustAdjust stock quantities INVENTORY_TRANSFERinventory.transferTransfer stock between locations ORDERS_VIEWorders.viewView orders ORDERS_CREATEorders.createCreate orders ORDERS_MANAGEorders.manageUpdate order metadata ORDERS_FULFILLorders.fulfillMark orders as fulfilled ORDERS_CANCELorders.cancelCancel orders ORDERS_EXPORTorders.exportExport orders to file PAYMENTS_VIEWpayments.viewView payment records PAYMENTS_RECORDpayments.recordRecord a payment CUSTOMERS_VIEWcustomers.viewView customer list CUSTOMERS_EXPORTcustomers.exportExport customer data APPOINTMENTS_VIEWappointments.viewView appointments APPOINTMENTS_MANAGEappointments.manageCreate and update appointments STAFF_VIEWstaff.viewView staff list STAFF_MANAGEstaff.manageInvite and manage staff STAFF_ASSIGN_ROLESstaff.assign_rolesAssign roles to staff MARKETING_VIEWmarketing.viewView campaigns and leads MARKETING_MANAGEmarketing.manageCreate and manage campaigns ANALYTICS_VIEWanalytics.viewView analytics dashboards ANALYTICS_EXPORTanalytics.exportExport analytics data INTEGRATIONS_VIEWintegrations.viewView integrations INTEGRATIONS_MANAGEintegrations.manageConfigure integrations WEBHOOKS_VIEWwebhooks.viewView webhook configurations WEBHOOKS_MANAGEwebhooks.manageManage webhook configurations AI_USEai.useAccess AI generation tools
Role presets
config/rbac.php defines presets for common staff roles tied to a plan capability:
Preset Base role Key permissions Requires capability cashieroperatororders.view, orders.manage, payments.recordcheckout_basickitchen_staffoperatororders.view, orders.fulfillkitchen_displayservice_provideroperatorappointments.view, appointments.manageappointment_bookingwarehouse_clerkoperatorinventory.view, inventory.adjust, inventory.transferinventory_tracking
Checking permissions in code
In PHP (Spatie)
// Check a permission
$user -> can ( 'orders.view' );
// Check a role
$user -> hasRole ( 'admin' );
// Assign a role to a user
$user -> assignRole ( 'manager' );
// Give a direct permission
$user -> givePermissionTo ( 'catalog.publish' );
Checking system role
// Check super admin (email whitelist OR role column)
$user -> isSuperAdmin ();
// Check tenant ownership (membership or owner column)
$user -> isTenantOwner ();
In Filament resources
Filament resources respect Laravel’s Gate. Use canAccess or canViewAny methods that delegate to the registered Policy:
// ActivityLogResource — only super_admin and auditor
public static function canAccess () : bool
{
$user = auth () -> user ();
return $user -> isSuperAdmin () || $rawRole === 'auditor' ;
}
Tenant ownership middleware
The tenant.ownership middleware (EnsureTenantOwnership) is applied to all protected tenant API routes:
Route :: middleware ([ 'auth:sanctum' , 'tenant.ownership' , 'throttle:api' ]) -> group ( function () {
Route :: prefix ( 'orders' ) -> group ( function () { ... });
Route :: prefix ( 'settings' ) -> group ( function () { ... });
Route :: prefix ( 'reports' ) -> group ( function () { ... });
});
For every route-model-bound parameter, the middleware verifies that the model’s tenant_id belongs to one of the authenticated user’s tenants. On a mismatch:
A TENANT_OWNERSHIP_VIOLATION event is logged to the security log channel.
The request is aborted with 404 (not 403) to avoid disclosing resource existence.
Models covered: Product, Order, Coupon, MenuSection, Lead, Campaign.
Impersonation
Super-admins can impersonate tenant users directly from the Filament admin panel. The original admin session is preserved in impersonator_id and impersonator_name session keys.
Leaving impersonation
The ImpersonationController::leave method:
Verifies that an active impersonation session exists (impersonator_id in session).
Logs the impersonation_ended event with the original admin ID, impersonated user ID, IP, and user agent.
Clears the impersonation session keys.
Logs out the impersonated user and restores the original admin session.
Regenerates the session ID to prevent session fixation.
$this -> logger -> info ( 'impersonation_ended' , [
'original_admin_id' => $impersonatorId ,
'impersonated_user_id' => $impersonatedUserId ,
'ip_address' => $request -> ip (),
'user_agent' => $request -> userAgent (),
'timestamp' => now () -> toIso8601String (),
]);
$request -> session () -> forget ([ 'impersonator_id' , 'impersonator_name' , 'password_hash_check' ]);
$this -> auth -> logout ();
$admin = User :: query () -> find ( $impersonatorId );
$this -> auth -> login ( $admin );
$request -> session () -> regenerate ();
If the original admin account cannot be found after impersonation ends (e.g., it was deleted), the session is fully invalidated and the browser is redirected to /login. This failure is logged at emergency severity.