oidc-lib
Version:
A library for creating OIDC Service Providers
1,290 lines (1,182 loc) • 112 kB
JavaScript
"use strict"
Object.defineProperty(exports, "__esModule", { value: true });
const HUBINSTANCEMONIKER = 'hub-instance-info';
const HUBFLOCKMONIKER = 'hub-flock-info';
const HUBFLOCKRECORD = 'hub-flock-record';
const DIDNOTPRESENT = 'did-not-present';
const SUPPRESSENCRYPTIONMONIKER = 'suppress-encryption';
var METADATACOLLECTIONNAME;
const _CONSTRUCTOR = 'hub_provider';
module.exports = {
_constructor = _CONSTRUCTOR,
hub_provider: hub_provider,
loadHubInfo: loadHubInfo,
HUBINSTANCEMONIKER: HUBINSTANCEMONIKER,
HUBFLOCKMONIKER: HUBFLOCKMONIKER,
DIDNOTPRESENT: DIDNOTPRESENT,
flockMembershipFactory: flockMembershipFactory,
enableFlock: enableFlock,
joinExistingFlock: joinExistingFlock,
extendExistingFlock: extendExistingFlock,
syncExistingFlock: syncExistingFlock,
hubExport: hubExport,
simpleResolverFactory: simpleResolverFactory,
backgroundSyncControllerFactory: backgroundSyncControllerFactory,
deviceManagerFactory: deviceManagerFactory
}
var keyManagement = require('../../claimer_sts/key_management');
var indexed_db_module = require('./indexed_db');
var moduleName = 'hub_db';
const HUBKEY = 'HUBKEY';
var resolverCache = {};
var hubConfig;
var simpleResolver;
function hub_provider(){
var pk;
const HttpStatusCodes = { NOTFOUND: 404 };
var indexed_db;
Object.defineProperty(this, "initialize", {
value: function(pkInput, contentModuleName, provider){
return new Promise((resolve, reject) => {
var hubDbInfo;
var hubDbContents = {};
var schemas = [];
var hubInfo;
pk = pkInput;
hubConfig = pk.util.config.hub;
METADATACOLLECTIONNAME = indexed_db_module.METADATACOLLECTIONNAME;
loadHubInfo(HUBINSTANCEMONIKER)
.then(instanceInfo => {
if (instanceInfo){
hubInfo = instanceInfo;
if (hubInfo.instanceDid !== DIDNOTPRESENT && syncInForeground()){
return pk.util.simpleResolver.resolve(hubConfig.did)
}
else{
// by default assume connection
return true;
}
}
}, err => {
reject(err);
})
.then(res => {
if (res){
pk.util.offline = false;
}
}, err => {
pk.util.offline = true;
})
.then(() => {
indexed_db = new indexed_db_module.indexed_db_provider();
return indexed_db.initialize(pkInput, contentModuleName, indexed_db)
})
.then(dbInfo => {
if (dbInfo){
hubDbInfo = {
contentModuleName: dbInfo.contentModuleName,
collections: dbInfo.collections,
databaseDefinition: dbInfo.databaseDefinition,
databaseName: dbInfo.databaseName,
databaseUrl: dbInfo.databaseUrl,
schemaVersion: dbInfo.schemaVersion,
provider: this,
dbInfo: dbInfo
};
hubDbInfo.authenticationBundle = new authenticationBundleFactory(hubInfo);
resolve(hubDbInfo);
}
}, function(err){
pk.util.log_detail('ERROR getting authentication token', err);
})
});
}
})
Object.defineProperty(this, "fullSync", {
value: function(hubDbInfo){
return new Promise((resolve, reject) => {
if (pk.util.offline || !hubDbInfo.authenticationBundle || hubDbInfo.authenticationBundle.instanceDid === undefined){
return resolve(true);
}
var collectionNames = [];
var hubCollectionPromises = [];
var hubDbContents = {};
var localDbContents = {};
if (syncInForeground() === false){
resolve(false);
return;
}
var i=0;
for (var collectionName in hubDbInfo.collections){
collectionNames[i++] = collectionName;
hubCollectionPromises.push(hubDbInfo.provider.queryHubCollection(hubDbInfo, collectionName, {}, { decrypt: false }));
}
Promise.all(hubCollectionPromises)
.then(collectionInfos => {
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;
}
hubDbContents[collectionName] = hubCollection;
}
var promises = [];
//query the underlying indexeddb so it is not decrypted
for (var collectionName in hubDbInfo.collections){
promises.push(hubDbInfo.dbInfo.provider.queryCollection(hubDbInfo.dbInfo, collectionName, {}));
}
return Promise.all(promises);
}, err => {
reject(err);
})
.then(dbCollections => {
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;
}
localDbContents[collectionNames[i]] = localCollection;
}
hubDbInfo.suppressHubUpdate = true;
var localUpdatePromises = []
for (var collection in hubDbContents){
var hubCollection = hubDbContents[collection];
for (var element in hubCollection){
var hubElement = hubCollection[element];
var localElement = localDbContents[collection][element];
if (localElement === undefined || Date.parse(localElement.didMeta.updated_at) < Date.parse(hubElement.didMeta.updated_at)){
localUpdatePromises.push(hubDbInfo.dbInfo.provider.createOrUpdateDocument(hubDbInfo.dbInfo, collection, hubElement, null));
}
}
}
return Promise.all(localUpdatePromises);
}, err => {
reject(err);
})
.then(localUpdateResults => {
hubDbInfo.suppressHubUpdate = false;
var hubUpdatePromises = []
for (var collectionName in localDbContents){
var localCollection = localDbContents[collectionName];
for (var elementId in localCollection){
var element = localCollection[elementId];
if (element.didMeta.object_id === undefined){
hubUpdatePromises.push(updateHub(hubDbInfo, collectionName, element, { encrypt: false }));
}
}
}
return Promise.all(hubUpdatePromises);
}, err => {
hubDbInfo.suppressHubUpdate = false;
reject(err);
})
.then(hubUpdateResults => {
var date = new Date();
var now = date.getTime();
pk.util.sync_info.last_sync_time = now;
resolve(true);
}, err => {
reject(err);
delete hubDbInfo.suppressHubUpdate;
})
});
}
})
Object.defineProperty(this, "createOrUpdateDocument", {
value: function (hubDbInfo, collectionName, objectToWrite) {
return new Promise((resolve, reject) => {
updateHub(hubDbInfo, collectionName, objectToWrite)
.then(function(writeResult){
resolve(objectToWrite);
}, function(err){
reject(err);
})
});
}
});
Object.defineProperty(this, "getDocument", {
value: function (hubDbInfo, collectionName, local_id) {
return new Promise((resolve, reject) => {
indexed_db.getDocument(hubDbInfo.dbInfo, collectionName, local_id)
.then( record => {
return decryptDbRecord(hubDbInfo, collectionName, record);
}, err => {
reject(err);
})
.then( plaintext => {
resolve(plaintext);
}, err => {
reject(err);
})
});
}
});
Object.defineProperty(this, "queryCollection", {
value: function (hubDbInfo, collectionName, queryDictionaryOrString, indexName) {
return new Promise((resolve, reject) => {
var encryptedRecords;
indexed_db.queryCollection(hubDbInfo.dbInfo, collectionName, queryDictionaryOrString, indexName)
.then(function(queryResult){
encryptedRecords = queryResult;
var plaintextPromises = [];
for (var i=0; i < queryResult.length; i++){
var record = queryResult[i];
plaintextPromises.push(decryptDbRecord(hubDbInfo, collectionName, record));
}
return Promise.all(plaintextPromises);
}, function(err){
reject(err);
})
.then(plaintextRecords => {
if (plaintextRecords){
resolve(plaintextRecords);
}
}, err => {
reject(err);
})
});
}
});
Object.defineProperty(this, "deleteDocument", {
value: function(hubDbInfo, collectionName, local_id){
return indexed_db.deleteDocument(hubDbInfo.dbInfo, collectionName, local_id);
}
});
// options: decrypt - when false, do not decrypt payload
// revisions - when true, return revisions
Object.defineProperty(this, "queryHubCollection", {
value: function (hubDbInfo, collectionName, queryDictionary, options) {
var collectionInfo = hubDbInfo.collections[collectionName];
var decodedCommitArray = [];
return new Promise((resolve, reject) => {
var message = {
"@context": "https://schema.identity.foundation/0.1",
"@type": "ObjectQueryRequest",
"iss": hubDbInfo.authenticationBundle.instanceDid,
"aud": hubConfig.did,
"sub": hubDbInfo.authenticationBundle.flockDid,
"query": {
"interface": collectionInfo.hubInterface,
"context": collectionInfo.hubContext,
"type": collectionInfo.hubType
// "object_id": ["3a9de008f526d239..", "a8f3e7..."]
}
}
hubRequest(message, hubDbInfo.authenticationBundle, hubConfig.did)
.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 'ObjectQueryResponse':
return(response.objects);
break;
}
}
}, function(err){
reject(err);
})
.then(objectMetaArray => {
if (objectMetaArray){
var objectIdArray = [];
if (objectMetaArray.length < 1){
// nothing to return
resolve([]);
return;
}
for (var i=0; i < objectMetaArray.length; i++){
var obj = objectMetaArray[i];
objectIdArray.push(obj.id);
}
var message = {
"@context": "https://schema.identity.foundation/0.1",
"@type": "CommitQueryRequest",
"iss": hubDbInfo.authenticationBundle.instanceDid,
"aud": hubConfig.did,
"sub": hubDbInfo.authenticationBundle.flockDid,
"query": {
"object_id": objectIdArray
}
}
return hubRequest(message, hubDbInfo.authenticationBundle, 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 'CommitQueryResponse':
return(response.commits);
break;
}
}
}, function(err){
reject(err);
})
.then(rawCommitArray => {
if (rawCommitArray){
var plaintextPromises = [];
for (var i=0; i < rawCommitArray.length; i++){
var commit = rawCommitArray[i];
commit.protected = JSON.parse(pk.base64url.decode(commit.protected));
commit.payload = JSON.parse(pk.base64url.decode(commit.payload));
decodedCommitArray.push(commit);
if (options && options.decrypt === false){
plaintextPromises.push(commit.payload);
}
else{
plaintextPromises.push(decryptDbRecord(hubDbInfo, collectionName, commit.payload));
}
}
return Promise.all(plaintextPromises);
}
}, err => {
reject(err);
})
.then(plaintextPayloads => {
if (plaintextPayloads){
var hubCollection = {};
for (var i=0; i < plaintextPayloads.length; i++){
var plaintext = plaintextPayloads[i];
var commit = decodedCommitArray[i];
commit.payload = plaintext;
var object_id = commit.header.object_id;
if (object_id === undefined){
object_id = commit.protected.object_id;
}
var hubObject = hubCollection[object_id];
if (hubObject === undefined){
hubObject = {
object_id: object_id,
integral: {},
revisions: []
};
}
hubObject.revisions.push(commit);
hubCollection[object_id] = hubObject;
}
return integrateHubCollection(hubCollection);
}
}, err => {
reject(err);
})
.then(integratedCollection => {
if (integratedCollection){
var hubCollection = [];
for (var key in integratedCollection){
var record = integratedCollection[key].integral;
var matchFound = true;
if (queryDictionary){
for (var prop in queryDictionary){
if (record[prop] !== queryDictionary[prop]){
matchFound = false;
break;
}
}
}
if (matchFound){
// when revisions required substitute
// the integratedCollection for a record
if (options && options.revisions === true){
var ic = integratedCollection[key];
ic.didMeta = {
object_id: ic.object_id
};
hubCollection.push(ic);
}
else{
if (record.didMeta === undefined){
record.didMeta = {};
}
record.didMeta.object_id = key;
var revisions = integratedCollection[key].revisions;
record.didMeta.revision = revisions[revisions.length - 1].header.rev;
hubCollection.push(record);
}
}
}
resolve(hubCollection);
}
}, err => {
reject(err);
})
});
}
});
function integrateHubCollection(hubCollection){
for(var key in hubCollection){
var hubObject = hubCollection[key];
if (hubObject.revisions.length > 0){
hubObject.revisions.sort(compareRevisions);
var assembledObject = hubObject.revisions[hubObject.revisions.length - 1];
var record = assembledObject.payload;
record.didMeta.object_id = hubObject.object_id;
record.didMeta.updated_at = assembledObject.protected.committed_at;
hubObject.integral = record;
}
}
return hubCollection;
}
function compareRevisions(a, b){
return Date.parse(a.protected.committed_at) - Date.parse(b.protected.committed_at);
}
}
//////////////////////////////////////////////////////////////////////////////////////
var fetch_func;
if (typeof window === 'undefined'){
const node_fetch_1 = __importDefault(require("node-fetch"));
fetch_func = node_fetch_1;
}
else {
fetch_func = fetch_proxy;
}
function fetch_proxy(request, options){
return fetch (request, options);
}
function loadHubInfo(moniker, options, initializedPk){
var createIfAbsent = false;
var generateDid = false;
var delta = false;
if (options){
if (options.createIfAbsent){
createIfAbsent = true;
}
if (options.generateDid){
generateDid = true;
}
}
return new Promise((resolve, reject) => {
var privateKeys = [];
var updatedInfo;
var hubInfo = new hubInfoFactory(moniker);
hubInfo.load()
.then(() => {
if (hubInfo.privateKeys !== undefined){
return Promise.resolve(true);
}
else if (createIfAbsent === false){
return resolve(hubInfo);
}
else{
delta = true;
var currentKeyRoot = 'key-';
var currentKeyCount = 0;
var currentKeyId;
var genParams;
var keystore = pk.jose.JWK.createKeyStore();
var date = new Date();
currentKeyId = currentKeyRoot + (currentKeyCount++).toString();
genParams = {
alg: 'RS256',
key_ops: ['sign', 'verify'],
kid: currentKeyId
};
return keystore.generate("RSA", 2048, genParams);
}
}, err => {
reject(err);
})
.then( key => {
if (key !== undefined){
if (key === true){
privateKeys = hubInfo.privateKeys;
}
else{
privateKeys.push(key.toJSON(true));
}
if (hubInfo.instanceDid && hubInfo.instanceDid !== DIDNOTPRESENT){
return hubInfo.instanceDid;
}
else{
if (generateDid === false){
return DIDNOTPRESENT;
}
else {
if (initializedPk === undefined){
initializedPk = pk;
}
delta = true;
return genDid(initializedPk, privateKeys);
}
}
}
}, function(err){
reject(err);
})
.then(did => {
if (did){
var noDid;
if (did === true){
did = noDid;
}
if (!delta){
resolve(hubInfo);
return;
}
else{
updatedInfo = new hubInfoFactory(moniker, privateKeys, did, hubInfo.flockDid);
return updatedInfo.load();
}
}
}, err => {
reject(err);
})
.then(doUpdated => {
if (doUpdated){
return updatedInfo.save();
}
}, err => {
reject(err);
})
.then(updateSaved => {
if (updateSaved){
resolve(updatedInfo);
}
}, err => {
reject(err);
})
});
}
function simpleResolverFactory(discoveryEndpoint){
//TODO: the version should be in the config
var _endpointPath = discoveryEndpoint + '/1.0/identifiers/';
var _resolveCache = {};
Object.defineProperty(this, "resolve", {
value: function(did) {
return new Promise((resolve, reject) => {
if (!did){
reject('resolver invoked with undefined or empty did');
}
var cacheHit = resolverCache[did];
if (cacheHit) {
return resolve(cacheHit);
}
fetch_func(_endpointPath + did)
.then(function(response){
return response.json();
}, function(err){
reject(err);
})
.then(function(result){
if (result){
var didDocument = result.document;
if (!didDocument.publicKey) {
reject ('Could not find public keys for ' + recipient);
return;
}
var jwk = didDocument.publicKey[0].publicKeyJwk;
result.verify = cloneJwk(jwk, { key_ops: ['verify'], alg: 'RS256' });
result.encrypt = cloneJwk(jwk, { key_ops: ['encrypt'], alg: 'RSA-OAEP' });
resolverCache[did] = result;
return resolve(result);
}
}, function(err){
reject(err);
})
})
}
});
}
function shakespeareHandler(){
if ('caches' in window) {
/*
* Check if the service worker has already cached this city's weather
* data. If the service worker has the data, then display the cached
* data while the app fetches the latest data.
*/
caches.match(url).then(function(response) {
if (response) {
response.json().then(function updateFromCache(json) {
var results = json.query.results;
results.key = key;
results.label = label;
results.created = json.query.created;
app.updateForecastCard(results);
});
}
});
}
}
function cloneJwk(jwk, delta){
var clone = {};
for(var prop in jwk){
if (delta[prop] === undefined){
clone[prop] = jwk[prop];
}
}
for (var prop in delta){
clone[prop] = delta[prop];
}
return clone;
}
function toPublic(jwk){
if (jwk.kty !== 'RSA'){
throw 'toPublic only supports RSA';
}
var publicKey = {
alg: jwk.alg,
e: jwk.e,
key_ops: jwk.key_ops,
kid: jwk.kid,
kty: jwk.kty,
n: jwk.n
}
return publicKey;
}
function backgroundSyncControllerFactory(){
const SYNC_TIMER_INTERLUDE = 2; // interval between sync of collections in seconds
const RESYNC_INTERLUDE = 4 * 3600; // resync interlude in seconds
const UPDATE_HUB_INTERLUDE = 60;
const _resyncThreshold = (RESYNC_INTERLUDE) / SYNC_TIMER_INTERLUDE; // 30 => 5 minutes
const _beginningDelay = (6 / SYNC_TIMER_INTERLUDE) + 1;
const _updateHubDelay = (UPDATE_HUB_INTERLUDE/SYNC_TIMER_INTERLUDE) + 1;
const MODE_SYNCHRONIZING = 1;
const MODE_WAITING = 0;
var _syncQueue = [];
var _appRegistrations = {};
var _syncIndex = 0;
var _interval = 0;
var _timer;
var _mode = MODE_WAITING;
var _resyncCounter = 0;
var _updateHubThreshold = _resyncThreshold;
var _checkForDeltas = false;
var _inTick = false;
Object.defineProperty(this, "syncQueue", {
get: function() {
return _syncQueue;
},
enumerable: true
});
Object.defineProperty(this, "registerAppCallback", {
value: function(collectionName, callback) {
_appRegistrations[collectionName] = callback;
}
});
Object.defineProperty(this, "begin", {
value: function() {
_syncIndex = 0;
_interval = SYNC_TIMER_INTERLUDE * 1000;
_timer = setInterval(tick, _interval);
_resyncCounter = _resyncThreshold - _beginningDelay;
}
});
function tick(){
var syncStart;
var syncEnd;
var syncDuration;
var taskId;
var task;
var syncConfigPromise;
if (pk.util.offline || pk.util.hubInstance.instanceDid === DIDNOTPRESENT){
return;
}
if (_inTick){
return;
}
else{
_inTick = true;
}
if (_mode === MODE_WAITING){
if (_resyncCounter < _resyncThreshold){
if (_updateHubThreshold === 0 || _resyncCounter++ < _updateHubThreshold){
_inTick = false;
return;
}
else{
_updateHubThreshold = 0;
_syncQueue = [];
_syncIndex = 0;
for (var key in pk.dbs){
var dbInfo = pk.dbs[key];
for (var collectionName in dbInfo.collections){
var collectionInfo = dbInfo.collections[collectionName];
if (collectionInfo.highWater > 0){
_syncQueue.push({db: key, collection: collectionName, status: null, syncDuration: 0});
collectionInfo.highWater = 0;
}
}
}
if (_syncQueue.length > 0){
syncConfigPromise = Promise.resolve('SyncLocalChanges');
_mode = MODE_SYNCHRONIZING;
}
else{
var regInfo = {
flockDid: pk.util.hubInstance.flockDid,
instanceDid: pk.util.hubInstance.instanceDid
};
syncConfigPromise = syncStateReceiver(regInfo);
}
}
}
else{
_syncQueue = [];
_syncIndex = 0;
for (var key in pk.dbs){
var dbInfo = pk.dbs[key];
for (var collectionName in dbInfo.collections){
_syncQueue.push({db: key, collection: collectionName, status: null, syncDuration: 0});
}
}
_resyncCounter = 0;
syncConfigPromise = Promise.resolve('SyncAllCollections')
_mode = MODE_SYNCHRONIZING;
}
}
else{
syncConfigPromise = Promise.resolve('SyncInProgress');
}
syncConfigPromise.then(syncControl => {
if (syncControl){
if (typeof syncControl === 'object'){
if (syncControl.delta){
for (var key in pk.dbs){
var dbInfo = pk.dbs[key];
for (var collectionName in dbInfo.collections){
if (syncControl.delta[collectionName] !== undefined){
_syncQueue.push({db: key, collection: collectionName, status: null, syncDuration: 0});
}
}
}
if (_syncQueue.length > 0){
syncControl = 'SyncRemoteDeltas';
_mode = MODE_SYNCHRONIZING;
}
else {
_updateHubThreshold = _resyncCounter + _updateHubDelay;
return;
}
}
}
return syncControl;
}
}, err => {
reject(err);
})
.then(processSync => {
if (processSync){
task = _syncQueue[_syncIndex];
taskId = _syncIndex;
syncStart = new Date().getTime();
var registered = _appRegistrations[_syncQueue[taskId].collection];
return syncCollection(task, registered);
}
}, err => {
reject(err);
})
.then(result => {
if (typeof result === 'number'){
syncEnd = new Date().getTime();
syncDuration = syncEnd - syncStart;
_syncQueue[taskId] = {db: task.db, collection: task.collection, status: result, syncDuration: syncDuration};
_syncIndex++;
if (_syncIndex >= _syncQueue.length){
_mode = MODE_WAITING;
var changeInfo = {
flockDid: pk.util.hubInstance.flockDid,
instanceDid: pk.util.hubInstance.instanceDid,
changeInfo: {
highWater: {}
}
};
for (var key in _syncQueue){
var info = _syncQueue[key];
changeInfo.changeInfo.highWater[info.collection] = info.status;
}
// sync cycle is complete
pk.util.sync_info.last_sync_time = syncEnd;
return syncStateSender(changeInfo);
}
}
}, err => {
syncEnd = new Date().getTime();
syncDuration = syncEnd - syncStart;
_syncQueue[_syncIndex] = {db: task.db, collection: task.collection, status: err, syncDuration: syncDuration};
_mode = MODE_WAITING;
})
.then(sendResult => {
if (sendResult){
_updateHubThreshold = _resyncCounter + _updateHubDelay;
}
_inTick = false;
}, err => {
pk.util.log_detail("Tick Error so increasing hub delay: ", err);
_updateHubThreshold = _resyncCounter + (_updateHubDelay *10);
_inTick = false;
})
}
function syncStateSender(stateObj){
return new Promise((resolve, reject) => {
var requestString = JSON.stringify(stateObj);
fetch_func(hubConfig.endpoints.changeNotifications, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': requestString.length.toString()
},
body: requestString
})
.then(result => {
if (result){
if (result.status !== 200) {
var msg = 'syncStateSender returns ' + result.status.toString() + ': ' + result.statusText;
reject(msg);
}
else {
resolve(result.json());
}
}
}, err => {
reject(err);
})
});
}
function syncStateReceiver(stateObj){
return new Promise((resolve, reject) => {
var uri = hubConfig.endpoints.changeNotifications + pk.util.createParameterString(stateObj);
fetch_func(uri)
.then(result => {
if (result){
if (result.status !== 200) {
var msg = 'syncStateReceiver returns ' + result.status.toString() + ': ' + result.statusText;
pk.util.log_debug(msg);
}
else {
return result.json();
}
}
}, err => {
reject(err);
pk.util.log_detail('Error retrieving changeNotification', err);
})
.then(syncResponse => {
if (syncResponse){
resolve(syncResponse);
}
}, err => {
reject(err);
pk.util.log_detail('Error retrieving syncResponse', err);
})
});
}
}
function syncCollection(task, registered){
var hubDbInfo = pk.dbs[task.db];
var changePromises = [];
var highWater = 0;
return new Promise((resolve, reject) => {
if (pk.util.offline || !hubDbInfo.authenticationBundle || hubDbInfo.authenticationBundle.instanceDid === undefined){
return resolve(true);
}
var collectionName = task.collection;
var keyPath = hubDbInfo.collections[collectionName].keyPath;
var hubCollection = {};
var localCollection = {};
hubDbInfo.provider.queryHubCollection(hubDbInfo, collectionName, {}, { decrypt: false })
.then(collectionElements => {
for (var j=0; j < collectionElements.length; j++){
var hubElement = collectionElements[j];
hubCollection[hubElement.didMeta.object_id] = hubElement;
var upd = Date.parse(hubElement.didMeta.updated_at);
if (upd > highWater){
highWater = upd;
}
}
return hubDbInfo.dbInfo.provider.queryCollection(hubDbInfo.dbInfo, collectionName, {});
}, err => {
reject(err);
})
.then(dbCollection => {
var sync_id;
var unsynced_count = 0;
for (var j=0; j < dbCollection.length; j++){
var localElement = dbCollection[j];
var upd = Date.parse(localElement.didMeta.updated_at);
if (upd > highWater){
highWater = upd;
}
if (localElement.didMeta.object_id === undefined){
sync_id = '_' + (unsynced_count++).toString();
}
else{
sync_id = localElement.didMeta.object_id;
}
localCollection[sync_id] = localElement;
}
var localUpdatePromises = [];
hubDbInfo.suppressHubUpdate = true;
for (var element in hubCollection){
var hubElement = hubCollection[element];
var localElement = localCollection[element];
if (localElement === undefined || Date.parse(localElement.didMeta.updated_at) < Date.parse(hubElement.didMeta.updated_at)){
if (registered){
changePromises.push(decryptDbRecord(hubDbInfo, collectionName, hubElement));
}
localUpdatePromises.push(hubDbInfo.dbInfo.provider.createOrUpdateDocument(hubDbInfo.dbInfo, collectionName, hubElement));
}
}
return Promise.all(localUpdatePromises);
}, err => {
reject(err);
})
.then(localUpdateResults => {
hubDbInfo.suppressHubUpdate = false;
var hubUpdatePromises = []
for (var elementId in localCollection){
var element = localCollection[elementId];
if (element.didMeta.object_id === undefined){
if (registered){
changePromises.push(decryptDbRecord(hubDbInfo, collectionName, element));
}
hubUpdatePromises.push(updateHub(hubDbInfo, collectionName, element, { encrypt: false }));
}
}
return Promise.all(hubUpdatePromises);
}, err => {
hubDbInfo.suppressHubUpdate = false;
reject(err);
})
.then(hubUpdateResults => {
if (hubUpdateResults){
return Promise.all(changePromises)
}
}, err => {
reject(err);
})
.then(changes => {
if (changes){
if (registered && changes.length > 0){
registered(changes);
}
resolve(highWater);
}
}, err => {
reject(err);
})
});
}
function hubInfoFactory(personaId, privateKeys, instanceDid, flockDid){
var _personaId = personaId;
var _instanceDid = instanceDid;
var _flockDid = flockDid;
var _privateKeys = privateKeys;
var _kidRoot, _signKey, _verifyKey, _encryptKey, _decryptKey;
Object.defineProperty(this, "load", {
value: function() {
return new Promise((resolve, reject) => {
const use_new_key = '**USENEWKEY**';
var storagePromise;
if (_privateKeys){
storagePromise = Promise.resolve(use_new_key);
}
else{
storagePromise = pk.util.local_store.get(personaId, 'utf-8');
}
storagePromise
.then(instanceState => {
if (instanceState === undefined){ // no previous storage
resolve();
return;
}
else if (instanceState === use_new_key){
return true;
}
else {
var hubInstanceStorage = JSON.parse(instanceState);
_instanceDid = hubInstanceStorage.instanceDid;
_flockDid = hubInstanceStorage.flockDid;
_privateKeys = hubInstanceStorage.privateKeys;
return true;
}
}, err => {
reject(err);
})
.then(setUpKeys => {
if (setUpKeys){
_kidRoot = _instanceDid + "#";
// set up _privateSigningKey
var jwk = _privateKeys[0];
var instanceKid = _kidRoot + jwk.kid;
_signKey = cloneJwk(jwk, { alg: 'RS256', key_ops: ["sign"], kid: instanceKid })
_verifyKey = cloneJwk(toPublic(jwk), { alg: 'RS256', key_ops: ["verify"], kid: instanceKid });
_decryptKey = cloneJwk(jwk, { alg: 'RSA-OAEP', key_ops: ["decrypt"], kid: instanceKid })
_encryptKey = cloneJwk(toPublic(jwk), { alg: 'RSA-OAEP', key_ops: ["encrypt"], kid: instanceKid });
resolve(true);
}
}, err => {
reject(err);
})
})
}
});
Object.defineProperty(this, "personaId", {
get: function() {
return _personaId;
},
enumerable: true
});
Object.defineProperty(this, "instanceDid", {
get: function() {
return _instanceDid;
},
set: function(value) {
_instanceDid = value;
},
enumerable: true
});
Object.defineProperty(this, "flockDid", {
get: function() {
return _flockDid;
},
set: function(value) {
_flockDid = value;
},
enumerable: true
});
Object.defineProperty(this, "privateKeys", {
get: function() {
return _privateKeys;
},
enumerable: false
});
Object.defineProperty(this, "signKey", {
get: function() {
return _signKey;
},
enumerable: false
});
Object.defineProperty(this, "verifyKey", {
get: function() {
return _verifyKey;
},
enumerable: false
});
Object.defineProperty(this, "decryptKey", {
get: function() {
return _decryptKey;
},
enumerable: false
});
Object.defineProperty(this, "encryptKey", {
get: function() {
return _encryptKey;
},
enumerable: false
});
Object.defineProperty(this, "save", {
value: function(changedPersonaId) {
return new Promise((resolve, reject) => {
if (changedPersonaId){
_personaId = changedPersonaId;
}
var saveBundle = {
instanceDid: _instanceDid,
flockDid: _flockDid,
personaId: _personaId,
privateKeys: _privateKeys
}
pk.util.local_store.set(_personaId, JSON.stringify(saveBundle))
.then(result => {
if (result){
resolve(true);
}
}, err => {
reject(err);
})
})
}
});
}
/////////////////////////////////////////////////////////
function hubRequest(hubMessage, authenticationBundle, hubDid){
return new Promise((resolve, reject) => {
var hubMessageString = JSON.stringify(hubMessage);
var hubRequestor = new hubRequestorFactory();
hubRequestor.execute(hubMessageString, authenticationBundle, hubDid)
.then(function(response){
resolve(response);
}, function(err){
reject(err);
})
});
}
function hubRequestorFactory() {
var _decryptKey;
Object.defineProperty(this, "execute", {
value: function(message, authenticationBundle, partnerDid) {
return new Promise((resolve, reject) => {
_decryptKey = authenticationBundle.decryptKey;
authenticationBundle.getAccessToken(partnerDid)
.then(accessToken => {
if (accessToken){
return getAuthenticatedRequest(message, authenticationBundle.signKey, partnerDid, accessToken);
}
}, err => {
reject(err);
})
.then(authenticatedRequest => {
return completeRequest(authenticatedRequest, partnerDid);
}, err => {
reject(err);
})
.then(verifiedPayload => {
resolve(verifiedPayload);
}, err => {
reject(err);
})
})
}
});
Object.defineProperty(this, "getAccessToken", {
value: function(instanceInfo, partnerDid) {
return new Promise((resolve, reject) => {
_decryptKey= instanceInfo.decryptKey;
getAuthenticatedRequest('', instanceInfo.signKey, partnerDid, null)
.then(authenticatedRequest => {
return completeRequest(authenticatedRequest, partnerDid);
}, err => {
reject(err);
})
.then(verifiedPayload => {
resolve(verifiedPayload);
}, err => {
reject(err);
})
});
}
});
function completeRequest(requestString, partnerDid){
return new Promise((resolve, reject) => {
fetch_func(hubConfig.endpoints.hub, {
method: 'POST',
headers: {
'Content-Type': 'application/jose',