.do
Enterprise

RBAC & Fine-Grained Authorization

Built-in role-based access control and fine-grained authorization - define permissions once, enforce everywhere automatically

The .do platform provides comprehensive access control out of the box. Define roles and permissions using platform primitives, and authorization is automatically enforced across your entire application.

How It Works

Authorization is built into every platform operation:

// Define roles and permissions
const roles = {
  admin: {
    can: ['project.create', 'project.read', 'project.update', 'project.delete', 'user.manage'],
    description: 'Full access to all resources'
  },
  editor: {
    can: ['project.create', 'project.read', 'project.update'],
    description: 'Can create and edit projects'
  },
  viewer: {
    can: ['project.read'],
    description: 'Read-only access'
  }
}

// Assign roles to users
await $.User.assign.Role({ userId, roleId: 'editor' })

// Platform automatically checks permissions
const projects = await $.Project.list()
// Only returns projects user is authorized to see

await $.Project.update(projectId, { name: 'New Name' })
// Platform checks: Does user have 'project.update' permission?
// If not, throws UnauthorizedException

Role-Based Access Control (RBAC)

Define roles that group permissions together:

Built-in Roles

Every application gets standard roles:

// Organization-level roles
{
  owner: ['*'],                    // Full access to everything
  admin: ['user.manage', '*.read', '*.write'],
  member: ['*.read'],
  guest: ['public.read']
}

// Resource-specific roles
{
  'project:owner': ['project.delete', 'project.admin'],
  'project:contributor': ['project.write', 'project.read'],
  'project:viewer': ['project.read']
}

Custom Roles

Define roles specific to your application:

// Define custom role
const customRole = await $.Role.create({
  name: 'finance-analyst',
  description: 'Access to financial data and reports',
  permissions: [
    'report.financial.read',
    'report.financial.export',
    'dashboard.finance.view',
    'data.revenue.read',
    'data.expenses.read'
  ]
})

// Assign to users
await $.User.assign.Role({
  userId: analyst.id,
  roleId: customRole.id
})

// Platform enforces automatically
const report = await $.Report.financial.generate()
// Platform checks: Does user have 'report.financial.read'?

Fine-Grained Authorization (FGA)

Go beyond roles with attribute-based and relationship-based access control:

Attribute-Based Access

Control access based on user attributes, resource properties, and context:

// Define attribute-based policy
await $.Policy.create({
  name: 'sensitive-data-access',
  effect: 'allow',
  actions: ['data.pii.read'],
  conditions: {
    // Must be in specific department
    'user.department': ['legal', 'compliance'],

    // Must have completed training
    'user.training.privacy': true,

    // Only during business hours
    'request.time': { between: ['09:00', '17:00'] },

    // Only from corporate network
    'request.ipRange': '10.0.0.0/8'
  }
})

// Platform evaluates policy automatically
const sensitiveData = await $.Data.pii.read()
// Checks all conditions, allows/denies accordingly

Relationship-Based Access

Control access based on relationships between users and resources:

// Define relationships
await $.Project.assign.owner({ projectId, userId })
await $.Project.assign.contributor({ projectId, userId: editor.id })
await $.Project.assign.viewer({ projectId, userId: reader.id })

// Platform uses relationships for authorization
await $.Project.update(projectId, { status: 'archived' })
// Platform checks: Is user the owner? Or has 'project.admin' permission?

// Query based on relationships
const myProjects = await $.Project.list({
  where: {
    // Automatically scoped to projects user owns, contributes to, or can view
  }
})

Hierarchical Access

Model organizational hierarchies:

// Define hierarchy
const org = await $.Organization.create({
  name: 'Acme Corp',
  structure: {
    departments: [
      {
        name: 'Engineering',
        teams: [
          { name: 'Backend', manager: user1 },
          { name: 'Frontend', manager: user2 }
        ]
      },
      {
        name: 'Sales',
        teams: [
          { name: 'Enterprise', manager: user3 }
        ]
      }
    ]
  }
})

// Managers inherit permissions for their team
// Department heads inherit permissions for all teams
// Org admins inherit permissions for entire organization

// Platform resolves hierarchy automatically
const teamMetrics = await $.Metrics.team.get(teamId)
// Allowed if:
// - User is team member
// - User is team manager
// - User is department head
// - User is org admin

Permission Model

Permissions follow a clear naming pattern:

// Format: resource.action
'project.create'
'project.read'
'project.update'
'project.delete'

// Namespaced permissions
'report.financial.read'
'report.financial.export'
'data.pii.read'
'data.pii.write'

// Wildcards
'project.*'           // All project actions
'*.read'             // Read on all resources
'*'                  // All permissions

Permission Checks

The platform checks permissions automatically:

// Explicit check
if (await $.User.can(userId, 'project.delete', projectId)) {
  await $.Project.delete(projectId)
}

// Automatic enforcement
await $.Project.delete(projectId)
// Platform throws UnauthorizedException if not authorized

// Batch check
const permissions = await $.User.permissions(userId)
// Returns: ['project.read', 'project.update', ...]

Access Control Lists (ACLs)

Define per-resource permissions:

// Set ACL on resource
await $.Project.acl.set(projectId, {
  owner: userId,
  readers: [user2.id, user3.id],
  writers: [user4.id],
  admins: [user5.id]
})

// Grant specific permission
await $.Project.acl.grant(projectId, {
  userId: user6.id,
  permission: 'project.comment'
})

// Revoke permission
await $.Project.acl.revoke(projectId, {
  userId: user6.id,
  permission: 'project.comment'
})

// Query ACL
const acl = await $.Project.acl.get(projectId)
// Returns: { owner: userId, readers: [...], writers: [...] }

