Home Doh Ref
Dohballs
  • 📁 doh_modules
    • 📦 dataforge
    • 📦 express
    • 📁 sso
    • 📁 user

Doh Permission System: Context-Aware Authorization

Flexible Authorization Beyond Traditional Models

Modern applications often require authorization logic that goes beyond simple roles or static attributes. Access frequently depends on the specific item being accessed, the runtime state, or a combination of factors. Traditional models can struggle:

  • Role-Based Access Control (RBAC): Simple to start but often too rigid for fine-grained, object-specific rules.
  • Access Control Lists (ACLs): Granular but difficult to manage at scale.
  • Attribute-Based Access Control (ABAC): Powerful and flexible but can lead to complex policy engines where understanding effective permissions becomes challenging. Developers often need to build the entire attribute and policy system from scratch.

The Doh Permission System offers a pragmatic, context-aware alternative designed for modularity, clarity, and developer experience. It combines the best aspects of group-based management with dynamic, context-sensitive evaluation.

Feature Comparison

Feature Doh Permission System RBAC (Role-Based) ABAC (Attribute-Based) ACLs (Access Control Lists)
Granularity High: action:context evaluated against runtime objects. Low: Based on broad user roles. High: Based on arbitrary attributes. High: Per-user, per-object.
Flexibility High: Composable groups, dynamic conditions (user/context), inheritance, negation. Low: Static roles, limited context. High: Flexible policy language. Moderate: Grouping helps, but lists.
Manageability Moderate: Group composition requires thought, but modularity helps. Pod config allows overrides. High: Simple role definitions. Low: Complex policy management. Low: Can become very large.
Context Awareness High: Centered around runtimeContext objects and definePermissionContext functions. Low: Usually none or minimal. High: Via environment/resource attributes. Moderate: Object-focused.
Modularity/Extensibility Very High: Modules define own actions/contexts/base groups. Apps compose these via inheritance/Pod config. Low: Roles are usually global. Moderate: Policies can be modularized. Low: Lists are typically flat.
Developer Experience Clear Doh.permit checks. Modules provide working permission examples to build upon, reducing complexity vs. implementing ABAC/ACLs from scratch. Implicit actions simplify definitions. Simple: Easy to understand roles. Complex: Requires policy expertise. Tedious: Managing individual entries.

Migrating from Legacy Middleware to Direct Permission Checks

The older approach using Users.permit() middleware lacks context awareness and is being phased out:

// ❌ DEPRECATED: Using middleware without context
Router.AddRoute('/api/articles/:id', [Users.permit('edit:article')], handler);

Instead, rely on the global authentication middleware and perform direct checks with context:

// ✅ RECOMMENDED: Global middleware + direct contextual checks
Router.AddRoute('/api/articles/:id', [], async function(data, req, res, callback) {
  const article = await Articles.getById(data.id);
  
  // Pass the actual article object as runtime context
  if (!await Doh.permit(req.user, 'edit:article', article)) {
    return Router.SendJSON(res, { success: false, error: 'Insufficient permissions' }, 403, callback);
  }
  
  // Continue with article editing...
});

This approach allows for fine-grained permission logic based on the actual object being accessed.

Modular & Composable Design

A key advantage of Doh's system is its modularity. Instead of a single, monolithic authorization engine, Doh encourages a composable approach:

  1. Modules Define Building Blocks: Each Doh module (representing a feature like blogging, user profiles, or forums) can define the actions ('publish', 'edit_profile'), contexts ('blog_post', 'user_profile'), and base permission groups ('blog_author_base', 'profile_owner_base') relevant to its specific domain.
  2. Applications Compose Roles: The main application developer then builds application-specific roles (e.g., 'senior_editor', 'community_manager') by inheriting from these module-provided base groups.
  3. Configuration Refines: Further customization and overrides can be applied using Pod configuration (Doh.pod.Users.groups), allowing adjustments to inheritance or permissions without modifying the original module code.

This approach means developers integrating a module get a working, self-contained permission model for that feature out-of-the-box. They can use it directly or easily compose it into their broader application roles, significantly reducing the effort compared to designing complex ABAC policies or ACL structures from the ground up.

The Core Concepts

  1. Actions: Operations (e.g., read, edit). Defined implicitly by their use in permission strings.
  2. Permission Contexts: Define when a permission applies (e.g., user_profile, current_user_profile, document_owner). Defined via Doh.definePermissionContext(name, conditionFunctionOrInheritedName). The conditionFunction receives (user, runtimeContext) and returns true if the context applies.
  3. Permission Strings: Format action:context (e.g., 'edit:user_profile'). Negated via ~~ prefix (e.g., '~~delete:comment').
  4. Permission Groups: Named collections of permission strings and inheritance rules. Defined via Doh.definePermissionGroup(name, options).
    • assignable: (Boolean, default false) Can this group be directly assigned to a user's groups array?
    • condition: (Function | String | Undefined) Determines dynamic membership.
      • condition(user): Static condition based only on user properties.
      • condition(user, context): Contextual condition based on user and the runtime object.
      • 'groupName': Inherits the condition function from another group.
    • inherits: (Array) Names of groups to inherit permissions from.
    • permissions: (Array) List of permission strings ('action:context' or '~~action:context').
  5. Runtime Context: The actual object/data being acted upon during a Doh.permit() check (e.g., the specific blogPost object).

