Class

AccessControlService

AccessControlService()

Constructor

# 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:

  1. DenyAll check first - Banned users blocked immediately
  2. Guest check - Public resources only for guests
  3. Ownership check - Grant if actor owns resource
  4. 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

View Source src/common/access-control/AccessControlService.js, line 150

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)

View Source src/common/access-control/AccessControlService.js, line 377

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:

  1. DenyAll role → Returns false (highest priority)
  2. Guest role → Returns true only for public resources
  3. Resource ownership → Returns true if actor.id matches resource.ownerId
  4. 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

View Source src/common/access-control/AccessControlService.js, line 456

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)

AccessControlService(optionsopt)

Constructor

# new AccessControlService(optionsopt)

Construct AccessControlService with optional custom policies.

The policies parameter is reserved for future extensibility. Currently, the service uses a simple role-based + ownership model, but custom policies can be added for fine-grained control (e.g., "only verified users can publish", "only premium users can feature content").

Current Evaluation Logic:

  • DenyAll role → Deny
  • Guest role → Public resources only
  • Resource ownership (actor.id === resource.ownerId) → Grant
  • Default → Deny
Parameters:
Name Type Attributes Default Description
options Object <optional>
{}

Configuration options

policies Object <optional>
null

Custom policy functions (future expansion)

policies[policyName] function <optional>

Policy function: (actor, resource) => boolean

View Source src/common/access-control/AccessControlService.js, line 340

Examples
// Basic construction (default policies)
const service = new AccessControlService();
// With custom policies for future extensibility
const service = new AccessControlService({
  policies: {
    'icons:publish': (actor, resource) => {
      // Only verified users can publish pending icons
      return actor.isVerified && resource.status === 'pending';
    },
    'icons:feature': (actor, resource) => {
      // Only premium users can feature icons
      return actor.isPremium === true;
    }
  }
});
// Singleton pattern (recommended for app-wide use)
// acl-singleton.js
const AccessControlService = require('./AccessControlService');
module.exports = new AccessControlService();

// app.js
const acl = require('./acl-singleton');
app.put('/api/icons/:id', async (req, res) => {
  const allowed = await acl.enforce({ actor: req.user, ... });
  // ...
});

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)

View Source src/common/access-control/AccessControlService.js, line 377

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:

  1. DenyAll role → Returns false (highest priority)
  2. Guest role → Returns true only for public resources
  3. Resource ownership → Returns true if actor.id matches resource.ownerId
  4. 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

View Source src/common/access-control/AccessControlService.js, line 456

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)