@@ -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) {
|
||||||
|
|||||||
@@ -20,11 +20,15 @@ angular
|
|||||||
});
|
});
|
||||||
|
|
||||||
function showSpinner() {
|
function showSpinner() {
|
||||||
|
if(scope.hidePromise) {
|
||||||
|
$timeout.cancel(scope.hidePromise);
|
||||||
|
scope.hidePromise = null;
|
||||||
|
}
|
||||||
showElement(true);
|
showElement(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideSpinner() {
|
function hideSpinner() {
|
||||||
$timeout(showElement.bind(this, false), getDelay());
|
scope.hidePromise = $timeout(showElement.bind(this, false), getDelay());
|
||||||
}
|
}
|
||||||
|
|
||||||
function showElement(show) {
|
function showElement(show) {
|
||||||
|
|||||||
@@ -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,20 +27,57 @@ 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.newPasswordData = {};
|
||||||
|
|
||||||
|
$scope.showPasswordChangeForm = function() {
|
||||||
|
$scope.newPasswordData.showForm = true;
|
||||||
|
}
|
||||||
|
|
||||||
$scope.submitPasswordChange = function() {
|
$scope.submitPasswordChange = function() {
|
||||||
$scope.passwordChangeData.status = "Generating New Keys...";
|
|
||||||
|
|
||||||
$timeout(function(){
|
if($scope.newPasswordData.newPassword != $scope.newPasswordData.newPasswordConfirmation) {
|
||||||
if(data.password != data.password_confirmation) {
|
alert("Your new password does not match its confirmation.");
|
||||||
alert("Your new password does not match its confirmation.");
|
$scope.newPasswordData.status = null;
|
||||||
return;
|
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() {
|
$scope.loginSubmitPressed = function() {
|
||||||
@@ -116,6 +149,8 @@ class AccountMenu {
|
|||||||
$scope.importData = null;
|
$scope.importData = null;
|
||||||
if(!response) {
|
if(!response) {
|
||||||
alert("There was an error importing your data. Please try again.");
|
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) {
|
$scope.importJSONData = function(data, password, callback) {
|
||||||
console.log("Importing data", data);
|
|
||||||
|
|
||||||
var onDataReady = function() {
|
var onDataReady = function() {
|
||||||
var items = modelManager.mapResponseItemsToLocalModels(data.items);
|
var items = modelManager.mapResponseItemsToLocalModels(data.items);
|
||||||
items.forEach(function(item){
|
items.forEach(function(item){
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
class EncryptionHelper {
|
class EncryptionHelper {
|
||||||
|
|
||||||
static encryptItem(item, key) {
|
static encryptItem(item, key) {
|
||||||
var item_key = null;
|
var item_key = Neeto.crypto.generateRandomEncryptionKey();
|
||||||
if(item.enc_item_key) {
|
item.enc_item_key = Neeto.crypto.encryptText(item_key, 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 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);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class ModelManager {
|
|||||||
this.itemChangeObservers = [];
|
this.itemChangeObservers = [];
|
||||||
this.items = [];
|
this.items = [];
|
||||||
this._extensions = [];
|
this._extensions = [];
|
||||||
|
this.acceptableContentTypes = ["Note", "Tag", "Extension"];
|
||||||
}
|
}
|
||||||
|
|
||||||
get allItems() {
|
get allItems() {
|
||||||
@@ -62,7 +63,7 @@ class ModelManager {
|
|||||||
for (var json_obj of items) {
|
for (var json_obj of items) {
|
||||||
json_obj = _.omit(json_obj, omitFields || [])
|
json_obj = _.omit(json_obj, omitFields || [])
|
||||||
var item = this.findItem(json_obj["uuid"]);
|
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) {
|
if(item) {
|
||||||
this.removeItemLocally(item)
|
this.removeItemLocally(item)
|
||||||
}
|
}
|
||||||
@@ -225,6 +226,17 @@ class ModelManager {
|
|||||||
item.removeAllRelationships();
|
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) {
|
removeItemLocally(item, callback) {
|
||||||
_.pull(this.items, item);
|
_.pull(this.items, item);
|
||||||
|
|
||||||
|
|||||||
@@ -98,10 +98,37 @@ 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) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@@ -116,18 +143,17 @@ class SyncManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isContinuationSync = this.needsMoreSync;
|
var isContinuationSync = this.syncStatus.needsMoreSync;
|
||||||
|
|
||||||
this.repeatOnCompletion = false;
|
|
||||||
this.syncStatus.syncOpInProgress = true;
|
this.syncStatus.syncOpInProgress = true;
|
||||||
|
|
||||||
let submitLimit = 100;
|
let submitLimit = 100;
|
||||||
var subItems = allDirtyItems.slice(0, submitLimit);
|
var subItems = allDirtyItems.slice(0, submitLimit);
|
||||||
if(subItems.length < allDirtyItems.length) {
|
if(subItems.length < allDirtyItems.length) {
|
||||||
// more items left to be synced, repeat
|
// more items left to be synced, repeat
|
||||||
this.needsMoreSync = true;
|
this.syncStatus.needsMoreSync = true;
|
||||||
} else {
|
} else {
|
||||||
this.needsMoreSync = false;
|
this.syncStatus.needsMoreSync = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!isContinuationSync) {
|
if(!isContinuationSync) {
|
||||||
@@ -153,10 +179,10 @@ class SyncManager {
|
|||||||
this.$rootScope.$broadcast("sync:updated_token", this.syncToken);
|
this.$rootScope.$broadcast("sync:updated_token", this.syncToken);
|
||||||
|
|
||||||
var retrieved = this.handleItemsResponse(response.retrieved_items, null);
|
var retrieved = this.handleItemsResponse(response.retrieved_items, null);
|
||||||
|
|
||||||
// merge only metadata for saved items
|
// 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,
|
// we write saved items to disk now because it clears their dirty status then saves
|
||||||
// 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
|
// if we saved items before completion, we had have to save them as dirty and save them again on success as clean
|
||||||
// testing.
|
|
||||||
var omitFields = ["content", "auth_hash"];
|
var omitFields = ["content", "auth_hash"];
|
||||||
var saved = this.handleItemsResponse(response.saved_items, omitFields);
|
var saved = this.handleItemsResponse(response.saved_items, omitFields);
|
||||||
|
|
||||||
@@ -172,14 +198,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.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 () {
|
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 {
|
} else {
|
||||||
if(callback) {
|
this.callQueuedCallbacksAndCurrent(callback, response);
|
||||||
callback(response);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}.bind(this))
|
}.bind(this))
|
||||||
@@ -193,9 +222,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))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,7 +241,7 @@ class SyncManager {
|
|||||||
var item = this.modelManager.findItem(itemResponse.uuid);
|
var item = this.modelManager.findItem(itemResponse.uuid);
|
||||||
var error = mapping.error;
|
var error = mapping.error;
|
||||||
if(error.tag == "uuid_conflict") {
|
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);
|
this.modelManager.alternateUUIDForItem(item, handleNext);
|
||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,13 @@
|
|||||||
%div{"ng-if" => "user"}
|
%div{"ng-if" => "user"}
|
||||||
%h2 {{user.email}}
|
%h2 {{user.email}}
|
||||||
%p {{server}}
|
%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
|
%a.block.mt-5{"ng-click" => "showCredentials = !showCredentials"} Show Credentials
|
||||||
%section.gray-bg.mt-10.medium-padding{"ng-if" => "showCredentials"}
|
%section.gray-bg.mt-10.medium-padding{"ng-if" => "showCredentials"}
|
||||||
%label.block
|
%label.block
|
||||||
@@ -36,13 +43,23 @@
|
|||||||
%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{"href" => "{{dashboardURL()}}", "target" => "_blank"} Standard File Dashboard
|
|
||||||
|
|
||||||
%div.bold.mt-10.blue{"delay-hide" => "true", "show" => "syncStatus.syncOpInProgress", "delay" => "1000"}
|
%a.block.mt-5{"ng-click" => "newPasswordData.changePassword = !newPasswordData.changePassword"} Change Password
|
||||||
.spinner.inline.mr-5.blue
|
%section.gray-bg.mt-10.medium-padding{"ng-if" => "newPasswordData.changePassword"}
|
||||||
Syncing
|
%p.bold Change Password (Beta)
|
||||||
%span{"ng-if" => "syncStatus.total > 0"}: {{syncStatus.current}}/{{syncStatus.total}}
|
%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.bold.mt-10.red.block{"ng-if" => "syncStatus.error"} Error syncing: {{syncStatus.error.message}}
|
%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
|
.medium-v-space
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user