UNPKG

@iota-big3/sdk-security

Version:

Advanced security features including zero trust, quantum-safe crypto, and ML threat detection

298 lines 11.1 kB
"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