Async SF API

This commit is contained in:
Mo Bitar
2018-05-21 21:26:36 -05:00
parent 367fbeed20
commit f43186d3e1
16 changed files with 180 additions and 176 deletions

View File

@@ -1,3 +1,9 @@
{ {
"presets": ["env"] "presets": ["env"],
"plugins": [
["transform-runtime", {
"polyfill": false,
"regenerator": true
}]
]
} }

View File

@@ -13,7 +13,7 @@ class AccountMenu {
$timeout, storageManager, $compile, archiveManager) { $timeout, storageManager, $compile, archiveManager) {
'ngInject'; 'ngInject';
$scope.formData = {mergeLocal: true, url: syncManager.serverURL, ephemeral: false}; $scope.formData = {mergeLocal: true, url: syncManager.serverURL, ephemeral: false, email: "a@bitar.io", user_password: "password"};
$scope.user = authManager.user; $scope.user = authManager.user;
$scope.server = syncManager.serverURL; $scope.server = syncManager.serverURL;
@@ -262,26 +262,28 @@ class AccountMenu {
}.bind(this) }.bind(this)
if(data.auth_params) { if(data.auth_params) {
SFJS.crypto.computeEncryptionKeysForUser(password, data.auth_params, (keys) => { SFJS.crypto.computeEncryptionKeysForUser(password, data.auth_params).then((keys) => {
try { try {
SFItemTransformer.decryptMultipleItems(data.items, keys, false); /* throws = false as we don't want to interrupt all decryption if just one fails */ SFJS.itemTransformer.decryptMultipleItems(data.items, keys, false) /* throws = false as we don't want to interrupt all decryption if just one fails */
// delete items enc_item_key since the user's actually key will do the encrypting once its passed off .then(() => {
data.items.forEach(function(item){ // delete items enc_item_key since the user's actually key will do the encrypting once its passed off
item.enc_item_key = null; data.items.forEach(function(item){
item.auth_hash = null; item.enc_item_key = null;
}); item.auth_hash = null;
});
var errorCount = 0; var errorCount = 0;
// Don't import items that didn't decrypt properly // Don't import items that didn't decrypt properly
data.items = data.items.filter(function(item){ data.items = data.items.filter(function(item){
if(item.errorDecrypting) { if(item.errorDecrypting) {
errorCount++; errorCount++;
return false; return false;
} }
return true; return true;
})
onDataReady(errorCount);
}) })
onDataReady(errorCount);
} }
catch (e) { catch (e) {
console.log("Error decrypting", e); console.log("Error decrypting", e);

View File

@@ -174,7 +174,7 @@ class PasswordWizard {
// Ensure value for current password matches what's saved // Ensure value for current password matches what's saved
let authParams = authManager.getAuthParams(); let authParams = authManager.getAuthParams();
let password = $scope.formData.currentPassword; let password = $scope.formData.currentPassword;
SFJS.crypto.computeEncryptionKeysForUser(password, authParams, (keys) => { SFJS.crypto.computeEncryptionKeysForUser(password, authParams).then((keys) => {
let success = keys.mk === authManager.keys().mk; let success = keys.mk === authManager.keys().mk;
if(success) { if(success) {
this.currentServerPw = keys.pw; this.currentServerPw = keys.pw;
@@ -202,7 +202,10 @@ class PasswordWizard {
let currentServerPw = this.currentServerPw; let currentServerPw = this.currentServerPw;
SFJS.crypto.generateInitialEncryptionKeysForUser(authManager.user.email, newUserPassword, (newKeys, newAuthParams) => { SFJS.crypto.generateInitialEncryptionKeysForUser(authManager.user.email, newUserPassword).then((results) => {
let newKeys = results.newKeys;
let newAuthParams = results.newAuthParams;
// perform a sync beforehand to pull in any last minutes changes before we change the encryption key (and thus cant decrypt new changes) // perform a sync beforehand to pull in any last minutes changes before we change the encryption key (and thus cant decrypt new changes)
syncManager.sync((response) => { syncManager.sync((response) => {
authManager.changePassword(currentServerPw, newKeys, newAuthParams, (response) => { authManager.changePassword(currentServerPw, newKeys, newAuthParams, (response) => {

View File

@@ -9,7 +9,7 @@ class Item {
this.observers = []; this.observers = [];
if(!this.uuid) { if(!this.uuid) {
this.uuid = SFJS.crypto.generateUUID(); this.uuid = SFJS.crypto.generateUUIDSync();
} }
} }

View File

@@ -3,34 +3,35 @@ class ItemParams {
constructor(item, keys, version) { constructor(item, keys, version) {
this.item = item; this.item = item;
this.keys = keys; this.keys = keys;
this.version = version || SFJS.crypto.version(); this.version = version || SFJS.version();
} }
paramsForExportFile(includeDeleted) { async paramsForExportFile(includeDeleted) {
this.additionalFields = ["updated_at"]; this.additionalFields = ["updated_at"];
this.forExportFile = true; this.forExportFile = true;
if(includeDeleted) { if(includeDeleted) {
return this.__params(); return this.__params();
} else { } else {
return _.omit(this.__params(), ["deleted"]); var result = await this.__params();
return _.omit(result, ["deleted"]);
} }
} }
paramsForExtension() { async paramsForExtension() {
return this.paramsForExportFile(); return this.paramsForExportFile();
} }
paramsForLocalStorage() { async paramsForLocalStorage() {
this.additionalFields = ["updated_at", "dirty", "errorDecrypting"]; this.additionalFields = ["updated_at", "dirty", "errorDecrypting"];
this.forExportFile = true; this.forExportFile = true;
return this.__params(); return this.__params();
} }
paramsForSync() { async paramsForSync() {
return this.__params(); return this.__params();
} }
__params() { async __params() {
console.assert(!this.item.dummy, "Item is dummy, should not have gotten here.", this.item.dummy) console.assert(!this.item.dummy, "Item is dummy, should not have gotten here.", this.item.dummy)
@@ -39,7 +40,7 @@ class ItemParams {
// Items should always be encrypted for export files. Only respect item.doNotEncrypt for remote sync params. // Items should always be encrypted for export files. Only respect item.doNotEncrypt for remote sync params.
var doNotEncrypt = this.item.doNotEncrypt() && !this.forExportFile; var doNotEncrypt = this.item.doNotEncrypt() && !this.forExportFile;
if(this.keys && !doNotEncrypt) { if(this.keys && !doNotEncrypt) {
var encryptedParams = SFItemTransformer.encryptItem(this.item, this.keys, this.version); var encryptedParams = await SFJS.itemTransformer.encryptItem(this.item, this.keys, this.version);
_.merge(params, encryptedParams); _.merge(params, encryptedParams);
if(this.version !== "001") { if(this.version !== "001") {
@@ -47,7 +48,7 @@ class ItemParams {
} }
} }
else { else {
params.content = this.forExportFile ? this.item.createContentJSONFromProperties() : "000" + SFJS.crypto.base64(JSON.stringify(this.item.createContentJSONFromProperties())); params.content = this.forExportFile ? this.item.createContentJSONFromProperties() : "000" + await SFJS.crypto.base64(JSON.stringify(this.item.createContentJSONFromProperties()));
if(!this.forExportFile) { if(!this.forExportFile) {
params.enc_item_key = null; params.enc_item_key = null;
params.auth_hash = null; params.auth_hash = null;

View File

@@ -60,17 +60,18 @@ class ActionsManager {
switch (action.verb) { switch (action.verb) {
case "get": { case "get": {
this.httpManager.getAbsolute(action.url, {}, function(response){ this.httpManager.getAbsolute(action.url, {}, (response) => {
action.error = false; action.error = false;
var items = response.items || [response.item]; var items = response.items || [response.item];
SFItemTransformer.decryptMultipleItems(items, this.authManager.keys()); SFJS.itemTransformer.decryptMultipleItems(items, this.authManager.keys()).then(() => {
items = this.modelManager.mapResponseItemsToLocalModels(items, ModelManager.MappingSourceRemoteActionRetrieved); items = this.modelManager.mapResponseItemsToLocalModels(items, ModelManager.MappingSourceRemoteActionRetrieved);
for(var item of items) { for(var item of items) {
item.setDirty(true); item.setDirty(true);
} }
this.syncManager.sync(null); this.syncManager.sync(null);
customCallback({items: items}); customCallback({items: items});
}.bind(this), function(response){ })
}, (response) => {
action.error = true; action.error = true;
customCallback(null); customCallback(null);
}) })
@@ -80,13 +81,13 @@ class ActionsManager {
case "render": { case "render": {
this.httpManager.getAbsolute(action.url, {}, function(response){ this.httpManager.getAbsolute(action.url, {}, (response) => {
action.error = false; action.error = false;
SFItemTransformer.decryptItem(response.item, this.authManager.keys()); SFJS.itemTransformer.decryptItem(response.item, this.authManager.keys()).then(() => {
var item = this.modelManager.createItem(response.item, true /* Dont notify observers */); var item = this.modelManager.createItem(response.item, true /* Dont notify observers */);
customCallback({item: item}); customCallback({item: item});
})
}.bind(this), function(response){ }, (response) => {
action.error = true; action.error = true;
customCallback(null); customCallback(null);
}) })
@@ -102,22 +103,15 @@ class ActionsManager {
} }
case "post": { case "post": {
var params = {}; this.outgoingParamsForItem(item, extension, decrypted).then((itemParams) => {
var params = {
items: [itemParams] // Wrap it in an array
}
if(action.all) { this.performPost(action, extension, params, function(response){
var items = this.modelManager.allItemsMatchingTypes(action.content_types); customCallback(response);
params.items = items.map(function(item){ });
var params = this.outgoingParamsForItem(item, extension, decrypted); })
return params;
}.bind(this))
} else {
params.items = [this.outgoingParamsForItem(item, extension, decrypted)];
}
this.performPost(action, extension, params, function(response){
customCallback(response);
});
break; break;
} }
@@ -130,7 +124,7 @@ class ActionsManager {
action.lastExecuted = new Date(); action.lastExecuted = new Date();
} }
outgoingParamsForItem(item, extension, decrypted = false) { async outgoingParamsForItem(item, extension, decrypted = false) {
var keys = this.authManager.keys(); var keys = this.authManager.keys();
if(decrypted) { if(decrypted) {
keys = null; keys = null;

View File

@@ -24,22 +24,23 @@ class ArchiveManager {
protocolVersion = this.authManager.protocolVersion(); protocolVersion = this.authManager.protocolVersion();
} }
} }
var data = this.__itemsData(keys, authParams, protocolVersion); this.__itemsData(keys, authParams, protocolVersion).then((data) => {
this.__downloadData(data, `SN Archive - ${new Date()}.txt`); this.__downloadData(data, `SN Archive - ${new Date()}.txt`);
// download as zipped plain text files // download as zipped plain text files
if(!keys) { if(!keys) {
var notes = this.modelManager.allItemsMatchingTypes(["Note"]); var notes = this.modelManager.allItemsMatchingTypes(["Note"]);
this.__downloadZippedNotes(notes); this.__downloadZippedNotes(notes);
} }
})
} }
/* /*
Private Private
*/ */
__itemsData(keys, authParams, protocolVersion) { async __itemsData(keys, authParams, protocolVersion) {
let data = this.modelManager.getAllItemsJSONData(keys, authParams, protocolVersion); let data = await this.modelManager.getAllItemsJSONData(keys, authParams, protocolVersion);
let blobData = new Blob([data], {type: 'text/json'}); let blobData = new Blob([data], {type: 'text/json'});
return blobData; return blobData;
} }

View File

@@ -83,37 +83,8 @@ angular.module('app')
} }
} }
this.costMinimumForVersion = function(version) {
// all current versions have a min of 3000
// future versions will increase this
return SFJS.crypto.costMinimumForVersion(version);
}
this.isProtocolVersionSupported = function(version) { this.isProtocolVersionSupported = function(version) {
return SFJS.crypto.supportedVersions().includes(version); return SFJS.supportedVersions().includes(version);
}
/* Upon sign in to an outdated version, the user will be presented with an alert requiring them to confirm
understanding they are signing in with an older version of the protocol, and must upgrade immediately after completing sign in.
*/
this.isProtocolVersionOutdated = function(version) {
// YYYY-MM-DD
let expirationDates = {
"001" : Date.parse("2018-01-01"),
"002" : Date.parse("2019-06-01"),
}
let date = expirationDates[version];
if(!date) {
// No expiration date, is active version
return false;
}
let expired = new Date() > date;
return expired;
}
this.supportsPasswordDerivationCost = function(cost) {
return SFJS.crypto.supportsPasswordDerivationCost(cost);
} }
this.getAuthParamsForEmail = function(url, email, extraParams, callback) { this.getAuthParamsForEmail = function(url, email, extraParams, callback) {
@@ -130,7 +101,7 @@ angular.module('app')
} }
this.login = function(url, email, password, ephemeral, strictSignin, extraParams, callback) { this.login = function(url, email, password, ephemeral, strictSignin, extraParams, callback) {
this.getAuthParamsForEmail(url, email, extraParams, function(authParams){ this.getAuthParamsForEmail(url, email, extraParams, (authParams) => {
// SF3 requires a unique identifier in the auth params // SF3 requires a unique identifier in the auth params
authParams.identifier = email; authParams.identifier = email;
@@ -147,7 +118,7 @@ angular.module('app')
if(!this.isProtocolVersionSupported(authParams.version)) { if(!this.isProtocolVersionSupported(authParams.version)) {
var message; var message;
if(SFJS.crypto.isVersionNewerThanLibraryVersion(authParams.version)) { if(SFJS.isVersionNewerThanLibraryVersion(authParams.version)) {
// The user has a new account type, but is signing in to an older client. // The user has a new account type, but is signing in to an older client.
message = "This version of the application does not support your newer account type. Please upgrade to the latest version of Standard Notes to sign in."; message = "This version of the application does not support your newer account type. Please upgrade to the latest version of Standard Notes to sign in.";
} else { } else {
@@ -158,14 +129,14 @@ angular.module('app')
return; return;
} }
if(this.isProtocolVersionOutdated(authParams.version)) { if(SFJS.isProtocolVersionOutdated(authParams.version)) {
let message = `The encryption version for your account, ${authParams.version}, is outdated and requires upgrade. You may proceed with login, but are advised to follow prompts for Security Updates once inside. Please visit standardnotes.org/help/security for more information.\n\nClick 'OK' to proceed with login.` let message = `The encryption version for your account, ${authParams.version}, is outdated and requires upgrade. You may proceed with login, but are advised to follow prompts for Security Updates once inside. Please visit standardnotes.org/help/security for more information.\n\nClick 'OK' to proceed with login.`
if(!confirm(message)) { if(!confirm(message)) {
return; return;
} }
} }
if(!this.supportsPasswordDerivationCost(authParams.pw_cost)) { if(!SFJS.supportsPasswordDerivationCost(authParams.pw_cost)) {
let message = "Your account was created on a platform with higher security capabilities than this browser supports. " + let message = "Your account was created on a platform with higher security capabilities than this browser supports. " +
"If we attempted to generate your login keys here, it would take hours. " + "If we attempted to generate your login keys here, it would take hours. " +
"Please use a browser with more up to date security capabilities, like Google Chrome or Firefox, to log in." "Please use a browser with more up to date security capabilities, like Google Chrome or Firefox, to log in."
@@ -173,7 +144,7 @@ angular.module('app')
return; return;
} }
var minimum = this.costMinimumForVersion(authParams.version); var minimum = SFJS.costMinimumForVersion(authParams.version);
if(authParams.pw_cost < minimum) { if(authParams.pw_cost < minimum) {
let message = "Unable to login due to insecure password parameters. Please visit standardnotes.org/help/security for more information."; let message = "Unable to login due to insecure password parameters. Please visit standardnotes.org/help/security for more information.";
callback({error: {message: message}}); callback({error: {message: message}});
@@ -182,7 +153,7 @@ angular.module('app')
if(strictSignin) { if(strictSignin) {
// Refuse sign in if authParams.version is anything but the latest version // Refuse sign in if authParams.version is anything but the latest version
var latestVersion = SFJS.crypto.version(); var latestVersion = SFJS.version();
if(authParams.version !== latestVersion) { if(authParams.version !== latestVersion) {
let message = `Strict sign in refused server sign in parameters. The latest security version is ${latestVersion}, but your account is reported to have version ${authParams.version}. If you'd like to proceed with sign in anyway, please disable strict sign in and try again.`; let message = `Strict sign in refused server sign in parameters. The latest security version is ${latestVersion}, but your account is reported to have version ${authParams.version}. If you'd like to proceed with sign in anyway, please disable strict sign in and try again.`;
callback({error: {message: message}}); callback({error: {message: message}});
@@ -190,24 +161,25 @@ angular.module('app')
} }
} }
SFJS.crypto.computeEncryptionKeysForUser(password, authParams, function(keys){ SFJS.crypto.computeEncryptionKeysForUser(password, authParams).then((keys) => {
var requestUrl = url + "/auth/sign_in"; var requestUrl = url + "/auth/sign_in";
var params = _.merge({password: keys.pw, email: email}, extraParams); var params = _.merge({password: keys.pw, email: email}, extraParams);
httpManager.postAbsolute(requestUrl, params, function(response){
httpManager.postAbsolute(requestUrl, params, (response) => {
this.setEphemeral(ephemeral); this.setEphemeral(ephemeral);
this.handleAuthResponse(response, email, url, authParams, keys); this.handleAuthResponse(response, email, url, authParams, keys);
this.checkForSecurityUpdate(); this.checkForSecurityUpdate();
$timeout(() => callback(response)); $timeout(() => callback(response));
}.bind(this), function(response){ }, (response) => {
console.error("Error logging in", response); console.error("Error logging in", response);
if(typeof response !== 'object') { if(typeof response !== 'object') {
response = {error: {message: "A server error occurred while trying to sign in. Please try again."}}; response = {error: {message: "A server error occurred while trying to sign in. Please try again."}};
} }
$timeout(() => callback(response)); $timeout(() => callback(response));
}) });
}.bind(this)); });
}.bind(this)) })
} }
this.handleAuthResponse = function(response, email, url, authParams, keys) { this.handleAuthResponse = function(response, email, url, authParams, keys) {
@@ -238,7 +210,10 @@ angular.module('app')
} }
this.register = function(url, email, password, ephemeral, callback) { this.register = function(url, email, password, ephemeral, callback) {
SFJS.crypto.generateInitialEncryptionKeysForUser(email, password, (keys, authParams) => { SFJS.crypto.generateInitialEncryptionKeysForUser(email, password).then((results) => {
let keys = results.keys;
let authParams = results.authParams;
var requestUrl = url + "/auth"; var requestUrl = url + "/auth";
var params = _.merge({password: keys.pw, email: email}, authParams); var params = _.merge({password: keys.pw, email: email}, authParams);
@@ -277,12 +252,12 @@ angular.module('app')
this.updateAuthParams = function(authParams, callback) { this.updateAuthParams = function(authParams, callback) {
var requestUrl = storageManager.getItem("server") + "/auth/update"; var requestUrl = storageManager.getItem("server") + "/auth/update";
var params = authParams; var params = authParams;
httpManager.postAbsolute(requestUrl, params, function(response) { httpManager.postAbsolute(requestUrl, params, (response) => {
storageManager.setItem("auth_params", JSON.stringify(authParams)); storageManager.setItem("auth_params", JSON.stringify(authParams));
if(callback) { if(callback) {
callback(response); callback(response);
} }
}.bind(this), function(response){ }, function(response){
var error = response; var error = response;
console.error("Update error:", response); console.error("Update error:", response);
if(callback) { if(callback) {
@@ -297,7 +272,7 @@ angular.module('app')
return; return;
} }
let latest = SFJS.crypto.version(); let latest = SFJS.version();
if(this.protocolVersion() !== latest) { if(this.protocolVersion() !== latest) {
// Prompt user to perform security update // Prompt user to perform security update

View File

@@ -716,7 +716,7 @@ class ComponentManager {
console.log("Web|componentManager|registerComponentWindow", component); console.log("Web|componentManager|registerComponentWindow", component);
} }
component.window = componentWindow; component.window = componentWindow;
component.sessionKey = SFJS.crypto.generateUUID(); component.sessionKey = SFJS.crypto.generateUUIDSync();
this.sendMessageToComponent(component, { this.sendMessageToComponent(component, {
action: "component-registered", action: "component-registered",
sessionKey: component.sessionKey, sessionKey: component.sessionKey,

View File

@@ -36,7 +36,7 @@ class DesktopManager {
Sending a component in its raw state is really slow for the desktop app Sending a component in its raw state is really slow for the desktop app
Keys are not passed into ItemParams, so the result is not encrypted Keys are not passed into ItemParams, so the result is not encrypted
*/ */
convertComponentForTransmission(component) { async convertComponentForTransmission(component) {
return new ItemParams(component).paramsForExportFile(true); return new ItemParams(component).paramsForExportFile(true);
} }
@@ -44,14 +44,15 @@ class DesktopManager {
syncComponentsInstallation(components) { syncComponentsInstallation(components) {
if(!this.isDesktop) return; if(!this.isDesktop) return;
var data = components.map((component) => { Promise.all(components.map((component) => {
return this.convertComponentForTransmission(component); return this.convertComponentForTransmission(component);
})).then((data) => {
this.installationSyncHandler(data);
}) })
this.installationSyncHandler(data);
} }
installComponent(component) { async installComponent(component) {
this.installComponentHandler(this.convertComponentForTransmission(component)); this.installComponentHandler(await this.convertComponentForTransmission(component));
} }
registerUpdateObserver(callback) { registerUpdateObserver(callback) {
@@ -128,7 +129,7 @@ class DesktopManager {
} }
} }
desktop_requestBackupFile() { desktop_requestBackupFile(callback) {
var keys, authParams, protocolVersion; var keys, authParams, protocolVersion;
if(this.authManager.offline() && this.passcodeManager.hasPasscode()) { if(this.authManager.offline() && this.passcodeManager.hasPasscode()) {
keys = this.passcodeManager.keys(); keys = this.passcodeManager.keys();
@@ -140,13 +141,14 @@ class DesktopManager {
protocolVersion = this.authManager.protocolVersion(); protocolVersion = this.authManager.protocolVersion();
} }
let data = this.modelManager.getAllItemsJSONData( this.modelManager.getAllItemsJSONData(
keys, keys,
authParams, authParams,
protocolVersion, protocolVersion,
true /* return null on empty */ true /* return null on empty */
); ).then((data) => {
return data; callback(data);
})
} }
desktop_setMajorDataChangeHandler(handler) { desktop_setMajorDataChangeHandler(handler) {

View File

@@ -59,7 +59,7 @@ class ModelManager {
var newItem = this.createItem(item); var newItem = this.createItem(item);
newItem.uuid = SFJS.crypto.generateUUID(); newItem.uuid = SFJS.crypto.generateUUIDSync();
// Update uuids of relationships // Update uuids of relationships
newItem.informReferencesOfUUIDChange(item.uuid, newItem.uuid); newItem.informReferencesOfUUIDChange(item.uuid, newItem.uuid);
@@ -455,24 +455,25 @@ class ModelManager {
Archives Archives
*/ */
getAllItemsJSONData(keys, authParams, protocolVersion, returnNullIfEmpty) { async getAllItemsJSONData(keys, authParams, protocolVersion, returnNullIfEmpty) {
var items = _.map(this.allItems, (item) => { return Promise.all(this.allItems.map((item) => {
var itemParams = new ItemParams(item, keys, protocolVersion); var itemParams = new ItemParams(item, keys, protocolVersion);
return itemParams.paramsForExportFile(); return itemParams.paramsForExportFile();
}); })).then((items) => {
if(returnNullIfEmpty && items.length == 0) {
return null;
}
if(returnNullIfEmpty && items.length == 0) { var data = {items: items}
return null;
}
var data = {items: items} if(keys) {
// auth params are only needed when encrypted with a standard file key
data["auth_params"] = authParams;
}
if(keys) { return JSON.stringify(data, null, 2 /* pretty print */);
// auth params are only needed when encrypted with a standard file key })
data["auth_params"] = authParams;
}
return JSON.stringify(data, null, 2 /* pretty print */);
} }

View File

@@ -32,7 +32,7 @@ angular.module('app')
this.unlock = function(passcode, callback) { this.unlock = function(passcode, callback) {
var params = this.passcodeAuthParams(); var params = this.passcodeAuthParams();
SFJS.crypto.computeEncryptionKeysForUser(passcode, params, (keys) => { SFJS.crypto.computeEncryptionKeysForUser(passcode, params).then((keys) => {
if(keys.pw !== params.hash) { if(keys.pw !== params.hash) {
callback(false); callback(false);
return; return;
@@ -40,16 +40,20 @@ angular.module('app')
this._keys = keys; this._keys = keys;
this._authParams = params; this._authParams = params;
this.decryptLocalStorage(keys, params); this.decryptLocalStorage(keys, params).then(() => {
this._locked = false; this._locked = false;
callback(true); callback(true);
})
}); });
} }
this.setPasscode = (passcode, callback) => { this.setPasscode = (passcode, callback) => {
var uuid = SFJS.crypto.generateUUID(); var uuid = SFJS.crypto.generateUUIDSync();
SFJS.crypto.generateInitialEncryptionKeysForUser(uuid, passcode).then((results) => {
let keys = results.keys;
let authParams = results.authParams;
SFJS.crypto.generateInitialEncryptionKeysForUser(uuid, passcode, (keys, authParams) => {
authParams.hash = keys.pw; authParams.hash = keys.pw;
this._keys = keys; this._keys = keys;
this._hasPasscode = true; this._hasPasscode = true;
@@ -83,9 +87,9 @@ angular.module('app')
storageManager.setItemsMode(authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true); storageManager.setItemsMode(authManager.isEphemeralSession() ? StorageManager.Ephemeral : StorageManager.FixedEncrypted, true);
} }
this.decryptLocalStorage = function(keys, authParams) { this.decryptLocalStorage = async function(keys, authParams) {
storageManager.setKeys(keys, authParams); storageManager.setKeys(keys, authParams);
storageManager.decryptStorage(); return storageManager.decryptStorage();
} }
} }
}); });

View File

@@ -154,12 +154,14 @@ class StorageManager {
// Save new encrypted storage in Fixed storage // Save new encrypted storage in Fixed storage
var params = new ItemParams(encryptedStorage, this.encryptedStorageKeys, this.encryptedStorageAuthParams.version); var params = new ItemParams(encryptedStorage, this.encryptedStorageKeys, this.encryptedStorageAuthParams.version);
this.setItem("encryptedStorage", JSON.stringify(params.paramsForSync()), StorageManager.Fixed); params.paramsForSync().then((syncParams) => {
this.setItem("encryptedStorage", JSON.stringify(syncParams), StorageManager.Fixed);
})
} }
decryptStorage() { async decryptStorage() {
var stored = JSON.parse(this.getItem("encryptedStorage", StorageManager.Fixed)); var stored = JSON.parse(this.getItem("encryptedStorage", StorageManager.Fixed));
SFItemTransformer.decryptItem(stored, this.encryptedStorageKeys); await SFJS.itemTransformer.decryptItem(stored, this.encryptedStorageKeys);
var encryptedStorage = new EncryptedStorage(stored); var encryptedStorage = new EncryptedStorage(stored);
for(var key of Object.keys(encryptedStorage.storage)) { for(var key of Object.keys(encryptedStorage.storage)) {

View File

@@ -29,24 +29,26 @@ class SyncManager {
var version = this.authManager.offline() ? this.passcodeManager.protocolVersion() : this.authManager.protocolVersion(); var version = this.authManager.offline() ? this.passcodeManager.protocolVersion() : this.authManager.protocolVersion();
var keys = this.authManager.offline() ? this.passcodeManager.keys() : this.authManager.keys(); var keys = this.authManager.offline() ? this.passcodeManager.keys() : this.authManager.keys();
var params = items.map(function(item) {
Promise.all(items.map(async (item) => {
var itemParams = new ItemParams(item, keys, version); var itemParams = new ItemParams(item, keys, version);
itemParams = itemParams.paramsForLocalStorage(); itemParams = await itemParams.paramsForLocalStorage();
if(offlineOnly) { if(offlineOnly) {
delete itemParams.dirty; delete itemParams.dirty;
} }
return itemParams; return itemParams;
}.bind(this)); })).then((params) => {
this.storageManager.saveModels(params, callback);
this.storageManager.saveModels(params, callback); })
} }
loadLocalItems(callback) { loadLocalItems(callback) {
var params = this.storageManager.getAllModels(function(items){ var params = this.storageManager.getAllModels((items) => {
var items = this.handleItemsResponse(items, null, ModelManager.MappingSourceLocalRetrieved); this.handleItemsResponse(items, null, ModelManager.MappingSourceLocalRetrieved).then((items) => {
Item.sortItemsByDate(items); Item.sortItemsByDate(items);
callback(items); callback(items);
}.bind(this)) })
})
} }
syncOffline(items, callback) { syncOffline(items, callback) {
@@ -54,7 +56,7 @@ class SyncManager {
for(var item of items) { for(var item of items) {
item.updated_at = new Date(); item.updated_at = new Date();
} }
this.writeItemsToLocalStorage(items, true, function(responseItems){ this.writeItemsToLocalStorage(items, true, (responseItems) => {
// delete anything needing to be deleted // delete anything needing to be deleted
for(var item of items) { for(var item of items) {
if(item.deleted) { if(item.deleted) {
@@ -70,7 +72,7 @@ class SyncManager {
if(callback) { if(callback) {
callback({success: true}); callback({success: true});
} }
}.bind(this)) })
} }
@@ -201,7 +203,7 @@ class SyncManager {
this.syncLocked = false; this.syncLocked = false;
} }
sync(callback, options = {}, source) { async sync(callback, options = {}, source) {
if(this.syncLocked) { if(this.syncLocked) {
console.log("Sync Locked, Returning;"); console.log("Sync Locked, Returning;");
@@ -281,11 +283,14 @@ class SyncManager {
var params = {}; var params = {};
params.limit = 150; params.limit = 150;
params.items = _.map(subItems, function(item){
await Promise.all(subItems.map((item) => {
var itemParams = new ItemParams(item, keys, version); var itemParams = new ItemParams(item, keys, version);
itemParams.additionalFields = options.additionalFields; itemParams.additionalFields = options.additionalFields;
return itemParams.paramsForSync(); return itemParams.paramsForSync();
}.bind(this)); })).then((itemsParams) => {
params.items = itemsParams;
})
for(var item of subItems) { for(var item of subItems) {
// Reset dirty counter to 0, since we're about to sync it. // Reset dirty counter to 0, since we're about to sync it.
@@ -300,7 +305,7 @@ class SyncManager {
this.stopCheckingIfSyncIsTakingTooLong(); this.stopCheckingIfSyncIsTakingTooLong();
}.bind(this); }.bind(this);
var onSyncSuccess = function(response) { var onSyncSuccess = async function(response) {
// Check to make sure any subItem hasn't been marked as dirty again while a sync was ongoing // Check to make sure any subItem hasn't been marked as dirty again while a sync was ongoing
var itemsToClearAsDirty = []; var itemsToClearAsDirty = [];
for(var item of subItems) { for(var item of subItems) {
@@ -325,8 +330,7 @@ class SyncManager {
// Map retrieved items to local data // Map retrieved items to local data
// Note that deleted items will not be returned // Note that deleted items will not be returned
var retrieved var retrieved = await this.handleItemsResponse(response.retrieved_items, null, ModelManager.MappingSourceRemoteRetrieved);
= this.handleItemsResponse(response.retrieved_items, null, ModelManager.MappingSourceRemoteRetrieved);
// Append items to master list of retrieved items for this ongoing sync operation // Append items to master list of retrieved items for this ongoing sync operation
this.allRetreivedItems = this.allRetreivedItems.concat(retrieved); this.allRetreivedItems = this.allRetreivedItems.concat(retrieved);
@@ -337,8 +341,7 @@ class SyncManager {
var omitFields = ["content", "auth_hash"]; var omitFields = ["content", "auth_hash"];
// Map saved items to local data // Map saved items to local data
var saved = var saved = await this.handleItemsResponse(response.saved_items, omitFields, ModelManager.MappingSourceRemoteSaved);
this.handleItemsResponse(response.saved_items, omitFields, ModelManager.MappingSourceRemoteSaved);
// Append items to master list of saved items for this ongoing sync operation // Append items to master list of saved items for this ongoing sync operation
this.allSavedItems = this.allSavedItems.concat(saved); this.allSavedItems = this.allSavedItems.concat(saved);
@@ -418,9 +421,9 @@ class SyncManager {
} }
} }
handleItemsResponse(responseItems, omitFields, source) { async handleItemsResponse(responseItems, omitFields, source) {
var keys = this.authManager.keys() || this.passcodeManager.keys(); var keys = this.authManager.keys() || this.passcodeManager.keys();
SFItemTransformer.decryptMultipleItems(responseItems, keys); await SFJS.itemTransformer.decryptMultipleItems(responseItems, keys);
var items = this.modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields, source); var items = this.modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields, source);
// During the decryption process, items may be marked as "errorDecrypting". If so, we want to be sure // During the decryption process, items may be marked as "errorDecrypting". If so, we want to be sure
@@ -446,7 +449,7 @@ class SyncManager {
} }
} }
handleUnsavedItemsResponse(unsaved) { async handleUnsavedItemsResponse(unsaved) {
if(unsaved.length == 0) { if(unsaved.length == 0) {
return; return;
} }
@@ -454,7 +457,7 @@ class SyncManager {
console.log("Handle unsaved", unsaved); console.log("Handle unsaved", unsaved);
var i = 0; var i = 0;
var handleNext = () => { var handleNext = async () => {
if(i >= unsaved.length) { if(i >= unsaved.length) {
// Handled all items // Handled all items
this.sync(null, {additionalFields: ["created_at", "updated_at"]}); this.sync(null, {additionalFields: ["created_at", "updated_at"]});
@@ -463,7 +466,7 @@ class SyncManager {
var mapping = unsaved[i]; var mapping = unsaved[i];
var itemResponse = mapping.item; var itemResponse = mapping.item;
SFItemTransformer.decryptMultipleItems([itemResponse], this.authManager.keys()); await SFJS.itemTransformer.decryptMultipleItems([itemResponse], this.authManager.keys());
var item = this.modelManager.findItem(itemResponse.uuid); var item = this.modelManager.findItem(itemResponse.uuid);
if(!item) { if(!item) {

9
package-lock.json generated
View File

@@ -1001,6 +1001,15 @@
"regenerator-transform": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz" "regenerator-transform": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz"
} }
}, },
"babel-plugin-transform-runtime": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.23.0.tgz",
"integrity": "sha1-iEkNRGUC6puOfvsP4J7E2ZR5se4=",
"dev": true,
"requires": {
"babel-runtime": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz"
}
},
"babel-plugin-transform-strict-mode": { "babel-plugin-transform-strict-mode": {
"version": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz", "version": "https://registry.npmjs.org/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz",
"integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=", "integrity": "sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g=",

View File

@@ -12,6 +12,7 @@
"angular": "^1.6.1", "angular": "^1.6.1",
"angular-mocks": "^1.6.1", "angular-mocks": "^1.6.1",
"babel-cli": "^6.18.0", "babel-cli": "^6.18.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.1.1", "babel-preset-env": "^1.1.1",
"babel-preset-es2016": "^6.16.0", "babel-preset-es2016": "^6.16.0",
"bower": "^1.8.0", "bower": "^1.8.0",