# new AccessControlService()
Role-Based Access Control (RBAC) service with hierarchical priority enforcement.
AccessControlService provides a simple yet powerful RBAC implementation that:
- Evaluates access based on roles (DenyAll, Customer, Guest)
- Supports resource ownership (users can access their own resources)
- Integrates with service layer via withAccessControl mixin
- Uses priority-based evaluation (DenyAll > Ownership > Default Deny)
- Handles string/number ID comparison safely
How Priority Evaluation Works:
- DenyAll check first - Banned users blocked immediately
- Guest check - Public resources only for guests
- Ownership check - Grant if actor owns resource
- Default deny - Secure by default (least privilege)
Integration Patterns:
Pattern 1: Automatic Service-Level Enforcement (Recommended)
// Service with automatic ACL checking
class IconService extends withAccessControl(BaseService) {
// All CRUD methods automatically enforce permissions
}
// Usage
await iconService.update(
iconId,
{ name: 'new-name' },
{ actor: currentUser } // ACL checked automatically
);
Pattern 2: Manual HTTP Middleware
// Manual enforcement in route handlers
app.put('/api/icons/:id', authenticate, async (req, res) => {
const icon = await iconService.getById(req.params.id);
const allowed = await acl.enforce({
actor: req.user,
action: 'update',
resource: icon
});
if (!allowed) return res.status(403).json({ error: 'Forbidden' });
// ... proceed with update
});
Pattern 3: Reusable ACL Middleware
// Generic middleware factory
function aclMiddleware(resourceLoader, action) {
return async (req, res, next) => {
const resource = await resourceLoader(req);
const allowed = await acl.enforce({
actor: req.user,
action,
resource
});
if (!allowed) return res.status(403).json({ error: 'Forbidden' });
next();
};
}
// Use in routes
app.put('/api/icons/:id',
authenticate,
aclMiddleware(req => iconService.getById(req.params.id), 'update'),
updateIconHandler
);
Performance Considerations:
- Role checks are O(n) where n = number of roles (typically 1-3)
- Ownership check is O(1) - simple ID comparison
- No database queries - operates on in-memory objects
- Average execution time: < 0.1ms per enforce() call
Examples
// Basic construction (most common)
const acl = new AccessControlService();
// Guest can only access public resources
const result = await acl.enforce({
actor: { id: null, roles: [{ value: 'ROLE_GUEST' }] },
action: 'read',
resource: { isPublic: true }
});
// Returns: true (public resource)
// Owner can access their own resources
const result = await acl.enforce({
actor: { id: 123, roles: [{ value: 'ROLE_CUSTOMER' }] },
action: 'update',
resource: { ownerId: 123 }
});
// Returns: true
// Non-owner cannot access other's resources
const result = await acl.enforce({
actor: { id: 123, roles: [{ value: 'ROLE_CUSTOMER' }] },
action: 'delete',
resource: { ownerId: 456 }
});
// Returns: false
// DenyAll blocks all access
const result = await acl.enforce({
actor: {
id: 1,
roles: [
{ value: 'ROLE_DENYALL' },
{ value: 'ROLE_CUSTOMER' }
]
},
action: 'read',
resource: { ownerId: 1 }
});
// Returns: false (DenyAll has highest priority)
// Custom policies for extensibility
const acl = new AccessControlService({
policies: {
'icons:publish': (actor, resource) => {
return actor.isVerified && resource.status === 'pending';
},
'icons:download': (actor, resource) => {
return resource.isPublic || actor.hasPurchased(resource.id);
}
}
});
Methods
# actorHasRole(actor, roleEnum) → {boolean}
Checks if an actor has a specific role.
Performs case-insensitive role matching and handles various edge cases including null actors, missing roles arrays, and roles without values.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
actor |
Object
|
The user or entity to check |
||
roles |
Array.<Object>
|
<optional> |
[] | Array of role objects |
roles[].value |
string
|
The role value/name |
||
roleEnum |
string
|
UserRoles
|
The role to check for (case-insensitive) |
True if actor has the specified role
boolean
Examples
const hasGuest = service.actorHasRole(user, UserRoles.Guest);
// Returns: true if user has Guest role
// Case-insensitive matching
const actor = { roles: [{ value: 'ROLE_CUSTOMER' }] };
service.actorHasRole(actor, 'role_customer'); // true
service.actorHasRole(actor, UserRoles.Customer); // true
// Handles null/undefined actors
service.actorHasRole(null, UserRoles.Customer); // false
service.actorHasRole({ roles: [] }, UserRoles.Customer); // false
# async enforce(params) → {Promise.<boolean>}
Enforces access control for a given request.
Evaluates access based on the following priority order:
- DenyAll role → Returns false (highest priority)
- Guest role → Returns true only for public resources
- Resource ownership → Returns true if actor.id matches resource.ownerId
- Default → Returns false
Note: ID comparison uses string coercion to handle both string and numeric IDs.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
params |
Object
|
Enforcement parameters |
|
actor |
Object
|
The user requesting access |
|
actor.id |
number
|
string
|
<optional> |
Actor's unique identifier |
actor.roles |
Array.<Object>
|
<optional> |
Actor's roles |
action |
string
|
<optional> |
The action being attempted (e.g., 'read', 'write', 'delete') |
resource |
Object
|
<optional> |
The resource being accessed |
resource.ownerId |
number
|
string
|
<optional> |
Owner's ID if applicable |
True if access is granted, false otherwise
Promise.<boolean>
Examples
// Guest can access public resources
const result = await service.enforce({
actor: { id: null, roles: [{ value: UserRoles.Guest }] },
action: 'read',
resource: { isPublic: true }
});
// Returns: true (public resource)
// Owner has access to their own resources
const result = await service.enforce({
actor: { id: 123, roles: [{ value: UserRoles.Customer }] },
action: 'read',
resource: { ownerId: 123 }
});
// Returns: true (ownership grants access)
// DenyAll prevents all access
const result = await service.enforce({
actor: {
id: 1,
roles: [
{ value: UserRoles.DenyAll },
{ value: UserRoles.Customer }
]
},
action: 'read',
resource: { ownerId: 1 }
});
// Returns: false (DenyAll has highest priority)
// Non-owner without elevated roles denied
const result = await service.enforce({
actor: { id: 123, roles: [{ value: UserRoles.Customer }] },
action: 'read',
resource: { ownerId: 456 }
});
// Returns: false (default deny)
// Handles string/number ID comparison
const result = await service.enforce({
actor: { id: '123', roles: [{ value: UserRoles.Customer }] },
action: 'read',
resource: { ownerId: 123 } // Number
});
// Returns: true (string '123' equals number 123)