🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
667 lines
23 KiB
Plaintext
Executable File
667 lines
23 KiB
Plaintext
Executable File
ACLS(7) RSpade Developer Manual ACLS(7)
|
|
|
|
NAME
|
|
acls - Role-based access control with supplementary permissions
|
|
|
|
SYNOPSIS
|
|
PHP (server-side):
|
|
Permission::has_permission(User_Model::PERM_MANAGE_SITE_USERS)
|
|
Permission::has_role(User_Model::ROLE_SITE_ADMIN)
|
|
$user->has_permission(User_Model::PERM_EDIT_DATA)
|
|
$user->get_resolved_permissions() // All permissions as array
|
|
$user->can_admin_role($target_user->role_id)
|
|
|
|
JavaScript (client-side):
|
|
Permission.has_permission(User_Model.PERM_EDIT_DATA)
|
|
Permission.has_any_permission([User_Model.PERM_EDIT_DATA, User_Model.PERM_VIEW_DATA])
|
|
Permission.has_all_permissions([...])
|
|
Permission.has_role(User_Model.ROLE_MANAGER)
|
|
Permission.can_admin_role(User_Model.ROLE_USER)
|
|
|
|
DESCRIPTION
|
|
RSpade provides a role-based access control (RBAC) system where:
|
|
|
|
1. Users have a primary role on their site membership (users.role_id)
|
|
2. Roles grant a predefined set of permissions
|
|
3. Supplementary permissions can GRANT or DENY specific permissions per-user
|
|
4. Permission checks resolve: role grants → DENY override → GRANT override
|
|
|
|
Key design principles:
|
|
|
|
Identity vs Membership
|
|
login_users = identity (email, password, one per person)
|
|
users = site membership (role, permissions, many per login_user)
|
|
|
|
Roles and permissions attach to site memberships (users table),
|
|
not login identities. One person can have different roles on
|
|
different sites.
|
|
|
|
Integer Constants
|
|
All roles and permissions are integer constants, not strings.
|
|
This provides type safety, IDE autocompletion, and works with
|
|
the RSX enum system for magic properties.
|
|
|
|
Hierarchical Roles
|
|
Roles are hierarchical. Higher roles inherit all permissions
|
|
from lower roles. Role IDs are ordered by privilege level
|
|
(lower ID = more privilege).
|
|
|
|
Supplementary Permissions
|
|
Individual users can have permissions granted or denied beyond
|
|
their role defaults. DENY always wins over role grants. GRANT
|
|
adds permissions the role doesn't provide.
|
|
|
|
ARCHITECTURE
|
|
|
|
Database Tables
|
|
|
|
users (site membership)
|
|
role_id Primary role for this site membership
|
|
... Other membership fields
|
|
|
|
user_permissions (supplementary)
|
|
user_id FK to users
|
|
permission_id Which permission constant
|
|
is_grant 1 = GRANT, 0 = DENY
|
|
|
|
Permission Resolution Order
|
|
|
|
1. Check if user role is DISABLED → deny all
|
|
2. Check user_permissions for explicit DENY → deny if found
|
|
3. Check user_permissions for explicit GRANT → allow if found
|
|
4. Check role's default permissions → allow if included
|
|
5. Deny (permission not granted)
|
|
|
|
Role Hierarchy
|
|
|
|
ID Constant Label Can Admin Roles
|
|
--- -------- ----- ---------------
|
|
100 ROLE_DEVELOPER Developer 200-800 (system only)
|
|
200 ROLE_ROOT_ADMIN Root Admin 300-800 (system only)
|
|
300 ROLE_SITE_OWNER Site Owner 400-800
|
|
400 ROLE_SITE_ADMIN Site Admin 500-800
|
|
500 ROLE_MANAGER Manager 600-800
|
|
600 ROLE_USER User (none)
|
|
700 ROLE_VIEWER Viewer (none)
|
|
800 ROLE_DISABLED Disabled (none)
|
|
|
|
IDs are 100-based for future expansion. Lower ID = higher privilege.
|
|
"Can Admin Roles" prevents privilege escalation (Site Admin can't
|
|
create Site Owner). Developer and Root Admin are system-assigned only.
|
|
|
|
PERMISSIONS
|
|
|
|
Core Permissions (granted by role)
|
|
|
|
ID Constant Granted By Default To
|
|
-- -------- ---------------------
|
|
1 PERM_MANAGE_SITES_ROOT Developer, Root Admin only
|
|
2 PERM_MANAGE_SITE_BILLING Site Owner+
|
|
3 PERM_MANAGE_SITE_SETTINGS Site Admin+
|
|
4 PERM_MANAGE_SITE_USERS Site Admin+
|
|
5 PERM_VIEW_USER_ACTIVITY Manager+
|
|
6 PERM_EDIT_DATA User+
|
|
7 PERM_VIEW_DATA Viewer+
|
|
|
|
Supplementary Permissions (not granted by any role by default)
|
|
|
|
ID Constant Purpose
|
|
-- -------- -------
|
|
8 PERM_API_ACCESS Allow API key creation/usage
|
|
9 PERM_DATA_EXPORT Allow bulk data export
|
|
|
|
Role-Permission Matrix
|
|
|
|
Permission Dev Root Owner Admin Mgr User View Dis
|
|
---------- --- ---- ----- ----- --- ---- ---- ---
|
|
MANAGE_SITES_ROOT X X
|
|
MANAGE_SITE_BILLING X X X
|
|
MANAGE_SITE_SETTINGS X X X X
|
|
MANAGE_SITE_USERS X X X X
|
|
VIEW_USER_ACTIVITY X X X X X
|
|
EDIT_DATA X X X X X X
|
|
VIEW_DATA X X X X X X X
|
|
API_ACCESS - - - - - - -
|
|
DATA_EXPORT - - - - - - -
|
|
|
|
Legend: X = granted by role, - = must be granted individually
|
|
|
|
MODEL IMPLEMENTATION
|
|
|
|
User_Model Definition
|
|
|
|
class User_Model extends Rsx_Model_Abstract
|
|
{
|
|
// Role constants (100-based, lower = higher privilege)
|
|
const ROLE_DEVELOPER = 100;
|
|
const ROLE_ROOT_ADMIN = 200;
|
|
const ROLE_SITE_OWNER = 300;
|
|
const ROLE_SITE_ADMIN = 400;
|
|
const ROLE_MANAGER = 500;
|
|
const ROLE_USER = 600;
|
|
const ROLE_VIEWER = 700;
|
|
const ROLE_DISABLED = 800;
|
|
|
|
// Permission constants
|
|
const PERM_MANAGE_SITES_ROOT = 1;
|
|
const PERM_MANAGE_SITE_BILLING = 2;
|
|
const PERM_MANAGE_SITE_SETTINGS = 3;
|
|
const PERM_MANAGE_SITE_USERS = 4;
|
|
const PERM_VIEW_USER_ACTIVITY = 5;
|
|
const PERM_EDIT_DATA = 6;
|
|
const PERM_VIEW_DATA = 7;
|
|
const PERM_API_ACCESS = 8;
|
|
const PERM_DATA_EXPORT = 9;
|
|
|
|
public static $enums = [
|
|
'role_id' => [
|
|
300 => [
|
|
'constant' => 'ROLE_SITE_OWNER',
|
|
'label' => 'Site Owner',
|
|
'permissions' => [2, 3, 4, 5, 6, 7],
|
|
'can_admin_roles' => [400, 500, 600, 700, 800],
|
|
],
|
|
// ... additional roles
|
|
],
|
|
];
|
|
|
|
// Get all resolved permissions (role + supplementary applied)
|
|
public function get_resolved_permissions(): array
|
|
{
|
|
if ($this->role_id === self::ROLE_DISABLED) {
|
|
return [];
|
|
}
|
|
$permissions = $this->role_id__permissions ?? [];
|
|
// Add supplementary GRANTs, remove supplementary DENYs
|
|
// ... (see User_Model for full implementation)
|
|
return $permissions;
|
|
}
|
|
|
|
public function has_permission(int $permission): bool
|
|
{
|
|
return in_array($permission, $this->get_resolved_permissions(), true);
|
|
}
|
|
|
|
public function can_admin_role(int $role_id): bool
|
|
{
|
|
return in_array($role_id, $this->role_id__can_admin_roles ?? [], true);
|
|
}
|
|
}
|
|
|
|
Magic Properties (via enum system, BEM-style double underscore)
|
|
|
|
$user->role_id__label // "Site Admin"
|
|
$user->role_id__permissions // [3,4,5,6,7]
|
|
$user->role_id__can_admin_roles // [400,500,600,700,800]
|
|
|
|
PERMISSION CLASS API
|
|
|
|
Static Methods (use current session user)
|
|
|
|
Permission::has_permission(int $permission): bool
|
|
Check if current logged-in user has permission.
|
|
Returns false if not logged in or no site selected.
|
|
|
|
if (Permission::has_permission(User_Model::PERM_MANAGE_SITE_USERS)) {
|
|
// Show user management UI
|
|
}
|
|
|
|
Permission::has_role(int $role_id): bool
|
|
Check if current user has at least the specified role.
|
|
"At least" means same or higher privilege (lower role_id).
|
|
|
|
if (Permission::has_role(User_Model::ROLE_SITE_ADMIN)) {
|
|
// User is Site Admin or higher (Owner, Root)
|
|
}
|
|
|
|
Permission::get_user(): ?User_Model
|
|
Get current user's site membership record.
|
|
Returns null if not logged in or no site selected.
|
|
|
|
$user = Permission::get_user();
|
|
if ($user && $user->has_permission(User_Model::PERM_EDIT_DATA)) {
|
|
// ...
|
|
}
|
|
|
|
Instance Methods (on User_Model)
|
|
|
|
$user->has_permission(int $permission): bool
|
|
Check if this specific user has permission.
|
|
|
|
$user->get_resolved_permissions(): array
|
|
Get all resolved permission IDs for this user.
|
|
Applies role defaults + grants - denies.
|
|
|
|
$user->can_admin_role(int $role_id): bool
|
|
Check if user can create/edit users with given role.
|
|
|
|
$user->has_supplementary_grant(int $permission): bool
|
|
Check if user has explicit GRANT for permission.
|
|
|
|
$user->has_supplementary_deny(int $permission): bool
|
|
Check if user has explicit DENY for permission.
|
|
|
|
JAVASCRIPT PERMISSION CLASS
|
|
|
|
The Permission class provides client-side permission checking using
|
|
pre-resolved permissions from window.rsxapp.resolved_permissions.
|
|
|
|
This array is computed server-side and includes role defaults with
|
|
supplementary grants added and denies removed, ensuring JS checks
|
|
match PHP exactly.
|
|
|
|
Static Methods
|
|
|
|
Permission.is_logged_in(): boolean
|
|
Check if user is authenticated.
|
|
|
|
if (Permission.is_logged_in()) {
|
|
// Show authenticated UI
|
|
}
|
|
|
|
Permission.get_user(): Object|null
|
|
Get current user object from rsxapp.
|
|
|
|
Permission.has_permission(permission): boolean
|
|
Check if user has specific permission.
|
|
|
|
if (Permission.has_permission(User_Model.PERM_EDIT_DATA)) {
|
|
// Show edit button
|
|
}
|
|
|
|
Permission.has_any_permission(permissions): boolean
|
|
Check if user has ANY of the listed permissions.
|
|
|
|
if (Permission.has_any_permission([
|
|
User_Model.PERM_EDIT_DATA,
|
|
User_Model.PERM_VIEW_DATA
|
|
])) {
|
|
// User can view or edit
|
|
}
|
|
|
|
Permission.has_all_permissions(permissions): boolean
|
|
Check if user has ALL of the listed permissions.
|
|
|
|
if (Permission.has_all_permissions([
|
|
User_Model.PERM_MANAGE_SITE_USERS,
|
|
User_Model.PERM_VIEW_USER_ACTIVITY
|
|
])) {
|
|
// User can manage users AND view activity
|
|
}
|
|
|
|
Permission.has_role(role_id): boolean
|
|
Check if user has at least the specified role level.
|
|
Lower role_id = higher privilege.
|
|
|
|
if (Permission.has_role(User_Model.ROLE_MANAGER)) {
|
|
// User is Manager or higher (Admin, Owner, etc.)
|
|
}
|
|
|
|
Permission.can_admin_role(role_id): boolean
|
|
Check if user can administer users with given role.
|
|
|
|
if (Permission.can_admin_role(User_Model.ROLE_USER)) {
|
|
// Show role assignment dropdown including User role
|
|
}
|
|
|
|
Permission.get_resolved_permissions(): number[]
|
|
Get array of all permission IDs the user has.
|
|
|
|
Data Source
|
|
|
|
The Permission class reads from window.rsxapp.user.resolved_permissions,
|
|
which is populated via User_Model::toArray() from get_resolved_permissions().
|
|
Returns empty array if user is not authenticated.
|
|
|
|
ROUTE PROTECTION
|
|
|
|
Using #[Auth] Attribute
|
|
|
|
Standard permission checks work with #[Auth]:
|
|
|
|
#[Route('/settings/users')]
|
|
#[Auth('Permission::authenticated()')]
|
|
public static function users(Request $request, array $params = [])
|
|
{
|
|
// Manual permission check inside route
|
|
if (!Permission::has_permission(User_Model::PERM_MANAGE_SITE_USERS)) {
|
|
return response_error(Ajax::ERROR_UNAUTHORIZED);
|
|
}
|
|
// ...
|
|
}
|
|
|
|
Custom Permission Methods
|
|
|
|
Define reusable permission checks in rsx/permission.php:
|
|
|
|
class Permission
|
|
{
|
|
public static function can_manage_users(): bool
|
|
{
|
|
return self::has_permission(User_Model::PERM_MANAGE_SITE_USERS);
|
|
}
|
|
|
|
public static function can_edit(): bool
|
|
{
|
|
return self::has_permission(User_Model::PERM_EDIT_DATA);
|
|
}
|
|
}
|
|
|
|
Usage in routes:
|
|
|
|
#[Route('/users/create')]
|
|
#[Auth('Permission::can_manage_users()')]
|
|
public static function create(Request $request, array $params = [])
|
|
{
|
|
// Guaranteed to have PERM_MANAGE_SITE_USERS
|
|
}
|
|
|
|
SUPPLEMENTARY PERMISSIONS
|
|
|
|
Purpose
|
|
|
|
Supplementary permissions allow per-user exceptions to role defaults:
|
|
|
|
- GRANT: Give a permission the role doesn't include
|
|
- DENY: Remove a permission the role normally includes
|
|
|
|
Common use cases:
|
|
- Grant API access to specific users regardless of role
|
|
- Deny export access to a user who normally has it
|
|
- Temporary elevated permissions during onboarding
|
|
|
|
Database Table
|
|
|
|
user_permissions
|
|
id BIGINT PRIMARY KEY
|
|
user_id BIGINT NOT NULL (FK to users)
|
|
permission_id INT NOT NULL
|
|
is_grant TINYINT(1) NOT NULL (1=GRANT, 0=DENY)
|
|
created_at TIMESTAMP
|
|
updated_at TIMESTAMP
|
|
|
|
UNIQUE KEY (user_id, permission_id)
|
|
|
|
Management API
|
|
|
|
// Grant a permission
|
|
User_Permission_Model::grant($user_id, User_Model::PERM_API_ACCESS);
|
|
|
|
// Deny a permission
|
|
User_Permission_Model::deny($user_id, User_Model::PERM_DATA_EXPORT);
|
|
|
|
// Remove supplementary (revert to role default)
|
|
User_Permission_Model::remove($user_id, User_Model::PERM_API_ACCESS);
|
|
|
|
// Get all supplementary permissions for user
|
|
$supplementary = User_Permission_Model::for_user($user_id);
|
|
|
|
Resolution Priority
|
|
|
|
DENY always wins. Order of precedence:
|
|
|
|
1. Explicit DENY → permission denied
|
|
2. Explicit GRANT → permission granted
|
|
3. Role default → permission granted if in role
|
|
4. Not granted → permission denied
|
|
|
|
Example: User is Site Admin (has PERM_MANAGE_SITE_USERS by role)
|
|
|
|
- No supplementary → has permission (from role)
|
|
- GRANT added → has permission (redundant but harmless)
|
|
- DENY added → NO permission (DENY overrides role)
|
|
- Both GRANT and DENY → NO permission (DENY wins)
|
|
|
|
EXAMPLES
|
|
|
|
Example 1: Check Permission in Controller
|
|
|
|
#[Ajax_Endpoint]
|
|
#[Auth('Permission::authenticated()')]
|
|
public static function delete_user(Request $request, array $params = [])
|
|
{
|
|
if (!Permission::has_permission(User_Model::PERM_MANAGE_SITE_USERS)) {
|
|
return response_error(Ajax::ERROR_UNAUTHORIZED, 'Cannot manage users');
|
|
}
|
|
|
|
$target_id = $params['user_id'];
|
|
$target = User_Model::find($target_id);
|
|
|
|
// Check can admin this user's role
|
|
$current = Permission::get_user();
|
|
if (!$current->can_admin_role($target->role_id)) {
|
|
return response_error(Ajax::ERROR_UNAUTHORIZED, 'Cannot modify this user');
|
|
}
|
|
|
|
$target->delete();
|
|
return ['success' => true];
|
|
}
|
|
|
|
Example 2: Conditional UI Based on Permissions
|
|
|
|
// In controller, pass permissions to view
|
|
return rsx_view('Settings_Users', [
|
|
'can_create' => Permission::has_permission(User_Model::PERM_MANAGE_SITE_USERS),
|
|
'can_export' => Permission::has_permission(User_Model::PERM_DATA_EXPORT),
|
|
]);
|
|
|
|
// In jqhtml template
|
|
<% if (this.args.can_create) { %>
|
|
<button @click=this.create_user>Add User</button>
|
|
<% } %>
|
|
|
|
Example 3: Role Assignment Validation
|
|
|
|
#[Ajax_Endpoint]
|
|
public static function update_user_role(Request $request, array $params = [])
|
|
{
|
|
$target = User_Model::find($params['user_id']);
|
|
$new_role_id = $params['role_id'];
|
|
|
|
$current = Permission::get_user();
|
|
|
|
// Can't change own role
|
|
if ($target->id === $current->id) {
|
|
return response_error(Ajax::ERROR_VALIDATION, 'Cannot change own role');
|
|
}
|
|
|
|
// Must be able to admin both current and new role
|
|
if (!$current->can_admin_role($target->role_id)) {
|
|
return response_error(Ajax::ERROR_UNAUTHORIZED, 'Cannot modify this user');
|
|
}
|
|
|
|
if (!$current->can_admin_role($new_role_id)) {
|
|
return response_error(Ajax::ERROR_UNAUTHORIZED, 'Cannot assign this role');
|
|
}
|
|
|
|
$target->role_id = $new_role_id;
|
|
$target->save();
|
|
|
|
return ['success' => true];
|
|
}
|
|
|
|
Example 4: Grant Supplementary Permission
|
|
|
|
// Admin grants API access to a regular user
|
|
$user = User_Model::find($user_id);
|
|
|
|
// Verify current user can admin this user
|
|
if (!Permission::get_user()->can_admin_role($user->role_id)) {
|
|
throw new Exception('Unauthorized');
|
|
}
|
|
|
|
User_Permission_Model::grant($user->id, User_Model::PERM_API_ACCESS);
|
|
|
|
// User now has API access despite being a regular User role
|
|
|
|
Example 5: Check Multiple Permissions
|
|
|
|
// User needs EITHER permission
|
|
$can_view = Permission::has_permission(User_Model::PERM_VIEW_DATA)
|
|
|| Permission::has_permission(User_Model::PERM_EDIT_DATA);
|
|
|
|
// User needs BOTH permissions
|
|
$can_admin = Permission::has_permission(User_Model::PERM_MANAGE_SITE_SETTINGS)
|
|
&& Permission::has_permission(User_Model::PERM_MANAGE_SITE_USERS);
|
|
|
|
ADDING NEW PERMISSIONS
|
|
|
|
1. Add constant to User_Model:
|
|
|
|
const PERM_NEW_FEATURE = 10;
|
|
|
|
2. Add to role definitions in $enums if role should grant it:
|
|
|
|
400 => [ // ROLE_SITE_ADMIN
|
|
'permissions' => [
|
|
// ... existing
|
|
self::PERM_NEW_FEATURE,
|
|
],
|
|
],
|
|
|
|
3. Run rsx:migrate:document_models to regenerate stubs
|
|
|
|
4. Use in code:
|
|
|
|
if (Permission::has_permission(User_Model::PERM_NEW_FEATURE)) {
|
|
// ...
|
|
}
|
|
|
|
ADDING NEW ROLES
|
|
|
|
1. Add constant (maintain hierarchy order, 100-based):
|
|
|
|
const ROLE_SUPERVISOR = 450; // Between Admin (400) and Manager (500)
|
|
|
|
2. Add to $enums with permissions and can_admin_roles:
|
|
|
|
450 => [
|
|
'constant' => 'ROLE_SUPERVISOR',
|
|
'label' => 'Supervisor',
|
|
'permissions' => [
|
|
self::PERM_VIEW_USER_ACTIVITY,
|
|
self::PERM_EDIT_DATA,
|
|
self::PERM_VIEW_DATA,
|
|
],
|
|
'can_admin_roles' => [500, 600, 700, 800],
|
|
],
|
|
|
|
3. Update can_admin_roles for roles above:
|
|
|
|
400 => [ // ROLE_SITE_ADMIN
|
|
'can_admin_roles' => [450, 500, 600, 700, 800], // Add new role ID
|
|
],
|
|
|
|
4. Run migration if role_id column needs updating
|
|
|
|
5. Run rsx:migrate:document_models
|
|
|
|
FRAMEWORK IMPLEMENTATION DETAILS
|
|
|
|
This section is for framework developers modifying the ACL system.
|
|
|
|
Core Files
|
|
|
|
rsx/models/user_model.php
|
|
Role and permission constants
|
|
$enums definition with role metadata
|
|
has_permission(), can_admin_role() methods
|
|
Supplementary permission lookup methods
|
|
|
|
rsx/permission.php
|
|
Permission class with static helper methods
|
|
has_permission(), has_role(), get_user()
|
|
Custom permission methods for #[Auth]
|
|
|
|
rsx/models/user_permission_model.php
|
|
Supplementary permissions CRUD
|
|
grant(), deny(), remove() static methods
|
|
|
|
Session Integration
|
|
|
|
Permission::get_user() retrieves current site membership:
|
|
|
|
1. Get login_user_id from Session::get_user_id()
|
|
2. Get site_id from Session::get_site_id()
|
|
3. Query users WHERE login_user_id AND site_id
|
|
4. Cache result for request duration
|
|
|
|
Caching Strategy
|
|
|
|
Supplementary permissions are loaded once per request:
|
|
|
|
1. First has_permission() call loads all user_permissions
|
|
2. Stored in User_Model instance property
|
|
3. Subsequent checks use cached data
|
|
4. No cache invalidation needed (request-scoped)
|
|
|
|
Enum Integration
|
|
|
|
The $enums system provides magic properties:
|
|
|
|
$user->role_permissions // Array from enum definition
|
|
$user->role_can_admin_roles // Array from enum definition
|
|
$user->role_label // String label
|
|
|
|
These are resolved via Rsx_Model_Abstract::__get()
|
|
|
|
FUTURE ENHANCEMENTS
|
|
|
|
Attribute-Based Permission Checks
|
|
|
|
Future goal: Declare permissions directly in route attributes.
|
|
|
|
#[Route('/settings/users')]
|
|
#[Auth('Permission::authenticated()')]
|
|
#[RequiresPermission(User_Model::PERM_MANAGE_SITE_USERS)]
|
|
public static function users(...)
|
|
|
|
This section will be replaced with implementation details when
|
|
attribute-based permission checking is complete.
|
|
|
|
Custom Filters
|
|
|
|
Future goal: Allow permission modifications based on context.
|
|
|
|
Use cases:
|
|
- Site-level feature toggles (site disables user management)
|
|
- Subscription limits (free plan removes export permission)
|
|
- Time-based access (permission valid only during hours)
|
|
- Feature flags (A/B testing permission-gated features)
|
|
|
|
Architecture concept:
|
|
|
|
Role Permissions
|
|
↓
|
|
Custom Filters (modify permission set)
|
|
↓
|
|
Supplementary GRANT/DENY
|
|
↓
|
|
Final Permission Decision
|
|
|
|
Filters would be registered callbacks that receive the permission
|
|
set and context, returning modified permissions. This allows
|
|
dynamic permission modification without changing role definitions.
|
|
|
|
This section will be replaced with implementation details when
|
|
custom filters are implemented.
|
|
|
|
MIGRATION FROM LEGACY
|
|
|
|
If upgrading from a system without ACLs:
|
|
|
|
1. Add role_id column to users table (default ROLE_USER)
|
|
2. Create user_permissions table
|
|
3. Assign appropriate roles to existing users
|
|
4. Add Permission checks to sensitive routes
|
|
5. Test with rsx:debug --user_id= to verify
|
|
|
|
SEE ALSO
|
|
auth - Authentication system (login, sessions, invitations)
|
|
enums - Enum system for role/permission metadata
|
|
routing - Route protection with #[Auth] attribute
|
|
session - Session management and user context
|
|
rsxapp - Global JS object containing resolved_permissions
|
|
|
|
RSpade 1.0 January 2026 ACLS(7)
|