How Permissions are Checked: Doh.permit()

Use Doh.permit(user, actionPairString, runtimeContext) to check permissions.

// Can the current user (req.user) publish this specific blogPost?
if (await Doh.permit(req.user, 'publish:blog_post', blogPost)) {
  // Yes
}

Evaluation Flow:

  1. Identify all groups the user belongs to (assigned static, dynamic static, dynamic contextual for the specific runtimeContext).
  2. Gather all granted and negated permission strings from these groups and their inheritance chains.
  3. Check if the specific permission (e.g., 'publish:blog_post') or a relevant wildcard ('*:*', 'publish:*', '*:blog_post') is negated. If yes -> DENY.
  4. Check if the specific permission or a relevant wildcard is granted. If yes -> ALLOW.
  5. Otherwise -> DENY.

(Negations take ultimate precedence).

Caution on Negations (~~):

While negations are powerful, use them sparingly. Because they take ultimate precedence, a negated permission inherited from a base group cannot be easily overridden or granted back by a more specific group or context.

Recommendation: Prefer explicit grants. Rely on the absence of a granted permission to imply denial. Reserve negations primarily for specific, final overrides in application-level roles or configurations, rather than in base, potentially inheritable groups defined by modules.

Defining Contexts and Groups

Contexts and Groups form the core of your permission logic.

// --- Defining Contexts ---
Doh.definePermissionContext('user_profile', (user, context) => !!context?.username);

Doh.definePermissionContext('current_user_profile', 
  (user, context) => !!context?.username && user?.username === context.username);

Doh.definePermissionContext('document', (user, context) => !!context?.documentId);

// --- Defining Groups ---

// Static condition: Any user with a username
Doh.definePermissionGroup('authenticated', {
  condition: (user) => !!user?.username,
  permissions: ['read:user_profile']
});

// Contextual condition: User owns the document being checked
Doh.definePermissionGroup('document_owner', {
  condition: (user, context) => context?.documentId && user?.id === context.ownerId,
  permissions: ['edit:document', 'delete:document']
});

// Assignable role inheriting multiple capabilities
Doh.definePermissionGroup('editor', {
  assignable: true,
  inherits: ['authenticated', 'document_owner'], // Gets base read + owner rights
  permissions: [
    'publish:document',
  ]
});

Configuration via Pods (Doh.pod.Users.groups)

You can define or extend group structures in pod.yaml (or via Doh.Pod). This is powerful for composing module-defined groups into application-specific roles.

  • Merging: Pod definitions merge with code definitions. Pods can add permissions and inherits to existing groups.
  • Defining assignable: You can define assignable: true (or false) for a group within the Pod configuration. If a group exists in both code and Pod, the assignable value from the Pod takes precedence. If defined only in the Pod, it respects the assignable value specified there (defaulting to false if omitted).
  • Defining condition: Cannot define condition functions in Pods (as they require JavaScript code). If merging with a code-defined group, the code's condition is retained.
// Example: In pod.yaml or via Doh.Pod('my_app_perms', { Users: { groups: ... }})
Users: {
  groups: {
    'site_admin': {
      // This Pod definition MERGES with any code definition for 'site_admin'
      assignable: true, // Explicitly making it assignable via Pod
      inherits: ['user_admin', 'content_admin', 'analytics_viewer'] // Adding inheritance
    },
    'new_pod_only_group': {
        // This group only exists because of the Pod config
        assignable: true, // Explicitly assignable
        permissions: ['read:special_report']
    }
    'existing_code_group': {
        // Assume 'existing_code_group' was defined in code without 'assignable: true'
        assignable: true // Pod configuration makes it assignable
        // It keeps its original condition (if any) and permissions from code,
        // plus any permissions/inherits added here in the Pod.
    }
  }
}

(Remember: Managing user assignments to assignable groups within Pods is typically handled by other modules/tools like user_host and doh poduser).

Administrative Tools

The Doh Permission System includes powerful administrative tools to help you understand, manage, and troubleshoot your permission model:

Permission Explorer (/admin/permissions)

A comprehensive visualization and analysis tool for understanding your permission model at scale. The Permission Explorer provides:

Overview Dashboard

  • System Health Metrics: Total contexts, groups, actions, and security warnings
  • Composition Analysis: Code vs Pod-defined groups, assignable groups, inheritance depth
  • Pattern Recognition: Common actions and contexts across your system
  • Security Alerts: Wildcard permissions, negations, and potential risks

