@iota-big3/sdk-security
Version:
Advanced security features including zero trust, quantum-safe crypto, and ML threat detection
298 lines • 11.1 kB
JavaScript
"use strict";
/**
* @iota-big3/sdk-security - Clean Access Control
* RBAC and ABAC implementations
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AccessControl = void 0;
const adapters_1 = require("./adapters");
class RBACManager {
constructor(integrations) {
this.roles = new Map();
this.userRoles = new Map();
this.integrations = integrations;
// Create adapters
if (integrations.database) {
this.database = (0, adapters_1.createDatabaseAdapter)(integrations.database);
}
if (integrations.eventBus) {
this.eventBus = (0, adapters_1.createEventsAdapter)(integrations.eventBus);
}
}
async createRole(role) {
// Store in memory
this.roles.set(role.id, role);
// Persist to database if available
if (this.database) {
await this.database.insert('security_roles', {
id: role.id,
name: role.name,
permissions: JSON.stringify(role.permissions),
created_at: new Date(),
updated_at: new Date()
});
}
// Emit event if event bus available
if (this.eventBus) {
this.eventBus.emit('security:role.created', {
roleId: role.id,
roleName: role.name,
timestamp: Date.now()
});
}
// Invalidate cache
if (this.integrations.cache) {
await this.integrations.cache.del(`roles:*`);
}
return true;
}
async assignRole(userId, roleId) {
if (!this.userRoles.has(userId)) {
this.userRoles.set(userId, new Set());
}
this.userRoles.get(userId).add(roleId);
// Persist to database
if (this.database) {
await this.database.insert('security_user_roles', {
user_id: userId,
role_id: roleId,
assigned_at: new Date()
});
}
// Emit event
if (this.eventBus) {
this.eventBus.emit('security:role.assigned', {
userId,
roleId,
timestamp: Date.now()
});
}
// Invalidate user cache
if (this.integrations.cache) {
await this.integrations.cache.del(`userRoles:${userId}`);
}
return true;
}
async checkPermission(userId, resource, action) {
const cacheKey = `access:${userId}:${resource}:${action}`;
// Check cache first
if (this.integrations.cache) {
const cached = await this.integrations.cache.get(cacheKey);
if (cached !== null) {
return cached;
}
}
// Get user roles from database if available
let userRoleIds = this.userRoles.get(userId);
if (!userRoleIds && this.database) {
const dbRoles = await this.database.query('SELECT role_id FROM security_user_roles WHERE user_id = ?', [userId]);
if (dbRoles.rows && dbRoles.rows.length > 0) {
userRoleIds = new Set(dbRoles.rows.map((r) => r.role_id));
this.userRoles.set(userId, userRoleIds);
}
}
if (!userRoleIds)
return false;
let allowed = false;
for (const roleId of userRoleIds) {
const role = await this.getRole(roleId);
if (!role)
continue;
for (const permission of role.permissions) {
if (this.matchResource(permission.resource, resource) &&
permission.actions.includes(action)) { // Changed from action to actions.includes
if (permission.effect === 'allow') {
allowed = true;
}
else if (permission.effect === 'deny') {
allowed = false;
break;
}
}
}
}
// Cache the result
if (this.integrations.cache) {
await this.integrations.cache.set(cacheKey, allowed, 300); // 5 minutes
}
// Emit permission check event
if (this.eventBus) {
this.eventBus.emit(allowed ? 'security:permission.granted' : 'security:permission.denied', {
subject: userId,
resource,
action,
timestamp: Date.now(),
...(allowed ? {} : { reason: 'No matching permission' })
});
}
return allowed;
}
async getRole(roleId) {
// Check memory first
let role = this.roles.get(roleId);
// Try database if not in memory
if (!role && this.database) {
const cacheKey = `roles:${roleId}`;
// Check cache
if (this.integrations.cache) {
const cached = await this.integrations.cache.get(cacheKey);
if (cached) {
role = cached;
this.roles.set(roleId, role);
return role;
}
}
// Load from database
const dbRole = await this.database.query('SELECT * FROM security_roles WHERE id = ?', [roleId]);
if (dbRole.rows && dbRole.rows.length > 0) {
const row = dbRole.rows[0];
role = {
id: row.id,
name: row.name,
permissions: JSON.parse(row.permissions)
};
this.roles.set(roleId, role);
// Cache it
if (this.integrations.cache) {
await this.integrations.cache.set(cacheKey, role, 3600); // 1 hour
}
}
}
return role;
}
matchResource(pattern, resource) {
// Simple wildcard matching
if (pattern.endsWith('*')) {
return resource.startsWith(pattern.slice(0, -1));
}
return pattern === resource;
}
}
class ABACManager {
constructor(integrations) {
this.policies = new Map();
this.attributeProviders = new Map();
this.integrations = integrations;
}
async loadPolicy(name, policy) {
this.policies.set(name, policy);
// Persist to database
if (this.integrations.database) {
await this.integrations.database.insert('security_policies', {
id: name,
name,
type: 'abac',
policy_data: policy,
enabled: true,
created_at: new Date(),
updated_at: new Date()
});
}
}
registerAttributeProvider(name, provider) {
this.attributeProviders.set(name, provider);
}
async evaluate(request) {
// Collect attributes from providers
const attributes = {};
for (const [name, provider] of this.attributeProviders) {
try {
const attrs = await provider(request);
Object.assign(attributes, attrs);
}
catch (error) {
if (this.integrations.logger) {
this.integrations.logger.error(`Attribute provider ${name} failed`, { error });
}
}
}
// In a real implementation, this would use OPA or similar policy engine
// For now, simple mock evaluation
const input = {
subject: request.subject,
action: request.action,
resource: request.resource,
attributes
};
// Check if we have working hours policy and it's during working hours
if (attributes.currentHour !== undefined) {
if (attributes.currentHour >= 8 && attributes.currentHour <= 17) {
return { allowed: true, reason: 'Access allowed during working hours' };
}
}
// Check role-based policy
if (input.subject?.role === 'teacher' && input.action === 'read') {
return { allowed: true, reason: 'Teacher role allows read access' };
}
return { allowed: false, reason: 'No matching policy' };
}
}
class AccessControl extends EventEmitter {
constructor(_rbacConfig, _abacConfig, _logger, integrations = {}) {
super();
// If logger is passed as third parameter (backward compatibility)
if (_logger && !integrations.logger) {
integrations.logger = _logger;
}
this.integrations = integrations;
this.rbac = new RBACManager(integrations);
this.abac = new ABACManager(integrations);
// Listen to events from other SDKs if eventBus is available
if (integrations.eventBus) {
this.setupEventListeners();
}
}
setupEventListeners() {
if (!this.integrations.eventBus)
return;
// Listen for user deletion to clean up roles
this.integrations.eventBus.on('auth:user.deleted', async (event) => {
const userId = event.userId;
if (this.integrations.logger) {
this.integrations.logger.info('Cleaning up user roles for deleted user', { userId });
}
// Remove from database
if (this.integrations.database) {
await this.integrations.database.query('DELETE FROM security_user_roles WHERE user_id = ?', [userId]);
}
// Clear cache
if (this.integrations.cache) {
await this.integrations.cache.del(`userRoles:${userId}`);
await this.integrations.cache.del(`access:${userId}:*`);
}
});
}
async checkAccess(request) {
const startTime = Date.now();
// First check RBAC
const rbacAllowed = await this.rbac.checkPermission(request.userId, request.resource, request.action);
if (!rbacAllowed) {
const decision = { allowed: false, reason: 'RBAC permission denied' };
// Log decision
if (this.integrations.logger) {
this.integrations.logger.info('Access denied by RBAC', {
...request,
duration: Date.now() - startTime
});
}
return decision;
}
// Then check ABAC
const abacDecision = await this.abac.evaluate({
subject: { userId: request.userId },
resource: request.resource,
action: request.action
});
// Log decision
if (this.integrations.logger) {
this.integrations.logger.info(`Access ${abacDecision.allowed ? 'granted' : 'denied'}`, {
...request,
reason: abacDecision.reason,
duration: Date.now() - startTime
});
}
return abacDecision;
}
}
exports.AccessControl = AccessControl;
//# sourceMappingURL=access-control.js.map