Policy
SwirlDB uses a JSON-based policy engine for fine-grained access control. Policies are defined as configuration rules with priority-based evaluation, path pattern matching with variable substitution, and support for role-based and custom authorization logic.
Policy Architecture
JSON-Based Configuration
All policies are defined in a JSON configuration file with these core concepts:
- Actor: Who is performing the operation (User, App, Server, Anonymous)
- Action: What operation is being performed (Read, Write, Subscribe)
- Path Pattern: Which data is being accessed (supports wildcards and variables)
- Effect: Allow or Deny
- Priority: Lower numbers = higher priority (first match wins)
Configuration Structure
Basic Example
A minimal policy configuration:
{
"policies": {
"rules": [
{
"priority": 10,
"actor": { "type": "User" },
"action": "Read",
"path_pattern": "/user/{actor.id}/**",
"effect": "Allow"
},
{
"priority": 100,
"actor": { "type": "Any" },
"action": "Read",
"path_pattern": "/**",
"effect": "Deny"
}
]
}
} In this example:
- Users can read their own data under
/user/{actor.id}/** - All other reads are denied (priority 100 runs after priority 10)
Rule Structure
Each policy rule has these fields:
{
"_description": "Optional description for documentation",
"priority": 10, // Lower = higher priority
"actor": {...}, // Actor pattern
"action": "Read" | "Write" | "Subscribe",
"path_pattern": "/path/**", // Path with wildcards
"effect": "Allow" | "Deny"
} Actor Patterns
User Actor
Match users, optionally filtering by ID or role:
// Any user
{ "type": "User" }
// Specific user ID
{ "type": "User", "id": "alice" }
// Users with specific role
{ "type": "User", "role": "admin" }
// Specific user with specific role
{ "type": "User", "id": "alice", "role": "editor" } App and Server Actors
// Any app
{ "type": "App" }
// Specific app
{ "type": "App", "id": "mobile-client" }
// Any server instance
{ "type": "Server" }
// Specific server
{ "type": "Server", "id": "sync-coordinator" } Special Actors
// Anonymous (no JWT)
{ "type": "Anonymous" }
// Any actor (matches all)
{ "type": "Any" } Path Patterns
Wildcard Matching
Path patterns support two types of wildcards:
*— Matches exactly one path segment**— Matches zero or more path segments
// Exact match
"/config/version"
// Single wildcard (any organization)
"/org/*/members"
// Multi wildcard (any depth under /user)
"/user/**"
// Combined
"/org/*/teams/*/members" Variable Substitution
Use actor attributes in path patterns:
// Match user's own data
"/user/{actor.id}/**"
// Match organization data
"/org/{actor.org_id}/**"
// Match team data
"/team/{actor.team_id}/**"
// Match app-specific data
"/app/{actor.app_id}/**"
// Role-based path
"/access/{actor.role}/**" How it works: The engine substitutes {actor.id} with the actual actor's ID before matching.
Common Patterns
User Data Isolation
Users can only access their own data:
{
"policies": {
"rules": [
{
"priority": 10,
"actor": { "type": "User" },
"action": "Read",
"path_pattern": "/user/{actor.id}/**",
"effect": "Allow"
},
{
"priority": 10,
"actor": { "type": "User" },
"action": "Write",
"path_pattern": "/user/{actor.id}/**",
"effect": "Allow"
},
{
"priority": 100,
"actor": { "type": "Any" },
"action": "Read",
"path_pattern": "/**",
"effect": "Deny"
},
{
"priority": 100,
"actor": { "type": "Any" },
"action": "Write",
"path_pattern": "/**",
"effect": "Deny"
}
]
}
} Role-Based Access Control
Different permissions per role:
{
"policies": {
"rules": [
{
"_description": "Admins can do anything",
"priority": 1,
"actor": { "type": "User", "role": "admin" },
"action": "Write",
"path_pattern": "/**",
"effect": "Allow"
},
{
"_description": "Editors can write documents",
"priority": 10,
"actor": { "type": "User", "role": "editor" },
"action": "Write",
"path_pattern": "/documents/**",
"effect": "Allow"
},
{
"_description": "All users can read",
"priority": 20,
"actor": { "type": "User" },
"action": "Read",
"path_pattern": "/**",
"effect": "Allow"
},
{
"_description": "Default deny",
"priority": 1000,
"actor": { "type": "Any" },
"action": "Write",
"path_pattern": "/**",
"effect": "Deny"
}
]
}
} Organization Isolation (Multi-Tenancy)
Users can only access their organization's data:
{
"policies": {
"rules": [
{
"priority": 10,
"actor": { "type": "User" },
"action": "Read",
"path_pattern": "/org/{actor.org_id}/**",
"effect": "Allow"
},
{
"priority": 10,
"actor": { "type": "User" },
"action": "Write",
"path_pattern": "/org/{actor.org_id}/**",
"effect": "Allow"
},
{
"priority": 100,
"actor": { "type": "Any" },
"action": "Read",
"path_pattern": "/**",
"effect": "Deny"
}
]
}
} Public Read, Private Write
Anyone can read, only authenticated users can write:
{
"policies": {
"rules": [
{
"priority": 10,
"actor": { "type": "Any" },
"action": "Read",
"path_pattern": "/**",
"effect": "Allow"
},
{
"priority": 10,
"actor": { "type": "User" },
"action": "Write",
"path_pattern": "/**",
"effect": "Allow"
},
{
"priority": 100,
"actor": { "type": "Anonymous" },
"action": "Write",
"path_pattern": "/**",
"effect": "Deny"
}
]
}
} Field-Level Permissions
Different permissions for different fields:
{
"policies": {
"rules": [
{
"_description": "Public profile fields - anyone can read",
"priority": 5,
"actor": { "type": "Any" },
"action": "Read",
"path_pattern": "/user/*/name",
"effect": "Allow"
},
{
"_description": "Private fields - only owner",
"priority": 10,
"actor": { "type": "User" },
"action": "Read",
"path_pattern": "/user/{actor.id}/email",
"effect": "Allow"
},
{
"_description": "Deny all other email access",
"priority": 100,
"actor": { "type": "Any" },
"action": "Read",
"path_pattern": "/user/*/email",
"effect": "Deny"
}
]
}
} Loading Configuration
Rust
use swirldb_core::policy::PolicyEngine;
// From JSON string
let config_json = std::fs::read_to_string("policy.json")?;
let engine = PolicyEngine::from_json(&config_json)?;
// Evaluate a policy
let decision = engine.evaluate(&actor, Action::Read, "/user/alice/prefs");
if decision.is_allowed() {
println!("Access granted!");
} else {
println!("Access denied!");
} TypeScript/Browser
// Load configuration
const config = await fetch('/policy.json').then(r => r.json());
const db = await SwirlDB.withConfig(config);
// Access is automatically checked on all operations
try {
const data = db.data.user.alice.prefs.$value;
console.log('Access granted:', data);
} catch (error) {
console.error('Access denied:', error);
} Actor Creation
From JWT Claims
Typically, actors are created from JWT tokens on the server:
use swirldb_core::policy::Actor;
use std::collections::HashMap;
// Extract claims from JWT
let mut claims = HashMap::new();
claims.insert("type".to_string(), json!("user"));
claims.insert("sub".to_string(), json!("alice"));
claims.insert("org_id".to_string(), json!("acme-corp"));
claims.insert("role".to_string(), json!("admin"));
let actor = Actor::from_jwt_claims(claims)?;
// Now use actor in policy evaluation
let decision = engine.evaluate(&actor, Action::Write, "/org/acme-corp/docs"); Anonymous Actor
For unauthenticated requests:
let actor = Actor::anonymous();
// This actor will only match:
// { "type": "Anonymous" }
// { "type": "Any" } Best Practices
Priority Guidelines
- 1-10: Highest priority, admin overrides
- 10-50: Specific permissions (user/org/role-based)
- 100-500: General permissions
- 1000+: Default deny rules
Always use default deny at the end with high priority number.
Path Organization
- Use consistent path hierarchies:
/user/{id}/resource - Put tenant/org ID early in path:
/org/{org_id}/data - Group related data:
/team/{id}/projects/** - Use descriptive segments:
/documents/public/**
Security
- Deny by default: Always end with a catch-all deny rule
- Server-side enforcement: Never trust client-side policy checks
- Validate JWT: Always verify token signature before creating actor
- Test thoroughly: Unit test all policy rules with various actors
Full Configuration Example
{
"policies": {
"rules": [
{
"_description": "Admins can do everything",
"priority": 1,
"actor": { "type": "User", "role": "admin" },
"action": "Write",
"path_pattern": "/**",
"effect": "Allow"
},
{
"_description": "Admins can read everything",
"priority": 1,
"actor": { "type": "User", "role": "admin" },
"action": "Read",
"path_pattern": "/**",
"effect": "Allow"
},
{
"_description": "Users can read their own data",
"priority": 10,
"actor": { "type": "User" },
"action": "Read",
"path_pattern": "/user/{actor.id}/**",
"effect": "Allow"
},
{
"_description": "Users can write their own data",
"priority": 10,
"actor": { "type": "User" },
"action": "Write",
"path_pattern": "/user/{actor.id}/**",
"effect": "Allow"
},
{
"_description": "Users can read org data",
"priority": 20,
"actor": { "type": "User" },
"action": "Read",
"path_pattern": "/org/{actor.org_id}/**",
"effect": "Allow"
},
{
"_description": "Editors can write org documents",
"priority": 20,
"actor": { "type": "User", "role": "editor" },
"action": "Write",
"path_pattern": "/org/{actor.org_id}/documents/**",
"effect": "Allow"
},
{
"_description": "Public data readable by all",
"priority": 50,
"actor": { "type": "Any" },
"action": "Read",
"path_pattern": "/public/**",
"effect": "Allow"
},
{
"_description": "Default deny reads",
"priority": 1000,
"actor": { "type": "Any" },
"action": "Read",
"path_pattern": "/**",
"effect": "Deny"
},
{
"_description": "Default deny writes",
"priority": 1000,
"actor": { "type": "Any" },
"action": "Write",
"path_pattern": "/**",
"effect": "Deny"
}
]
}
}