UNPKG

anon-identity

Version:

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

466 lines (462 loc) 19.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ActivityExporter = exports.ExportFormat = void 0; const activity_encryption_1 = require("./activity-encryption"); var ExportFormat; (function (ExportFormat) { ExportFormat["JSON"] = "json"; ExportFormat["CSV"] = "csv"; ExportFormat["PDF"] = "pdf"; ExportFormat["XML"] = "xml"; })(ExportFormat || (exports.ExportFormat = ExportFormat = {})); class ActivityExporter { constructor(searchService) { this.searchService = searchService; this.encryptionService = new activity_encryption_1.ActivityEncryption(); } /** * Export activities based on query and options */ async exportActivities(query, options) { // Get activities based on query const searchResult = await this.searchService.searchActivities({ ...query, ...options.filters, dateRange: options.dateRange || query.dateRange, limit: query.limit || 10000 // Default export limit }); const activities = searchResult.activities; if (activities.length === 0) { throw new Error('No activities found matching the export criteria'); } // Process activities based on options const processedActivities = this.processActivitiesForExport(activities, options); // Generate export data let exportData; let contentType; switch (options.format) { case ExportFormat.JSON: exportData = this.exportToJSON(processedActivities, options); contentType = 'application/json'; break; case ExportFormat.CSV: exportData = this.exportToCSV(processedActivities, options); contentType = 'text/csv'; break; case ExportFormat.PDF: exportData = await this.exportToPDF(processedActivities, options); contentType = 'application/pdf'; break; case ExportFormat.XML: exportData = this.exportToXML(processedActivities, options); contentType = 'application/xml'; break; default: throw new Error(`Unsupported export format: ${options.format}`); } // Handle compression if (options.compression) { exportData = await this.compressData(exportData); contentType = 'application/gzip'; } // Handle encryption if (options.encryption?.enabled && options.encryption.key) { const encryptedData = await this.encryptExportData(exportData, options.encryption.key); exportData = JSON.stringify(encryptedData); contentType = 'application/json'; // Encrypted data is JSON-wrapped } // Generate filename const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = this.generateFilename(options.format, timestamp, options); // Calculate checksum const checksum = this.calculateChecksum(exportData); return { data: exportData, filename, contentType, size: Buffer.isBuffer(exportData) ? exportData.length : Buffer.byteLength(exportData), checksum, metadata: { exportedAt: new Date(), totalRecords: activities.length, format: options.format, encrypted: options.encryption?.enabled || false, compressed: options.compression || false } }; } /** * Generate compliance report */ async generateComplianceReport(agentDID, template, period) { const query = { agentDID, dateRange: period, sortBy: 'timestamp', sortOrder: 'asc', limit: 50000 // High limit for compliance reports }; const activities = await this.searchService.searchActivities(query); // Generate summary statistics const summary = await this.searchService.getActivitySummary(agentDID, this.determinePeriodType(period.start, period.end), period.start); const reportData = this.generateComplianceReportData(activities.activities, summary, template, period); // Export as PDF for compliance reports const options = { format: ExportFormat.PDF, includeDetails: template.includeDetails, includeMetadata: true }; const pdfData = await this.exportToPDF(reportData, options, template); const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = `compliance-report-${agentDID.split(':').pop()}-${timestamp}.pdf`; return { data: pdfData, filename, contentType: 'application/pdf', size: pdfData.length, checksum: this.calculateChecksum(pdfData), metadata: { exportedAt: new Date(), totalRecords: activities.total, format: ExportFormat.PDF, encrypted: false, compressed: false } }; } /** * Create cryptographic audit proof */ async createAuditProof(activities, privateKey) { if (activities.length === 0) { throw new Error('Cannot create audit proof for empty activity set'); } // Create merkle root from activities const merkleRoot = activity_encryption_1.ActivityEncryption.createBatchMerkleRoot(activities); // Get activity identifiers (IDs or IPFS hashes) const activityIdentifiers = activities.map(activity => activity.ipfsHash || activity.id); // Create verification data const sortedActivities = [...activities].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); const verificationData = { totalActivities: activities.length, dateRange: { start: sortedActivities[0].timestamp, end: sortedActivities[sortedActivities.length - 1].timestamp }, checksum: this.calculateChecksum(JSON.stringify(activities.map(a => ({ id: a.id, timestamp: a.timestamp, agentDID: a.agentDID, type: a.type, status: a.status })))) }; // Create signature (simplified - in production would use proper key management) const dataToSign = JSON.stringify({ merkleRoot, timestamp: new Date(), activities: activityIdentifiers, verificationData }); const signature = privateKey ? await this.signData(dataToSign, privateKey) : this.calculateChecksum(dataToSign); // Fallback to checksum if no key return { merkleRoot, timestamp: new Date(), activities: activityIdentifiers, signature, verificationData }; } /** * Verify audit proof integrity */ async verifyAuditProof(proof, activities, publicKey) { const errors = []; try { // Verify merkle root const calculatedMerkleRoot = activity_encryption_1.ActivityEncryption.createBatchMerkleRoot(activities); if (calculatedMerkleRoot !== proof.merkleRoot) { errors.push('Merkle root mismatch - activities may have been tampered with'); } // Verify activity count if (activities.length !== proof.verificationData.totalActivities) { errors.push(`Activity count mismatch: expected ${proof.verificationData.totalActivities}, got ${activities.length}`); } // Verify date range const sortedActivities = [...activities].sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); if (sortedActivities.length > 0) { const actualStart = sortedActivities[0].timestamp; const actualEnd = sortedActivities[sortedActivities.length - 1].timestamp; if (actualStart.getTime() !== proof.verificationData.dateRange.start.getTime()) { errors.push('Start date mismatch in verification data'); } if (actualEnd.getTime() !== proof.verificationData.dateRange.end.getTime()) { errors.push('End date mismatch in verification data'); } } // Verify checksum const calculatedChecksum = this.calculateChecksum(JSON.stringify(activities.map(a => ({ id: a.id, timestamp: a.timestamp, agentDID: a.agentDID, type: a.type, status: a.status })))); if (calculatedChecksum !== proof.verificationData.checksum) { errors.push('Checksum mismatch - activity data may have been modified'); } // Verify signature (simplified) if (publicKey) { const dataToVerify = JSON.stringify({ merkleRoot: proof.merkleRoot, timestamp: proof.timestamp, activities: proof.activities, verificationData: proof.verificationData }); const signatureValid = await this.verifySignature(dataToVerify, proof.signature, publicKey); if (!signatureValid) { errors.push('Digital signature verification failed'); } } } catch (error) { errors.push(`Verification error: ${error}`); } return { valid: errors.length === 0, errors }; } // Private helper methods processActivitiesForExport(activities, options) { return activities.map(activity => { const processed = { id: activity.id, timestamp: activity.timestamp.toISOString(), agentDID: options.anonymize ? this.anonymizeDID(activity.agentDID) : activity.agentDID, parentDID: options.anonymize ? this.anonymizeDID(activity.parentDID) : activity.parentDID, serviceDID: options.anonymize ? this.anonymizeDID(activity.serviceDID) : activity.serviceDID, type: activity.type, status: activity.status, scopes: activity.scopes }; if (options.includeDetails) { processed.details = activity.details; } if (options.includeMetadata) { processed.metadata = { ipfsHash: activity.ipfsHash, signature: activity.signature, duration: activity.duration, sessionId: activity.sessionId }; } return processed; }); } exportToJSON(activities, options) { const exportData = { metadata: { exportedAt: new Date().toISOString(), totalRecords: activities.length, format: 'json', options: { includeDetails: options.includeDetails, includeMetadata: options.includeMetadata, anonymized: options.anonymize } }, activities }; return JSON.stringify(exportData, null, 2); } exportToCSV(activities, options) { if (activities.length === 0) { return 'No data to export'; } // Get all unique keys from activities const allKeys = new Set(); activities.forEach(activity => { Object.keys(activity).forEach(key => { if (key !== 'details' || options.includeDetails) { allKeys.add(key); } }); }); const headers = Array.from(allKeys).sort(); const csvLines = [headers.join(',')]; activities.forEach(activity => { const row = headers.map(header => { const value = activity[header]; if (value === null || value === undefined) { return ''; } if (typeof value === 'object') { return `"${JSON.stringify(value).replace(/"/g, '""')}"`; } if (typeof value === 'string' && (value.includes(',') || value.includes('"') || value.includes('\n'))) { return `"${value.replace(/"/g, '""')}"`; } return String(value); }); csvLines.push(row.join(',')); }); return csvLines.join('\n'); } async exportToPDF(activities, options, template) { // This is a simplified PDF generation // In production, you would use a library like PDFKit or Puppeteer const content = template ? this.generateCompliancePDFContent(activities, template) : this.generateStandardPDFContent(activities, options); // Convert to buffer (simplified - would generate actual PDF) return Buffer.from(content, 'utf8'); } exportToXML(activities, options) { const xmlActivities = activities.map(activity => { const xmlFields = Object.entries(activity) .map(([key, value]) => { if (typeof value === 'object') { return ` <${key}>${JSON.stringify(value)}</${key}>`; } return ` <${key}>${this.escapeXML(String(value))}</${key}>`; }) .join('\n'); return ` <activity>\n${xmlFields}\n </activity>`; }).join('\n'); return `<?xml version="1.0" encoding="UTF-8"?> <export> <metadata> <exportedAt>${new Date().toISOString()}</exportedAt> <totalRecords>${activities.length}</totalRecords> <format>xml</format> </metadata> <activities> ${xmlActivities} </activities> </export>`; } generateComplianceReportData(activities, summary, template, period) { return { reportInfo: { type: template.type, agent: summary.agentDID, period: { start: period.start.toISOString(), end: period.end.toISOString(), type: template.periodType || 'custom' }, generatedAt: new Date().toISOString() }, summary: { totalActivities: summary.totalActivities, errorRate: summary.errorRate, averageDuration: summary.averageDuration, byType: summary.byType, byStatus: summary.byStatus, byService: summary.byService }, activities: template.includeDetails ? activities : [], compliance: { dataRetention: template.dataRetention, privacyCompliance: template.privacyCompliance, auditRequirements: template.auditRequirements } }; } generateStandardPDFContent(activities, options) { const header = `AGENT ACTIVITY EXPORT REPORT Generated: ${new Date().toISOString()} Total Records: ${activities.length} Format: PDF `; const activityContent = activities.slice(0, 1000).map((activity, index) => { return `${index + 1}. ${activity.timestamp} - ${activity.type} (${activity.status}) Agent: ${activity.agentDID} Service: ${activity.serviceDID} Scopes: ${activity.scopes.join(', ')} `; }).join('\n'); return header + '\n\n' + activityContent; } generateCompliancePDFContent(data, template) { return `COMPLIANCE REPORT - ${template.type.toUpperCase()} Report Information: - Agent: ${data.reportInfo.agent} - Period: ${data.reportInfo.period.start} to ${data.reportInfo.period.end} - Generated: ${data.reportInfo.generatedAt} Summary: - Total Activities: ${data.summary.totalActivities} - Error Rate: ${(data.summary.errorRate * 100).toFixed(2)}% - Average Duration: ${data.summary.averageDuration.toFixed(2)}ms Compliance Information: - Data Retention: ${data.compliance.dataRetention} - Privacy Compliance: ${data.compliance.privacyCompliance} - Audit Requirements: ${data.compliance.auditRequirements} Activity Breakdown: ${Object.entries(data.summary.byType).map(([type, count]) => `- ${type}: ${count}`).join('\n')} `; } async compressData(data) { // Simplified compression - would use zlib in production const input = Buffer.isBuffer(data) ? data : Buffer.from(data); return input; // Return as-is for now } async encryptExportData(data, key) { const dataString = Buffer.isBuffer(data) ? data.toString() : data; // Use existing encryption service for consistency const activity = { data: dataString }; return await this.encryptionService.encryptActivity(activity, key); } generateFilename(format, timestamp, options) { const base = `activity-export-${timestamp}`; const suffix = options.anonymize ? '-anonymized' : ''; const compression = options.compression ? '.gz' : ''; return `${base}${suffix}.${format}${compression}`; } calculateChecksum(data) { const crypto = require('crypto'); return crypto.createHash('sha256').update(data).digest('hex'); } async signData(data, privateKey) { // Simplified signing - would use proper cryptographic signing in production return this.calculateChecksum(data + privateKey.toString()); } async verifySignature(data, signature, publicKey) { // Simplified verification - would use proper cryptographic verification in production return signature.length > 0; // Always return true for demo } anonymizeDID(did) { const parts = did.split(':'); if (parts.length >= 3) { const hash = this.calculateChecksum(did).substring(0, 8); return `${parts[0]}:${parts[1]}:anon_${hash}`; } return did; } escapeXML(text) { return text .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&apos;'); } determinePeriodType(start, end) { const diffMs = end.getTime() - start.getTime(); const diffDays = diffMs / (1000 * 60 * 60 * 24); if (diffDays <= 1) return 'hour'; if (diffDays <= 7) return 'day'; if (diffDays <= 31) return 'week'; if (diffDays <= 365) return 'month'; return 'year'; } } exports.ActivityExporter = ActivityExporter; //# sourceMappingURL=activity-exporter.js.map