UNPKG

anon-identity

Version:

Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure

376 lines 16.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CascadingRevocationManager = void 0; const message_factory_1 = require("../communication/message-factory"); const activity_logger_1 = require("../activity/activity-logger"); const types_1 = require("../activity/types"); class CascadingRevocationManager { constructor(agentManager, chainValidator, communicationManager, activityLogger) { this.agentManager = agentManager; this.chainValidator = chainValidator; this.communicationManager = communicationManager; this.activityLogger = activityLogger; this.revocationAudit = new Map(); this.activeRevocations = new Set(); } /** * Revokes an agent and optionally cascades to all sub-agents */ async revokeAgent(request) { const auditId = this.generateAuditId(); const result = { success: false, revokedAgents: [], failedRevocations: [], notificationsSent: 0, auditEntries: 0 }; // Prevent concurrent revocations of the same agent if (this.activeRevocations.has(request.targetAgentDID)) { throw new Error(`Revocation already in progress for agent ${request.targetAgentDID}`); } this.activeRevocations.add(request.targetAgentDID); try { // Create audit entry const auditEntry = { id: auditId, targetAgentDID: request.targetAgentDID, revokedBy: request.revokedBy, reason: request.reason, timestamp: request.timestamp, cascading: request.cascading, serviceDID: request.serviceDID, effectiveDate: request.effectiveDate || request.timestamp, childRevocations: [], notificationsSent: [], status: 'pending' }; this.revocationAudit.set(auditId, auditEntry); result.auditEntries++; // Log revocation initiation await this.activityLogger.logActivity((0, activity_logger_1.createActivity)(types_1.ActivityType.REVOCATION, { agentDID: request.targetAgentDID, parentDID: '', serviceDID: request.serviceDID || 'all', status: types_1.ActivityStatus.SUCCESS, scopes: [], details: { action: 'revocation_initiated', reason: request.reason, revokedBy: request.revokedBy, cascading: request.cascading, auditId } })); // Get the target agent const targetAgent = this.agentManager.getAgent(request.targetAgentDID); if (!targetAgent) { auditEntry.status = 'failed'; result.failedRevocations.push({ agentDID: request.targetAgentDID, error: 'Agent not found' }); return result; } // Revoke the target agent const revokeResult = await this.performSingleRevocation(targetAgent, request, auditEntry); if (revokeResult.success) { result.revokedAgents.push(request.targetAgentDID); } else { result.failedRevocations.push({ agentDID: request.targetAgentDID, error: revokeResult.error || 'Unknown error' }); } // If cascading is enabled, find and revoke all sub-agents if (request.cascading) { const subAgents = await this.findSubAgents(request.targetAgentDID); for (const subAgent of subAgents) { try { const subRevocationRequest = { ...request, targetAgentDID: subAgent.did, reason: `Cascaded from parent revocation: ${request.reason}` }; const subResult = await this.performSingleRevocation(subAgent, subRevocationRequest, auditEntry); if (subResult.success) { result.revokedAgents.push(subAgent.did); auditEntry.childRevocations.push(subAgent.did); } else { result.failedRevocations.push({ agentDID: subAgent.did, error: subResult.error || 'Unknown error' }); } } catch (error) { result.failedRevocations.push({ agentDID: subAgent.did, error: error instanceof Error ? error.message : 'Unknown error' }); } } } // Send notifications result.notificationsSent = await this.sendRevocationNotifications(request, result.revokedAgents, auditEntry); // Update audit status if (result.failedRevocations.length === 0) { auditEntry.status = 'completed'; result.success = true; } else if (result.revokedAgents.length > 0) { auditEntry.status = 'partial'; result.success = true; // Partial success } else { auditEntry.status = 'failed'; } // Log completion await this.activityLogger.logActivity((0, activity_logger_1.createActivity)(types_1.ActivityType.REVOCATION, { agentDID: request.targetAgentDID, parentDID: '', serviceDID: request.serviceDID || 'all', status: result.success ? types_1.ActivityStatus.SUCCESS : types_1.ActivityStatus.FAILED, scopes: [], details: { action: 'revocation_completed', auditId, revokedCount: result.revokedAgents.length, failedCount: result.failedRevocations.length, notificationsSent: result.notificationsSent } })); return result; } finally { this.activeRevocations.delete(request.targetAgentDID); } } /** * Performs revocation of a single agent */ async performSingleRevocation(agent, request, auditEntry) { try { // Mark agent as revoked (in practice, this might involve updating database records) // For now, we'll remove the agent's delegation capabilities // Revoke all delegation credentials for this agent const credentials = this.agentManager.getDelegationCredentials(agent.did); if (request.serviceDID) { // Service-specific revocation const filteredCredentials = credentials.filter(cred => !cred.credentialSubject.services[request.serviceDID]); // In a real implementation, you would update the credential storage // For now, we'll use the existing revocation method const revoked = this.agentManager.revokeServiceAccess(agent.did, request.serviceDID); if (!revoked) { return { success: false, error: 'Failed to revoke service access' }; } } else { // Complete agent revocation const revoked = this.agentManager.deleteAgent(agent.did); if (!revoked) { return { success: false, error: 'Failed to delete agent' }; } } return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Finds all sub-agents of a given agent */ async findSubAgents(parentAgentDID) { const allAgents = this.agentManager.listAgents(parentAgentDID); const subAgents = []; // Direct children subAgents.push(...allAgents); // Recursive search for deeper sub-agents for (const agent of allAgents) { const deeperSubAgents = await this.findSubAgents(agent.did); subAgents.push(...deeperSubAgents); } return subAgents; } /** * Sends revocation notifications to relevant parties */ async sendRevocationNotifications(request, revokedAgents, auditEntry) { let notificationsSent = 0; for (const agentDID of revokedAgents) { try { // Find agents that might need to be notified about this revocation const notificationTargets = await this.findNotificationTargets(agentDID); for (const targetDID of notificationTargets) { try { const message = message_factory_1.MessageFactory.createRevocationNotification(request.revokedBy, targetDID, agentDID, request.reason, { effectiveDate: request.effectiveDate, cascading: request.cascading, metadata: { auditId: auditEntry.id, serviceDID: request.serviceDID } }); await this.communicationManager.sendMessage(message); auditEntry.notificationsSent.push(targetDID); notificationsSent++; } catch (error) { // Log notification failure but don't fail the entire operation await this.activityLogger.logActivity((0, activity_logger_1.createActivity)(types_1.ActivityType.COMMUNICATION, { agentDID: request.revokedBy, parentDID: '', serviceDID: 'notification', status: types_1.ActivityStatus.FAILED, scopes: [], details: { action: 'revocation_notification_failed', targetDID, error: error instanceof Error ? error.message : 'Unknown error' } })); } } } catch (error) { // Log error finding notification targets console.error(`Failed to find notification targets for ${agentDID}:`, error); } } return notificationsSent; } /** * Finds agents that should be notified about a revocation */ async findNotificationTargets(revokedAgentDID) { const targets = new Set(); try { // Find parent agents in the delegation chain const agent = this.agentManager.getAgent(revokedAgentDID); if (agent) { // Add parent to notification list targets.add(agent.parentDID); // Find root of the chain for notification let currentDID = agent.parentDID; const visited = new Set(); while (currentDID && !visited.has(currentDID)) { visited.add(currentDID); const parentAgent = this.agentManager.getAgent(currentDID); if (parentAgent) { targets.add(parentAgent.did); currentDID = parentAgent.parentDID; } else { // Reached a user DID targets.add(currentDID); break; } } } // Find peer agents that might be affected // (This is application-specific logic that could be extended) } catch (error) { console.error('Error finding notification targets:', error); } return Array.from(targets); } /** * Gets revocation audit trail for an agent */ getRevocationAudit(agentDID) { const entries = Array.from(this.revocationAudit.values()); if (agentDID) { return entries.filter(entry => entry.targetAgentDID === agentDID || entry.childRevocations.includes(agentDID)); } return entries; } /** * Checks if an agent has been revoked */ isAgentRevoked(agentDID, serviceDID) { const auditEntries = this.getRevocationAudit(agentDID); return auditEntries.some(entry => { if (entry.status !== 'completed' && entry.status !== 'partial') { return false; } // Check if revocation is for this specific service or global if (serviceDID && entry.serviceDID && entry.serviceDID !== serviceDID) { return false; } return entry.targetAgentDID === agentDID || entry.childRevocations.includes(agentDID); }); } /** * Gets revocation statistics */ getRevocationStats() { const entries = Array.from(this.revocationAudit.values()); const cascadingCount = entries.filter(e => e.cascading).length; const serviceSpecificCount = entries.filter(e => e.serviceDID).length; const totalChildRevocations = entries.reduce((sum, e) => sum + e.childRevocations.length, 0); const totalNotifications = entries.reduce((sum, e) => sum + e.notificationsSent.length, 0); return { totalRevocations: entries.length, cascadingRevocations: cascadingCount, serviceSpecificRevocations: serviceSpecificCount, averageChildRevocations: entries.length > 0 ? totalChildRevocations / entries.length : 0, notificationsSent: totalNotifications }; } /** * Purges old audit entries */ purgeOldAuditEntries(olderThan) { const initialSize = this.revocationAudit.size; for (const [id, entry] of this.revocationAudit.entries()) { if (entry.timestamp < olderThan) { this.revocationAudit.delete(id); } } return initialSize - this.revocationAudit.size; } /** * Exports audit trail for compliance */ exportAuditTrail(format = 'json') { const entries = Array.from(this.revocationAudit.values()); if (format === 'csv') { const headers = [ 'id', 'targetAgentDID', 'revokedBy', 'reason', 'timestamp', 'cascading', 'serviceDID', 'effectiveDate', 'childRevocations', 'notificationsSent', 'status' ]; const rows = entries.map(entry => [ entry.id, entry.targetAgentDID, entry.revokedBy, entry.reason, entry.timestamp.toISOString(), entry.cascading.toString(), entry.serviceDID || '', entry.effectiveDate.toISOString(), entry.childRevocations.length.toString(), entry.notificationsSent.length.toString(), entry.status ]); return [headers, ...rows].map(row => row.join(',')).join('\n'); } return JSON.stringify(entries, null, 2); } generateAuditId() { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2); return `rev_${timestamp}_${random}`; } } exports.CascadingRevocationManager = CascadingRevocationManager; //# sourceMappingURL=cascading-revocation-manager.js.map