Context Management

  • Visual Context Map: All permission contexts with usage statistics
  • Health Indicators: Active, unused, complex, and inherited contexts
  • Inheritance Visualization: See which contexts inherit from others
  • Usage Analytics: Which groups use each context and how frequently

Group Analysis

  • Security Risk Assessment: Identifies high-risk groups with excessive permissions
  • Inheritance Network: Interactive visualization of group inheritance relationships
    • Multi-level hierarchy display (Root → Level 1 → Level 2...)
    • Multiple inheritance support with visual indicators
    • Clickable navigation between related groups
  • Permission Breakdown: Granted vs negated permissions with risk scoring
  • Group Health Dashboard: Assignable groups, security risks, inheritance depth

Interactive Features

  • Detail Panels: Click any group or context for comprehensive information
  • Smart Filtering: Filter by assignable, dynamic, static, or security-risk groups
  • Live Search: Real-time filtering across all permission components
  • Background Dismissal: Click empty areas to clear detail views
  • Responsive Design: Works seamlessly across devices

Permission Simulation

  • Runtime Testing: Test permission checks with actual user accounts
  • Context Simulation: Provide runtime context objects to test contextual permissions
  • Trace Analysis: See exactly why a permission was granted or denied
  • User Permission Explorer: Analyze effective permissions for specific users

User Admin Panel (/admin/users)

Note: Provided by the user_host module - see doh_modules/user/user_host/README.md for details

The User Admin Panel integrates seamlessly with the Permission System to provide:

User Management

  • Group Assignment: Assign/remove users from assignable permission groups
  • Permission Visualization: See all effective permissions for each user
  • Dynamic Group Indicators: Shows which groups users belong to via conditions
  • Bulk Operations: Manage multiple users efficiently

Integration Features

  • Live Permission Updates: Changes reflect immediately in permission checks
  • Group Validation: Prevents assignment to non-assignable groups
  • Permission Preview: See how group changes affect user permissions
  • Audit Trail: Track permission changes over time

Usage Examples

Accessing the Administrative Tools

// Ensure users have appropriate permissions to access admin tools
Doh.definePermissionContext('permission_explorer', (user, context) => {
  return context && context.path === '/admin/permissions';
});

// In your route handler
Router.AddRoute('/admin/permissions', [], async function(data, req, res) {
  if (!req.user) {
    res.status(401).send('Authentication required');
    return;
  }
  
  if (!await Doh.permit(req.user, 'view:permission_explorer', { path: '/admin/permissions' })) {
    res.status(403).send('Permission denied');
    return;
  }
  
  // Render the Permission Explorer
  Router.SendHTML(res, 'permission_explorer_html', ['permission_explorer_page'], 'Permission Model Explorer');
});

API Integration

The Permission Explorer provides REST APIs for external integrations:

// Get complete permission model data
const response = await fetch('/api/admin/permission-explorer/data');
const permissionData = await response.json();

// Simulate permission checks
const simulation = await fetch('/api/admin/permission-explorer/simulate', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    username: 'john_doe',
    permissionString: 'edit:document',
    runtimeContext: { documentId: 123, ownerId: 456 }
  })
});

// Get user's effective permissions
const userPerms = await fetch('/api/admin/permission-explorer/user-permissions/john_doe');

Security Considerations

Admin Access Control

// Restrict admin tool access appropriately
Doh.definePermissionGroup('system_admin', {
  assignable: true,
  permissions: [
    'view:permission_explorer',
    'view:user_admin',
    'manage:user_groups',
    'simulate:permissions'
  ]
});

Audit and Monitoring

  • All permission changes are logged
  • Simulation requests are tracked
  • Admin access is audited
  • Security alerts for risky configurations

Best Practices for Admin Tools

Understanding Your Permission Model

  1. Start with Overview: Use the overview dashboard to understand system-wide patterns
  2. Identify Security Risks: Review groups with wildcard permissions or excessive access
  3. Trace Inheritance: Use the inheritance network to understand permission flow
  4. Test Scenarios: Use simulation to verify permission logic works as expected

Managing Complex Inheritance

  1. Visualize Before Changes: Use the inheritance network before modifying groups
  2. Test Impact: Simulate permission checks after inheritance changes
  3. Monitor Depth: Keep inheritance chains reasonable (typically ≤ 3 levels)
  4. Document Decisions: Use group names and Pod configs to document intent

Security Monitoring

  1. Regular Reviews: Periodically review high-risk groups and permissions
  2. Permission Audits: Use the explorer to identify over-privileged accounts
  3. Context Usage: Monitor unused contexts - they may indicate dead code
  4. Simulation Testing: Regularly test edge cases and security boundaries

Last updated: 10/22/2025