UNPKG

anon-identity

Version:

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

422 lines 16.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ActivityArchivalService = void 0; const activity_exporter_1 = require("./activity-exporter"); class ActivityArchivalService { constructor(searchService, exporter, index, ipfsStorage, config = {}) { this.policies = new Map(); this.rules = []; this.archivalRecords = new Map(); this.gdprRecords = new Map(); this.searchService = searchService; this.exporter = exporter; this.index = index; this.ipfsStorage = ipfsStorage; this.config = { defaultRetentionDays: 90, maxRetentionDays: 2555, // 7 years for compliance complianceMode: false, auditLogging: true, autoArchival: true, notificationEnabled: true, warningDays: 30, ...config }; this.initializeDefaultPolicies(); this.startArchivalScheduler(); } /** * Add archival policy */ addPolicy(policy) { this.policies.set(policy.id, policy); } /** * Add archival rule */ addRule(rule) { this.rules.push(rule); this.rules.sort((a, b) => b.priority - a.priority); // Higher priority first } /** * Archive activities based on criteria */ async archiveActivities(query, policyId, options = {}) { const policy = this.policies.get(policyId); if (!policy) { throw new Error(`Archival policy not found: ${policyId}`); } // Get activities to archive const searchResult = await this.searchService.searchActivities({ ...query, limit: 50000 // High limit for archival }); if (searchResult.activities.length === 0) { throw new Error('No activities found matching archival criteria'); } const activities = searchResult.activities; // Export activities for archival const exportResult = await this.exporter.exportActivities(query, { format: activity_exporter_1.ExportFormat.JSON, includeDetails: true, includeMetadata: true, compression: policy.compressionEnabled, encryption: policy.encryptionRequired ? { enabled: true, key: options.encryptionKey } : undefined }); // Store archived data let location; if (this.ipfsStorage) { // Store in IPFS const ipfsResult = await this.storeInIPFS(exportResult.data); location = ipfsResult; } else { // Store locally (simplified) location = `local://archive-${Date.now()}.json`; } // Create audit proof if requested let proof; if (options.createProof) { proof = await this.exporter.createAuditProof(activities, options.encryptionKey); } else { // Create minimal proof proof = await this.exporter.createAuditProof(activities); } // Create archival record const archivalRecord = { id: this.generateId('archive'), originalActivityIds: activities.map(a => a.id), archiveDate: new Date(), policy: policyId, location, checksum: exportResult.checksum, proof, metadata: { totalActivities: activities.length, dateRange: { start: activities[activities.length - 1].timestamp, // Oldest first end: activities[0].timestamp // Newest first }, format: exportResult.metadata.format, compressed: exportResult.metadata.compressed, encrypted: exportResult.metadata.encrypted } }; this.archivalRecords.set(archivalRecord.id, archivalRecord); // Remove original activities if requested if (options.deleteOriginals) { await this.deleteActivitiesFromIndex(activities.map(a => a.id)); } // Log archival action if audit logging is enabled if (this.config.auditLogging) { console.log(`Archived ${activities.length} activities using policy ${policyId}`); } return archivalRecord; } /** * Restore activities from archive */ async restoreActivities(archivalRecordId) { const record = this.archivalRecords.get(archivalRecordId); if (!record) { throw new Error(`Archival record not found: ${archivalRecordId}`); } // Retrieve archived data let archivedData; if (record.location.startsWith('ipfs://') || record.location.length === 46) { // IPFS storage if (!this.ipfsStorage) { throw new Error('IPFS storage not available for restoration'); } archivedData = await this.retrieveFromIPFS(record.location); } else { // Local storage (simplified) throw new Error('Local archive restoration not implemented'); } // Parse activities let activities; try { const parsedData = JSON.parse(archivedData.toString()); if (record.metadata.encrypted) { throw new Error('Cannot restore encrypted archive without decryption key'); } activities = parsedData.activities || parsedData; } catch (error) { throw new Error(`Failed to parse archived data: ${error}`); } // Verify integrity using audit proof const verification = await this.exporter.verifyAuditProof(record.proof, activities); if (!verification.valid) { throw new Error(`Archive integrity verification failed: ${verification.errors.join(', ')}`); } // Log restoration if audit logging is enabled if (this.config.auditLogging) { console.log(`Restored ${activities.length} activities from archive ${archivalRecordId}`); } return activities; } /** * Delete expired activities based on retention policies */ async deleteExpiredActivities() { const results = { deleted: 0, archived: 0, warned: 0 }; const now = new Date(); // Process each active rule for (const rule of this.rules.filter(r => r.active)) { const policy = this.policies.get(rule.policy.id); if (!policy) continue; const cutoffDate = new Date(now.getTime() - policy.retentionPeriod * 24 * 60 * 60 * 1000); const warningDate = new Date(now.getTime() - (policy.retentionPeriod - this.config.warningDays) * 24 * 60 * 60 * 1000); const query = { agentDID: rule.agentDID, parentDID: rule.parentDID, serviceDID: rule.serviceDID, types: rule.activityTypes, dateRange: { start: new Date(0), // Beginning of time end: cutoffDate }, limit: 10000 }; const expiredActivities = await this.searchService.searchActivities(query); if (expiredActivities.activities.length > 0) { if (policy.autoDelete) { // Delete immediately await this.deleteActivitiesFromIndex(expiredActivities.activities.map(a => a.id)); results.deleted += expiredActivities.activities.length; } else { // Archive before deletion await this.archiveActivities(query, policy.id, { deleteOriginals: true }); results.archived += expiredActivities.activities.length; } } // Check for activities approaching expiration if (this.config.notificationEnabled) { const warningQuery = { ...query, dateRange: { start: new Date(0), end: warningDate } }; const warningActivities = await this.searchService.searchActivities(warningQuery); if (warningActivities.activities.length > 0) { results.warned += warningActivities.activities.length; await this.sendExpirationWarning(rule, warningActivities.activities); } } } return results; } /** * Register GDPR compliance information */ registerGDPRCompliance(parentDID, info) { this.gdprRecords.set(parentDID, info); } /** * Handle GDPR data subject request (right to be forgotten) */ async handleGDPRDataDeletion(parentDID) { const gdprInfo = this.gdprRecords.get(parentDID); if (!gdprInfo) { throw new Error(`No GDPR compliance information found for ${parentDID}`); } // Delete all activities for this data subject const query = { parentDID, limit: 50000 }; const activities = await this.searchService.searchActivities(query); await this.deleteActivitiesFromIndex(activities.activities.map(a => a.id)); // Delete related archives const relatedArchives = Array.from(this.archivalRecords.values()) .filter(record => record.originalActivityIds.some(id => activities.activities.some(a => a.id === id))); for (const archive of relatedArchives) { await this.deleteArchive(archive.id); } // Log GDPR deletion if (this.config.auditLogging) { console.log(`GDPR deletion completed for ${parentDID}: ${activities.total} activities, ${relatedArchives.length} archives`); } return { activitiesDeleted: activities.total, archivesDeleted: relatedArchives.length }; } /** * Generate data retention report */ async generateRetentionReport() { const stats = this.index.getStats(); const now = new Date(); // Calculate activities by age const activitiesByAge = { '0-30 days': 0, '31-90 days': 0, '91-365 days': 0, '1+ years': 0 }; // This would need to query the index for age-based counts // Simplified for now const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); const ninetyDaysAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000); const oneYearAgo = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000); const recentQuery = await this.searchService.searchActivities({ dateRange: { start: thirtyDaysAgo, end: now }, limit: 10000 }); activitiesByAge['0-30 days'] = recentQuery.total; // Calculate upcoming expirations let upcomingExpirations = 0; for (const rule of this.rules.filter(r => r.active)) { const policy = this.policies.get(rule.policy.id); if (!policy) continue; const expirationDate = new Date(now.getTime() + this.config.warningDays * 24 * 60 * 60 * 1000); const cutoffDate = new Date(expirationDate.getTime() - policy.retentionPeriod * 24 * 60 * 60 * 1000); const expiringQuery = await this.searchService.searchActivities({ agentDID: rule.agentDID, parentDID: rule.parentDID, dateRange: { start: new Date(0), end: cutoffDate }, limit: 1000 }); upcomingExpirations += expiringQuery.total; } return { totalActivities: stats.totalActivities, activitiesByAge, archivalRecords: this.archivalRecords.size, upcomingExpirations, complianceStatus: this.config.complianceMode ? 'enabled' : 'disabled' }; } // Private helper methods initializeDefaultPolicies() { // GDPR compliance policy this.addPolicy({ id: 'gdpr-default', name: 'GDPR Default Retention', description: 'Default GDPR-compliant data retention policy', retentionPeriod: 90, // 3 months archiveAfter: 60, autoDelete: false, compressionEnabled: true, encryptionRequired: true, complianceStandard: 'gdpr', triggers: [ { type: 'time', condition: { days: 90 }, action: 'archive' } ] }); // Standard policy this.addPolicy({ id: 'standard', name: 'Standard Retention', description: 'Standard data retention policy', retentionPeriod: 365, // 1 year archiveAfter: 180, autoDelete: false, compressionEnabled: true, encryptionRequired: false, triggers: [ { type: 'time', condition: { days: 180 }, action: 'archive' } ] }); // Long-term policy this.addPolicy({ id: 'long-term', name: 'Long-term Retention', description: 'Long-term data retention for compliance', retentionPeriod: 2555, // 7 years archiveAfter: 365, autoDelete: false, compressionEnabled: true, encryptionRequired: true, triggers: [ { type: 'time', condition: { days: 365 }, action: 'archive' } ] }); } startArchivalScheduler() { if (!this.config.autoArchival) return; // Run archival check every day setInterval(async () => { try { await this.deleteExpiredActivities(); } catch (error) { console.error('Archival scheduler error:', error); } }, 24 * 60 * 60 * 1000); // 24 hours } async deleteActivitiesFromIndex(activityIds) { for (const id of activityIds) { await this.index.removeActivity(id); } } async storeInIPFS(data) { if (!this.ipfsStorage) { throw new Error('IPFS storage not available'); } // Create a temporary activity to use IPFS storage const tempActivity = { id: 'archive-temp', data: Buffer.isBuffer(data) ? data.toString() : data }; const result = await this.ipfsStorage.storeActivity(tempActivity); return result.ipfsHash; } async retrieveFromIPFS(hash) { if (!this.ipfsStorage) { throw new Error('IPFS storage not available'); } const activity = await this.ipfsStorage.retrieveActivity(hash); return Buffer.from(activity.data); } async deleteArchive(archiveId) { const record = this.archivalRecords.get(archiveId); if (!record) return; // Delete from IPFS if stored there if (this.ipfsStorage && (record.location.startsWith('ipfs://') || record.location.length === 46)) { // IPFS doesn't support deletion, but we can unpin try { await this.ipfsStorage.unpinActivity(record.location); } catch (error) { console.warn(`Failed to unpin IPFS content: ${error}`); } } this.archivalRecords.delete(archiveId); } async sendExpirationWarning(rule, activities) { // Simplified warning - in production would send email/notification console.log(`Warning: ${activities.length} activities will expire soon for rule ${rule.policy.name}`); } generateId(prefix) { return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } } exports.ActivityArchivalService = ActivityArchivalService; //# sourceMappingURL=activity-archival-service.js.map