@@ -73,13 +73,12 @@ angular.module('app.frontend')
|
||||
}
|
||||
|
||||
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){
|
||||
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){
|
||||
this.handleAuthResponse(response, email, url, authParams, mk, keys.pw);
|
||||
this.handleAuthResponse(response, email, url, authParams, keys.mk, keys.pw);
|
||||
callback(response);
|
||||
}.bind(this))
|
||||
.catch(function(response){
|
||||
@@ -91,7 +90,9 @@ angular.module('app.frontend')
|
||||
}
|
||||
|
||||
this.handleAuthResponse = function(response, email, url, authParams, mk, pw) {
|
||||
localStorage.setItem("server", url);
|
||||
if(url) {
|
||||
localStorage.setItem("server", url);
|
||||
}
|
||||
localStorage.setItem("user", JSON.stringify(response.plain().user));
|
||||
localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"])));
|
||||
localStorage.setItem("mk", mk);
|
||||
@@ -101,13 +102,12 @@ angular.module('app.frontend')
|
||||
|
||||
this.register = function(url, email, password, callback) {
|
||||
Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys, authParams){
|
||||
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){
|
||||
this.handleAuthResponse(response, email, url, authParams, mk, keys.pw);
|
||||
this.handleAuthResponse(response, email, url, authParams, keys.mk, keys.pw);
|
||||
callback(response);
|
||||
}.bind(this))
|
||||
.catch(function(response){
|
||||
@@ -117,55 +117,26 @@ angular.module('app.frontend')
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
// this.changePassword = function(current_password, new_password) {
|
||||
// this.getAuthParamsForEmail(email, function(authParams){
|
||||
// if(!authParams) {
|
||||
// callback(null);
|
||||
// return;
|
||||
// }
|
||||
// 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){
|
||||
// var data = {};
|
||||
// data.current_password = currentKeys.pw;
|
||||
// data.password = newKeys.pw;
|
||||
// data.password_confirmation = newKeys.pw;
|
||||
//
|
||||
// var user = this.user;
|
||||
//
|
||||
// this._performPasswordChange(currentKeys, newKeys, function(response){
|
||||
// if(response && !response.error) {
|
||||
// // this.showNewPasswordForm = false;
|
||||
// // reencrypt data with new mk
|
||||
// this.reencryptAllItemsAndSave(user, newKeys.mk, currentKeys.mk, function(success){
|
||||
// if(success) {
|
||||
// this.setMk(newKeys.mk);
|
||||
// alert("Your password has been changed and your data re-encrypted.");
|
||||
// } else {
|
||||
// // rollback password
|
||||
// this._performPasswordChange(newKeys, currentKeys, function(response){
|
||||
// alert("There was an error changing your password. Your password has been rolled back.");
|
||||
// window.location.reload();
|
||||
// })
|
||||
// }
|
||||
// }.bind(this));
|
||||
// } else {
|
||||
// // this.showNewPasswordForm = false;
|
||||
// alert("There was an error changing your password. Please try again.");
|
||||
// }
|
||||
// }.bind(this))
|
||||
// }.bind(this));
|
||||
// }.bind(this));
|
||||
// }.bind(this));
|
||||
// }
|
||||
this.changePassword = function(email, new_password, callback) {
|
||||
Neeto.crypto.generateInitialEncryptionKeysForUser({password: new_password, email: email}, function(keys, authParams){
|
||||
var requestUrl = localStorage.getItem("server") + "/auth/change_pw";
|
||||
var request = Restangular.oneUrl(requestUrl, requestUrl);
|
||||
var params = _.merge({new_password: keys.pw}, authParams);
|
||||
_.merge(request, params);
|
||||
|
||||
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){
|
||||
callback(response);
|
||||
})
|
||||
request.post().then(function(response){
|
||||
this.handleAuthResponse(response, email, null, authParams, keys.mk, keys.pw);
|
||||
callback(response.plain());
|
||||
}.bind(this))
|
||||
.catch(function(response){
|
||||
var error = response.data;
|
||||
if(!error) {
|
||||
error = {message: "Something went wrong while changing your password. Your password was not changed. Please try again."}
|
||||
}
|
||||
console.log("Change pw error", response);
|
||||
callback({error: error});
|
||||
})
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
this.staticifyObject = function(object) {
|
||||
|
||||
@@ -20,11 +20,15 @@ angular
|
||||
});
|
||||
|
||||
function showSpinner() {
|
||||
if(scope.hidePromise) {
|
||||
$timeout.cancel(scope.hidePromise);
|
||||
scope.hidePromise = null;
|
||||
}
|
||||
showElement(true);
|
||||
}
|
||||
|
||||
function hideSpinner() {
|
||||
$timeout(showElement.bind(this, false), getDelay());
|
||||
scope.hidePromise = $timeout(showElement.bind(this, false), getDelay());
|
||||
}
|
||||
|
||||
function showElement(show) {
|
||||
|
||||
@@ -15,10 +15,6 @@ class AccountMenu {
|
||||
|
||||
$scope.syncStatus = syncManager.syncStatus;
|
||||
|
||||
$scope.changePasswordPressed = function() {
|
||||
$scope.showNewPasswordForm = !$scope.showNewPasswordForm;
|
||||
}
|
||||
|
||||
$scope.encryptionKey = function() {
|
||||
return syncManager.masterKey;
|
||||
}
|
||||
@@ -31,20 +27,57 @@ class AccountMenu {
|
||||
return `${$scope.server}/dashboard/?server=${$scope.server}&id=${$scope.user.email}&pw=${$scope.serverPassword()}`;
|
||||
}
|
||||
|
||||
$scope.newPasswordData = {};
|
||||
|
||||
$scope.showPasswordChangeForm = function() {
|
||||
$scope.newPasswordData.showForm = true;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
if($scope.newPasswordData.newPassword != $scope.newPasswordData.newPasswordConfirmation) {
|
||||
alert("Your new password does not match its confirmation.");
|
||||
$scope.newPasswordData.status = null;
|
||||
return;
|
||||
}
|
||||
|
||||
authManager.changePassword($scope.passwordChangeData.current_password, $scope.passwordChangeData.new_password, function(response){
|
||||
var email = $scope.user.email;
|
||||
if(!email) {
|
||||
alert("We don't have your email stored. Please log out then log back in to fix this issue.");
|
||||
$scope.newPasswordData.status = null;
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.newPasswordData.status = "Generating New Keys...";
|
||||
$scope.newPasswordData.showForm = false;
|
||||
|
||||
// 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(function(response){
|
||||
authManager.changePassword(email, $scope.newPasswordData.newPassword, function(response){
|
||||
if(response.error) {
|
||||
alert("There was an error changing your password. Please try again.");
|
||||
$scope.newPasswordData.status = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// re-encrypt all items
|
||||
$scope.newPasswordData.status = "Re-encrypting all items with your new key...";
|
||||
|
||||
modelManager.setAllItemsDirty();
|
||||
syncManager.sync(function(response){
|
||||
if(response.error) {
|
||||
alert("There was an error re-encrypting your items. Your password was changed, but not all your items were properly re-encrypted and synced. You should try syncing again. If all else fails, you should restore your notes from backup.")
|
||||
return;
|
||||
}
|
||||
$scope.newPasswordData.status = "Successfully changed password and re-encrypted all items.";
|
||||
$timeout(function(){
|
||||
alert("Your password has been changed, and your items successfully re-encrypted and synced. You must sign out of all other signed in applications and sign in again, or else you may corrupt your data.")
|
||||
$scope.newPasswordData = {};
|
||||
}, 1000)
|
||||
});
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
$scope.loginSubmitPressed = function() {
|
||||
@@ -116,6 +149,8 @@ class AccountMenu {
|
||||
$scope.importData = null;
|
||||
if(!response) {
|
||||
alert("There was an error importing your data. Please try again.");
|
||||
} else {
|
||||
alert("Your data was successfully imported.")
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -149,8 +184,6 @@ class AccountMenu {
|
||||
}
|
||||
|
||||
$scope.importJSONData = function(data, password, callback) {
|
||||
console.log("Importing data", data);
|
||||
|
||||
var onDataReady = function() {
|
||||
var items = modelManager.mapResponseItemsToLocalModels(data.items);
|
||||
items.forEach(function(item){
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
class EncryptionHelper {
|
||||
|
||||
static encryptItem(item, key) {
|
||||
var item_key = null;
|
||||
if(item.enc_item_key) {
|
||||
// we reuse the key, but this is optional
|
||||
item_key = Neeto.crypto.decryptText(item.enc_item_key, key);
|
||||
} else {
|
||||
item_key = Neeto.crypto.generateRandomEncryptionKey();
|
||||
item.enc_item_key = Neeto.crypto.encryptText(item_key, key);
|
||||
}
|
||||
var item_key = Neeto.crypto.generateRandomEncryptionKey();
|
||||
item.enc_item_key = Neeto.crypto.encryptText(item_key, key);
|
||||
|
||||
var ek = Neeto.crypto.firstHalfOfKey(item_key);
|
||||
var ak = Neeto.crypto.secondHalfOfKey(item_key);
|
||||
|
||||
@@ -8,6 +8,7 @@ class ModelManager {
|
||||
this.itemChangeObservers = [];
|
||||
this.items = [];
|
||||
this._extensions = [];
|
||||
this.acceptableContentTypes = ["Note", "Tag", "Extension"];
|
||||
}
|
||||
|
||||
get allItems() {
|
||||
@@ -62,7 +63,7 @@ class ModelManager {
|
||||
for (var json_obj of items) {
|
||||
json_obj = _.omit(json_obj, omitFields || [])
|
||||
var item = this.findItem(json_obj["uuid"]);
|
||||
if(json_obj["deleted"] == true) {
|
||||
if(json_obj["deleted"] == true || !_.includes(this.acceptableContentTypes, json_obj["content_type"])) {
|
||||
if(item) {
|
||||
this.removeItemLocally(item)
|
||||
}
|
||||
@@ -225,6 +226,17 @@ class ModelManager {
|
||||
item.removeAllRelationships();
|
||||
}
|
||||
|
||||
/* Used when changing encryption key */
|
||||
setAllItemsDirty() {
|
||||
var relevantItems = this.allItems.filter(function(item){
|
||||
return _.includes(this.acceptableContentTypes, item.content_type);
|
||||
}.bind(this));
|
||||
|
||||
for(var item of relevantItems) {
|
||||
item.setDirty(true);
|
||||
}
|
||||
}
|
||||
|
||||
removeItemLocally(item, callback) {
|
||||
_.pull(this.items, item);
|
||||
|
||||
|
||||
@@ -98,10 +98,37 @@ class SyncManager {
|
||||
return this._cursorToken;
|
||||
}
|
||||
|
||||
get queuedCallbacks() {
|
||||
if(!this._queuedCallbacks) {
|
||||
this._queuedCallbacks = [];
|
||||
}
|
||||
return this._queuedCallbacks;
|
||||
}
|
||||
|
||||
clearQueuedCallbacks() {
|
||||
this._queuedCallbacks = [];
|
||||
}
|
||||
|
||||
callQueuedCallbacksAndCurrent(currentCallback, response) {
|
||||
var allCallbacks = this.queuedCallbacks;
|
||||
if(currentCallback) {
|
||||
allCallbacks.push(currentCallback);
|
||||
}
|
||||
if(allCallbacks.length) {
|
||||
for(var eachCallback of allCallbacks) {
|
||||
eachCallback(response);
|
||||
}
|
||||
this.clearQueuedCallbacks();
|
||||
}
|
||||
}
|
||||
|
||||
sync(callback, options = {}) {
|
||||
|
||||
if(this.syncStatus.syncOpInProgress) {
|
||||
this.repeatOnCompletion = true;
|
||||
if(callback) {
|
||||
this.queuedCallbacks.push(callback);
|
||||
}
|
||||
console.log("Sync op in progress; returning.");
|
||||
return;
|
||||
}
|
||||
@@ -116,18 +143,17 @@ class SyncManager {
|
||||
return;
|
||||
}
|
||||
|
||||
var isContinuationSync = this.needsMoreSync;
|
||||
var isContinuationSync = this.syncStatus.needsMoreSync;
|
||||
|
||||
this.repeatOnCompletion = false;
|
||||
this.syncStatus.syncOpInProgress = true;
|
||||
|
||||
let submitLimit = 100;
|
||||
var subItems = allDirtyItems.slice(0, submitLimit);
|
||||
if(subItems.length < allDirtyItems.length) {
|
||||
// more items left to be synced, repeat
|
||||
this.needsMoreSync = true;
|
||||
this.syncStatus.needsMoreSync = true;
|
||||
} else {
|
||||
this.needsMoreSync = false;
|
||||
this.syncStatus.needsMoreSync = false;
|
||||
}
|
||||
|
||||
if(!isContinuationSync) {
|
||||
@@ -153,10 +179,10 @@ class SyncManager {
|
||||
this.$rootScope.$broadcast("sync:updated_token", this.syncToken);
|
||||
|
||||
var retrieved = this.handleItemsResponse(response.retrieved_items, null);
|
||||
|
||||
// merge only metadata for saved items
|
||||
// Update 2/9/17: I just realized we may not need to handle saved_items anymore. We used to do this because we wanted to merge presentation-related metadata,
|
||||
// but that has since been removed. Since this function is an important part of the functioning of the app, I'm not going to remove it just yet without careful
|
||||
// testing.
|
||||
// we write saved items to disk now because it clears their dirty status then saves
|
||||
// if we saved items before completion, we had have to save them as dirty and save them again on success as clean
|
||||
var omitFields = ["content", "auth_hash"];
|
||||
var saved = this.handleItemsResponse(response.saved_items, omitFields);
|
||||
|
||||
@@ -172,14 +198,17 @@ class SyncManager {
|
||||
this.syncToken = response.sync_token;
|
||||
this.cursorToken = response.cursor_token;
|
||||
|
||||
if(this.cursorToken || this.repeatOnCompletion || this.needsMoreSync) {
|
||||
if(this.cursorToken || this.syncStatus.needsMoreSync) {
|
||||
setTimeout(function () {
|
||||
this.sync(callback, options);
|
||||
}.bind(this), 10); // wait 10ms to allow UI to update
|
||||
} else if(this.repeatOnCompletion) {
|
||||
this.repeatOnCompletion = false;
|
||||
setTimeout(function () {
|
||||
this.sync(callback, options);
|
||||
}.bind(this), 10); // wait 10ms to allow UI to update
|
||||
} else {
|
||||
if(callback) {
|
||||
callback(response);
|
||||
}
|
||||
this.callQueuedCallbacksAndCurrent(callback, response);
|
||||
}
|
||||
|
||||
}.bind(this))
|
||||
@@ -193,9 +222,7 @@ class SyncManager {
|
||||
|
||||
this.$rootScope.$broadcast("sync:error", error);
|
||||
|
||||
if(callback) {
|
||||
callback({error: "Sync error"});
|
||||
}
|
||||
this.callQueuedCallbacksAndCurrent(callback, {error: "Sync error"});
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
@@ -214,7 +241,7 @@ class SyncManager {
|
||||
var item = this.modelManager.findItem(itemResponse.uuid);
|
||||
var error = mapping.error;
|
||||
if(error.tag == "uuid_conflict") {
|
||||
// uuid conflicts can occur if a user attempts to import an old data archive with uuids form the old account into a new account
|
||||
// uuid conflicts can occur if a user attempts to import an old data archive with uuids from the old account into a new account
|
||||
this.modelManager.alternateUUIDForItem(item, handleNext);
|
||||
}
|
||||
++i;
|
||||
|
||||
@@ -124,6 +124,10 @@
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.italic {
|
||||
font-style: italic !important;
|
||||
}
|
||||
|
||||
.normal {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,13 @@
|
||||
%div{"ng-if" => "user"}
|
||||
%h2 {{user.email}}
|
||||
%p {{server}}
|
||||
%div.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress || syncStatus.needsMoreSync", "delay" => "1000"}
|
||||
.spinner.inline.mr-5.blue
|
||||
{{"Syncing" + (syncStatus.total > 0 ? ":" : "")}}
|
||||
%span{"ng-if" => "syncStatus.total > 0"} {{syncStatus.current}}/{{syncStatus.total}}
|
||||
%p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}}
|
||||
|
||||
%a.block.mt-15{"href" => "{{dashboardURL()}}", "target" => "_blank"} → Standard File Dashboard
|
||||
%a.block.mt-5{"ng-click" => "showCredentials = !showCredentials"} Show Credentials
|
||||
%section.gray-bg.mt-10.medium-padding{"ng-if" => "showCredentials"}
|
||||
%label.block
|
||||
@@ -36,13 +43,23 @@
|
||||
%label.block.mt-5.mb-0
|
||||
Server password:
|
||||
.wrap.normal.mt-1 {{serverPassword() ? serverPassword() : 'Not available. Sign out then sign back in to compute.'}}
|
||||
%a.block.mt-5{"href" => "{{dashboardURL()}}", "target" => "_blank"} Standard File Dashboard
|
||||
|
||||
%div.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress", "delay" => "1000"}
|
||||
.spinner.inline.mr-5.blue
|
||||
Syncing
|
||||
%span{"ng-if" => "syncStatus.total > 0"}: {{syncStatus.current}}/{{syncStatus.total}}
|
||||
%p.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}}
|
||||
%a.block.mt-5{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password
|
||||
%section.gray-bg.mt-10.medium-padding{"ng-if" => "newPasswordData.changePassword"}
|
||||
%p.bold Change Password (Beta)
|
||||
%p.mt-10 Since your encryption key is based on your password, changing your password requires all your notes and tags to be re-encrypted using your new key.
|
||||
%p.mt-5 If you have thousands of items, this can take several minutes — you must keep the application window open during this process.
|
||||
%p.mt-5 After changing your password, you must log out of all other applications currently signed in to your account.
|
||||
%p.bold.mt-5 It is highly recommended you download a backup of your data before proceeding.
|
||||
%div.mt-10{"ng-if" => "!newPasswordData.status"}
|
||||
%a.red.mr-5{"ng-if" => "!newPasswordData.showForm", "ng-click" => "showPasswordChangeForm()"} Continue
|
||||
%a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel
|
||||
%div.mt-10{"ng-if" => "newPasswordData.showForm"}
|
||||
%form
|
||||
%input.form-control{"type" => "text", "ng-model" => "newPasswordData.newPassword", "placeholder" => "Enter new password"}
|
||||
%input.form-control{"type" => "text", "ng-model" => "newPasswordData.newPasswordConfirmation", "placeholder" => "Confirm new password"}
|
||||
%button.btn.dark-button.btn-block{"ng-click" => "submitPasswordChange()"} Submit
|
||||
%p.italic.mt-10{"ng-if" => "newPasswordData.status"} {{newPasswordData.status}}
|
||||
|
||||
.medium-v-space
|
||||
|
||||
|
||||
Reference in New Issue
Block a user