diff --git a/app/assets/javascripts/app/directives/views/accountMenu.js b/app/assets/javascripts/app/directives/views/accountMenu.js index 4ba475b5c..a743309bc 100644 --- a/app/assets/javascripts/app/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/directives/views/accountMenu.js @@ -17,9 +17,9 @@ class AccountMenu { $scope.user = authManager.user; $scope.server = syncManager.serverURL; - $timeout(() => { - $scope.openPasswordWizard("change-pw"); - }, 0) + // $timeout(() => { + // $scope.openPasswordWizard("change-pw"); + // }, 0) $scope.close = function() { $timeout(() => { diff --git a/app/assets/javascripts/app/directives/views/passwordWizard.js b/app/assets/javascripts/app/directives/views/passwordWizard.js index 7072f3b01..9669450b7 100644 --- a/app/assets/javascripts/app/directives/views/passwordWizard.js +++ b/app/assets/javascripts/app/directives/views/passwordWizard.js @@ -8,9 +8,20 @@ class PasswordWizard { }; } - controller($scope, modelManager, archiveManager, $timeout) { + link($scope, el, attrs) { + $scope.el = el; + } + + controller($scope, modelManager, archiveManager, authManager, syncManager, $timeout) { 'ngInject'; + $scope.dismiss = function() { + $scope.el.remove(); + $scope.$destroy(); + } + + $scope.formData = {}; + const IntroStep = 0; const BackupStep = 1; const SignoutStep = 2; @@ -21,7 +32,7 @@ class PasswordWizard { let DefaultContinueTitle = "Continue"; $scope.continueTitle = DefaultContinueTitle; - $scope.step = PasswordStep; + $scope.step = IntroStep; $scope.titleForStep = function(step) { switch (step) { @@ -30,9 +41,11 @@ class PasswordWizard { case SignoutStep: return "Sign out of all your devices"; case PasswordStep: - return $scope.changePassword ? "Enter password information" : "Enter your current password"; + return $scope.changePassword ? "Password information" : "Enter your current password"; case SyncStep: return "Encrypt and sync data with new keys"; + case FinishStep: + return "Sign back in to your devices"; default: return null; } @@ -49,6 +62,12 @@ class PasswordWizard { }(); $scope.continue = function() { + + if($scope.step == FinishStep) { + $scope.dismiss(); + return; + } + let next = () => { $scope.step += 1; $scope.initializeStep($scope.step); @@ -74,86 +93,118 @@ class PasswordWizard { $scope.showSpinner = true; $scope.continueTitle = "Generating Keys..."; $timeout(() => { - $scope.validatePasswordInformation(() => { + $scope.validateCurrentPassword((success) => { $scope.showSpinner = false; $scope.continueTitle = DefaultContinueTitle; - callback(); + if(success) { + callback(); + } }); }) } } } + let FailedSyncMessage = "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."; + $scope.initializeStep = function(step) { if(step == SyncStep) { $scope.lockContinue = true; - $scope.resyncData(() => { - $scope.lockContinue = false; + $scope.formData.status = "Processing encryption keys..."; + $scope.formData.processing = true; + + $scope.processPasswordChange((passwordSuccess) => { + $scope.formData.statusError = $scope.formData.processing = !passwordSuccess; + if(passwordSuccess) { + $scope.formData.status = "Encrypting data with new keys..."; + + $scope.resyncData((syncSuccess) => { + $scope.formData.statusError = $scope.formData.processing = !syncSuccess; + if(syncSuccess) { + $scope.lockContinue = false; + + if($scope.changePassword) { + $scope.formData.status = "Successfully changed password and re-encrypted all items. Press Continue to proceed."; + } else if($scope.securityUpdate) { + $scope.formData.status = "Successfully performed security update and re-encrypted all items. Press Continue to proceed."; + } + } else { + $scope.formData.status = FailedSyncMessage; + } + }) + } else { + $scope.formData.status = "Unable to process your password. Please try again."; + } }) } + + else if(step == FinishStep) { + $scope.continueTitle = "Finish"; + } } - $scope.validatePasswordInformation = function(callback) { - $timeout(() => { - callback(); - }, 1000) + $scope.validateCurrentPassword = function(callback) { + let currentPassword = $scope.formData.currentPassword; + let newPass = $scope.securityUpdate ? currentPassword : $scope.formData.newPassword; + + if($scope.changePassword) { + if(!newPass || newPass.length == 0) { + callback(false); + return; + } + + if(newPass != $scope.formData.newPasswordConfirmation) { + alert("Your new password does not match its confirmation."); + $scope.formData.status = null; + callback(false); + return; + } + } + + if(!authManager.user.email) { + alert("We don't have your email stored. Please log out then log back in to fix this issue."); + $scope.formData.status = null; + callback(false); + return; + } + + // Ensure value for current password matches what's saved + let authParams = authManager.getAuthParams(); + let password = $scope.formData.currentPassword; + SFJS.crypto.computeEncryptionKeysForUser(password, authParams, (keys) => { + let success = keys.mk === authManager.keys().mk; + if(!success) { + alert("The current password you entered is not correct. Please try again."); + } + $timeout(() => callback(success)); + }); } $scope.resyncData = function(callback) { - $timeout(() => { - callback(); - }, 2000) + modelManager.setAllItemsDirty(); + syncManager.sync((response) => { + if(response.error) { + alert(FailedSyncMessage) + $timeout(() => callback(false)); + } else { + $timeout(() => callback(true)); + } + }); } - $scope.submitPasswordChange = function() { - - let newPass = $scope.newPasswordData.newPassword; - let currentPass = $scope.newPasswordData.currentPassword; - - if(!newPass || newPass.length == 0) { - return; - } - - if(newPass != $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..."; - $scope.newPasswordData.showForm = false; + $scope.processPasswordChange = function(callback) { + let currentPassword = $scope.formData.currentPassword; + let newPass = $scope.securityUpdate ? currentPassword : $scope.formData.newPassword; // 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, currentPass, newPass, function(response){ + syncManager.sync((response) => { + authManager.changePassword(currentPassword, newPass, (response) => { if(response.error) { alert("There was an error changing your password. Please try again."); - $scope.newPasswordData.status = null; - return; + $timeout(() => callback(false)); + } else { + $timeout(() => callback(true)); } - - // 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) - }); }) }, null, "submitPasswordChange") } diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 6e945ce0b..90d1db941 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -235,7 +235,8 @@ angular.module('app') }); } - this.changePassword = function(email, current_password, new_password, callback) { + this.changePassword = function(current_password, new_password, callback) { + let email = this.user.email; SFJS.crypto.generateInitialEncryptionKeysForUser(email, new_password, (keys, authParams) => { var requestUrl = storageManager.getItem("server") + "/auth/change_pw"; var params = _.merge({current_password: current_password, new_password: keys.pw}, authParams); diff --git a/app/assets/stylesheets/app/_modals.scss b/app/assets/stylesheets/app/_modals.scss index 1de310e20..29171da36 100644 --- a/app/assets/stylesheets/app/_modals.scss +++ b/app/assets/stylesheets/app/_modals.scss @@ -51,6 +51,13 @@ } } + &.small { + > .content { + width: 700px; + height: 335px; + } + } + .background { position: absolute; z-index: -1; diff --git a/app/assets/stylesheets/app/_ui.scss b/app/assets/stylesheets/app/_ui.scss index f952bb177..b71e3927c 100644 --- a/app/assets/stylesheets/app/_ui.scss +++ b/app/assets/stylesheets/app/_ui.scss @@ -70,6 +70,10 @@ $screen-md-max: ($screen-lg-min - 1) !default; margin-top: 10px !important; } +.mr-5 { + margin-right: 5px !important; +} + .faded { opacity: 0.5; } @@ -107,10 +111,10 @@ $screen-md-max: ($screen-lg-min - 1) !default; font-weight: normal !important; } -.small { +.small-text { font-size: 10px; } -.medium { +.medium-text { font-size: 14px !important; } diff --git a/app/assets/templates/directives/account-menu.html.haml b/app/assets/templates/directives/account-menu.html.haml index 477915a79..093fdc6b9 100644 --- a/app/assets/templates/directives/account-menu.html.haml +++ b/app/assets/templates/directives/account-menu.html.haml @@ -76,42 +76,6 @@ %a.panel-row.condensed{"ng-click" => "openPasswordWizard('change-pw')"} Change Password - -# .notification.warning{"ng-if" => "newPasswordData.changePassword"} - -# %h1.title Change Password - -# .text - -# %p 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 If you have thousands of items, this can take several minutes — you must keep the application window open during this process. - -# %p After changing your password, you must log out of all other applications currently signed in to your account. - -# %p.bold It is highly recommended you download a backup of your data before proceeding. - -# .panel-row{"ng-if" => "!newPasswordData.status"} - -# .horizontal-group{"ng-if" => "!newPasswordData.showForm"} - -# %a.red{"ng-click" => "showPasswordChangeForm()"} Continue - -# %a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel - -# .panel-row{"ng-if" => "newPasswordData.showForm"} - -# %form.panel-form.stretch - -# %input{:type => 'password', "ng-model" => "newPasswordData.newPassword", "placeholder" => "Enter new password"} - -# %input{:type => 'password', "ng-model" => "newPasswordData.newPasswordConfirmation", "placeholder" => "Confirm new password"} - -# .button-group.stretch.panel-row.form-submit - -# .button.info{"type" => "submit", "ng-click" => "submitPasswordChange()"} - -# .label Submit - -# %a{"ng-click" => "newPasswordData.changePassword = false; newPasswordData.showForm = false"} Cancel - -# - -# %p.italic.mt-10{"ng-if" => "newPasswordData.status"} {{newPasswordData.status}} - - %a.panel-row.condensed{"ng-if" => "securityUpdateAvailable()", "ng-click" => "clickedSecurityUpdate()"} Security Update Available - .notification.default{"ng-if" => "securityUpdateData.showForm"} - %p - %a{"href" => "https://standardnotes.org/help/security-update", "target" => "_blank"} Learn more. - %form.panel-form.stretch{"ng-if" => "!securityUpdateData.processing", "ng-submit" => "submitSecurityUpdateForm()"} - %p Enter your password to update: - %input.panel-row{:type => 'password', "ng-model" => "securityUpdateData.password", "placeholder" => "Enter password"} - .button-group.stretch.panel-row.form-submit - %button.button.info{"ng-type" => "submit"} - .label Update - .panel-row{"ng-if" => "securityUpdateData.processing"} - %p.info Processing... - - .panel-section %h3.title.panel-row Encryption %h5.subtitle.info.panel-row{"ng-if" => "encryptionEnabled()"} diff --git a/app/assets/templates/directives/actions-menu.html.haml b/app/assets/templates/directives/actions-menu.html.haml index 52470f972..67f700294 100644 --- a/app/assets/templates/directives/actions-menu.html.haml +++ b/app/assets/templates/directives/actions-menu.html.haml @@ -20,7 +20,7 @@ access to this note. -.modal.medium{"ng-if" => "renderData.showRenderModal", "ng-click" => "$event.stopPropagation();"} +.modal.medium-text{"ng-if" => "renderData.showRenderModal", "ng-click" => "$event.stopPropagation();"} .content .sn-component .panel diff --git a/app/assets/templates/directives/editor-menu.html.haml b/app/assets/templates/directives/editor-menu.html.haml index 6787ab986..e70f4b80b 100644 --- a/app/assets/templates/directives/editor-menu.html.haml +++ b/app/assets/templates/directives/editor-menu.html.haml @@ -10,7 +10,7 @@ "has-button" => "selectedEditor == editor || defaultEditor == editor", "button-text" => "defaultEditor == editor ? 'Undefault' : 'Set Default'", "button-action" => "toggleDefaultForEditor(editor)", "button-class" => "defaultEditor == editor ? 'warning' : 'info'"} .column{"ng-if" => "component.conflict_of || shouldDisplayRunningLocallyLabel(editor)"} - %strong.red.medium{"ng-if" => "editor.conflict_of"} Conflicted copy + %strong.red.medium-text{"ng-if" => "editor.conflict_of"} Conflicted copy .sublabel{"ng-if" => "shouldDisplayRunningLocallyLabel(editor)"} Running Locally %a.no-decoration{"ng-if" => "editors.length == 0", "href" => "https://standardnotes.org/extensions", "target" => "blank"} @@ -22,5 +22,5 @@ %menu-row{"ng-repeat" => "component in stack", "ng-click" => "selectComponent($event, component)", "title" => "component.name", "circle" => "stackComponentEnabled(component) ? 'success' : 'danger'"} .column{"ng-if" => "component.conflict_of || shouldDisplayRunningLocallyLabel(component)"} - %strong.red.medium{"ng-if" => "component.conflict_of"} Conflicted copy + %strong.red.medium-text{"ng-if" => "component.conflict_of"} Conflicted copy .sublabel{"ng-if" => "shouldDisplayRunningLocallyLabel(component)"} Running Locally diff --git a/app/assets/templates/directives/password-wizard.html.haml b/app/assets/templates/directives/password-wizard.html.haml index 27d0dc2a1..e3f65b494 100644 --- a/app/assets/templates/directives/password-wizard.html.haml +++ b/app/assets/templates/directives/password-wizard.html.haml @@ -1,20 +1,24 @@ -.modal.medium +.modal.small .content .sn-component .panel .header %h1.title {{title}} - %a.close-button{"ng-click" => "close()"} Close + %a.close-button{"ng-click" => "dismiss()"} Close .content %div{"ng-if" => "step == 0"} %div{"ng-if" => "changePassword"} - %p 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 If you have thousands of items, this can take several minutes—you must keep the application window open during this process. + %h3.title.panel-row Change your password + %p Because your encryption key is based on your password, changing your password requires all your data to be re-encrypted using your new key. + %p This process will guide you through changing your password. + %p If you have many items, re-uploading your data can take several minutes. You must keep the application window open during this process. %div{"ng-if" => "securityUpdate"} + %h3.title.panel-row Perform security update %p Welcome to the security update process. - %p.info Press Continue to proceed. + .panel-row + %strong.info Press Continue to proceed. .panel-section{"ng-if" => "step > 0"} @@ -59,7 +63,20 @@ %p.panel-row.danger Do not close this window until this process completes. + %p.panel-row + .spinner.small.inline.info.mr-5{"ng-if" => "formData.processing"} + .inline.bold{"ng-class" => "{'info' : !formData.statusError, 'error' : formData.statusError}"} + {{formData.status}} + + %div{"ng-if" => "step == 5"} + %div{"ng-if" => "changePassword"} + %p.panel-row Your password has been successfully changed. + %div{"ng-if" => "securityUpdate"} + %p.panel-row The security update has been successfully applied to your account. + + %p.panel-row You may now sign back in to all your devices and close this window. + .footer %a.right{"ng-click" => "continue()", "ng-class" => "{'disabled' : lockContinue}"} - .spinner.small.inline.info{"ng-if" => "showSpinner", "style" => "margin-right: 5px;"} + .spinner.small.inline.info.mr-5{"ng-if" => "showSpinner"} {{continueTitle}} diff --git a/app/assets/templates/notes.html.haml b/app/assets/templates/notes.html.haml index 5cb953f68..bdcb4cacf 100644 --- a/app/assets/templates/notes.html.haml +++ b/app/assets/templates/notes.html.haml @@ -44,16 +44,16 @@ .infinite-scroll#notes-scrollable{"infinite-scroll" => "ctrl.paginate()", "can-load" => "true", "threshold" => "200"} .note{"ng-repeat" => "note in (ctrl.sortedNotes = (ctrl.tag.notes | filter: ctrl.filterNotes | sortBy: ctrl.sortBy | limitTo:ctrl.notesToDisplay)) track by note.uuid", "ng-click" => "ctrl.selectNote(note, true)", "ng-class" => "{'selected' : ctrl.selectedNote == note}"} - %strong.red.medium{"ng-if" => "note.conflict_of"} Conflicted copy - %strong.red.medium{"ng-if" => "note.errorDecrypting"} Error decrypting + %strong.red.medium-text{"ng-if" => "note.conflict_of"} Conflicted copy + %strong.red.medium-text{"ng-if" => "note.errorDecrypting"} Error decrypting .pinned.tinted{"ng-if" => "note.pinned", "ng-class" => "{'tinted-selected' : ctrl.selectedNote == note}"} %i.icon.ion-bookmark - %strong.medium Pinned + %strong.medium-text Pinned .archived.tinted{"ng-if" => "note.archived && !ctrl.tag.archiveTag", "ng-class" => "{'tinted-selected' : ctrl.selectedNote == note}"} %i.icon.ion-ios-box - %strong.medium Archived + %strong.medium-text Archived .tags-string{"ng-if" => "ctrl.shouldShowTags(note)"} .faded {{note.savedTagsString || note.tagsString()}} diff --git a/app/assets/templates/tags.html.haml b/app/assets/templates/tags.html.haml index e40505d20..80c48767d 100644 --- a/app/assets/templates/tags.html.haml +++ b/app/assets/templates/tags.html.haml @@ -22,8 +22,8 @@ "ng-change" => "ctrl.tagTitleDidChange(tag)", "ng-blur" => "ctrl.saveTag($event, tag)", "spellcheck" => "false"} .count {{ctrl.noteCount(tag)}} - .red.small.bold{"ng-if" => "tag.conflict_of"} Conflicted copy - .red.small.bold{"ng-if" => "tag.errorDecrypting"} Error decrypting + .red.small-text.bold{"ng-if" => "tag.conflict_of"} Conflicted copy + .red.small-text.bold{"ng-if" => "tag.errorDecrypting"} Error decrypting .menu{"ng-if" => "ctrl.selectedTag == tag"} %a.item{"ng-click" => "ctrl.selectedRenameTag($event, tag)", "ng-if" => "!ctrl.editingTag"} Rename