Authorization (RBAC)

XinAdmin implements a standard User-Role-Permission three-tier RBAC authorization model.

RBAC Model

┌──────────┐   many-to-many  ┌──────────┐   many-to-many  ┌──────────────┐
│  SysUser │ <──────────────>│ SysRole  │ <──────────────>│   SysRule    │
│  (User)  │ sys_user_role  │ (Role)   │ sys_role_rule  │ (Permission) │
└──────────┘                └──────────┘                └──────────────┘

Database Tables

TableDescription
sys_userSystem users (id, username, password, status, etc.)
sys_roleRoles (id, name, sort, status)
sys_user_roleUser-role pivot table
sys_rulePermission rules (id, key, type, name, path, icon, status, etc.)
sys_role_ruleRole-permission pivot table

Permission Rule Types

typeDescriptionExample key
menuMenu group, used to build navigation treesystem
routeRoute/page, corresponds to an accessible pagesystem.user
ruleAction permission, controls specific operations within a pagesystem.user.create, system.user.update, system.user.delete

Permission Retrieval Logic

// SysUserModel::access() - Get all permission keys for a user
public function access()
{
    // Super admin (ID=1): return all permissions
    if ($this->id === 1) {
        return SysRuleModel::pluck('key')->toArray();
    }
    // Regular user: retrieve via role-permission relationships
    return $this->roles()
        ->with('rules')
        ->get()
        ->pluck('rules.*.key')
        ->flatten()
        ->unique()
        ->toArray();
}

Super Admin

User ID = 1 is hardcoded as the super admin with the following privileges:

  • Automatically receives all permission keys
  • Always passes token ability checks
  • Cannot be deleted
  • Automatically gets the full menu tree
  • Role ID = 1 permissions cannot be modified

Super admin privileges are implemented at three levels: SysUserModel::access() returns all keys, SysRoleModel::getRuleIdsAttribute() returns all rules, and SysAccessToken::can() always returns true.

Dynamic Menus

After login, the backend returns the menu tree based on the user's role permissions:

  • Super admin: directly returns all status=1 menus/routes
  • Regular user: retrieves menus through the role → permission chain, builds a tree structure, and deduplicates

The frontend LayoutContext calls the menu() API on mount; MenuRender renders navigation based on menu type and hidden status.

Frontend Permission Control

AuthButton Component

Conditional rendering component based on permission key:

<AuthButton auth="system.user.create">
  <Button type="primary">Create User</Button>
</AuthButton>

When the user lacks permission, the component returns null and the button is not rendered. Implementation:

// AuthButton core logic
const access = useAuthStore(state => state.access);

if (!auth) return <>{children}</>;
if (!access.includes(auth)) return null;
return <>{children}</>;

useAuth Hook

Manual permission checking in component logic:

import useAuth from '@/hooks/useAuth';

function MyComponent() {
  const { auth } = useAuth();

  return (
    <>
      {/* Show only when authorized */}
      {auth('system.user.delete') && <Button danger>Delete</Button>}

      {/* Enable control only when authorized */}
      <Switch disabled={!auth('system.user.update')} />
    </>
  );
}

XinTable accessName

The accessName prop on XinTable auto-generates permission keys for operation buttons:

<XinTable accessName="system.user" ... />

// Auto-generates these permission controls:
//   Add button    → system.user.create
//   Edit button   → system.user.update
//   Delete button → system.user.delete

Permission Key Naming Convention

Use dot-separated hierarchical naming:

{module}.{submodule}.{action}

Examples:
  system.user.query      # System → Users → Query
  system.user.create     # System → Users → Create
  system.role.update     # System → Roles → Edit
  file.manage.delete     # File Management → Delete