UNPKG

oidc-lib

Version:

A library for creating OIDC Service Providers

595 lines (532 loc) 26.8 kB
const METADATACOLLECTIONNAME = "_exchangeMetadata"; const _CONSTRUCTOR = 'indexed_db_provider'; module.exports = { _constructor: _CONSTRUCTOR, indexed_db_provider: indexed_db_provider, METADATACOLLECTIONNAME: METADATACOLLECTIONNAME, }; var pk = null; var c_key = null; function indexed_db_provider(){ Object.defineProperty(this, "initialize", { value: function(pkInput, contentModuleName, provider){ return new Promise((resolve, reject) => { pk = pkInput; var dbInfo = { provider: provider, contentModuleName: contentModuleName }; validateModuleConfig(contentModuleName); dbInfo.schemaVersion = getDbModuleConfigProperty(contentModuleName, 'schemaVersion'); dbInfo.databaseName = getDbModuleConfigProperty(contentModuleName, 'databaseName'); dbInfo.databaseDefinition = { id: dbInfo.databaseName }; dbInfo.databaseUrl = `dbs/${dbInfo.databaseDefinition.id}`; // dbInfo.c_key is set in wallet_op.c dbInfo.collections = {}; var collections = getDbModuleConfigProperty(contentModuleName, 'collections'); for (var collectionName in collections){ var collection = collections[collectionName]; if (!collection.publicProperties){ collection.publicProperties = []; } var collectionInfo = { keyPath: collection.keyPath, indexes: collection.indexes, publicProperties: collection.publicProperties, highWater: 0, encryption: collection.encryption, hubContext: collection.hubContext ? collection.hubContext : 'https://schema.org', hubInterface: collection.hubInterface ? collection.hubInterface : 'Collections', hubType: collection.hubType ? collection.hubType : collectionName }; collectionInfo.publicProperties.push("didMeta"); collectionInfo.publicProperties.push(collectionInfo.keyPath); for (var i=0; i < collectionInfo.indexes.length; i++){ var index = collectionInfo.indexes[i]; if (Array.isArray(index.keyPath)){ for (var j=0; j < index.keyPath.length; j++){ var field = index.keyPath[j]; if (collectionInfo.publicProperties.indexOf(field) < 0){ collectionInfo.publicProperties.push(field); } } } else{ if (collectionInfo.publicProperties.indexOf(index.keyPath) < 0){ collectionInfo.publicProperties.push(index.keyPath); } } } dbInfo.collections[collectionName] = collectionInfo; } getDatabase(dbInfo) .then(db => { if (db){ // in other databases we need to cycle through get_collections // here the collections are created by schema applied // in getDatabase by a change to the database version number. dbInfo.db = db; resolve(dbInfo); } else{ reject('Database initialization failed for module ' + contentModuleName); } }, err => { reject(err); }) }) } }) Object.defineProperty(this, "initialize_local_storage", { value: function(pkInput, contentModuleName, provider){ return new Promise((resolve, reject) => { pk = pkInput; var localStorage = pk.util.config.localStorage; var dbInfo = { provider: provider, contentModuleName: contentModuleName }; dbInfo.schemaVersion = localStorage['schemaVersion']; dbInfo.databaseName = localStorage['databaseName']; dbInfo.databaseDefinition = { id: dbInfo.databaseName }; dbInfo.databaseUrl = `dbs/${dbInfo.databaseDefinition.id}`; dbInfo.collections = {}; var collections = localStorage['collections']; for (var collectionName in collections){ var collection = collections[collectionName]; var collectionInfo = { keyPath: collection.keyPath, indexes: collection.indexes, publicProperties: [], highWater: 0, encryption: collection.encryption, hubContext: collection.hubContext ? collection.hubContext : 'https://schema.org', hubInterface: collection.hubInterface ? collection.hubInterface : 'Collections', hubType: collection.hubType ? collection.hubType : collectionName }; collectionInfo.publicProperties.push(collectionInfo.keyPath); for (var i=0; i < collectionInfo.indexes.length; i++){ var index = collectionInfo.indexes[i]; if (Array.isArray(index.keyPath)){ for (var j=0; j < index.keyPath.length; j++){ var field = index.keyPath[j]; if (collectionInfo.publicProperties.indexOf(field) < 0){ collectionInfo.publicProperties.push(field); } } } else{ if (collectionInfo.publicProperties.indexOf(index.keyPath) < 0){ collectionInfo.publicProperties.push(index.keyPath); } } } dbInfo.collections[collectionName] = collectionInfo; } getDatabase(dbInfo) .then(db => { if (db){ // in other databases we need to cycle through get_collections // here the collections are created by schema applied // in getDatabase by a change to the database version number. dbInfo.db = db; resolve(dbInfo); } }, err => { reject(err); }) .then(function() {}, function(err){ if (err.code !== undefined){ switch (err.code){ case 'ENOENT': if (err.syscall === 'getaddrinfo'){ pk.util.log_debug('Unable to lookup database service in DNS in module ' + contentModuleName); pk.util.log_debug('Hostname: ' + err.hostname); return; } break; default: break; } } reject('Database initialization failed for module ' + contentModuleName); }) }) } }) function getDatabase (dbInfo) { return new Promise((resolve, reject) => { var version = dbInfo.schemaVersion; var request = indexedDB.open(dbInfo.databaseName, version); request.onerror = function(event) { pk.util.log_detail('error opening database', event); reject('error opening database' + event.currentTarget.error.message); } request.onsuccess = function(event) { resolve(event.target.result); }; request.onupgradeneeded = function(event) { var db = event.target.result; for (var collectionName in dbInfo.collections){ if (db.objectStoreNames.contains(collectionName)){ var objectStore = db.deleteObjectStore(collectionName); } var collectionInfo = dbInfo.collections[collectionName]; var objectStore = db.createObjectStore(collectionName, { keyPath: collectionInfo.keyPath }); if (collectionInfo.indexes !== undefined){ for (var i=0; i < collectionInfo.indexes.length; i++){ var indexInfo = collectionInfo.indexes[i]; var params = {}; if (indexInfo.unique){ params.unique = true; } var result = objectStore.createIndex(indexInfo.name, indexInfo.keyPath, params); } } } } }) } Object.defineProperty(this, "createOrUpdateDocument", { value: function(dbInfo, collectionName, objectToWrite, options) { return new Promise((resolve, reject) => { try{ if (!objectToWrite.didMeta){ objectToWrite.didMeta = {}; } if (!objectToWrite.didMeta.object_id){ objectToWrite.didMeta.object_id = objectToWrite.id; } if (!options || !options.suppress_time_update){ var date = new Date(); var update_time = date.getTime(); objectToWrite.didMeta.update_time = update_time; } if (dbInfo.collections[collectionName] === undefined){ reject('cannot createOrUpdate on unknown collection: ' + collectionName); } var encryptedObject = db_encrypt(dbInfo, collectionName, objectToWrite); var transaction = dbInfo.db.transaction([collectionName], "readwrite"); // report on the success of opening the transaction transaction.onerror = function(event) { pk.util.log_debug('<li>Transaction not opened: ', event); }; var objectStore = transaction.objectStore(collectionName); var request = objectStore.put(encryptedObject); request.onsuccess = function(event) { resolve(objectToWrite); }; request.onerror = function(event) { reject(event.target.error + ": " + collectionName); } } catch(err){ pk.util.log_error('createOrUpdataDocument', err); reject(err); } }) } }) Object.defineProperty(this, "getDocument", { value: function (dbInfo, collectionName, param3, param4) { var index_id; if (param4){ index_id = param3; local_id = param4; } else{ local_id = param3; } var collectionInfo = dbInfo.collections[collectionName]; return new Promise((resolve, reject) => { var transaction = dbInfo.db.transaction([collectionName]); var objectStore = transaction.objectStore(collectionName); var request; if (index_id){ var keyRangeValue = IDBKeyRange.lowerBound(local_id); var index = objectStore.index(index_id); index.openCursor(keyRangeValue).onsuccess = function(event) { var cursor = event.target.result; if(cursor) { if (cursor.value.did === local_id){ var record = cursor.value; db_decrypt(dbInfo, collectionName, record) .then(result => { resolve(result); }, err => { console.log("error1 in getDocument"); reject('error1 in getDocument'); }) } else{ cursor.continue(); } } else { reject('did not located'); } }; index.onerror = function(event) { reject('error getting document'); }; } else{ request = objectStore.get(local_id); request.onerror = function(event) { alert("error getting document"); reject(key.target.error); }; request.onsuccess = function(event) { if (event.target.result === undefined){ var err = { msg: "Document could not be found", code: 404 }; reject(err); } else{ var record = event.target.result; db_decrypt(dbInfo, collectionName, record) .then (result => { resolve(result); }, err => { console.log('error2 in getDocument'); reject('error2 in getDocument'); }) } } } }) } }) Object.defineProperty(this, "deleteDocument", { value: function(dbInfo, collectionName, local_id){ return new Promise((resolve, reject) => { var transaction = dbInfo.db.transaction([collectionName], "readwrite"); transaction.onsuccess = function(event){ resolve(true); }; transaction.onerror = function(event){ reject("transaction failed"); } var objectStore = transaction.objectStore(collectionName); var request = objectStore.delete(local_id); request.onsuccess = function(event) { resolve(true); }; request.onerror = function(event) { reject('document did not exist'); } }) } }) Object.defineProperty(this, "queryCollection", { value: function(dbInfo, collectionName, queryDictionaryOrString) { var queryResult = []; return new Promise((resolve, reject) => { var qDebug = false; if (qDebug){ console.log('\r\n\r\n*****************************************************************************'); console.log('qDebug: collectionName: ' + collectionName); console.log('qDebug: queryDictionaryOrString', JSON.stringify(queryDictionaryOrString, null, 4) + '\r\n'); console.log('*****************************************************************************\r\n'); } if (typeof queryDictionaryOrString !== 'object'){ throw ('indexed_db queryCollection currently does not support string queries'); } var collectionInfo = dbInfo.collections[collectionName]; var objectStore = dbInfo.db.transaction(collectionName).objectStore(collectionName); var cursor = objectStore.openCursor(); cursor.onsuccess = function(event) { var cursor = event.target.result; if (cursor) { var match = event.target.result.value; if (qDebug){ console.log('qDebug: cursor value ', JSON.stringify(match, null, 4) + '\r\n'); } var allClaimsOK = true for (var claimName in queryDictionaryOrString){ var dictValue = queryDictionaryOrString[claimName]; if (dictValue !== match[claimName]){ if (qDebug){ console.log('qDebug: no match on ' + claimName); } allClaimsOK = false; break; } } if (qDebug){ console.log('qDebug: allClaimsOK ', allClaimsOK) } if (allClaimsOK){ queryResult.push(match); } cursor.continue(); } else { // iteration complete for (var i=0; i < queryResult.length; i++){ db_decrypt(dbInfo, collectionName, queryResult[i]) .then(result => { if (qDebug){ console.log('qDebug: push result ', JSON.stringify(result, null, 4) + '\r\n'); } }, err => { console.log('error3 in queryCollection: ', err); }) } resolve(queryResult); } }; cursor.onerror = function(event){ alert('cursor onError'); } }) } }) function validateModuleConfig(contentModuleName){ if (contentModuleName !== 'sts'){ if (pk.util.config.content_modules[contentModuleName].db === undefined){ throw "The " + contentModuleName + " content module does not define a db property in claimer_config"; } } } function getDbModuleConfigProperty(contentModuleName, propertyName, throwOnUndefined) { if (throwOnUndefined === undefined || throwOnUndefined === true){ var dbInstance = pk.util.config.sts.db; if (contentModuleName !== 'sts'){ dbInstance = pk.util.config.content_modules[contentModuleName].db; } if (dbInstance[propertyName] === undefined){ throw "Configuration property " + propertyName + " is not defined in content module " + contentModuleName; } } return dbInstance[propertyName]; } function db_encrypt(dbInfo, collectionName, objectToWrite){ if (dbInfo.c_key === false){ return objectToWrite; } var toEncrypt = {}; var collection = dbInfo.collections[collectionName]; var publicProperties = {}; var doEncrypt = false; for (var propertyName in objectToWrite){ if (collection.publicProperties.includes(propertyName)){ publicProperties[propertyName] = objectToWrite[propertyName]; continue; } var newValue = undefined; switch (propertyName){ case 'private': case 'public': case 'symmetric': var propertyValue = objectToWrite[propertyName]; if (typeof propertyValue === 'object' && propertyValue.constructor.name === 'CryptoKey'){ newValue = {}; newValue.isCryptoKey = true; newValue.algorithm = propertyValue.algorithm; newValue.extractable = propertyValue.extractable; newValue.type = propertyValue.type; newValue.usages = propertyValue.usages; } break; default: break; } toEncrypt[propertyName] = newValue ? newValue : objectToWrite[propertyName]; doEncrypt = true; } if (!doEncrypt){ return objectToWrite; } var bufferToEncrypt = forge.util.createBuffer(JSON.stringify(toEncrypt), 'utf8'); var encryptedProperties = pk.pmanager.wallet_encrypt("AES-CBC", dbInfo.c_key, bufferToEncrypt); publicProperties['didMeta']['encrypted'] = encryptedProperties; return publicProperties; } async function db_decrypt(dbInfo, collectionName, objectToReturn){ try { var potentialError = 'db_decrypt'; var propertyName; var keyData; var decryptedProperties; if (dbInfo.c_key === false){ return objectToReturn; } // nothing to decrypt if (!objectToReturn.didMeta.encrypted){ return objectToReturn; } var collection = dbInfo.collections[collectionName]; if (objectToReturn.didMeta.encrypted){ var decryptedPayload = pk.pmanager.wallet_decrypt("AES-CBC", dbInfo.c_key, objectToReturn.didMeta.encrypted); delete objectToReturn.didMeta.encrypted; decryptedProperties = JSON.parse(decryptedPayload); for (propertyName in decryptedProperties){ switch (propertyName){ case 'private': case 'public': case 'symmetric': var propertyValue = decryptedProperties[propertyName]; // anomolies in decoding of cryptokey if (propertyValue.algorithm.name === "RSASSA-PKCS1-v1_5"){ var fixedAlg = { name: propertyValue.algorithm.name, hash: propertyValue.algorithm.hash.name } propertyValue.algorithm = fixedAlg; } if (propertyValue.isCryptoKey){ potentialError = 'db_encrypt: jwk_' + propertyName + ' import_key'; keyData = decryptedProperties['jwk_' + propertyName]; const result = await crypto.subtle.importKey( 'jwk', keyData, propertyValue.algorithm, propertyValue.extractable, propertyValue.usages ); decryptedProperties[propertyName] = result; } break; default: break; } objectToReturn[propertyName] = decryptedProperties[propertyName]; } } return objectToReturn; } catch (err){ console.log("**********************************************"); console.log(potentialError); console.log('propertyValue', propertyValue); console.log('keyData', keyData); console.log('error', err); console.log("**********************************************"); } } Object.defineProperty(this, "clear", { value: function(dbInfo){ return new Promise((resolve, reject) => { for (var collectionName in dbInfo.collections){ // open a read/write db transaction, ready for clearing the data var transaction = dbInfo.db.transaction([collectionName], "readwrite"); // report on the success of the transaction completing, when everything is done transaction.oncomplete = function(event) { resolve(true); }; transaction.onerror = function(event) { reject('Transaction not opened due to error: ' + transaction.error); }; // create an object store on the transaction var objectStore = transaction.objectStore(collectionName); // Make a request to clear all the data out of the object store var objectStoreRequest = objectStore.clear(); } }) } }) }