Distributed p2p database on IPFS with automatic peer synchronization and conflict-free writes
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Access controllers provide a pluggable system for managing database permissions in OrbitDB. They determine who can write to databases, read from them, and perform administrative operations.
Register and manage access controllers for different permission models.
/**
* Registers a custom access controller
* @param accessController Access controller implementation with type property
* @throws "AccessController does not contain required field 'type'"
* @throws "AccessController '${type}' already added"
*/
function useAccessController(accessController: AccessController): void;
interface AccessController {
/** Unique type identifier for this access controller */
type: string;
/** Checks if an entry can be appended to the log */
canAppend(entry: Entry, identityProvider: IdentityProvider): Promise<boolean>;
/** Optional: grants capabilities to identities */
grant?(capability: string, identity: string): Promise<void>;
/** Optional: revokes capabilities from identities */
revoke?(capability: string, identity: string): Promise<void>;
}Usage Examples:
import { useAccessController } from '@orbitdb/core';
// Register a custom access controller
const MyAccessController = {
type: 'my-custom-ac',
async canAppend(entry, identityProvider) {
// Custom permission logic
return entry.identity === 'allowed-user-id';
}
};
useAccessController(MyAccessController);
// Use the custom access controller
const db = await orbitdb.open('protected-db', {
AccessController: MyAccessController
});The default access controller that uses IPFS for permission management.
interface IPFSAccessController extends AccessController {
type: 'ipfs';
/**
* Checks if an identity can append entries to the database
* @param entry The entry being added
* @param identityProvider Identity provider for verification
* @returns Promise resolving to true if allowed
*/
canAppend(entry: Entry, identityProvider: IdentityProvider): Promise<boolean>;
/**
* Grants write permission to an identity
* @param capability The capability to grant (typically 'write')
* @param identity The identity to grant permission to
*/
grant(capability: string, identity: string): Promise<void>;
/**
* Revokes permission from an identity
* @param capability The capability to revoke
* @param identity The identity to revoke permission from
*/
revoke(capability: string, identity: string): Promise<void>;
}Usage Examples:
import { createOrbitDB, IPFSAccessController } from '@orbitdb/core';
const ipfs = await createHelia();
const orbitdb = await createOrbitDB({ ipfs });
// Create database with IPFS access controller (default)
const db = await orbitdb.open('my-db', {
AccessController: IPFSAccessController
});
// Grant write access to another identity
await db.access.grant('write', 'another-peer-id');
// Check if current identity can write
const canWrite = await db.access.canAppend(someEntry, identityProvider);
console.log('Can write:', canWrite);Enhanced access controller with OrbitDB-specific features.
interface OrbitDBAccessController extends AccessController {
type: 'orbitdb';
/**
* Checks if an identity can append entries with OrbitDB-specific rules
* @param entry The entry being added
* @param identityProvider Identity provider for verification
* @returns Promise resolving to true if allowed
*/
canAppend(entry: Entry, identityProvider: IdentityProvider): Promise<boolean>;
}Usage Examples:
import { createOrbitDB, OrbitDBAccessController } from '@orbitdb/core';
const ipfs = await createHelia();
const orbitdb = await createOrbitDB({ ipfs });
// Create database with OrbitDB access controller
const db = await orbitdb.open('enhanced-db', {
AccessController: OrbitDBAccessController
});
// OrbitDB access controller provides enhanced permission checking
const entry = await createSomeEntry();
const canAppend = await db.access.canAppend(entry, orbitdb.identities);Create custom access controllers for specific permission models.
/**
* Template for custom access controller implementation
*/
interface CustomAccessController extends AccessController {
type: string;
canAppend(entry: Entry, identityProvider: IdentityProvider): Promise<boolean>;
grant?(capability: string, identity: string): Promise<void>;
revoke?(capability: string, identity: string): Promise<void>;
/** Optional: validate access controller configuration */
validate?(config: any): boolean;
/** Optional: serialize access controller state */
serialize?(): any;
/** Optional: deserialize access controller state */
deserialize?(data: any): void;
}Usage Examples:
import { useAccessController } from '@orbitdb/core';
// Role-based access controller
const RoleBasedAccessController = {
type: 'role-based',
roles: new Map(), // identity -> role mapping
permissions: new Map(), // role -> permissions mapping
async canAppend(entry, identityProvider) {
const identity = entry.identity;
const role = this.roles.get(identity);
const permissions = this.permissions.get(role);
return permissions && permissions.includes('write');
},
async grant(capability, identity) {
// Grant role to identity
this.roles.set(identity, capability);
},
async revoke(capability, identity) {
// Remove role from identity
this.roles.delete(identity);
},
// Custom methods
setRole(identity, role) {
this.roles.set(identity, role);
},
defineRole(role, permissions) {
this.permissions.set(role, permissions);
}
};
// Register the custom access controller
useAccessController(RoleBasedAccessController);
// Use in database creation
const db = await orbitdb.open('role-based-db', {
AccessController: RoleBasedAccessController
});
// Configure roles and permissions
db.access.defineRole('admin', ['read', 'write', 'delete']);
db.access.defineRole('user', ['read', 'write']);
db.access.setRole('user123', 'user');
db.access.setRole('admin456', 'admin');Example of a more complex access controller requiring multiple signatures.
const MultiSigAccessController = {
type: 'multisig',
requiredSignatures: 2,
authorizedSigners: new Set(),
pendingOperations: new Map(),
async canAppend(entry, identityProvider) {
const identity = entry.identity;
// Check if signer is authorized
if (!this.authorizedSigners.has(identity)) {
return false;
}
const operationId = this.getOperationId(entry);
const signatures = this.pendingOperations.get(operationId) || new Set();
signatures.add(identity);
if (signatures.size >= this.requiredSignatures) {
// Enough signatures, allow operation
this.pendingOperations.delete(operationId);
return true;
} else {
// Store signature and wait for more
this.pendingOperations.set(operationId, signatures);
return false;
}
},
getOperationId(entry) {
// Create deterministic ID for the operation
return `${entry.payload.op}-${entry.payload.key}-${JSON.stringify(entry.payload.value)}`;
},
addSigner(identity) {
this.authorizedSigners.add(identity);
},
removeSigner(identity) {
this.authorizedSigners.delete(identity);
}
};
useAccessController(MultiSigAccessController);Access controller with time-based permissions.
const TimeBasedAccessController = {
type: 'time-based',
permissions: new Map(), // identity -> { start, end, capabilities }
async canAppend(entry, identityProvider) {
const identity = entry.identity;
const permission = this.permissions.get(identity);
if (!permission) {
return false;
}
const now = Date.now();
const isTimeValid = now >= permission.start && now <= permission.end;
const hasCapability = permission.capabilities.includes('write');
return isTimeValid && hasCapability;
},
async grant(capability, identity) {
const existing = this.permissions.get(identity) || { capabilities: [] };
if (!existing.capabilities.includes(capability)) {
existing.capabilities.push(capability);
this.permissions.set(identity, existing);
}
},
// Custom methods
grantTimeLimited(identity, capabilities, startTime, endTime) {
this.permissions.set(identity, {
start: startTime,
end: endTime,
capabilities: Array.isArray(capabilities) ? capabilities : [capabilities]
});
},
extendPermission(identity, newEndTime) {
const permission = this.permissions.get(identity);
if (permission) {
permission.end = newEndTime;
this.permissions.set(identity, permission);
}
}
};
useAccessController(TimeBasedAccessController);
// Usage
const db = await orbitdb.open('time-limited-db', {
AccessController: TimeBasedAccessController
});
// Grant write access for 1 hour
const oneHour = 60 * 60 * 1000;
db.access.grantTimeLimited(
'temp-user-id',
['read', 'write'],
Date.now(),
Date.now() + oneHour
);Configure access controllers when creating databases.
import { createOrbitDB, IPFSAccessController } from '@orbitdb/core';
const ipfs = await createHelia();
const orbitdb = await createOrbitDB({ ipfs });
// Configure access controller with initial permissions
const adminDb = await orbitdb.open('admin-db', {
AccessController: IPFSAccessController({
write: ['admin-identity-1', 'admin-identity-2'],
admin: ['admin-identity-1']
})
});
// Public read-only database
const publicDb = await orbitdb.open('public-db', {
AccessController: IPFSAccessController({
write: [orbitdb.identity.id], // Only creator can write
read: ['*'] // Anyone can read
})
});Handle access control errors appropriately.
import { createOrbitDB } from '@orbitdb/core';
const ipfs = await createHelia();
const orbitdb = await createOrbitDB({ ipfs });
try {
const db = await orbitdb.open('protected-db');
await db.add('Some data');
} catch (error) {
if (error.message.includes('Access denied')) {
console.error('Permission denied: Cannot write to this database');
console.log('Current identity:', orbitdb.identity.id);
} else if (error.message.includes('AccessController')) {
console.error('Access controller error:', error.message);
} else {
console.error('Unexpected error:', error.message);
}
}
// Check permissions before attempting operations
const db = await orbitdb.open('some-db');
const testEntry = { /* mock entry */ };
const canWrite = await db.access.canAppend(testEntry, orbitdb.identities);
if (canWrite) {
await db.add('Data');
} else {
console.log('No write permission for current identity');
}Access controllers are automatically invoked during database operations:
// When adding data, access controller checks permissions
const db = await orbitdb.open('my-db');
// This triggers access controller's canAppend method
await db.add('New data'); // May throw if access denied
// Same for other database operations
await db.put('key', 'value'); // For KeyValue databases
await db.del('key'); // For deletion operationsAccess controllers provide fine-grained control over database permissions while maintaining the distributed and decentralized nature of OrbitDB.
Install with Tessl CLI
npx tessl i tessl/npm-orbitdb--core