keymanager, syncmanager

This commit is contained in:
Mo Bitar
2017-01-25 15:52:03 -06:00
parent 13e6ac59a9
commit a76f725f7f
23 changed files with 1088 additions and 721 deletions

View File

@@ -20,9 +20,6 @@ angular.module('app.frontend', [
.config(function (RestangularProvider, apiControllerProvider) {
RestangularProvider.setDefaultHeaders({"Content-Type": "application/json"});
var url = apiControllerProvider.defaultServerURL();
RestangularProvider.setBaseUrl(url + "/api");
RestangularProvider.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
var token = localStorage.getItem("jwt");
if(token) {

View File

@@ -1,5 +1,5 @@
angular.module('app.frontend')
.directive("header", function(apiController, extensionManager){
.directive("header", function(apiController){
return {
restrict: 'E',
scope: {},
@@ -16,15 +16,9 @@ angular.module('app.frontend')
}
}
})
.controller('HeaderCtrl', function ($state, apiController, modelManager, $timeout, extensionManager, dbManager) {
.controller('HeaderCtrl', function (apiController, modelManager, $timeout, dbManager) {
this.user = apiController.user;
this.extensionManager = extensionManager;
this.loginData = {mergeLocal: true};
this.changePasswordPressed = function() {
this.showNewPasswordForm = !this.showNewPasswordForm;
}
this.accountMenuPressed = function() {
this.serverData = {url: apiController.getServer()};
@@ -32,126 +26,19 @@ angular.module('app.frontend')
this.showFaq = false;
this.showNewPasswordForm = false;
this.showExtensionsMenu = false;
this.showIOMenu = false;
}
this.toggleExtensions = function() {
this.showAccountMenu = false;
this.showIOMenu = false;
this.showExtensionsMenu = !this.showExtensionsMenu;
}
this.toggleExtensionForm = function() {
this.newExtensionData = {};
this.showNewExtensionForm = !this.showNewExtensionForm;
}
this.submitNewExtensionForm = function() {
if(this.newExtensionData.url) {
extensionManager.addExtension(this.newExtensionData.url, function(response){
if(!response) {
alert("Unable to register this extension. Make sure the link is valid and try again.");
} else {
this.newExtensionData.url = "";
this.showNewExtensionForm = false;
}
}.bind(this))
}
}
this.selectedAction = function(action, extension) {
action.running = true;
extensionManager.executeAction(action, extension, null, function(response){
action.running = false;
if(response && response.error) {
action.error = true;
alert("There was an error performing this action. Please try again.");
} else {
action.error = false;
apiController.sync(null);
}
})
}
this.syncProviderActionIsEnabled = function(action) {
var provider = apiController.syncProviderForURL(action.url);
if(!provider) {
return null;
}
return provider.status;
}
this.enableSyncProvider = function(action, extension, primary) {
if(extension.encrypted && !extension.ek) {
alert("You must set an encryption key for this extension before enabling this action.");
return;
}
var provider = apiController.findOrCreateSyncProviderForUrl(action.url);
provider.primary = primary;
provider.enabled = true;
provider.ek = extension.ek;
apiController.addSyncProvider(provider);
}
this.disableSyncProvider = function(action, extension) {
apiController.removeSyncProvider(apiController.syncProviderForURL(action.url));
}
this.deleteExtension = function(extension) {
if(confirm("Are you sure you want to delete this extension?")) {
extensionManager.deleteExtension(extension);
var syncProviderAction = extension.syncProviderAction;
if(syncProviderAction) {
apiController.removeSyncProvider(apiController.syncProviderForURL(syncProviderAction.url));
}
}
}
this.reloadExtensionsPressed = function() {
if(confirm("For your security, reloading extensions will disable any currently enabled sync providers and repeat actions.")) {
extensionManager.refreshExtensionsFromServer();
var syncProviderAction = extension.syncProviderAction;
if(syncProviderAction) {
apiController.removeSyncProvider(apiController.syncProviderForURL(syncProviderAction.url));
}
}
}
this.changeServer = function() {
apiController.setServer(this.serverData.url, true);
}
this.signOutPressed = function() {
this.toggleIO = function() {
this.showIOMenu = !this.showIOMenu;
this.showExtensionsMenu = false;
this.showAccountMenu = false;
apiController.signout(function(){
window.location.reload();
})
}
this.submitPasswordChange = function() {
this.passwordChangeData.status = "Generating New Keys...";
$timeout(function(){
if(data.password != data.password_confirmation) {
alert("Your new password does not match its confirmation.");
return;
}
apiController.changePassword(this.passwordChangeData.current_password, this.passwordChangeData.new_password, function(response){
})
}.bind(this))
}
this.localNotesCount = function() {
return modelManager.filteredNotes.length;
}
this.mergeLocalChanged = function() {
if(!this.loginData.mergeLocal) {
if(!confirm("Unchecking this option means any of the notes you have written while you were signed out will be deleted. Are you sure you want to discard these notes?")) {
this.loginData.mergeLocal = true;
}
}
}
this.refreshData = function() {
@@ -171,110 +58,4 @@ angular.module('app.frontend')
this.syncUpdated = function() {
this.lastSyncDate = new Date();
}
this.loginSubmitPressed = function() {
this.loginData.status = "Generating Login Keys...";
$timeout(function(){
apiController.login(this.loginData.email, this.loginData.user_password, function(response){
if(!response || response.error) {
var error = response ? response.error : {message: "An unknown error occured."}
this.loginData.status = null;
if(!response || (response && !response.didDisplayAlert)) {
alert(error.message);
}
} else {
this.onAuthSuccess(response.user);
}
}.bind(this));
}.bind(this))
}
this.submitRegistrationForm = function() {
this.loginData.status = "Generating Account Keys...";
$timeout(function(){
apiController.register(this.loginData.email, this.loginData.user_password, function(response){
if(!response || response.error) {
var error = response ? response.error : {message: "An unknown error occured."}
this.loginData.status = null;
alert(error.message);
} else {
this.onAuthSuccess(response.user);
}
}.bind(this));
}.bind(this))
}
this.encryptionStatusForNotes = function() {
var allNotes = modelManager.filteredNotes;
return allNotes.length + "/" + allNotes.length + " notes encrypted";
}
this.archiveEncryptionFormat = {encrypted: true};
this.downloadDataArchive = function() {
var link = document.createElement('a');
link.setAttribute('download', 'notes.json');
link.href = apiController.itemsDataFile(this.archiveEncryptionFormat.encrypted);
link.click();
}
this.performImport = function(data, password) {
this.importData.loading = true;
// allow loading indicator to come up with timeout
$timeout(function(){
apiController.importJSONData(data, password, function(success, response){
console.log("Import response:", success, response);
this.importData.loading = false;
if(success) {
this.importData = null;
} else {
alert("There was an error importing your data. Please try again.");
}
}.bind(this))
}.bind(this))
}
this.submitImportPassword = function() {
this.performImport(this.importData.data, this.importData.password);
}
this.importFileSelected = function(files) {
this.importData = {};
var file = files[0];
var reader = new FileReader();
reader.onload = function(e) {
var data = JSON.parse(e.target.result);
$timeout(function(){
if(data.auth_params) {
// request password
this.importData.requestPassword = true;
this.importData.data = data;
} else {
this.performImport(data, null);
}
}.bind(this))
}.bind(this)
reader.readAsText(file);
}
this.onAuthSuccess = function(user) {
var block = function(){
window.location.reload();
this.showLogin = false;
this.showRegistration = false;
}.bind(this);
if(!this.loginData.mergeLocal) {
dbManager.clearAllItems(function(){
block();
});
} else {
block();
}
}
});
});

View File

@@ -56,7 +56,7 @@ class Extension extends Item {
this.content_type = "Extension";
}
syncProviderAction() {
get syncProviderAction() {
return _.find(this.actions, {sync_provider: true})
}

View File

@@ -1,5 +1,6 @@
class SyncProvider {
constructor(obj) {
this.encrypted = true;
_.merge(this, obj);
}
@@ -28,8 +29,9 @@ class SyncProvider {
return {
enabled: this.enabled,
url: this.url,
encrypted: this.encrypted,
ek: this.ek
primary: this.primary,
keyName: this.keyName,
syncToken: this.syncToken
}
}

View File

@@ -7,24 +7,11 @@ angular.module('app.frontend')
return domain;
}
var url;
this.defaultServerURL = function() {
if(!url) {
url = localStorage.getItem("server");
if(!url) {
url = "https://n3.standardnotes.org";
}
}
return url;
this.$get = function($rootScope, Restangular, modelManager, dbManager, keyManager, syncManager) {
return new ApiController($rootScope, Restangular, modelManager, dbManager, keyManager, syncManager);
}
this.$get = function($rootScope, Restangular, modelManager, dbManager) {
return new ApiController($rootScope, Restangular, modelManager, dbManager);
}
function ApiController($rootScope, Restangular, modelManager, dbManager) {
function ApiController($rootScope, Restangular, modelManager, dbManager, keyManager, syncManager) {
var userData = localStorage.getItem("user");
if(userData) {
@@ -36,35 +23,15 @@ angular.module('app.frontend')
this.user = {uuid: idData};
}
}
this.syncToken = localStorage.getItem("syncToken");
/*
Config
*/
this.getServer = function() {
if(!url) {
url = localStorage.getItem("server");
if(!url) {
url = "https://n3.standardnotes.org";
this.setServer(url);
}
}
return url;
}
this.setServer = function(url, refresh) {
localStorage.setItem("server", url);
if(refresh) {
window.location.reload();
}
}
/*
Auth
*/
this.defaultServerURL = function() {
return localStorage.getItem("server") || "https://n3.standardnotes.org";
}
this.getAuthParams = function() {
return JSON.parse(localStorage.getItem("auth_params"));
}
@@ -73,8 +40,9 @@ angular.module('app.frontend')
return localStorage.getItem("jwt");
}
this.getAuthParamsForEmail = function(email, callback) {
var request = Restangular.one("auth", "params");
this.getAuthParamsForEmail = function(url, email, callback) {
var requestUrl = url + "/auth/params";
var request = Restangular.oneUrl(requestUrl, requestUrl);
request.get({email: email}).then(function(response){
callback(response.plain());
})
@@ -96,10 +64,10 @@ angular.module('app.frontend')
}
}
this.login = function(email, password, callback) {
this.getAuthParamsForEmail(email, function(authParams){
this.login = function(url, email, password, callback) {
this.getAuthParamsForEmail(url, email, function(authParams){
if(!authParams) {
callback(null);
callback({error: "Unable to get authentication parameters."});
return;
}
@@ -113,36 +81,44 @@ angular.module('app.frontend')
}
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){
this.setMk(keys.mk);
var request = Restangular.one("auth/sign_in");
var mk = keys.mk;
var requestUrl = url + "/auth/sign_in";
var request = Restangular.oneUrl(requestUrl, requestUrl);
var params = {password: keys.pw, email: email};
_.merge(request, params);
request.post().then(function(response){
localStorage.setItem("server", url);
localStorage.setItem("jwt", response.token);
localStorage.setItem("user", JSON.stringify(response.user));
localStorage.setItem("auth_params", JSON.stringify(authParams));
keyManager.addKey(SNKeyName, mk);
this.addStandardFileSyncProvider(url);
callback(response);
})
}.bind(this))
.catch(function(response){
console.log("Error logging in", response);
callback(response.data);
})
}.bind(this));
}.bind(this))
}
this.register = function(email, password, callback) {
this.register = function(url, email, password, callback) {
Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys, authParams){
this.setMk(keys.mk);
keys.mk = null;
var request = Restangular.one("auth");
var mk = keys.mk;
var requestUrl = url + "/auth";
var request = Restangular.oneUrl(requestUrl, requestUrl);
var params = _.merge({password: keys.pw, email: email}, authParams);
_.merge(request, params);
request.post().then(function(response){
localStorage.setItem("server", url);
localStorage.setItem("jwt", response.token);
localStorage.setItem("user", JSON.stringify(response.user));
localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"])));
keyManager.addKey(SNKeyName, mk);
this.addStandardFileSyncProvider(url);
callback(response);
})
}.bind(this))
.catch(function(response){
callback(response.data);
})
@@ -190,8 +166,9 @@ angular.module('app.frontend')
// }.bind(this));
// }
this._performPasswordChange = function(email, current_keys, new_keys, callback) {
var request = Restangular.one("auth");
this._performPasswordChange = function(url, email, current_keys, new_keys, callback) {
var requestUrl = url + "/auth";
var request = Restangular.oneUrl(requestUrl, requestUrl);
var params = {password: new_keys.pw, password_confirmation: new_keys.pw, current_password: current_keys.pw, email: email};
_.merge(request, params);
request.patch().then(function(response){
@@ -204,73 +181,6 @@ angular.module('app.frontend')
Sync
*/
this.syncProviderForURL = function(url) {
return _.find(this.syncProviders, {url: url});
}
this.findOrCreateSyncProviderForUrl = function(url) {
var provider = _.find(this.syncProviders, {url: url});
if(!provider) {
provider = new SyncProvider({url: url})
}
return provider;
}
this.setEncryptionStatusForProviderURL = function(providerURL, encrypted) {
this.providerForURL(providerURL).encrypted = encrypted;
this.persistSyncProviders();
}
this.loadSyncProviders = function() {
var providers = [];
var saved = localStorage.getItem("syncProviders");
if(saved) {
var parsed = JSON.parse(saved);
for(var p of parsed) {
providers.push(new SyncProvider(p));
}
} else {
// no providers saved, use default
if(this.isUserSignedIn()) {
var defaultProvider = new SyncProvider(this.getServer() + "/items/sync", true);
providers.push(defaultProvider);
}
}
this.syncProviders = providers;
}
this.loadSyncProviders();
this.addSyncProvider = function(syncProvider) {
if(syncProvider.primary) {
for(var provider of this.syncProviders) {
provider.primary = false;
}
}
// since we're adding a new provider, we need to send it EVERYTHING we have now.
syncProvider.addPendingItems(modelManager.allItems);
this.syncProviders.push(syncProvider);
this.persistSyncProviders();
}
this.removeSyncProvider = function(provider) {
_.pull(this.syncProviders, provider);
this.persistSyncProviders();
}
this.persistSyncProviders = function() {
localStorage.setItem("syncProviders", JSON.stringify(_.map(this.syncProviders, function(provider) {
return provider.asJSON()
})));
}
this.setSyncToken = function(syncToken) {
this.syncToken = syncToken;
localStorage.setItem("syncToken", this.syncToken);
}
this.syncWithOptions = function(callback, options = {}) {
var allDirtyItems = modelManager.getDirtyItems();
@@ -306,7 +216,7 @@ angular.module('app.frontend')
}
for(let provider of this.syncProviders) {
if(provider.enabled == false) {
if(!provider.enabled) {
continue;
}
provider.addPendingItems(allDirtyItems);
@@ -354,18 +264,17 @@ angular.module('app.frontend')
return this.paramsForItem(item, provider.encrypted, provider.ek, options.additionalFields, false);
}.bind(this));
if(provider.primary) {
// only primary providers receive items (or care about received items)
request.sync_token = this.syncToken;
request.cursor_token = provider.cursorToken;
}
request.sync_token = provider.syncToken;
request.cursor_token = provider.cursorToken;
request.post().then(function(response) {
console.log("Sync completion", response);
provider.syncToken = response.sync_token;
if(provider.primary) {
// handle sync token
this.setSyncToken(response.sync_token);
$rootScope.$broadcast("sync:updated_token", this.syncToken);
$rootScope.$broadcast("sync:updated_token", provider.syncToken);
// handle cursor token (more results waiting, perform another sync)
provider.cursorToken = response.cursor_token;
@@ -382,6 +291,7 @@ angular.module('app.frontend')
}
provider.syncOpInProgress = false;
this.didMakeChangesToSyncProviders();
if(provider.cursorToken || provider.repeatOnCompletion == true) {
this.__performSyncWithProvider(provider, options, callback);
@@ -434,12 +344,13 @@ angular.module('app.frontend')
}
this.handleItemsResponse = function(responseItems, omitFields, syncProvider) {
this.decryptItemsWithKey(responseItems, syncProvider ? syncProvider.ek : null);
var ek = syncProvider ? keyManager.keyForName(syncProvider.keyName).key : null;
this.decryptItemsWithKey(responseItems, ek);
return modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields);
}
this.paramsForExportFile = function(item, ek, encrypted) {
return _.omit(this.paramsForItem(item, encrypted, ["created_at", "updated_at"], true), ["deleted"]);
return _.omit(this.paramsForItem(item, encrypted, ek, ["created_at", "updated_at"], true), ["deleted"]);
}
this.paramsForExtension = function(item, encrypted) {
@@ -482,8 +393,10 @@ angular.module('app.frontend')
*/
this.clearSyncToken = function() {
this.syncToken = null;
localStorage.removeItem("syncToken");
var primary = this.primarySyncProvider();
if(primary) {
primary.syncToken = null;
}
}
this.importJSONData = function(data, password, callback) {
@@ -526,7 +439,7 @@ angular.module('app.frontend')
Export
*/
this.itemsDataFile = function(encrypted) {
this.itemsDataFile = function(encrypted, custom_ek) {
var textFile = null;
var makeTextFile = function (text) {
var data = new Blob([text], {type: 'text/json'});
@@ -543,15 +456,21 @@ angular.module('app.frontend')
return textFile;
}.bind(this);
var ek = custom_ek;
if(encrypted && !custom_ek) {
ek = this.retrieveMk();
}
var items = _.map(modelManager.allItemsMatchingTypes(["Tag", "Note"]), function(item){
return this.paramsForExportFile(item, encrypted);
return this.paramsForExportFile(item, ek, encrypted);
}.bind(this));
var data = {
items: items
}
if(encrypted) {
if(encrypted && !custom_ek) {
// auth params are only needed when encrypted with a standard file key
data["auth_params"] = this.getAuthParams();
}
@@ -616,7 +535,8 @@ angular.module('app.frontend')
localStorage.setItem('mk', mk);
}
this.signout = function(callback) {
this.signoutOfStandardFile = function(callback) {
this.removeStandardFileSyncProvider();
dbManager.clearAllItems(function(){
localStorage.clear();
callback();

View File

@@ -0,0 +1,17 @@
class AccountDataMenu {
constructor() {
this.restrict = "E";
this.templateUrl = "frontend/directives/account-data-menu.html";
this.scope = {
};
}
controller($scope, apiController, modelManager) {
'ngInject';
}
}
angular.module('app.frontend').directive('accountDataMenu', () => new AccountDataMenu);

View File

@@ -0,0 +1,23 @@
class AccountKeysSection {
constructor() {
this.restrict = "E";
this.templateUrl = "frontend/directives/account-keys-section.html";
this.scope = {
};
}
controller($scope, apiController, keyManager) {
'ngInject';
$scope.newKeyData = {};
$scope.keys = keyManager.keys;
$scope.submitNewKeyForm = function() {
keyManager.addKey($scope.newKeyData.name, $scope.newKeyData.key);
$scope.newKeyData.showForm = false;
}
}
}
angular.module('app.frontend').directive('accountKeysSection', () => new AccountKeysSection);

View File

@@ -0,0 +1,36 @@
class AccountSyncSection {
constructor() {
this.restrict = "E";
this.templateUrl = "frontend/directives/account-sync-section.html";
this.scope = {
};
}
controller($scope, apiController, modelManager, keyManager) {
'ngInject';
$scope.syncProviders = apiController.syncProviders;
$scope.newSyncData = {showAddSyncForm: false}
$scope.keys = keyManager.keys;
$scope.submitExternalSyncURL = function() {
apiController.addSyncProviderFromURL($scope.newSyncData.url);
$scope.newSyncData.showAddSyncForm = false;
}
$scope.enableSyncProvider = function(provider, primary) {
if(!provider.keyName) {
alert("You must choose an encryption key for this provider before enabling it.");
return;
}
apiController.enableSyncProvider(provider, primary);
}
$scope.removeSyncProvider = function(provider) {
apiController.removeSyncProvider(provider);
}
}
}
angular.module('app.frontend').directive('accountSyncSection', () => new AccountSyncSection);

View File

@@ -0,0 +1,113 @@
class AccountVendorAccountSection {
constructor() {
this.restrict = "E";
this.templateUrl = "frontend/directives/account-vendor-account-section.html";
this.scope = {
};
}
controller($scope, apiController, modelManager, $timeout, dbManager) {
'ngInject';
$scope.loginData = {mergeLocal: true, url: apiController.defaultServerURL()};
$scope.user = apiController.user;
$scope.changePasswordPressed = function() {
$scope.showNewPasswordForm = !$scope.showNewPasswordForm;
}
$scope.signOutPressed = function() {
$scope.showAccountMenu = false;
apiController.signoutOfStandardFile(function(){
window.location.reload();
})
}
$scope.submitPasswordChange = function() {
$scope.passwordChangeData.status = "Generating New Keys...";
$timeout(function(){
if(data.password != data.password_confirmation) {
alert("Your new password does not match its confirmation.");
return;
}
apiController.changePassword($scope.passwordChangeData.current_password, $scope.passwordChangeData.new_password, function(response){
})
})
}
$scope.localNotesCount = function() {
return modelManager.filteredNotes.length;
}
$scope.mergeLocalChanged = function() {
if(!$scope.loginData.mergeLocal) {
if(!confirm("Unchecking this option means any of the notes you have written while you were signed out will be deleted. Are you sure you want to discard these notes?")) {
$scope.loginData.mergeLocal = true;
}
}
}
$scope.loginSubmitPressed = function() {
$scope.loginData.status = "Generating Login Keys...";
console.log("logging in with url", $scope.loginData.url);
$timeout(function(){
apiController.login($scope.loginData.url, $scope.loginData.email, $scope.loginData.user_password, function(response){
if(!response || response.error) {
var error = response ? response.error : {message: "An unknown error occured."}
$scope.loginData.status = null;
if(!response || (response && !response.didDisplayAlert)) {
alert(error.message);
}
} else {
$scope.onAuthSuccess(response.user);
}
});
})
}
$scope.submitRegistrationForm = function() {
$scope.loginData.status = "Generating Account Keys...";
$timeout(function(){
apiController.register($scope.loginData.url, $scope.loginData.email, $scope.loginData.user_password, function(response){
if(!response || response.error) {
var error = response ? response.error : {message: "An unknown error occured."}
$scope.loginData.status = null;
alert(error.message);
} else {
$scope.onAuthSuccess(response.user);
}
});
})
}
$scope.encryptionStatusForNotes = function() {
var allNotes = modelManager.filteredNotes;
return allNotes.length + "/" + allNotes.length + " notes encrypted";
}
$scope.onAuthSuccess = function(user) {
var block = function(){
window.location.reload();
$scope.showLogin = false;
$scope.showRegistration = false;
};
if(!$scope.loginData.mergeLocal) {
dbManager.clearAllItems(function(){
block();
});
} else {
block();
}
}
}
}
angular.module('app.frontend').directive('accountVendorAccountSection', () => new AccountVendorAccountSection);

View File

@@ -0,0 +1,109 @@
class GlobalExtensionsMenu {
constructor() {
this.restrict = "E";
this.templateUrl = "frontend/directives/global-extensions-menu.html";
this.scope = {
};
}
controller($scope, apiController, extensionManager) {
'ngInject';
$scope.extensionManager = extensionManager;
$scope.toggleExtensionForm = function() {
$scope.newExtensionData = {};
$scope.showNewExtensionForm = !$scope.showNewExtensionForm;
}
$scope.submitNewExtensionForm = function() {
if($scope.newExtensionData.url) {
extensionManager.addExtension($scope.newExtensionData.url, function(response){
if(!response) {
alert("Unable to register this extension. Make sure the link is valid and try again.");
} else {
$scope.newExtensionData.url = "";
$scope.showNewExtensionForm = false;
}
})
}
}
$scope.selectedAction = function(action, extension) {
action.running = true;
extensionManager.executeAction(action, extension, null, function(response){
action.running = false;
if(response && response.error) {
action.error = true;
alert("There was an error performing this action. Please try again.");
} else {
action.error = false;
apiController.sync(null);
}
})
}
$scope.changeExtensionEncryptionFormat = function(encrypted, extension) {
var provider = $scope.syncProviderForExtension(extension);
if(provider) {
if(confirm("Changing encryption status will update all your items and re-sync them back to the server. This can take several minutes. Are you sure you want to continue?")) {
extensionManager.changeExtensionEncryptionFormat(encrypted, extension);
apiController.resyncAllDataForProvider(provider);
} else {
// revert setting
console.log("reverting");
extension.encrypted = extensionManager.extensionUsesEncryptedData(extension);
}
} else {
extensionManager.changeExtensionEncryptionFormat(encrypted, extension);
}
}
$scope.deleteExtension = function(extension) {
if(confirm("Are you sure you want to delete this extension?")) {
extensionManager.deleteExtension(extension);
var syncProviderAction = extension.syncProviderAction;
if(syncProviderAction) {
apiController.removeSyncProvider(apiController.syncProviderForURL(syncProviderAction.url));
}
}
}
$scope.reloadExtensionsPressed = function() {
if(confirm("For your security, reloading extensions will disable any currently enabled sync providers and repeat actions.")) {
extensionManager.refreshExtensionsFromServer();
var syncProviderAction = extension.syncProviderAction;
if(syncProviderAction) {
apiController.removeSyncProvider(apiController.syncProviderForURL(syncProviderAction.url));
}
}
}
$scope.setEncryptionKeyForExtension = function(extension) {
extension.formData.changingKey = false;
var ek = extension.formData.ek;
extensionManager.setEkForExtension(extension, ek);
if(extension.formData.changingKey) {
var syncAction = extension.syncProviderAction;
if(syncAction) {
var provider = apiController.syncProviderForURL(syncAction.url);
provider.ek = ek;
apiController.didMakeChangesToSyncProviders();
apiController.resyncAllDataForProvider(provider);
}
}
}
$scope.changeEncryptionKeyPressed = function(extension) {
if(!confirm("Changing your encryption key will re-encrypt all your notes with the new key and sync them back to the server. This can take several minutes. We strongly recommend downloading a backup of your notes before continuing.")) {
return;
}
extension.formData.changingKey = true;
}
}
}
angular.module('app.frontend').directive('globalExtensionsMenu', () => new GlobalExtensionsMenu);

View File

@@ -0,0 +1,79 @@
class ImportExportMenu {
constructor() {
this.restrict = "E";
this.templateUrl = "frontend/directives/import-export-menu.html";
this.scope = {
};
}
controller($scope, apiController, $timeout) {
'ngInject';
$scope.archiveFormData = {encryption_type: $scope.user ? 'mk' : 'ek'};
$scope.user = apiController.user;
$scope.downloadDataArchive = function() {
if(!apiController.isUserSignedIn() && $scope.archiveFormData.encryption_type == 'ek') {
if(!$scope.archiveFormData.ek) {
alert("You must set an encryption key to export the data encrypted.")
return;
}
}
var link = document.createElement('a');
link.setAttribute('download', 'notes.json');
var ek = $scope.archiveFormData.encryption_type == 'ek' ? $scope.archiveFormData.ek : null;
var encrypted = $scope.archiveFormData.encryption_type != 'none';
link.href = apiController.itemsDataFile(encrypted, ek);
link.click();
}
$scope.performImport = function(data, password) {
$scope.importData.loading = true;
// allow loading indicator to come up with timeout
$timeout(function(){
apiController.importJSONData(data, password, function(success, response){
console.log("Import response:", success, response);
$scope.importData.loading = false;
if(success) {
$scope.importData = null;
} else {
alert("There was an error importing your data. Please try again.");
}
})
})
}
$scope.submitImportPassword = function() {
$scope.performImport($scope.importData.data, $scope.importData.password);
}
$scope.importFileSelected = function(files) {
$scope.importData = {};
var file = files[0];
var reader = new FileReader();
reader.onload = function(e) {
var data = JSON.parse(e.target.result);
$timeout(function(){
if(data.auth_params) {
// request password
$scope.importData.requestPassword = true;
$scope.importData.data = data;
} else {
$scope.performImport(data, null);
}
})
}
reader.readAsText(file);
}
}
}
angular.module('app.frontend').directive('importExportMenu', () => new ImportExportMenu);

View File

@@ -6,6 +6,7 @@ class ExtensionManager {
this.apiController = apiController;
this.enabledRepeatActionUrls = JSON.parse(localStorage.getItem("enabledRepeatActionUrls")) || [];
this.decryptedExtensions = JSON.parse(localStorage.getItem("decryptedExtensions")) || [];
this.extensionEks = JSON.parse(localStorage.getItem("extensionEks")) || {};
modelManager.addItemSyncObserver("extensionManager", "Extension", function(items){
for (var ext of items) {
@@ -31,6 +32,15 @@ class ExtensionManager {
})
}
ekForExtension(extension) {
return this.extensionEks[extension.url];
}
setEkForExtension(extension, ek) {
this.extensionEks[extension.url] = ek;
localStorage.setItem("extensionEks", JSON.stringify(this.extensionEks));
}
actionWithURL(url) {
for (var extension of this.extensions) {
return _.find(extension.actions, {url: url})
@@ -42,6 +52,7 @@ class ExtensionManager {
}
changeExtensionEncryptionFormat(encrypted, extension) {
console.log("changing encryption status");
if(encrypted) {
_.pull(this.decryptedExtensions, extension.url);
} else {

View File

@@ -0,0 +1,26 @@
class KeyManager {
constructor() {
this.keys = JSON.parse(localStorage.getItem("keys")) || [];
}
addKey(name, key) {
this.keys.push({name: name, key: key});
this.persist();
}
keyForName(name) {
return _.find(this.keys, {name: name});
}
deleteKey(name) {
_.pull(this.keys, {name: name});
this.persist();
}
persist() {
localStorage.setItem("keys", JSON.stringify(this.keys));
}
}
angular.module('app.frontend').service('keyManager', KeyManager);

View File

@@ -0,0 +1,111 @@
class SyncManager {
let SNKeyName = "Standard Notes Key";
constructor(modelManager) {
this.modelManager = modelManager;
}
syncProviderForURL(url) {
var provider = _.find(this.syncProviders, {url: url});
return provider;
}
findOrCreateSyncProviderForUrl(url) {
var provider = _.find(this.syncProviders, {url: url});
if(!provider) {
provider = new SyncProvider({url: url})
}
return provider;
}
setEncryptionStatusForProviderURL(providerURL, encrypted) {
this.providerForURL(providerURL).encrypted = encrypted;
this.didMakeChangesToSyncProviders();
}
primarySyncProvider() {
return _.find(this.syncProviders, {primary: true});
}
removeStandardFileSyncProvider() {
var sfProvider = _.find(this.syncProviders, {url: this.defaultServerURL() + "/items/sync"})
_.pull(this.syncProviders, sfProvider);
this.didMakeChangesToSyncProviders();
}
addStandardFileSyncProvider(url) {
var defaultProvider = new SyncProvider({url: url + "/items/sync", primary: this.syncProviders.length == 0});
defaultProvider.keyName = SNKeyName;
defaultProvider.enabled = this.syncProviders.length == 0;
this.syncProviders.push(defaultProvider);
return defaultProvider;
}
didMakeChangesToSyncProviders() {
localStorage.setItem("syncProviders", JSON.stringify(_.map(this.syncProviders, function(provider) {
return provider.asJSON()
})));
}
loadSyncProviders() {
this.syncProviders = [];
var saved = localStorage.getItem("syncProviders");
if(saved) {
var parsed = JSON.parse(saved);
for(var p of parsed) {
this.syncProviders.push(new SyncProvider(p));
}
} else {
// no providers saved, use default
if(this.isUserSignedIn()) {
var defaultProvider = this.addStandardFileSyncProvider(this.defaultServerURL());
defaultProvider.syncToken = localStorage.getItem("syncToken");
// migrate old key structure to new
var mk = localStorage.getItem("mk");
if(mk) {
keyManager.addKey(SNKeyName, mk);
localStorage.removeItem("mk");
}
this.didMakeChangesToSyncProviders();
}
}
}
this.loadSyncProviders();
addSyncProviderFromURL(url) {
var provider = new SyncProvider({url: url});
this.syncProviders.push(provider);
this.didMakeChangesToSyncProviders();
}
enableSyncProvider(syncProvider, primary) {
if(primary) {
for(var provider of this.syncProviders) {
provider.primary = false;
}
}
syncProvider.enabled = true;
syncProvider.primary = primary;
// since we're enabling a new provider, we need to send it EVERYTHING we have now.
syncProvider.addPendingItems(this.modelManager.allItems);
this.didMakeChangesToSyncProviders();
}
resyncAllDataForProvider(syncProvider) {
syncProvider.addPendingItems(this.modelManager.allItems);
this.sync();
}
removeSyncProvider(provider) {
_.pull(this.syncProviders, provider);
this.didMakeChangesToSyncProviders();
}
}
angular.module('app.frontend').service('syncManager', SyncManager);