๐งช Plugin System Guide โ
Fire Shield v3.0.0 introduces a powerful plugin system that allows you to extend RBAC functionality with custom logic, integrations, and automation.
What are Plugins? โ
Plugins are modular components that hook into Fire Shield's lifecycle to add custom functionality. They can:
- Log permission checks to databases or external services
- Track role changes for audit trails
- Validate permissions against external systems
- Rate limit permission checks to prevent abuse
- Cache results in external systems like Redis
- Send notifications when permissions change
- And much more!
Plugin Interface โ
Every plugin implements the RBACPlugin interface:
import { RBACPlugin } from '@fire-shield/core';
class MyPlugin implements RBACPlugin {
// Plugin name (must be unique)
name: string = 'my-plugin';
// Optional: Called when permission is checked
onPermissionCheck?(event: AuditEvent): Promise<void> | void;
// Optional: Called when a role is created
onRoleAdded?(roleName: string, permissions: string[]): Promise<void> | void;
// Optional: Called when a permission is registered
onPermissionRegistered?(permissionName: string, bit: number): Promise<void> | void;
}Plugin Hooks โ
onPermissionCheck(event) โ
Triggered after every permission check.
Event Type: AuditEvent
interface AuditEvent {
type: 'permission_check';
userId: string;
permission: string;
allowed: boolean;
reason?: string;
context?: Record<string, any>;
timestamp: number;
}onRoleAdded(roleName, permissions) โ
Triggered after a role is created.
Parameters:
roleName: Name of the new rolepermissions: Array of permission strings assigned to the role
onPermissionRegistered(permissionName, bit) โ
Triggered after a permission is registered (bit-based system only).
Parameters:
permissionName: Name of the new permissionbit: Bit value assigned to the permission
Registering Plugins โ
import { RBAC, RBACPlugin } from '@fire-shield/core';
// Create plugin
const plugin = new MyPlugin();
// Create RBAC instance
const rbac = new RBAC();
// Register plugin
await rbac.registerPlugin(plugin);
// Now all hooks will trigger
rbac.hasPermission(user, 'posts:write');Plugin Examples โ
1. Database Audit Plugin โ
Log all permission checks to a database for compliance and auditing.
import { RBACPlugin } from '@fire-shield/core';
export class DatabaseAuditPlugin implements RBACPlugin {
name = 'database-audit';
constructor(private db: any) {}
async onPermissionCheck(event) {
await this.db.insert({
type: 'permission_check',
userId: event.userId,
permission: event.permission,
allowed: event.allowed,
reason: event.reason,
timestamp: event.timestamp
});
}
async onRoleAdded(roleName: string, permissions: string[]) {
await this.db.insert({
type: 'role_added',
roleName,
permissions,
timestamp: Date.now()
});
}
}
// Usage
const plugin = new DatabaseAuditPlugin(database);
await rbac.registerPlugin(plugin);2. Analytics Plugin โ
Track permission check events for analytics and monitoring.
import { RBACPlugin } from '@fire-shield/core';
export class AnalyticsPlugin implements RBACPlugin {
name = 'analytics';
private events: AuditEvent[] = [];
onPermissionCheck(event) {
this.events.push(event);
// Send to analytics service
this.analytics.track('permission_check', {
userId: event.userId,
permission: event.permission,
allowed: event.allowed,
timestamp: Date.now()
});
}
}
// Usage
const plugin = new AnalyticsPlugin(analyticsService);
await rbac.registerPlugin(plugin);3. Rate Limiting Plugin โ
Prevent excessive permission checks to protect against abuse.
import { RBACPlugin } from '@fire-shield/core';
export class RateLimitPlugin implements RBACPlugin {
name = 'rate-limiter';
private limits: Map<string, { count: number; window: number }> = new Map();
onPermissionCheck(event) {
const key = `${event.userId}:${event.permission}`;
const now = Date.now();
const limit = this.limits.get(key) || { count: 0, window: 1000 };
// Reset if window expired
if (now > limit.window) {
limit.count = 0;
limit.window = now + 60000; // 1 minute window
}
limit.count++;
if (limit.count > 100) {
throw new Error(`Rate limit exceeded for ${event.permission}`);
}
this.limits.set(key, limit);
}
}
// Usage
const plugin = new RateLimitPlugin();
await rbac.registerPlugin(plugin);4. Cache Plugin โ
Cache permission check results in external systems like Redis.
import { RBACPlugin } from '@fire-shield/core';
export class RedisCachePlugin implements RBACPlugin {
name = 'redis-cache';
private ttl: number = 60; // 60 seconds
constructor(private redis: any, options: { ttl?: number } = {}) {
this.ttl = options.ttl ?? this.ttl;
}
async onPermissionCheck(event) {
const key = `rbac:${event.userId}:${event.permission}`;
// Try to get from cache
const cached = await this.redis.get(key);
if (cached !== null) {
return;
}
// Set in cache
await this.redis.setex(key, event.allowed, this.ttl);
}
}
// Usage
const plugin = new RedisCachePlugin(redisClient, { ttl: 120 });
await rbac.registerPlugin(plugin);5. Validation Plugin โ
Validate permissions against an external system before allowing access.
import { RBACPlugin } from '@fire-shield/core';
export class ExternalValidationPlugin implements RBACPlugin {
name = 'external-validation';
constructor(private api: any) {}
async onPermissionCheck(event) {
if (!event.allowed) return;
// Validate against external system
const valid = await this.api.validatePermission(
event.userId,
event.permission
);
if (!valid) {
throw new Error(`Permission ${event.permission} not valid for user ${event.userId}`);
}
}
}
// Usage
const plugin = new ExternalValidationPlugin(apiClient);
await rbac.registerPlugin(plugin);6. Notification Plugin โ
Send notifications when roles or permissions change.
import { RBACPlugin } from '@fire-shield/core';
export class NotificationPlugin implements RBACPlugin {
name = 'notification';
constructor(private notifier: any) {}
async onRoleAdded(roleName: string, permissions: string[]) {
await this.notifier.send({
title: 'New Role Created',
message: `Role "${roleName}" was created with ${permissions.length} permissions`
});
}
async onPermissionRegistered(permissionName: string, bit: number) {
await this.notifier.send({
title: 'New Permission Registered',
message: `Permission "${permissionName}" (bit: ${bit}) was registered`
});
}
}
// Usage
const plugin = new NotificationPlugin(emailService);
await rbac.registerPlugin(plugin);Plugin Lifecycle โ
1. Registration โ
await rbac.registerPlugin(plugin);2. Execution โ
Plugins are triggered during RBAC operations:
hasPermission()โonPermissionCheck()createRole()โonRoleAdded()registerPermission()โonPermissionRegistered()
3. Error Handling โ
Plugin errors don't break RBAC operations:
// Plugin throws error
class BuggyPlugin implements RBACPlugin {
name = 'buggy';
onPermissionCheck() {
throw new Error('Oops!');
}
}
// RBAC catches and logs error, continues operation
await rbac.registerPlugin(new BuggyPlugin());
rbac.hasPermission(user, 'posts:write'); // Still works, error logged4. Unregistration โ
await rbac.unregisterPlugin('plugin-name');Best Practices โ
1. Keep Plugins Lightweight โ
Plugins should be fast and efficient to avoid slowing down permission checks.
// Good: Fast operation
onPermissionCheck(event) {
this.logger.log(event); // Fast
}
// Bad: Blocking operation
onPermissionCheck(event) {
await this.database.query('SELECT * FROM huge_table'); // Slow!
}2. Handle Errors Gracefully โ
Always catch and handle errors in your plugins.
onPermissionCheck(event) {
try {
this.api.track(event);
} catch (error) {
console.error('Plugin error:', error);
// Don't throw - let RBAC continue
}
}3. Use Async for I/O Operations โ
Use async/await for database calls, network requests, etc.
async onPermissionCheck(event) {
await this.database.insert(event); // Use async
}4. Test Your Plugins โ
Write tests for your plugins to ensure they work correctly.
describe('MyPlugin', () => {
it('should log permission checks', async () => {
const plugin = new MyPlugin(logger);
const event = {
type: 'permission_check',
userId: '123',
permission: 'posts:read',
allowed: true,
timestamp: Date.now()
};
await plugin.onPermissionCheck(event);
expect(logger.log).toHaveBeenCalledWith(event);
});
});Plugin Architecture โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ RBAC Instance โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ PluginManager โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ Plugin 1 โ โ โ
โ โ โ Plugin 2 โ โ โ
โ โ โ Plugin 3 โ โ โ
โ โ โ ... โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Permission Check Flow:
1. User calls rbac.hasPermission()
2. RBAC checks permission
3. PluginManager.onPermissionCheck() triggered
4. All plugins receive event
5. Result returned to userAdvanced Topics โ
Plugin Dependencies โ
Plugins can depend on other plugins. Execute plugins in specific order:
// Register in order - plugins execute in registration order
await rbac.registerPlugin(cachePlugin);
await rbac.registerPlugin(auditPlugin);
await rbac.registerPlugin(notificationPlugin);Plugin Configuration โ
Pass configuration to plugins:
class ConfigurablePlugin implements RBACPlugin {
name = 'configurable';
private options: any;
constructor(options: any) {
this.options = options;
}
onPermissionCheck(event) {
if (this.options.logDenied && !event.allowed) {
console.log('Denied:', event.permission);
}
}
}
const plugin = new ConfigurablePlugin({ logDenied: true });
await rbac.registerPlugin(plugin);Plugin State Management โ
Plugins can maintain internal state:
class StatefulPlugin implements RBACPlugin {
name = 'stateful';
private state: Map<string, any> = new Map();
onPermissionCheck(event) {
this.state.set(`${event.userId}:${event.permission}`, event);
}
getState() {
return this.state;
}
}Debugging Plugins โ
1. Check Registered Plugins โ
const allPlugins = rbac.getAllPlugins();
console.log('Plugins:', allPlugins.map(p => p.name));2. Get Specific Plugin โ
const plugin = rbac.getPlugin('database-audit');
console.log('Plugin:', plugin);3. Enable Plugin Debugging โ
class DebugPlugin implements RBACPlugin {
name = 'debug';
private enabled: boolean;
constructor(enabled: boolean = false) {
this.enabled = enabled;
}
onPermissionCheck(event) {
if (this.enabled) {
console.log('[DEBUG]', event);
}
}
}
// Enable debug mode
const plugin = new DebugPlugin(true);
await rbac.registerPlugin(plugin);Troubleshooting โ
Plugin Not Triggering โ
Problem: Plugin hooks not being called.
Solution: Check that:
- Plugin is registered:
rbac.getPlugin('plugin-name') - Plugin implements hooks:
onPermissionCheckis defined - RBAC methods are being called:
hasPermission(),createRole(), etc.
Plugin Slows Down Permissions โ
Problem: Permission checks are slow after adding plugin.
Solution:
- Keep plugin logic lightweight
- Use async for I/O operations
- Consider caching plugin results
Plugin Errors Break RBAC โ
Problem: Plugin errors crash the RBAC instance.
Solution: RBAC automatically catches plugin errors. Check console for error messages:
// Plugin errors are logged like:
// Plugin error: <error message>Next Steps โ
- Create your first plugin
- Test it thoroughly
- Share it with the community
- Contribute to Fire Shield plugin ecosystem
Community Plugins โ
Coming soon! The Fire Shield community will have a plugin marketplace where you can:
- Browse plugins created by others
- Share your own plugins
- Rate and review plugins
- Get inspiration for new plugins
Need Help? โ
- Check the API Reference
- Ask questions in GitHub Discussions
- Report bugs in GitHub Issues
Happy plugin building! ๐งช
