change pw wip

This commit is contained in:
Mo Bitar
2017-02-10 11:49:50 -06:00
parent ae9899dee1
commit 577f286352
7 changed files with 134 additions and 79 deletions

View File

@@ -73,13 +73,12 @@ angular.module('app.frontend')
} }
Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){ Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){
var mk = keys.mk;
var requestUrl = url + "/auth/sign_in"; var requestUrl = url + "/auth/sign_in";
var request = Restangular.oneUrl(requestUrl, requestUrl); var request = Restangular.oneUrl(requestUrl, requestUrl);
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){
this.handleAuthResponse(response, email, url, authParams, mk, keys.pw); this.handleAuthResponse(response, email, url, authParams, keys.mk, keys.pw);
callback(response); callback(response);
}.bind(this)) }.bind(this))
.catch(function(response){ .catch(function(response){
@@ -91,7 +90,9 @@ angular.module('app.frontend')
} }
this.handleAuthResponse = function(response, email, url, authParams, mk, pw) { 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("user", JSON.stringify(response.plain().user));
localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"]))); localStorage.setItem("auth_params", JSON.stringify(_.omit(authParams, ["pw_nonce"])));
localStorage.setItem("mk", mk); localStorage.setItem("mk", mk);
@@ -101,13 +102,12 @@ angular.module('app.frontend')
this.register = function(url, email, password, callback) { this.register = function(url, email, password, callback) {
Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys, authParams){ Neeto.crypto.generateInitialEncryptionKeysForUser({password: password, email: email}, function(keys, authParams){
var mk = keys.mk;
var requestUrl = url + "/auth"; var requestUrl = url + "/auth";
var request = Restangular.oneUrl(requestUrl, requestUrl); var request = Restangular.oneUrl(requestUrl, requestUrl);
var params = _.merge({password: keys.pw, email: email}, authParams); 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){
this.handleAuthResponse(response, email, url, authParams, mk, keys.pw); this.handleAuthResponse(response, email, url, authParams, keys.mk, keys.pw);
callback(response); callback(response);
}.bind(this)) }.bind(this))
.catch(function(response){ .catch(function(response){
@@ -117,55 +117,26 @@ angular.module('app.frontend')
}.bind(this)); }.bind(this));
} }
// this.changePassword = function(current_password, new_password) { this.changePassword = function(email, new_password, callback) {
// this.getAuthParamsForEmail(email, function(authParams){ Neeto.crypto.generateInitialEncryptionKeysForUser({password: new_password, email: email}, function(keys, authParams){
// if(!authParams) { var requestUrl = localStorage.getItem("server") + "/auth/change_pw";
// callback(null); var request = Restangular.oneUrl(requestUrl, requestUrl);
// return; var params = _.merge({new_password: keys.pw}, authParams);
// } _.merge(request, params);
// 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._performPasswordChange = function(url, email, current_keys, new_keys, callback) { request.post().then(function(response){
var requestUrl = url + "/auth"; this.handleAuthResponse(response, email, null, authParams, keys.mk, keys.pw);
var request = Restangular.oneUrl(requestUrl, requestUrl); callback(response.plain());
var params = {password: new_keys.pw, password_confirmation: new_keys.pw, current_password: current_keys.pw, email: email}; }.bind(this))
_.merge(request, params); .catch(function(response){
request.patch().then(function(response){ var error = response.data;
callback(response); 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) { this.staticifyObject = function(object) {

View File

@@ -15,10 +15,6 @@ class AccountMenu {
$scope.syncStatus = syncManager.syncStatus; $scope.syncStatus = syncManager.syncStatus;
$scope.changePasswordPressed = function() {
$scope.showNewPasswordForm = !$scope.showNewPasswordForm;
}
$scope.encryptionKey = function() { $scope.encryptionKey = function() {
return syncManager.masterKey; return syncManager.masterKey;
} }
@@ -31,19 +27,50 @@ class AccountMenu {
return `${$scope.server}/dashboard/?server=${$scope.server}&id=${$scope.user.email}&pw=${$scope.serverPassword()}`; return `${$scope.server}/dashboard/?server=${$scope.server}&id=${$scope.user.email}&pw=${$scope.serverPassword()}`;
} }
$scope.submitPasswordChange = function() { $scope.newPasswordData = {};
$scope.passwordChangeData.status = "Generating New Keys...";
$timeout(function(){ $scope.showPasswordChangeForm = function() {
if(data.password != data.password_confirmation) { $scope.newPasswordData.showNewPasswordForm = true;
alert("Your new password does not match its confirmation."); }
$scope.submitPasswordChange = function() {
if($scope.newPasswordData.newPassword != $scope.newPasswordData.newPasswordConfirmation) {
alert("Your new password does not match its confirmation.");
$scope.newPasswordData.status = null;
return;
}
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...";
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; return;
} }
authManager.changePassword($scope.passwordChangeData.current_password, $scope.passwordChangeData.new_password, function(response){ // 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.";
alert("Your password has been changed, and your items successfully re-encrypted and synced. Be sure to log out on all other signed in applications.")
$timeout(function(){
$scope.newPasswordData = {};
}, 1000)
});
}) })
} }

View File

@@ -2,13 +2,13 @@ class EncryptionHelper {
static encryptItem(item, key) { static encryptItem(item, key) {
var item_key = null; var item_key = null;
if(item.enc_item_key) { // if(item.enc_item_key) {
// we reuse the key, but this is optional // // we reuse the key, but this is optional
item_key = Neeto.crypto.decryptText(item.enc_item_key, key); // item_key = Neeto.crypto.decryptText(item.enc_item_key, key);
} else { // } else {
item_key = Neeto.crypto.generateRandomEncryptionKey(); item_key = Neeto.crypto.generateRandomEncryptionKey();
item.enc_item_key = Neeto.crypto.encryptText(item_key, key); item.enc_item_key = Neeto.crypto.encryptText(item_key, key);
} // }
var ek = Neeto.crypto.firstHalfOfKey(item_key); var ek = Neeto.crypto.firstHalfOfKey(item_key);
var ak = Neeto.crypto.secondHalfOfKey(item_key); var ak = Neeto.crypto.secondHalfOfKey(item_key);

View File

@@ -225,6 +225,13 @@ class ModelManager {
item.removeAllRelationships(); item.removeAllRelationships();
} }
/* Used when changing encryption key */
setAllItemsDirty() {
for(var item of this.allItems) {
item.setDirty(true);
}
}
removeItemLocally(item, callback) { removeItemLocally(item, callback) {
_.pull(this.items, item); _.pull(this.items, item);

View File

@@ -98,10 +98,38 @@ class SyncManager {
return this._cursorToken; 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) {
console.log(allCallbacks.length, "queued callbacks");
for(var eachCallback of allCallbacks) {
eachCallback(response);
}
this.clearQueuedCallbacks();
}
}
sync(callback, options = {}) { sync(callback, options = {}) {
if(this.syncStatus.syncOpInProgress) { if(this.syncStatus.syncOpInProgress) {
this.repeatOnCompletion = true; this.repeatOnCompletion = true;
if(callback) {
this.queuedCallbacks.push(callback);
}
console.log("Sync op in progress; returning."); console.log("Sync op in progress; returning.");
return; return;
} }
@@ -118,7 +146,6 @@ class SyncManager {
var isContinuationSync = this.needsMoreSync; var isContinuationSync = this.needsMoreSync;
this.repeatOnCompletion = false;
this.syncStatus.syncOpInProgress = true; this.syncStatus.syncOpInProgress = true;
let submitLimit = 100; let submitLimit = 100;
@@ -172,14 +199,17 @@ class SyncManager {
this.syncToken = response.sync_token; this.syncToken = response.sync_token;
this.cursorToken = response.cursor_token; this.cursorToken = response.cursor_token;
if(this.cursorToken || this.repeatOnCompletion || this.needsMoreSync) { if(this.cursorToken || this.needsMoreSync) {
setTimeout(function () { setTimeout(function () {
this.sync(callback, options); this.sync(callback, options);
}.bind(this), 10); // wait 10ms to allow UI to update }.bind(this), 10); // wait 10ms to allow UI to update
} else if(this.repeatOnCompletion) {
this.repeatOnCompletion = false;
setTimeout(function () {
this.sync(null, options);
}.bind(this), 10); // wait 10ms to allow UI to update
} else { } else {
if(callback) { this.callQueuedCallbacksAndCurrent(callback, response);
callback(response);
}
} }
}.bind(this)) }.bind(this))
@@ -193,9 +223,7 @@ class SyncManager {
this.$rootScope.$broadcast("sync:error", error); this.$rootScope.$broadcast("sync:error", error);
if(callback) { this.callQueuedCallbacksAndCurrent(callback, {error: "Sync error"});
callback({error: "Sync error"});
}
}.bind(this)) }.bind(this))
} }

View File

@@ -124,6 +124,10 @@
font-weight: bold !important; font-weight: bold !important;
} }
.italic {
font-style: italic !important;
}
.normal { .normal {
font-weight: normal !important; font-weight: normal !important;
} }

View File

@@ -36,6 +36,24 @@
%label.block.mt-5.mb-0 %label.block.mt-5.mb-0
Server password: Server password:
.wrap.normal.mt-1 {{serverPassword() ? serverPassword() : 'Not available. Sign out then sign back in to compute.'}} .wrap.normal.mt-1 {{serverPassword() ? serverPassword() : 'Not available. Sign out then sign back in to compute.'}}
%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 encrpytion 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
%a.red.mr-5{"ng-if" => "!newPasswordData.showNewPasswordForm", "ng-click" => "showPasswordChangeForm()"} Continue
%a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showNewPasswordForm = false"} Cancel
%div.mt-10{"ng-if" => "newPasswordData.showNewPasswordForm"}
%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}}
%a.block.mt-5{"href" => "{{dashboardURL()}}", "target" => "_blank"} Standard File Dashboard %a.block.mt-5{"href" => "{{dashboardURL()}}", "target" => "_blank"} Standard File Dashboard
%div.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress", "delay" => "1000"} %div.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress", "delay" => "1000"}