oidc-lib
Version:
A library for creating OIDC Service Providers
1,325 lines (1,228 loc) • 47.5 kB
JavaScript
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 = pk.util.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 = pk.util.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 = pk.util.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);
}
}