Multi-Tenancy Integration

Authorization is automatically scoped to organizations:

// All queries automatically scoped
const projects = await $.Project.list()
// Platform adds:
// - organizationId filter
// - user permission check
// - only returns authorized projects

// Cross-organization access requires explicit permission
await $.Policy.create({
  name: 'cross-org-support',
  effect: 'allow',
  actions: ['*.read'],
  conditions: {
    'user.role': 'support-engineer',
    'user.crossOrgAccess': true
  }
})

Delegation

Users can delegate permissions to others:

// Delegate permission temporarily
await $.User.delegate({
  from: managerId,
  to: teamLeadId,
  permissions: ['team.approve.expense'],
  expires: addDays(new Date(), 7)
})

// Team lead can now approve expenses
await $.Expense.approve(expenseId)
// Platform checks delegation, allows if valid

// Delegation automatically expires
// Platform revokes after expiry date

Audit & Compliance

All authorization decisions are logged:

// Every access check is logged
await $.Project.read(projectId)
// Logs: {
//   userId,
//   action: 'project.read',
//   resourceId: projectId,
//   decision: 'allow',
//   reason: 'user has project.read via editor role',
//   timestamp
// }

// Denied access is logged too
try {
  await $.Project.delete(projectId)
} catch (error) {
  // Logs: {
  //   userId,
  //   action: 'project.delete',
  //   resourceId: projectId,
  //   decision: 'deny',
  //   reason: 'user lacks project.delete permission',
  //   timestamp
  // }
}

// Query authorization audit log
const log = await $.AuditLog.list({
  type: 'authorization',
  userId,
  period: 'last_30_days'
})

API Authorization

APIs automatically enforce authorization:

// REST API endpoints check permissions
on($.API.get('/projects/:id'), async ({ params, user }) => {
  // Platform already checked 'project.read' permission
  const project = await $.Project.find(params.id)
  return { project }
})

on($.API.delete('/projects/:id'), async ({ params, user }) => {
  // Platform already checked 'project.delete' permission
  await $.Project.delete(params.id)
  return { success: true }
})

// GraphQL queries check field-level permissions
{
  project(id: "123") {
    name           # Requires 'project.read'
    description    # Requires 'project.read'
    revenue        # Requires 'project.revenue.read'
  }
}

Performance

Authorization checks are optimized for production:

  • Cached roles: User roles cached for 5 minutes
  • Cached permissions: Permission checks cached per request
  • Indexed relationships: Database indexes for fast lookups
  • <10ms overhead: Authorization adds minimal latency
// Warm cache
const permissions = await $.User.permissions(userId)
// First call: 50ms (database lookup)
// Subsequent calls: `<1ms` (cached)

// Batch authorization
const authorized = await $.User.authorizeMany(userId, [
  { action: 'project.read', resource: project1.id },
  { action: 'project.read', resource: project2.id },
  { action: 'project.read', resource: project3.id }
])
// Single database query, bulk permission check

Best Practices

1. Principle of Least Privilege

Grant minimum permissions necessary:

// Good: Specific permissions
const viewer = {
  permissions: ['project.read', 'comment.create']
}

// Avoid: Overly broad permissions
const badViewer = {
  permissions: ['*']  // Too much access
}

2. Use Roles, Not Individual Permissions

Group permissions into roles:

// Good: Role-based
await $.User.assign.Role({ userId, roleId: 'editor' })

// Avoid: Individual permissions
await $.User.grant({ userId, permission: 'project.create' })
await $.User.grant({ userId, permission: 'project.read' })
await $.User.grant({ userId, permission: 'project.update' })
// Harder to manage, audit, and revoke

3. Regular Access Reviews

Periodically review who has access:

// Get all users with admin access
const admins = await $.User.list({
  where: {
    roles: { contains: 'admin' }
  }
})

// Review and revoke if needed
for (const user of admins) {
  if (!shouldBeAdmin(user)) {
    await $.User.revoke.Role({ userId: user.id, roleId: 'admin' })
  }
}

4. Audit Critical Actions

Log sensitive operations:

on($.User.role.assigned, async ({ user, role }) => {
  if (role.name === 'admin') {
    await $.Alert.send({
      severity: 'high',
      message: `Admin role assigned to ${user.email}`,
      recipient: securityTeam
    })
  }
})

Migration

Moving from custom authorization:

Before: Custom Implementation

// Manual permission checks everywhere
app.delete('/projects/:id', async (req, res) => {
  const project = await db.projects.findById(req.params.id)

  // Custom permission logic
  if (req.user.id !== project.ownerId && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' })
  }

  await db.projects.delete(req.params.id)
  res.json({ success: true })
})

After: Platform Authorization

// Platform handles automatically
on($.API.delete('/projects/:id'), async ({ params }) => {
  // Platform already checked:
  // - Is user authenticated?
  // - Is user authorized for 'project.delete'?
  // - Does user have access to this specific project?

  await $.Project.delete(params.id)
  return { success: true }
})

Integration with External Systems

Sync with external authorization systems:

// Sync from external system
on($.Schedule.hourly, async () => {
  const externalRoles = await fetchFromOkta()

  for (const assignment of externalRoles) {
    await $.User.sync.Role({
      userId: assignment.userId,
      roleId: assignment.roleId,
      source: 'okta'
    })
  }
})

// Export to external system
on($.User.role.assigned, async ({ user, role }) => {
  await sendToExternalSystem({
    userId: user.id,
    role: role.name,
    action: 'assign'
  })
})

Next Steps


Authorization is complex. The .do platform makes it simple with built-in RBAC, fine-grained authorization, and automatic enforcement across your entire application.