Merge pull request #13 from standardnotes/offline

Offline
This commit is contained in:
Mo Bitar
2017-01-08 22:21:19 -06:00
committed by GitHub
19 changed files with 722 additions and 518 deletions

View File

@@ -1,13 +1,6 @@
class BaseCtrl { class BaseCtrl {
constructor($rootScope, modelManager) { constructor($rootScope, modelManager, apiController) {
// $rootScope.resetPasswordSubmit = function() { apiController.getCurrentUser(function(){});
// var new_keys = Neeto.crypto.generateEncryptionKeysForUser($rootScope.resetData.password, $rootScope.resetData.email);
// var data = _.clone($rootScope.resetData);
// data.password = new_keys.pw;
// data.password_confirmation = new_keys.pw;
// $auth.updatePassword(data);
// apiController.setMk(new_keys.mk);
// }
} }
} }

View File

@@ -5,8 +5,7 @@ angular.module('app.frontend')
scope: { scope: {
save: "&", save: "&",
remove: "&", remove: "&",
note: "=", note: "="
user: "="
}, },
templateUrl: 'frontend/editor.html', templateUrl: 'frontend/editor.html',
replace: true, replace: true,
@@ -124,6 +123,11 @@ angular.module('app.frontend')
statusTimeout = $timeout(function(){ statusTimeout = $timeout(function(){
this.noteStatus = "All changes saved" this.noteStatus = "All changes saved"
}.bind(this), 200) }.bind(this), 200)
} else {
if(statusTimeout) $timeout.cancel(statusTimeout);
statusTimeout = $timeout(function(){
this.noteStatus = "(Offline) — All changes saved"
}.bind(this), 200)
} }
}.bind(this)); }.bind(this));
} }
@@ -138,7 +142,7 @@ angular.module('app.frontend')
this.changesMade = function() { this.changesMade = function() {
this.note.hasChanges = true; this.note.hasChanges = true;
this.note.dummy = false; this.note.dummy = false;
if(this.user.uuid) { if(apiController.isUserSignedIn()) {
// signed out users have local autosave, dont need draft saving // signed out users have local autosave, dont need draft saving
apiController.saveDraftToDisk(this.note); apiController.saveDraftToDisk(this.note);
} }

View File

@@ -3,7 +3,6 @@ angular.module('app.frontend')
return { return {
restrict: 'E', restrict: 'E',
scope: { scope: {
user: "=",
logout: "&" logout: "&"
}, },
templateUrl: 'frontend/header.html', templateUrl: 'frontend/header.html',
@@ -21,6 +20,7 @@ angular.module('app.frontend')
}) })
.controller('HeaderCtrl', function ($state, apiController, modelManager, serverSideValidation, $timeout, extensionManager) { .controller('HeaderCtrl', function ($state, apiController, modelManager, serverSideValidation, $timeout, extensionManager) {
this.user = apiController.user;
this.extensionManager = extensionManager; this.extensionManager = extensionManager;
this.changePasswordPressed = function() { this.changePasswordPressed = function() {
@@ -104,7 +104,7 @@ angular.module('app.frontend')
return; return;
} }
apiController.changePassword(this.user, this.passwordChangeData.current_password, this.passwordChangeData.new_password, function(response){ apiController.changePassword(this.passwordChangeData.current_password, this.passwordChangeData.new_password, function(response){
}) })
@@ -193,24 +193,43 @@ angular.module('app.frontend')
link.click(); link.click();
} }
this.performImport = function(data, password) {
apiController.importJSONData(data, password, function(success, response){
console.log("import response", success, response);
if(success) {
this.importData = null;
} else {
alert("There was an error importing your data. Please try again.");
}
}.bind(this))
}
this.submitImportPassword = function() {
this.performImport(this.importData.data, this.importData.password);
}
this.importFileSelected = function(files) { this.importFileSelected = function(files) {
this.importData = {};
var file = files[0]; var file = files[0];
var reader = new FileReader(); var reader = new FileReader();
reader.onload = function(e) { reader.onload = function(e) {
apiController.importJSONData(e.target.result, function(success, response){ var data = JSON.parse(e.target.result);
console.log("import response", success, response); $timeout(function(){
if(success) { if(data.auth_params) {
// window.location.reload(); // request password
this.importData.requestPassword = true;
this.importData.data = data;
} else { } else {
alert("There was an error importing your data. Please try again."); this.performImport(data, null);
} }
}) }.bind(this))
} }.bind(this)
reader.readAsText(file); reader.readAsText(file);
} }
this.onAuthSuccess = function(user) { this.onAuthSuccess = function(user) {
this.user.uuid = user.uuid;
// if(this.user.shouldMerge && this.hasLocalData()) { // if(this.user.shouldMerge && this.hasLocalData()) {
// apiController.mergeLocalDataRemotely(this.user, function(){ // apiController.mergeLocalDataRemotely(this.user, function(){

View File

@@ -2,30 +2,17 @@ angular.module('app.frontend')
.controller('HomeCtrl', function ($scope, $rootScope, $timeout, apiController, modelManager) { .controller('HomeCtrl', function ($scope, $rootScope, $timeout, apiController, modelManager) {
$rootScope.bodyClass = "app-body-class"; $rootScope.bodyClass = "app-body-class";
var onUserSet = function() { apiController.loadLocalItems();
apiController.setUser($scope.defaultUser); $scope.allTag = new Tag({all: true});
$scope.allTag = new Tag({all: true}); $scope.allTag.title = "All";
$scope.allTag.title = "All"; $scope.tags = modelManager.tags;
$scope.tags = modelManager.tags; $scope.allTag.notes = modelManager.notes;
$scope.allTag.notes = modelManager.notes;
apiController.sync(null);
// refresh every 30s
setInterval(function () {
apiController.sync(null); apiController.sync(null);
// refresh every 30s }, 30000);
setInterval(function () {
apiController.sync(null);
}, 30000);
}
apiController.getCurrentUser(function(user){
if(user) {
$scope.defaultUser = user;
$rootScope.title = "Notes — Standard Notes";
onUserSet();
} else {
$scope.defaultUser = new User(apiController.loadLocalItemsAndUser());
onUserSet();
}
});
/* /*
Tags Ctrl Callbacks Tags Ctrl Callbacks
@@ -115,7 +102,10 @@ angular.module('app.frontend')
apiController.sync(function(response){ apiController.sync(function(response){
if(response && response.error) { if(response && response.error) {
alert("There was an error saving your note. Please try again."); if(!$scope.didShowErrorAlert) {
$scope.didShowErrorAlert = true;
alert("There was an error saving your note. Please try again.");
}
callback(false); callback(false);
} else { } else {
note.hasChanges = false; note.hasChanges = false;
@@ -147,8 +137,7 @@ angular.module('app.frontend')
*/ */
$scope.headerLogout = function() { $scope.headerLogout = function() {
$scope.defaultUser = apiController.loadLocalItemsAndUser(); apiController.clearLocalStorage();
$scope.tags = $scope.defaultUser.tags;
} }

View File

@@ -6,7 +6,6 @@ angular.module('app.frontend')
selectionMade: "&", selectionMade: "&",
remove: "&", remove: "&",
tag: "=", tag: "=",
user: "=",
removeTag: "&" removeTag: "&"
}, },
@@ -71,7 +70,7 @@ angular.module('app.frontend')
this.selectedTagShare = function() { this.selectedTagShare = function() {
this.showMenu = false; this.showMenu = false;
if(!this.user.uuid) { if(!apiController.isUserSignedIn()) {
alert("You must be signed in to share a tag."); alert("You must be signed in to share a tag.");
return; return;
} }

View File

@@ -9,7 +9,6 @@ angular.module('app.frontend')
save: "&", save: "&",
tags: "=", tags: "=",
allTag: "=", allTag: "=",
user: "=",
updateNoteTag: "&" updateNoteTag: "&"
}, },
templateUrl: 'frontend/tags.html', templateUrl: 'frontend/tags.html',

View File

@@ -1,11 +1,10 @@
angular.module('app.frontend') angular.module('app.frontend')
.controller('UsernameModalCtrl', function ($scope, apiController, Restangular, user, callback, $timeout) { .controller('UsernameModalCtrl', function ($scope, apiController, Restangular, callback, $timeout) {
$scope.formData = {}; $scope.formData = {};
$scope.saveUsername = function() { $scope.saveUsername = function() {
apiController.setUsername(user, $scope.formData.username, function(response){ apiController.setUsername($scope.formData.username, function(response){
var username = response.username; var username = response.username;
user.username = username;
callback(username); callback(username);
$scope.closeThisDialog(); $scope.closeThisDialog();
}) })

View File

@@ -45,6 +45,10 @@ class Item {
} }
} }
alternateUUID() {
this.uuid = Neeto.crypto.generateUUID();
}
setDirty(dirty) { setDirty(dirty) {
this.dirty = dirty; this.dirty = dirty;
@@ -101,9 +105,14 @@ class Item {
_.merge(this, _.omit(item, ["content"])); _.merge(this, _.omit(item, ["content"]));
} }
allReferencedObjects() {
// must override
return [];
}
referencesAffectedBySharingChange() { referencesAffectedBySharingChange() {
// should be overriden to determine which references should be decrypted/encrypted // should be overriden to determine which references should be decrypted/encrypted
return null; return [];
} }
isPublic() { isPublic() {

View File

@@ -61,6 +61,10 @@ class Note extends Item {
return filtered; return filtered;
} }
allReferencedObjects() {
return this.tags;
}
referencesAffectedBySharingChange() { referencesAffectedBySharingChange() {
return super.referencesAffectedBySharingChange(); return super.referencesAffectedBySharingChange();
} }

View File

@@ -59,6 +59,10 @@ class Tag extends Item {
return "Tag"; return "Tag";
} }
allReferencedObjects() {
return this.notes;
}
referencesAffectedBySharingChange() { referencesAffectedBySharingChange() {
return this.notes; return this.notes;
} }

View File

@@ -1,5 +0,0 @@
class User {
constructor(json_obj) {
_.merge(this, json_obj);
}
}

View File

@@ -26,9 +26,8 @@ angular.module('app.frontend')
function ApiController($rootScope, Restangular, modelManager, ngDialog) { function ApiController($rootScope, Restangular, modelManager, ngDialog) {
this.setUser = function(user) { this.user = {};
this.user = user; this.syncToken = localStorage.getItem("syncToken");
}
/* /*
Config Config
@@ -57,8 +56,16 @@ angular.module('app.frontend')
Auth Auth
*/ */
this.getAuthParams = function() {
return JSON.parse(localStorage.getItem("auth_params"));
}
this.isUserSignedIn = function() { this.isUserSignedIn = function() {
return this.user.email && this.retrieveMk(); return localStorage.getItem("jwt");
}
this.userId = function() {
return localStorage.getItem("uuid");
} }
this.getAuthParamsForEmail = function(email, callback) { this.getAuthParamsForEmail = function(email, callback) {
@@ -79,7 +86,8 @@ angular.module('app.frontend')
} }
Restangular.one("users/current").get().then(function(response){ Restangular.one("users/current").get().then(function(response){
var user = response.plain(); var user = response.plain();
callback(user); _.merge(this.user, user);
callback();
}.bind(this)) }.bind(this))
.catch(function(response){ .catch(function(response){
console.log("Error getting current user", response); console.log("Error getting current user", response);
@@ -93,13 +101,15 @@ angular.module('app.frontend')
callback(null); callback(null);
return; return;
} }
Neeto.crypto.computeEncryptionKeysForUser(_.merge({email: email, password: password}, authParams), function(keys){ Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){
this.setMk(keys.mk); this.setMk(keys.mk);
var request = Restangular.one("auth/sign_in"); var request = Restangular.one("auth/sign_in");
var params = {password: keys.pw, email: email}; var params = {password: keys.pw, email: email};
_.merge(request, params); _.merge(request, params);
request.post().then(function(response){ request.post().then(function(response){
localStorage.setItem("jwt", response.token); localStorage.setItem("jwt", response.token);
localStorage.setItem("uuid", response.uuid);
localStorage.setItem("auth_params", JSON.stringify(authParams));
callback(response); callback(response);
}) })
.catch(function(response){ .catch(function(response){
@@ -110,14 +120,16 @@ angular.module('app.frontend')
} }
this.register = function(email, password, callback) { this.register = function(email, password, callback) {
Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys){ Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys, authParams){
this.setMk(keys.mk); this.setMk(keys.mk);
keys.mk = null; keys.mk = null;
var request = Restangular.one("auth"); var request = Restangular.one("auth");
var params = _.merge({password: keys.pw, email: email}, keys); var params = _.merge({password: keys.pw, email: email}, authParams);
_.merge(request, params); _.merge(request, params);
request.post().then(function(response){ request.post().then(function(response){
localStorage.setItem("jwt", response.token); localStorage.setItem("jwt", response.token);
localStorage.setItem("uuid", response.uuid);
localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"])));
callback(response); callback(response);
}) })
.catch(function(response){ .catch(function(response){
@@ -126,46 +138,46 @@ angular.module('app.frontend')
}.bind(this)); }.bind(this));
} }
this.changePassword = function(user, current_password, new_password) { // this.changePassword = function(current_password, new_password) {
this.getAuthParamsForEmail(email, function(authParams){ // this.getAuthParamsForEmail(email, function(authParams){
if(!authParams) { // if(!authParams) {
callback(null); // callback(null);
return; // return;
} // }
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: current_password, email: user.email}, authParams), function(currentKeys) { // Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: current_password, email: user.email}, authParams), function(currentKeys) {
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: new_password, email: user.email}, authParams), function(newKeys){ // Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: new_password, email: user.email}, authParams), function(newKeys){
var data = {}; // var data = {};
data.current_password = currentKeys.pw; // data.current_password = currentKeys.pw;
data.password = newKeys.pw; // data.password = newKeys.pw;
data.password_confirmation = newKeys.pw; // data.password_confirmation = newKeys.pw;
//
var user = this.user; // var user = this.user;
//
this._performPasswordChange(currentKeys, newKeys, function(response){ // this._performPasswordChange(currentKeys, newKeys, function(response){
if(response && !response.error) { // if(response && !response.error) {
// this.showNewPasswordForm = false; // // this.showNewPasswordForm = false;
// reencrypt data with new mk // // reencrypt data with new mk
this.reencryptAllItemsAndSave(user, newKeys.mk, currentKeys.mk, function(success){ // this.reencryptAllItemsAndSave(user, newKeys.mk, currentKeys.mk, function(success){
if(success) { // if(success) {
this.setMk(newKeys.mk); // this.setMk(newKeys.mk);
alert("Your password has been changed and your data re-encrypted."); // alert("Your password has been changed and your data re-encrypted.");
} else { // } else {
// rollback password // // rollback password
this._performPasswordChange(newKeys, currentKeys, function(response){ // this._performPasswordChange(newKeys, currentKeys, function(response){
alert("There was an error changing your password. Your password has been rolled back."); // alert("There was an error changing your password. Your password has been rolled back.");
window.location.reload(); // window.location.reload();
}) // })
} // }
}.bind(this)); // }.bind(this));
} else { // } else {
// this.showNewPasswordForm = false; // // this.showNewPasswordForm = false;
alert("There was an error changing your password. Please try again."); // alert("There was an error changing your password. Please try again.");
} // }
}.bind(this)) // }.bind(this))
}.bind(this)); // }.bind(this));
}.bind(this)); // }.bind(this));
}.bind(this)); // }.bind(this));
} // }
this._performPasswordChange = function(email, current_keys, new_keys, callback) { this._performPasswordChange = function(email, current_keys, new_keys, callback) {
var request = Restangular.one("auth"); var request = Restangular.one("auth");
@@ -181,53 +193,44 @@ angular.module('app.frontend')
User User
*/ */
this.setUsername = function(user, username, callback) { this.setUsername = function(username, callback) {
var request = Restangular.one("users", user.uuid); var request = Restangular.one("users", this.userId());
request.username = username; request.username = username;
request.patch().then(function(response){ request.patch().then(function(response){
this.user.username = response.username;
callback(response.plain()); callback(response.plain());
})
}
/*
Ensures that if encryption is disabled, all local items are uncrypted,
and that if it's enabled, that all local items are encrypted
*/
this.verifyEncryptionStatusOfAllItems = function(user, callback) {
var allItems = user.filteredItems();
var itemsNeedingUpdate = [];
allItems.forEach(function(item){
if(!item.isPublic()) {
if(item.encryptionEnabled() && !item.isEncrypted()) {
itemsNeedingUpdate.push(item);
}
} else {
if(item.isEncrypted()) {
itemsNeedingUpdate.push(item);
}
}
}.bind(this)) }.bind(this))
if(itemsNeedingUpdate.length > 0) {
console.log("verifying encryption, items need updating", itemsNeedingUpdate);
this.saveBatchItems(user, itemsNeedingUpdate, callback)
}
} }
/* /*
Items Items
*/ */
this.setSyncToken = function(syncToken) {
this.syncToken = syncToken;
localStorage.setItem("syncToken", this.syncToken);
}
this.syncWithOptions = function(callback, options = {}) { this.syncWithOptions = function(callback, options = {}) {
if(!this.user.uuid) { this.writeAllItemsToLocalStorage(function(responseItems){
this.writeItemsToLocalStorage(function(responseItems){ if(!this.isUserSignedIn()) {
// is not signed in
var dirtyItems = modelManager.getDirtyItems();
// delete anything needing to be deleted
dirtyItems.forEach(function(item){
if(item.deleted) {
modelManager.removeItemLocally(item);
}
}.bind(this))
modelManager.clearDirtyItems(); modelManager.clearDirtyItems();
if(callback) { if(callback) {
callback(); callback();
} }
}.bind(this)) }
}.bind(this))
if(!this.isUserSignedIn()) {
return; return;
} }
@@ -245,7 +248,7 @@ angular.module('app.frontend')
request.post().then(function(response) { request.post().then(function(response) {
modelManager.clearDirtyItems(); modelManager.clearDirtyItems();
this.syncToken = response.sync_token; this.setSyncToken(response.sync_token);
$rootScope.$broadcast("sync:updated_token", this.syncToken); $rootScope.$broadcast("sync:updated_token", this.syncToken);
this.handleItemsResponse(response.retrieved_items, null); this.handleItemsResponse(response.retrieved_items, null);
@@ -253,13 +256,19 @@ angular.module('app.frontend')
var omitFields = ["content", "enc_item_key", "auth_hash"]; var omitFields = ["content", "enc_item_key", "auth_hash"];
this.handleItemsResponse(response.saved_items, omitFields); this.handleItemsResponse(response.saved_items, omitFields);
this.handleUnsavedItemsResponse(response.unsaved)
this.writeAllItemsToLocalStorage();
if(callback) { if(callback) {
callback(response); callback(response);
} }
}.bind(this)) }.bind(this))
.catch(function(response){ .catch(function(response){
console.log("Sync error: ", response); console.log("Sync error: ", response);
callback({error: "Sync error"}); if(callback) {
callback({error: "Sync error"});
}
}) })
} }
@@ -267,6 +276,28 @@ angular.module('app.frontend')
this.syncWithOptions(callback, undefined); this.syncWithOptions(callback, undefined);
} }
this.handleUnsavedItemsResponse = function(unsaved) {
if(unsaved.length == 0) {
return;
}
console.log("Handle unsaved", unsaved);
for(var mapping of unsaved) {
var itemResponse = mapping.item;
var item = modelManager.findItem(itemResponse.uuid);
var error = mapping.error;
if(error.tag == "uuid_conflict") {
item.alternateUUID();
item.setDirty(true);
item.allReferencedObjects().forEach(function(reference){
reference.setDirty(true);
})
}
}
this.syncWithOptions(null, {additionalFields: ["created_at", "updated_at"]});
}
this.handleItemsResponse = function(responseItems, omitFields) { this.handleItemsResponse = function(responseItems, omitFields) {
this.decryptItems(responseItems); this.decryptItems(responseItems);
return modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields); return modelManager.mapResponseItemsToLocalModelsOmittingFields(responseItems, omitFields);
@@ -314,7 +345,7 @@ angular.module('app.frontend')
} }
this.shareItem = function(item, callback) { this.shareItem = function(item, callback) {
if(!this.user.uuid) { if(!this.isUserSignedIn()) {
alert("You must be signed in to share."); alert("You must be signed in to share.");
return; return;
} }
@@ -333,7 +364,6 @@ angular.module('app.frontend')
template: 'frontend/modals/username.html', template: 'frontend/modals/username.html',
controller: 'UsernameModalCtrl', controller: 'UsernameModalCtrl',
resolve: { resolve: {
user: function() {return this.user}.bind(this),
callback: function() { callback: function() {
return shareFn; return shareFn;
} }
@@ -359,15 +389,39 @@ angular.module('app.frontend')
Import Import
*/ */
this.importJSONData = function(jsonString, callback) { this.importJSONData = function(data, password, callback) {
var data = JSON.parse(jsonString); console.log("Importing data", data);
console.log("importing data", data);
this.decryptItems(data.items); var onDataReady = function() {
modelManager.mapResponseItemsToLocalModels(data.items); modelManager.mapResponseItemsToLocalModels(data.items);
modelManager.allItems.forEach(function(item){ modelManager.allItems.forEach(function(item){
item.setDirty(true); item.setDirty(true);
}) })
this.syncWithOptions(callback, {additionalFields: ["created_at", "updated_at"]}); this.syncWithOptions(callback, {additionalFields: ["created_at", "updated_at"]});
}.bind(this)
if(data.auth_params) {
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, data.auth_params), function(keys){
var mk = keys.mk;
try {
this.decryptItemsWithKey(data.items, mk);
// delete items enc_item_key since the user's actually key will do the encrypting once its passed off
data.items.forEach(function(item){
item.enc_item_key = null;
item.auth_hash = null;
})
onDataReady();
}
catch (e) {
console.log("Error decrypting", e);
alert("There was an error decrypting your items. Make sure the password you entered is correct and try again.");
callback(false, null);
return;
}
}.bind(this));
} else {
onDataReady();
}
} }
/* /*
@@ -399,6 +453,10 @@ angular.module('app.frontend')
items: items items: items
} }
if(encrypted) {
data["auth_params"] = this.getAuthParams();
}
return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */)); return makeTextFile(JSON.stringify(data, null, 2 /* pretty print */));
} }
@@ -407,52 +465,57 @@ angular.module('app.frontend')
/* /*
Merging Merging
*/ */
this.mergeLocalDataRemotely = function(user, callback) { // this.mergeLocalDataRemotely = function(user, callback) {
var request = Restangular.one("users", user.uuid).one("merge"); // var request = Restangular.one("users", this.userId()).one("merge");
var tags = user.tags; // var tags = user.tags;
request.items = user.items; // request.items = user.items;
request.items.forEach(function(item){ // request.items.forEach(function(item){
if(item.tag_id) { // if(item.tag_id) {
var tag = tags.filter(function(tag){return tag.uuid == item.tag_id})[0]; // var tag = tags.filter(function(tag){return tag.uuid == item.tag_id})[0];
item.tag_name = tag.title; // item.tag_name = tag.title;
} // }
}) // })
request.post().then(function(response){ // request.post().then(function(response){
callback(); // callback();
localStorage.removeItem('user'); // localStorage.removeItem('user');
}) // })
// }
this.clearLocalStorage = function() {
localStorage.removeItem("items");
localStorage.removeItem("mk");
localStorage.removeItem("jwt");
localStorage.removeItem("uuid");
localStorage.removeItem("syncToken");
localStorage.removeItem("auth_params");
} }
this.staticifyObject = function(object) { this.staticifyObject = function(object) {
return JSON.parse(JSON.stringify(object)); return JSON.parse(JSON.stringify(object));
} }
this.writeItemsToLocalStorage = function(callback) { this.writeAllItemsToLocalStorage = function(callback) {
var items = _.map(modelManager.allItems, function(item){ var items = _.map(modelManager.allItems, function(item){
return this.paramsForItem(item, false, ["created_at", "updated_at"], false) return this.paramsForItem(item, this.isUserSignedIn(), ["created_at", "updated_at", "presentation_url"], false)
}.bind(this)); }.bind(this));
console.log("Writing items to local", items); // console.log("Writing items to local", items);
this.writeToLocalStorage('items', items); this.writeToLocalStorage('items', items);
callback(items); if(callback) {
callback(items);
}
} }
this.writeToLocalStorage = function(key, value) { this.writeToLocalStorage = function(key, value) {
localStorage.setItem(key, angular.toJson(value)); localStorage.setItem(key, angular.toJson(value));
} }
this.loadLocalItemsAndUser = function() { this.loadLocalItems = function() {
var user = {};
var items = JSON.parse(localStorage.getItem('items')) || []; var items = JSON.parse(localStorage.getItem('items')) || [];
items = this.handleItemsResponse(items, null); items = this.handleItemsResponse(items, null);
Item.sortItemsByDate(items); Item.sortItemsByDate(items);
user.items = items;
user.shouldMerge = true;
return user;
} }
/* /*
@@ -533,6 +596,10 @@ angular.module('app.frontend')
this.decryptItems = function(items) { this.decryptItems = function(items) {
var masterKey = this.retrieveMk(); var masterKey = this.retrieveMk();
this.decryptItemsWithKey(items, masterKey);
}
this.decryptItemsWithKey = function(items, key) {
for (var item of items) { for (var item of items) {
if(item.deleted == true) { if(item.deleted == true) {
continue; continue;
@@ -541,7 +608,7 @@ angular.module('app.frontend')
if(isString) { if(isString) {
if(item.content.substring(0, 3) == "001" && item.enc_item_key) { if(item.content.substring(0, 3) == "001" && item.enc_item_key) {
// is encrypted // is encrypted
this.decryptSingleItem(item, masterKey); this.decryptSingleItem(item, key);
} else { } else {
// is base64 encoded // is base64 encoded
item.content = Neeto.crypto.base64Decode(item.content.substring(3, item.content.length)) item.content = Neeto.crypto.base64Decode(item.content.substring(3, item.content.length))
@@ -551,7 +618,7 @@ angular.module('app.frontend')
} }
this.reencryptAllItemsAndSave = function(user, newMasterKey, oldMasterKey, callback) { this.reencryptAllItemsAndSave = function(user, newMasterKey, oldMasterKey, callback) {
var items = user.filteredItems(); var items = modelManager.allItems();
items.forEach(function(item){ items.forEach(function(item){
if(item.content.substring(0, 3) == "001" && item.enc_item_key) { if(item.content.substring(0, 3) == "001" && item.enc_item_key) {
// first decrypt item_key with old key // first decrypt item_key with old key

View File

@@ -255,6 +255,9 @@ class ExtensionManager {
performPost(action, extension, params, callback) { performPost(action, extension, params, callback) {
var request = this.Restangular.oneUrl(action.url, action.url); var request = this.Restangular.oneUrl(action.url, action.url);
if(this.extensionUsesEncryptedData(extension)) {
request.auth_params = this.apiController.getAuthParams();
}
_.merge(request, params); _.merge(request, params);
request.post().then(function(response){ request.post().then(function(response){

View File

@@ -80,7 +80,7 @@ class SNCrypto {
return CryptoJS.HmacSHA256(messageData, keyData).toString(); return CryptoJS.HmacSHA256(messageData, keyData).toString();
} }
computeEncryptionKeysForUser({email, password, pw_salt, pw_func, pw_alg, pw_cost, pw_key_size} = {}, callback) { computeEncryptionKeysForUser({password, pw_salt, pw_func, pw_alg, pw_cost, pw_key_size} = {}, callback) {
this.generateSymmetricKeyPair({password: password, pw_salt: pw_salt, this.generateSymmetricKeyPair({password: password, pw_salt: pw_salt,
pw_func: pw_func, pw_alg: pw_alg, pw_cost: pw_cost, pw_key_size: pw_key_size}, function(keys){ pw_func: pw_func, pw_alg: pw_alg, pw_cost: pw_cost, pw_key_size: pw_key_size}, function(keys){
var pw = keys[0]; var pw = keys[0];
@@ -95,11 +95,12 @@ class SNCrypto {
var {pw_func, pw_alg, pw_key_size, pw_cost} = defaults; var {pw_func, pw_alg, pw_key_size, pw_cost} = defaults;
var pw_nonce = this.generateRandomKey(); var pw_nonce = this.generateRandomKey();
var pw_salt = this.sha1(email + "SN" + pw_nonce); var pw_salt = this.sha1(email + "SN" + pw_nonce);
_.merge(defaults, {pw_salt: pw_salt, pw_nonce: pw_nonce})
this.generateSymmetricKeyPair(_.merge({email: email, password: password, pw_salt: pw_salt}, defaults), function(keys){ this.generateSymmetricKeyPair(_.merge({email: email, password: password, pw_salt: pw_salt}, defaults), function(keys){
var pw = keys[0]; var pw = keys[0];
var mk = keys[1]; var mk = keys[1];
callback(_.merge({pw: pw, mk: mk, pw_nonce: pw_nonce}, defaults)); callback({pw: pw, mk: mk}, defaults);
}); });
} }
} }

View File

@@ -129,7 +129,7 @@
margin-bottom: 8px; margin-bottom: 8px;
a { a {
font-size: 12px; font-size: 12px;
color: #00228f; color: $blue-color;
font-weight: bold; font-weight: bold;
} }
} }
@@ -152,7 +152,7 @@
margin-bottom: 34px; margin-bottom: 34px;
a { a {
color: #00228f; color: $blue-color;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
} }
@@ -186,6 +186,15 @@
} }
} }
.import-password {
margin-top: 14px;
> .field {
display: block;
margin: 5px 0px;
}
}
.encryption-confirmation { .encryption-confirmation {
position: relative; position: relative;
.buttons { .buttons {

View File

@@ -90,6 +90,10 @@
%a.disabled %a.disabled
%span %span
Import Data from Archive Import Data from Archive
.import-password{"ng-if" => "ctrl.importData.requestPassword"}
Enter the account password associated with the import file.
%input.field{"type" => "text", "ng-model" => "ctrl.importData.password"}
%button{"ng-click" => "ctrl.submitImportPassword()"} Decrypt & Import
.item .item
%a{"ng-click" => "ctrl.toggleExtensions()"} Extensions %a{"ng-click" => "ctrl.toggleExtensions()"} Extensions

View File

@@ -5,7 +5,7 @@
"tags" => "tags", "user" => "defaultUser", "update-note-tag" => "tagsUpdateNoteTag"} "tags" => "tags", "user" => "defaultUser", "update-note-tag" => "tagsUpdateNoteTag"}
%notes-section{"remove-tag" => "notesRemoveTag", "user" => "defaultUser", "add-new" => "notesAddNew", "selection-made" => "notesSelectionMade", %notes-section{"remove-tag" => "notesRemoveTag", "user" => "defaultUser", "add-new" => "notesAddNew", "selection-made" => "notesSelectionMade",
"tag" => "selectedTag", "user-id" => "defaultUser.uuid", "remove" => "deleteNote"} "tag" => "selectedTag", "remove" => "deleteNote"}
%editor-section{"ng-if" => "selectedNote", "note" => "selectedNote", "remove" => "deleteNote", %editor-section{"ng-if" => "selectedNote", "note" => "selectedNote", "remove" => "deleteNote",
"user" => "defaultUser", "save" => "saveNote"} "user" => "defaultUser", "save" => "saveNote"}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long