UNPKG

oidc-lib

Version:

A library for creating OIDC Service Providers

1,336 lines (1,237 loc) 47.7 kB
function flockMembershipFactory(hubDbInfo, instanceInfo){ var _hubDbInfo = hubDbInfo; var _flockDid = instanceInfo.flockDid; var _instanceDid = instanceInfo.instanceDid; var _schema = 'itsourweb.org/' + _hubDbInfo.databaseUrl + '/flockMetadata'; var _verifyKeys = {}; var _encryptKeys = {}; var _memberInfo = null; var _object_id; Object.defineProperty(this, "initialize", { value: function() { return new Promise((resolve, reject) => { var newCollections = []; var flockInfoCipher; var metadataElement; var delta = false; var target = { kind: HUBFLOCKRECORD, contentModuleName: _hubDbInfo.contentModuleName } hubDbInfo.provider.queryCollection(hubDbInfo, indexed_db_module.METADATACOLLECTIONNAME, target) .then(metadataElements => { if (instanceInfo.instanceDid === DIDNOTPRESENT || !syncInForeground()){ if (metadataElements.length > 0){ return metadataElements; } } if (instanceInfo.instanceDid !== DIDNOTPRESENT){ return hubDbInfo.provider.queryHubCollection(hubDbInfo, indexed_db_module.METADATACOLLECTIONNAME, target); } else{ return(false); } }, err => { reject(err); }) .then(metadataElements => { if (metadataElements === false){ delta = true; _memberInfo = { kind: HUBFLOCKRECORD, didMeta: {}, flockDid: _flockDid, contentModuleName: hubDbInfo.contentModuleName, memberships: {}, collectionInfo: {} }; _memberInfo.memberships[_instanceDid] = { role: 'owner' }; return _memberInfo; } else if (metadataElements.length > 0){ return metadataElements[0]; } }, err => { reject(err); }) .then(info => { if (info){ _memberInfo = info; var keystore; var genParams; var collectionKeyPromises = []; for (var contentModuleName in pk.dbs){ var dbInfo = pk.dbs[contentModuleName]; for (var collectionName in dbInfo.collections){ if (collectionName === indexed_db_module.METADATACOLLECTIONNAME){ continue; } if (_memberInfo.collectionInfo[collectionName] === undefined){ delta = true; if (keystore === undefined){ keystore = jose.JWK.createKeyStore(); genParams = { alg: 'A128GCM', use: 'enc' }; } newCollections.push(collectionName); collectionKeyPromises.push(keystore.generate("oct", 128, genParams)); } } } return Promise.all(collectionKeyPromises); } }, err => { reject(err); }) .then(newKeys => { if (newKeys){ for (var i=0; i < newKeys.length; i++){ _memberInfo.collectionInfo[newCollections[i]] = newKeys[i].toJSON(true); } if (delta){ return save(); } else{ resolve(true); } } }, err => { reject(err); }) .then(saveResult => { if (saveResult){ resolve(true); } }, err => { reject(err); }) }); } }); Object.defineProperty(this, "resolveKeys", { value: function() { return resolveKeys(); } }); Object.defineProperty(this, "addMember", { value: function(did) { return new Promise((resolve, reject) => { if (!did){ reject('addMember has invalid did'); } _memberInfo.memberships[did] = { role: 'owner' }; pk.util.simpleResolver.resolve(did) .then(didDoc => { var id = didDoc.document.id; _verifyKeys[id] = didDoc.verify; _encryptKeys[id] = didDoc.encrypt; resolve(this); }, err => { reject(err); }); }); }, enumerable: true }); Object.defineProperty(this, "removeMember", { value: function(did) { if (_memberInfo.memberships[did] !== undefined){ delete _memberInfo.memberships[did]; delete _verifyKeys[did]; delete _encryptKeys[did]; } return; }, enumerable: true }); Object.defineProperty(this, "object_id", { get: function() { return _object_id; }, enumerable: true }); Object.defineProperty(this, "flockDid", { get: function() { return _flockDid; }, enumerable: true }); Object.defineProperty(this, "memberInfo", { get: function() { return _memberInfo; }, enumerable: true }); Object.defineProperty(this, "memberVerifyKey", { value: function(did) { return _verifyKeys[did]; }, enumerable: true }); Object.defineProperty(this, "memberEncryptKey", { value: function(did) { return _encryptKeys[did]; }, enumerable: false }); Object.defineProperty(this, "collectionKey", { value: function(collectionName) { return _memberInfo.collectionInfo[collectionName]; }, enumerable: false }); /* Object.defineProperty(this, "toString", { value: function() { return toString(); } }); */ Object.defineProperty(this, "save", { value: function() { return save(); } }); function resolveKeys(){ return new Promise((resolve, reject) => { var didDocPromises = []; for (var memberDid in _memberInfo.memberships){ if (memberDid === DIDNOTPRESENT){ var pseudoDoc = { document: { id: DIDNOTPRESENT }, verify: instanceInfo.verifyKey, encrypt: instanceInfo.encryptKey } didDocPromises.push(pseudoDoc); } else{ didDocPromises.push(pk.util.simpleResolver.resolve(memberDid)); } } Promise.all(didDocPromises) .then(didDocs => { if (didDocs){ for (var i=0; i < didDocs.length; i++){ var didDoc = didDocs[i]; var id = didDoc.document.id; _verifyKeys[id] = didDoc.verify; _encryptKeys[id] = didDoc.encrypt; } resolve(true); } }, err => { reject(err); }) }); } function save(){ return new Promise((resolve, reject) => { if (_hubDbInfo.contentModuleName !== 'sts'){ resolve(true); return; } resolveKeys() .then( keysAvailable => { var keys = []; for (var did in _encryptKeys){ keys.push(_encryptKeys[did]); } return jose.JWK.asKeyStore({ keys }); }, err => { reject(err); }) .then(keystore => { return updateHub(_hubDbInfo, indexed_db_module.METADATACOLLECTIONNAME, _memberInfo, { keystore: keystore }); }, err => { reject(err); }) .then(updateResult => { if (updateResult){ _memberInfo.didMeta = updateResult.didMeta; return resolve(updateResult); } }, err => { reject(err); }) }); } } /* Invoked by inviter to set up a flock if one does not exist. A DID is generated for the non-addressable hub and its instance hubInfo becomes the flock's hubInfo. */ function enableFlock(){ return new Promise((resolve, reject) => { var hubFlockInfo; var initialInstanceInfo; var finalInstanceInfo; loadHubInfo(HUBINSTANCEMONIKER) .then(instanceInfo => { if (instanceInfo){ if (instanceInfo.flockDid){ resolve(instanceInfo); return; } else{ initialInstanceInfo = instanceInfo; return genDid(pk, instanceInfo.privateKeys); } } }, err => { return null; }) .then(flockDid => { if (flockDid){ // the initial hub info becomes that of the flock initialInstanceInfo.instanceDid = flockDid; initialInstanceInfo.flockDid = flockDid; return initialInstanceInfo.save(HUBFLOCKMONIKER); } }, err => { reject(err); }) .then(flockInstanceSaved => { if (flockInstanceSaved){ return pk.util.local_store.remove(HUBINSTANCEMONIKER); } }, err => { reject(err); }) .then(noDidInstanceRemoved => { if (noDidInstanceRemoved){ return loadHubInfo(HUBFLOCKMONIKER); } }, err => { reject(err); }) .then(flockInfo => { if (flockInfo){ hubFlockInfo = flockInfo; // a new hubInfo is created for the inviting non-addressable hub instance return loadHubInfo(HUBINSTANCEMONIKER, { createIfAbsent: true, generateDid: true}); } }, err => { reject(err); }) .then(instanceInfo => { if (instanceInfo){ // the new hubInfo gets the newly minted flockDid instanceInfo.flockDid = hubFlockInfo.flockDid; instanceInfo.save(); pk.util.hubInstance = instanceInfo; finalInstanceInfo = instanceInfo; var flockBundle = new authenticationBundleFactory(hubFlockInfo); var permissionPromises = []; var permissioningInfo = { hubContext: "schema.identity.foundation/0.1", hubType: "PermissionGrant" } permissionPromises.push(grantFlockPermissions( flockBundle, permissioningInfo, instanceInfo.instanceDid)); var collectionArray = []; for (var contentModuleName in pk.dbs){ var hubDbInfo = pk.dbs[contentModuleName]; for (var collectionName in hubDbInfo.collections){ if (collectionArray.indexOf(collectionName) >= 0){ continue; } collectionArray.push(collectionName); permissionPromises.push(grantFlockPermissions( flockBundle, hubDbInfo.collections[collectionName], instanceInfo.instanceDid)); } } return Promise.all(permissionPromises); } }, err => { return(err); }) .then(permissionGranted => { if (permissionGranted){ // the flock membership is updated to include // the newly minted instanceInfo rather than the DIDNOTPRESENT var stsDbInfo = pk.dbs['sts']; stsDbInfo.flockMembership.removeMember(DIDNOTPRESENT); stsDbInfo.authenticationBundle = new authenticationBundleFactory(finalInstanceInfo); var membership_promise = stsDbInfo.flockMembership.addMember(finalInstanceInfo.instanceDid); for (var cmName in pk.dbs){ if (cmName === 'sts'){ continue; } var dbInfo = pk.dbs[cmName]; dbInfo.authenticationBundle = new authenticationBundleFactory(finalInstanceInfo); dbInfo.flockMembership = stsDbInfo.flockMembership; } return membership_promise; } }, err => { reject(err); }) .then(addedMembers => { if (addedMembers){ var stsDbInfo = pk.dbs['sts']; var membership_promise = stsDbInfo.flockMembership.save(); for (var cmName in pk.dbs){ if (cmName === 'sts'){ continue; } var dbInfo = pk.dbs[cmName]; dbInfo.flockMembership = stsDbInfo.flockMembership; } return membership_promise; } }, err => { return(err); }) .then(metadataUpdates => { if (metadataUpdates){ // the system is reinitialized var reinitPromises = []; for (var cmName in pk.dbs){ var dbInfo = pk.dbs[cmName]; var provider = dbInfo.provider; if (provider.enableFlock !== undefined){ pk.util.sync_info.remove_sync_time(); reinitPromises.push(provider.initialize(pk, cmName, provider)); } } return Promise.all(reinitPromises); } }, err => { reject(err); }) .then(reinitializedDbs => { if (reinitializedDbs){ for (var i=0; i < reinitializedDbs.length; i++){ var reinit = reinitializedDbs[i]; pk.dbs[reinit.contentModuleName] = reinit; } resolve(finalInstanceInfo); } }, err => { reject(err); }) }); } function grantFlockPermissions(issuerBundle, collectionInfo, newMemberDid){ return new Promise((resolve, reject) => { var date = new Date(); var updatedISO = date.toISOString(); var hub_protected = { interface: "Permissions", context: "schema.identity.foundation/0.1", type: "PermissionGrant", operation: "create", committed_at: updatedISO, commit_strategy: "basic", sub: issuerBundle.flockDid, kid: issuerBundle.signKey.kid }; var hub_payload = { "@context": "schema.identity.foundation/0.1", "@type": "PermissionGrant", "owner": issuerBundle.flockDid, "grantee": newMemberDid, "context": collectionInfo.hubContext, "type": collectionInfo.hubType, "allow": "CRU-" }; var payloadString = JSON.stringify(hub_payload); jose.JWS.createSign({ format: 'flattened', fields: hub_protected }, issuerBundle.signKey).update(payloadString).final() .then(jws => { if (jws){ var message = { "@context": "https://schema.identity.foundation/0.1", "@type": "WriteRequest", "iss": issuerBundle.instanceDid, "aud": hubConfig.did, "sub": issuerBundle.flockDid, "commit": { "protected": jws.protected, "payload": jws.payload, "signature": jws.signature } }; return hubRequest(message, issuerBundle, hubConfig.did); } }, err => { reject(err); }) .then(function(resultString){ if (resultString){ var response = JSON.parse(resultString); switch (response['@type']){ case 'ErrorResponse': var msg = 'Error communicating with hub (' + response.error_code + ')'; if (response.developer_message){ msg += ': ' + response.developer_message; } reject(msg); break; case 'WriteResponse': if (hub_payload.didMeta === undefined){ hub_payload.didMeta = {}; } hub_payload.didMeta.object_id = response.revisions[0]; hub_payload.didMeta.updated_at = updatedISO; // return indexed_db.createOrUpdateDocument(hubDbInfo.dbInfo, collectionName, payload.data); resolve(true); break; } } }, function(err){ reject(err); }) }); } function joinExistingFlock(flockMembershipCipher, membershipExchangeKey){ return new Promise((resolve, reject) => { var jwkSet = { keys: [ membershipExchangeKey ] }; var flockDid; var hubInfo; jose.JWK.asKeyStore(jwkSet) .then(keyStore => { return jose.JWE.createDecrypt(keyStore).decrypt(flockMembershipCipher); }) .then(result => { if (result){ flockDid = Buffer(result.plaintext).toString('utf8'); return loadHubInfo(HUBINSTANCEMONIKER) } }, err => { reject(err); }) .then(instanceInfo => { if (instanceInfo){ instanceInfo.flockDid = flockDid; instanceInfo.save(); hubInfo = instanceInfo; var clearPromises = []; for (var key in pk.dbs){ var hubDbInfo = pk.dbs[key]; var indexDbInfo = hubDbInfo.dbInfo; clearPromises.push(hubDbInfo.dbInfo.provider.clear(indexDbInfo)); } return Promise.all(clearPromises); } }, err => { reject(err); }) .then(clearResults => { if (clearResults){ resolve({ status: 200 }); } }, err => { reject(err); }) }); } function extendExistingFlock(flockMembershipCipher, membershipExchangeKey){ var memberDid; return new Promise((resolve, reject) => { var jwkSet = { keys: [ membershipExchangeKey ] }; jose.JWK.asKeyStore(jwkSet) .then(keyStore => { return jose.JWE.createDecrypt(keyStore).decrypt(flockMembershipCipher); }) .then(result => { if (result){ memberDid = Buffer(result.plaintext).toString('utf8'); var additionPromises = []; for (var key in pk.dbs){ var hubDbInfo = pk.dbs[key]; var indexDbInfo = hubDbInfo.dbInfo; additionPromises.push(hubDbInfo.flockMembership.addMember(memberDid)); } return Promise.all(additionPromises); } }, err => { reject(err); }) .then(additions => { if (additions){ var savePromises = []; for (var i=0; i < additions.length; i++){ savePromises.push(additions[i].save()); } } return Promise.all(savePromises); }, err => { reject(err); }) .then(saveResult => { if (saveResult){ var hubDbInfo = pk.dbs['sts']; // grant the new member permission to add new members var issuerBundle = hubDbInfo.authenticationBundle; var permissionPromises = []; var permissioningInfo = { hubContext: "schema.identity.foundation/0.1", hubType: "PermissionGrant" } permissionPromises.push(grantFlockPermissions( issuerBundle, permissioningInfo, memberDid)); var collectionArray = []; for (var contentModuleName in pk.dbs){ var hubDbInfo = pk.dbs[contentModuleName]; for (var collectionName in hubDbInfo.collections){ if (collectionArray.indexOf(collectionName) >= 0){ continue; } collectionArray.push(collectionName); permissionPromises.push(grantFlockPermissions( issuerBundle, hubDbInfo.collections[collectionName], memberDid)); } } return Promise.all(permissionPromises); } }, err => { reject(err); }) .then(updateResult => { if (updateResult){ var syncPromises = []; for (var key in pk.dbs){ var hubDbInfo = pk.dbs[key]; syncPromises.push(hubDbInfo.provider.fullSync(hubDbInfo)); } return Promise.all(syncPromises); } }, err => { reject(err); }) .then(syncResult => { if (syncResult){ resolve({ status: 200 }); } }, err => { reject(err); }) }); } function syncExistingFlock(){ return new Promise((resolve, reject) => { pk.util.sync_info.last_sync_time = 0; loadHubInfo(HUBINSTANCEMONIKER) .then(instanceInfo => { if (instanceInfo){ var stsDbInfo = pk.dbs['sts']; stsDbInfo.authenticationBundle = new authenticationBundleFactory(instanceInfo); stsDbInfo.flockMembership = new flockMembershipFactory(stsDbInfo, instanceInfo); var membership_promise = stsDbInfo.flockMembership.initialize(); for (var cmName in pk.dbs){ if (cmName === 'sts'){ continue; } var dbInfo = pk.dbs[cmName]; dbInfo.authenticationBundle = new authenticationBundleFactory(instanceInfo); dbInfo.flockMembership = stsDbInfo.flockMembership; } return membership_promise; } }, err => { reject(err); }) .then(flockOk => { if (flockOk){ resolve(flockOk); } }, err => { reject(err); }) }); } function syncInForeground(){ if (pk.util.sync_info.last_sync_time){ return false; } else{ return true; } } function hubExport(){ return new Promise((resolve, reject) => { var hubExport = {}; var dbNames = []; pk.util.local_store.get(HUBFLOCKMONIKER, 'utf-8') .then(flockDefinition => { if (flockDefinition){ var hubInfo = JSON.parse(flockDefinition); hubExport[hubInfo.personaId] = hubInfo; } }, err => { return; }) .then(() => { return pk.util.local_store.get(HUBINSTANCEMONIKER, 'utf-8'); }, err => { reject(err); }) .then(instanceDefinition => { if (instanceDefinition){ var hubInfo = JSON.parse(instanceDefinition); hubExport[hubInfo.personaId] = hubInfo; hubExport.dbReports = {}; var dbReportPromises = []; for (var key in pk.dbs){ dbNames.push(key); dbReportPromises.push(getDbReport(key)); } Promise.all(dbReportPromises) } }, err => { reject(err); }) .then( dbReports => { for (var i=0; i < dbReports.length; i++){ hubExport.dbReports[dbNames[i]] = dbReports[i]; } resolve(hubExport); }, err => { reject(err); }) }); function getDbReport(key){ return new Promise((resolve, reject) => { var hubDbInfo = pk.dbs[key]; var dbReport = {}; var collectionNames = []; dbReport.flockMembership = hubDbInfo.flockMembership; var hubCollectionPromises = []; if (hubDbInfo.authenticationBundle.instanceDid !== DIDNOTPRESENT){ var i=0; var hubCollectionPromises = []; for (var collectionName in hubDbInfo.collections){ collectionNames[i++] = collectionName; hubCollectionPromises.push(hubDbInfo.provider.queryHubCollection(hubDbInfo, collectionName, {}, { revisions: true })); } } Promise.all(hubCollectionPromises) .then(collectionInfos => { if (collectionInfos){ dbReport.hubContents = {}; for (var i=0; i < collectionInfos.length; i++){ var collectionName = collectionNames[i]; var collectionElements = collectionInfos[i]; var hubCollection = {}; for (var j=0; j < collectionElements.length; j++){ var hubElement = collectionElements[j]; hubCollection[hubElement.didMeta.object_id] = hubElement; } dbReport.hubContents[collectionName] = hubCollection; } var dbCollectionPromises = []; //query the underlying indexeddb so it is not decrypted var i=0; for (var collectionName in hubDbInfo.collections){ collectionNames[i++] = collectionName; dbCollectionPromises.push(hubDbInfo.provider.queryCollection(hubDbInfo, collectionName, {})); } return Promise.all(dbCollectionPromises); } }, err => { reject(err); }) .then(dbCollections => { if (dbCollections){ dbReport.localContents = {}; for (var i=0; i < dbCollections.length; i++){ var dbCollection = dbCollections[i]; var sync_id; var unsynced_count = 0; var localCollection = {}; for (var j=0; j < dbCollection.length; j++){ var localElement = dbCollection[j]; if (localElement.didMeta.object_id === undefined){ sync_id = '_' + (unsynced_count++).toString(); } else{ sync_id = localElement.didMeta.object_id; } localCollection[sync_id] = localElement; } dbReport.localContents[collectionNames[i]] = localCollection; } resolve(dbReport); } }, err => { reject(err); }) }); } } function genDid(pk, privateKeys) { return new Promise((resolve, reject) => { var hubConfig = pk.util.config.hub; var publicKeyArray = []; for (var c=0; c < privateKeys.length; c++){ publicKeyArray.push(toPublic(privateKeys[c])); } var privateSigningKey = privateKeys[0]; const payload = { "didMethod": "test", "hubUri": hubConfig.endpoints.hub, "publicKey": publicKeyArray } var payloadString = JSON.stringify(payload); jose.JWS.createSign({ format: 'compact' }, privateSigningKey).update(payloadString).final() .then(function(jwt){ return fetch_func(hubConfig.endpoints.registration, { method: 'POST', body: jwt, headers: { 'Content-Type': 'application/jose+json', 'Content-Length': jwt.length.toString() } }) }) .then(function(responseObject){ return responseObject.text(); }, function(err){ reject('Error fetching registration'); }) .then(function(body){ if (body){ const response = JSON.parse(body); const createdDid = response.did; if (!createdDid){ reject(response); return; } else{ resolve(createdDid); } } }, function(err){ reject('Error getting body from registration'); }) }); } function authenticationBundleFactory(hubInfoInstance) { var _hubInfo = hubInfoInstance; var _accessTokens = {}; Object.defineProperty(this, "instanceDid", { get: function() { return _hubInfo.instanceDid; }, enumerable: true }); Object.defineProperty(this, "flockDid", { get: function() { if (_hubInfo.flockDid){ return _hubInfo.flockDid; } else{ return _hubInfo.instanceDid; } }, enumerable: true }); Object.defineProperty(this, "signKey", { get: function() { return _hubInfo.signKey; }, enumerable: false }); Object.defineProperty(this, "verifyKey", { get: function() { return _hubInfo.verifyKey; }, enumerable: false }); Object.defineProperty(this, "decryptKey", { get: function() { return _hubInfo.decryptKey; }, enumerable: false }); Object.defineProperty(this, "encryptKey", { get: function() { return _hubInfo.encryptKey; }, enumerable: false }); Object.defineProperty(this, "getAccessToken", { value: function(partnerDid) { var needToken = false; var accessToken = _accessTokens[partnerDid]; if (accessToken === undefined){ needToken = true; } else { var components = accessToken.split('.'); var content = JSON.parse(pk.base64url.decode(components[1])); var expdate = new Date(content.exp); var expms = expdate.getTime() - 30000; var date = new Date(); var now = date.getTime(); if (now > expms){ needToken = true; } } return new Promise((resolve, reject) => { if (!needToken){ return resolve(accessToken); } var hubRequestor = new hubRequestorFactory(); hubRequestor.getAccessToken(_hubInfo, partnerDid) .then(function(accessToken){ _accessTokens[partnerDid] = accessToken; resolve(accessToken); }, function(err){ reject(err); }) }); } }); } /////////////////////////////////////////////////////////////////////////////// function deviceManagerFactory(){ //TODO: the version should be in the config var _role; var _removeNotification; var _newDeviceInfo = {}; Object.defineProperty(this, "addDevice", { value: function(role) { return new Promise((resolve, reject) => { _role = role; var initialPromise; var flockFunctionalityRequired = pk.util.hubInstance.flockDid === undefined; if (_role === 'invite'){ if (flockFunctionalityRequired){ managerNotification('Registering your set of indentity hubs on the blockchain.'); } initialPromise = enableFlock(); } else{ if (flockFunctionalityRequired){ managerNotification('Registering your identity hub on the blockchain.'); } initialPromise = loadHubInfo(HUBINSTANCEMONIKER, { createIfAbsent: true, generateDid: true}); } initialPromise.then((instanceInfo) => { if (instanceInfo){ pk.util.hubInstance.instanceDid = instanceInfo.instanceDid; return window.crypto.subtle.generateKey( { name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey', 'deriveBits']); } }, err => { managerNotification(); managerNotification(err, 'alert-warning', true); }) .then(keypair => { if (keypair){ _newDeviceInfo.initiatingKey = keypair; return window.crypto.subtle.exportKey( 'jwk', keypair.publicKey ) } }, err => { reject(err); }) .then(publicJwk => { if (publicJwk){ managerNotification(); _newDeviceInfo.publicJwk = JSON.stringify(publicJwk); resolve(true); } }, err => { managerNotification(); reject(err); }) }) } }); Object.defineProperty(this, "executeDeviceAddition", { value: function(rendezvousValue) { return new Promise((resolve, reject) => { var sharedSecret; var httpStatus; var rendezvousId pk.simple_crypto.digestSha256(rendezvousValue) .then(hash => { if (hash){ rendezvousId = hash; var params = createParameterString({ operation: 'register', role: _role, localDeviceId: pk.util.hubInstance.instanceDid, identifier: rendezvousId, publicJwk: _newDeviceInfo.publicJwk }); var serviceUrl = window.location.origin; var rendezvousService = serviceUrl + '/tester/rendezvous'; var url = rendezvousService + '?' + params; return fetch_func(url); } }) .then(res => { if (res){ if (res.status !== 200) { httpStatus = res.status; } return res.text(); } }, err => { return reject(err); }) .then(response => { if (httpStatus){ return reject({ error: 'registration', status: httpStatus, message: response}); } if (response){ var partnerPublicJwkObj = JSON.parse(response); return window.crypto.subtle.importKey( 'jwk', partnerPublicJwkObj, { name: 'ECDH', namedCurve: 'P-256' }, true, []); } }, err => { return reject(err); }) .then(partnerImported => { if (partnerImported){ return window.crypto.subtle.deriveBits( { name: 'ECDH', namedCurve: 'P-256', public: partnerImported }, _newDeviceInfo.initiatingKey.privateKey, 256); } }, err => { return reject(err); }) .then(secret => { if (secret){ sharedSecret = secret; if (_role === 'invite'){ return getFlockMembershipCipher(sharedSecret, pk.util.hubInstance.flockDid); } else{ return getFlockMembershipCipher(sharedSecret, pk.util.hubInstance.instanceDid); } } }, err => { return reject(err); }) .then(flockMembershipCipher => { if (flockMembershipCipher){ _newDeviceInfo.flockMembershipCipher = flockMembershipCipher; var sharedSecret16 = new Uint16Array(sharedSecret); resolve(sharedSecret16); } }, err => { reject(err); }) }); } }); Object.defineProperty(this, "conveyFlockMembershipCipher", { value: function(rendezvousId, flockMembershipCipher) { return new Promise((resolve, reject) => { managerNotification('Adding the new hub'); var joinerStatus; var params = createParameterString({ operation: 'convey', role: _role, localDeviceId: pk.util.hubInstance.instanceDid, identifier: rendezvousId, flockMembershipCipher: flockMembershipCipher }); var serviceUrl = window.location.origin; var rendezvousService = serviceUrl + '/tester/rendezvous'; var url = rendezvousService + '?' + params; fetch_func(url) .then(res => { if (res.status !== 200) { reject(new Error(res.statusText)); } else { return res.text(); } }, err => { return reject(err); }) .then(response => { if (response){ // response string has been sent as JSON response = JSON.parse(response); if (_role === 'join'){ return joinExistingFlock(response, _newDeviceInfo.membershipExchangeKey); } else if (_role === 'invite'){ return extendExistingFlock(response, _newDeviceInfo.membershipExchangeKey); } } }, err => { reject(err); }) .then(result => { if (result){ joinerStatus = result; var params = createParameterString({ operation: 'complete', role: _role, localDeviceId: pk.util.hubInstance.instanceDid, identifier: rendezvousId, complete: JSON.stringify(joinerStatus) }); var serviceUrl = window.location.origin; var rendezvousService = serviceUrl + '/tester/rendezvous'; var url = rendezvousService + '?' + params; return fetch_func(url); } }, err => { reject(err); }) .then(res => { if (res.status !== 200) { reject(new Error(res.statusText)); } else { return res.text(); } }, err => { return reject(err); }) .then(response => { if (response){ var responseObj = JSON.parse(response); var message; var opStatus; if (_role === 'invite'){ opStatus = responseObj; } else{ opStatus = joinerStatus; } if (opStatus.error !== undefined){ message = opStatus.error; } if (opStatus.status === 200){ if (_role === 'join'){ managerNotification('Reinitializing this hub with data from your existing hubs'); return syncExistingFlock(); } else{ managerNotification('The new hub has joined the flock and is restarting.'); return true; } } else{ reject("unexpect result from clone " + message); } } }, err => { reject(err); }) .then(syncResult => { if (syncResult){ managerNotification(); window.location.reload(); resolve(true); } }, err => { reject(err); }) }) } }); Object.defineProperty(this, "exportFlockMembershipCipher", { value: function() { return new Promise((resolve, reject) => { resolve(_newDeviceInfo.flockMembershipCipher); }) } }) function getFlockMembershipCipher(sharedSecret, hubMembershipString) { return new Promise((resolve, reject) => { getMembershipExchangeKey(sharedSecret) .then(key => { _newDeviceInfo.membershipExchangeKey = key; return jose.JWE.createEncrypt({format: 'compact' }, key).update(hubMembershipString).final(); }, err => { reject(err); }) .then(cipher => { resolve(cipher); }, err => { reject(err); }) }); } function getMembershipExchangeKey(sharedSecret){ return new Promise((resolve, reject) => { var sharedSecret8 = new Uint8Array(sharedSecret); var oct = sharedSecret8.slice(0, 16); var keyString = pk.base64url.encode(oct); var jwk = { "kty": "oct", "kid": "1544716508360", "use": "enc", "alg": "A128GCM", "k": keyString }; jose.JWK.asKey(JSON.stringify(jwk), 'json') .then(key => { resolve(key); }, err => { reject(err); }) }); } function managerNotification(message, className, dismissable){ var payload = { action: 'managerNotification', message: message, className: className, dismissable: dismissable } pk.util.send_message_to_all_clients(payload); } } function createParameterString(obj){ var result = ''; var separator = ''; for (var key in obj){ result += separator + encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]); separator = '&'; } return result; }