UNPKG

anon-identity

Version:

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

446 lines 19.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HybridStorageProvider = void 0; const storage_factory_1 = require("../storage-factory"); const uuid_1 = require("uuid"); class HybridStorageProvider { constructor(config) { this.dataIndex = new Map(); this.config = config; // Initialize providers based on configuration this.providers = { local: storage_factory_1.StorageFactory.createProvider({ provider: 'memory' }), // Always have local }; if (config.blockchain) { this.providers.blockchain = storage_factory_1.StorageFactory.createProvider({ provider: 'blockchain', blockchain: config.blockchain, cache: config.cache, }); } if (config.ipfs) { this.providers.ipfs = storage_factory_1.StorageFactory.createProvider({ provider: 'ipfs', ipfs: config.ipfs, }); } // Start synchronization if enabled if (config.hybrid?.sync?.enabled) { this.startSync(); } } // Routing logic getProviderForDataType(dataType, dataSize) { const routing = this.config.hybrid?.routing; const thresholds = this.config.hybrid?.sizeThresholds; const providers = []; // Check explicit routing configuration if (routing) { const explicitRoute = routing[`${dataType}s`]; if (explicitRoute && this.providers[explicitRoute]) { providers.push(explicitRoute); } } // If no explicit routing, use intelligent routing based on data characteristics if (providers.length === 0) { switch (dataType) { case 'did': // DIDs are small and need high availability - blockchain + local if (this.providers.blockchain) providers.push('blockchain'); providers.push('local'); break; case 'revocation': // Revocations must be publicly verifiable - blockchain if (this.providers.blockchain) { providers.push('blockchain'); } else if (this.providers.ipfs) { providers.push('ipfs'); } providers.push('local'); break; case 'schema': // Schemas are public and rarely change - IPFS + blockchain if (this.providers.ipfs) providers.push('ipfs'); if (this.providers.blockchain) providers.push('blockchain'); providers.push('local'); break; case 'credential': // Route based on size if available if (dataSize && thresholds) { if (dataSize > (thresholds.useIPFS || 10240) && this.providers.ipfs) { providers.push('ipfs'); } else if (dataSize < (thresholds.useLocal || 1024)) { providers.push('local'); } else if (this.providers.blockchain) { providers.push('blockchain'); } } else { // Default: IPFS for full data, blockchain for hash if (this.providers.ipfs) providers.push('ipfs'); if (this.providers.blockchain) providers.push('blockchain'); providers.push('local'); } break; } } // Always ensure at least local storage if (!providers.includes('local')) { providers.push('local'); } return providers; } // Execute operation with fallback support async executeWithFallback(operation, providers, fallbackOperation) { const fallbackConfig = this.config.hybrid?.fallback; const maxRetries = fallbackConfig?.retries || 3; const retryDelay = fallbackConfig?.retryDelay || 1000; let lastError = null; for (const providerType of providers) { const provider = this.providers[providerType]; if (!provider) continue; for (let retry = 0; retry < maxRetries; retry++) { try { return await operation(provider); } catch (error) { lastError = error; console.warn(`Provider ${providerType} failed (retry ${retry + 1}/${maxRetries}):`, error); if (retry < maxRetries - 1) { await new Promise(resolve => setTimeout(resolve, retryDelay * (retry + 1))); } } } // Try fallback operation if main operation failed if (fallbackOperation && fallbackConfig?.enabled) { try { return await fallbackOperation(provider); } catch (fallbackError) { console.warn(`Fallback operation also failed for ${providerType}:`, fallbackError); } } } throw lastError || new Error('All storage providers failed'); } // Track where data is stored trackDataLocation(key, providers) { if (!this.dataIndex.has(key)) { this.dataIndex.set(key, new Set()); } providers.forEach(p => this.dataIndex.get(key).add(p)); } // DID Operations async storeDID(did, document) { const providers = this.getProviderForDataType('did'); const key = `did:${did}`; // Store in all designated providers const storePromises = providers.map(providerType => { const provider = this.providers[providerType]; if (provider) { return provider.storeDID(did, document) .then(() => this.trackDataLocation(key, [providerType])) .catch(err => console.error(`Failed to store DID in ${providerType}:`, err)); } return Promise.resolve(); }); await Promise.all(storePromises); } async resolveDID(did) { const key = `did:${did}`; const locations = this.dataIndex.get(key) || new Set(this.getProviderForDataType('did')); return this.executeWithFallback(async (provider) => provider.resolveDID(did), Array.from(locations)); } async listDIDs(owner) { // Aggregate from all providers const allDIDs = new Set(); for (const [providerType, provider] of Object.entries(this.providers)) { if (provider) { try { const dids = await provider.listDIDs(owner); dids.forEach(did => allDIDs.add(did)); } catch (error) { console.warn(`Failed to list DIDs from ${providerType}:`, error); } } } return Array.from(allDIDs); } // Credential Operations async storeCredential(credential) { const dataSize = JSON.stringify(credential).length; const providers = this.getProviderForDataType('credential', dataSize); const key = `credential:${credential.id}`; // For hybrid storage, we might store full data in IPFS and hash on blockchain if (providers.includes('blockchain') && providers.includes('ipfs')) { // Store full credential in IPFS if (this.providers.ipfs) { await this.providers.ipfs.storeCredential(credential); } // Store only hash/reference on blockchain if (this.providers.blockchain) { // The blockchain provider already handles this by storing hashes await this.providers.blockchain.storeCredential(credential); } this.trackDataLocation(key, ['blockchain', 'ipfs']); } else { // Store in all designated providers const storePromises = providers.map(providerType => { const provider = this.providers[providerType]; if (provider) { return provider.storeCredential(credential) .then(() => this.trackDataLocation(key, [providerType])) .catch(err => console.error(`Failed to store credential in ${providerType}:`, err)); } return Promise.resolve(); }); await Promise.all(storePromises); } } async getCredential(id) { const key = `credential:${id}`; const locations = this.dataIndex.get(key) || new Set(this.getProviderForDataType('credential')); // Prefer IPFS for full credential data const orderedLocations = Array.from(locations).sort((a, b) => { if (a === 'ipfs') return -1; if (b === 'ipfs') return 1; return 0; }); return this.executeWithFallback(async (provider) => provider.getCredential(id), orderedLocations); } async listCredentials(holder) { // Aggregate from all providers, removing duplicates const credentialsMap = new Map(); for (const [providerType, provider] of Object.entries(this.providers)) { if (provider) { try { const credentials = await provider.listCredentials(holder); credentials.forEach(cred => credentialsMap.set(cred.id, cred)); } catch (error) { console.warn(`Failed to list credentials from ${providerType}:`, error); } } } return Array.from(credentialsMap.values()); } async deleteCredential(id) { const key = `credential:${id}`; const locations = this.dataIndex.get(key) || new Set(['local']); // Delete from all locations const deletePromises = Array.from(locations).map(providerType => { const provider = this.providers[providerType]; if (provider && providerType !== 'blockchain') { // Can't delete from blockchain return provider.deleteCredential(id) .catch(err => console.error(`Failed to delete credential from ${providerType}:`, err)); } return Promise.resolve(); }); await Promise.all(deletePromises); this.dataIndex.delete(key); } // Revocation Operations async publishRevocation(issuerDID, revocationList) { const providers = this.getProviderForDataType('revocation'); const key = `revocation:${issuerDID}`; // Revocations should be on blockchain for public verifiability const storePromises = providers.map(providerType => { const provider = this.providers[providerType]; if (provider) { return provider.publishRevocation(issuerDID, revocationList) .then(() => this.trackDataLocation(key, [providerType])) .catch(err => console.error(`Failed to publish revocation in ${providerType}:`, err)); } return Promise.resolve(); }); await Promise.all(storePromises); } async checkRevocation(issuerDID, credentialId) { const key = `revocation:${issuerDID}`; const locations = this.dataIndex.get(key) || new Set(this.getProviderForDataType('revocation')); // Check blockchain first for most authoritative answer const orderedLocations = Array.from(locations).sort((a, b) => { if (a === 'blockchain') return -1; if (b === 'blockchain') return 1; return 0; }); try { return await this.executeWithFallback(async (provider) => provider.checkRevocation(issuerDID, credentialId), orderedLocations); } catch { return false; // Default to not revoked if check fails } } async getRevocationList(issuerDID) { const key = `revocation:${issuerDID}`; const locations = this.dataIndex.get(key) || new Set(this.getProviderForDataType('revocation')); return this.executeWithFallback(async (provider) => provider.getRevocationList(issuerDID), Array.from(locations)); } // Key Management (always local) async storeKeyPair(identifier, encryptedKeyPair) { return this.providers.local.storeKeyPair(identifier, encryptedKeyPair); } async retrieveKeyPair(identifier) { return this.providers.local.retrieveKeyPair(identifier); } async deleteKeyPair(identifier) { return this.providers.local.deleteKeyPair(identifier); } // Schema Operations async registerSchema(schema) { const providers = this.getProviderForDataType('schema'); const schemaId = schema.id || `schema:${(0, uuid_1.v4)()}`; const key = `schema:${schemaId}`; // Store in all designated providers let resultId = schemaId; for (const providerType of providers) { const provider = this.providers[providerType]; if (provider) { try { const id = await provider.registerSchema({ ...schema, id: schemaId }); resultId = id; this.trackDataLocation(key, [providerType]); } catch (err) { console.error(`Failed to register schema in ${providerType}:`, err); } } } return resultId; } async getSchema(schemaId) { const key = `schema:${schemaId}`; const locations = this.dataIndex.get(key) || new Set(this.getProviderForDataType('schema')); return this.executeWithFallback(async (provider) => provider.getSchema(schemaId), Array.from(locations)); } async listSchemas(issuerDID) { // Aggregate from all providers, removing duplicates const schemasMap = new Map(); for (const [providerType, provider] of Object.entries(this.providers)) { if (provider) { try { const schemas = await provider.listSchemas(issuerDID); schemas.forEach(schema => { if (schema.id) { schemasMap.set(schema.id, schema); } }); } catch (error) { console.warn(`Failed to list schemas from ${providerType}:`, error); } } } return Array.from(schemasMap.values()); } // General operations async clear() { // Clear all providers const clearPromises = Object.values(this.providers).map(provider => provider?.clear().catch(err => console.error('Failed to clear provider:', err))); await Promise.all(clearPromises); this.dataIndex.clear(); } // Synchronization startSync() { const interval = this.config.hybrid?.sync?.interval || 60000; // Default 1 minute this.syncInterval = setInterval(() => { this.performSync().catch(err => console.error('Synchronization failed:', err)); }, interval); } async performSync() { console.log('Starting hybrid storage synchronization...'); // Sync DIDs const allDIDs = await this.listDIDs(); for (const did of allDIDs) { await this.syncData('did', did); } // Additional sync operations can be added here console.log('Synchronization completed'); } async syncData(dataType, id) { const key = `${dataType}:${id}`; const locations = this.dataIndex.get(key); if (!locations || locations.size <= 1) { return; // No need to sync if data is in only one location } // Implement conflict resolution based on configuration const conflictResolution = this.config.hybrid?.sync?.conflictResolution || 'newest'; // This is a simplified sync - in production, you'd implement // proper conflict resolution and data comparison console.log(`Syncing ${key} across ${locations.size} locations`); } // Phone Number Operations async storePhoneNumber(userDID, phoneNumber) { // Route to local storage for privacy-sensitive data return this.providers.local.storePhoneNumber(userDID, phoneNumber); } async getPhoneNumber(userDID, phoneId) { return this.providers.local.getPhoneNumber(userDID, phoneId); } async listPhoneNumbers(userDID) { return this.providers.local.listPhoneNumbers(userDID); } async updatePhoneNumber(userDID, phoneId, phoneNumber) { return this.providers.local.updatePhoneNumber(userDID, phoneId, phoneNumber); } async deletePhoneNumber(userDID, phoneId) { return this.providers.local.deletePhoneNumber(userDID, phoneId); } // Address Operations async storeAddress(userDID, address) { // Route to local storage for privacy-sensitive data return this.providers.local.storeAddress(userDID, address); } async getAddress(userDID, addressId) { return this.providers.local.getAddress(userDID, addressId); } async listAddresses(userDID) { return this.providers.local.listAddresses(userDID); } async updateAddress(userDID, addressId, address) { return this.providers.local.updateAddress(userDID, addressId, address); } async deleteAddress(userDID, addressId) { return this.providers.local.deleteAddress(userDID, addressId); } // Email Address Operations async storeEmailAddress(userDID, emailAddress) { // Route to local storage for privacy-sensitive data return this.providers.local.storeEmailAddress(userDID, emailAddress); } async getEmailAddress(userDID, emailId) { return this.providers.local.getEmailAddress(userDID, emailId); } async listEmailAddresses(userDID) { return this.providers.local.listEmailAddresses(userDID); } async updateEmailAddress(userDID, emailId, emailAddress) { return this.providers.local.updateEmailAddress(userDID, emailId, emailAddress); } async deleteEmailAddress(userDID, emailId) { return this.providers.local.deleteEmailAddress(userDID, emailId); } // Cleanup destroy() { if (this.syncInterval) { clearInterval(this.syncInterval); } } } exports.HybridStorageProvider = HybridStorageProvider; //# sourceMappingURL=hybrid-storage-provider.js.map