From 42a7cb418c1ea201d56fb2d8b589f12588bf3256 Mon Sep 17 00:00:00 2001 From: Baptiste Grob <60621355+baptiste-grob@users.noreply.github.com> Date: Mon, 18 Jan 2021 13:40:10 +0100 Subject: [PATCH] refactor: remove privileges in favor of SNJS protections --- app/assets/javascripts/app.ts | 7 - .../javascripts/directives/views/index.ts | 2 - .../directives/views/privilegesAuthModal.ts | 128 ------------------ .../views/privilegesManagementModal.ts | 118 ---------------- .../directives/views/sessionsModal.tsx | 5 +- .../javascripts/services/archiveManager.ts | 56 ++++---- app/assets/javascripts/ui_models/app_state.ts | 4 +- .../javascripts/views/editor/editor-view.pug | 3 +- app/assets/stylesheets/_modals.scss | 49 ------- .../templates/directives/account-menu.pug | 4 - .../directives/privileges-auth-modal.pug | 37 ----- .../privileges-management-modal.pug | 51 ------- 12 files changed, 30 insertions(+), 434 deletions(-) delete mode 100644 app/assets/javascripts/directives/views/privilegesAuthModal.ts delete mode 100644 app/assets/javascripts/directives/views/privilegesManagementModal.ts delete mode 100644 app/assets/templates/directives/privileges-auth-modal.pug delete mode 100644 app/assets/templates/directives/privileges-management-modal.pug diff --git a/app/assets/javascripts/app.ts b/app/assets/javascripts/app.ts index 4051a7883af..1d414d8df06 100644 --- a/app/assets/javascripts/app.ts +++ b/app/assets/javascripts/app.ts @@ -44,8 +44,6 @@ import { PanelResizer, PasswordWizard, PermissionsModal, - PrivilegesAuthModal, - PrivilegesManagementModal, RevisionPreviewModal, HistoryMenu, SyncResolutionMenu, @@ -140,11 +138,6 @@ const startApplication: StartApplication = async function startApplication( .directive('panelResizer', () => new PanelResizer()) .directive('passwordWizard', () => new PasswordWizard()) .directive('permissionsModal', () => new PermissionsModal()) - .directive('privilegesAuthModal', () => new PrivilegesAuthModal()) - .directive( - 'privilegesManagementModal', - () => new PrivilegesManagementModal() - ) .directive('revisionPreviewModal', () => new RevisionPreviewModal()) .directive('historyMenu', () => new HistoryMenu()) .directive('syncResolutionMenu', () => new SyncResolutionMenu()) diff --git a/app/assets/javascripts/directives/views/index.ts b/app/assets/javascripts/directives/views/index.ts index adee8d90cbf..99931e2bbfe 100644 --- a/app/assets/javascripts/directives/views/index.ts +++ b/app/assets/javascripts/directives/views/index.ts @@ -8,8 +8,6 @@ export { MenuRow } from './menuRow'; export { PanelResizer } from './panelResizer'; export { PasswordWizard } from './passwordWizard'; export { PermissionsModal } from './permissionsModal'; -export { PrivilegesAuthModal } from './privilegesAuthModal'; -export { PrivilegesManagementModal } from './privilegesManagementModal'; export { RevisionPreviewModal } from './revisionPreviewModal'; export { HistoryMenu } from './historyMenu'; export { SyncResolutionMenu } from './syncResolutionMenu'; diff --git a/app/assets/javascripts/directives/views/privilegesAuthModal.ts b/app/assets/javascripts/directives/views/privilegesAuthModal.ts deleted file mode 100644 index e715ee9c4df..00000000000 --- a/app/assets/javascripts/directives/views/privilegesAuthModal.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { WebDirective } from './../../types'; -import { WebApplication } from '@/ui_models/application'; -import { ProtectedAction, PrivilegeCredential, PrivilegeSessionLength } from '@standardnotes/snjs'; -import template from '%/directives/privileges-auth-modal.pug'; - -type PrivilegesAuthModalScope = { - application: WebApplication - action: ProtectedAction - onSuccess: () => void - onCancel: () => void -} - -class PrivilegesAuthModalCtrl implements PrivilegesAuthModalScope { - $element: JQLite - $timeout: ng.ITimeoutService - application!: WebApplication - action!: ProtectedAction - onSuccess!: () => void - onCancel!: () => void - authParameters: Partial> = {} - sessionLengthOptions!: { value: PrivilegeSessionLength, label: string }[] - selectedSessionLength!: PrivilegeSessionLength - requiredCredentials!: PrivilegeCredential[] - failedCredentials!: PrivilegeCredential[] - - /* @ngInject */ - constructor( - $element: JQLite, - $timeout: ng.ITimeoutService - ) { - this.$element = $element; - this.$timeout = $timeout; - } - - $onInit() { - this.sessionLengthOptions = this.application!.privilegesService! - .getSessionLengthOptions(); - this.application.privilegesService!.getSelectedSessionLength() - .then((length) => { - this.$timeout(() => { - this.selectedSessionLength = length; - }); - }); - this.application.privilegesService!.netCredentialsForAction(this.action) - .then((credentials) => { - this.$timeout(() => { - this.requiredCredentials = credentials.sort(); - }); - }); - } - - selectSessionLength(length: PrivilegeSessionLength) { - this.selectedSessionLength = length; - } - - promptForCredential(credential: PrivilegeCredential) { - return this.application.privilegesService!.displayInfoForCredential(credential).prompt; - } - - cancel() { - this.dismiss(); - this.onCancel && this.onCancel(); - } - - isCredentialInFailureState(credential: PrivilegeCredential) { - if (!this.failedCredentials) { - return false; - } - return this.failedCredentials.find((candidate) => { - return candidate === credential; - }) != null; - } - - validate() { - const failed = []; - for (const cred of this.requiredCredentials) { - const value = this.authParameters[cred]; - if (!value || value.length === 0) { - failed.push(cred); - } - } - this.failedCredentials = failed; - return failed.length === 0; - } - - async submit() { - if (!this.validate()) { - return; - } - const result = await this.application.privilegesService!.authenticateAction( - this.action, - this.authParameters - ); - this.$timeout(() => { - if (result.success) { - this.application.privilegesService!.setSessionLength(this.selectedSessionLength); - this.onSuccess(); - this.dismiss(); - } else { - this.failedCredentials = result.failedCredentials; - } - }); - } - - dismiss() { - const elem = this.$element; - const scope = elem.scope(); - scope.$destroy(); - elem.remove(); - } -} - -export class PrivilegesAuthModal extends WebDirective { - constructor() { - super(); - this.restrict = 'E'; - this.template = template; - this.controller = PrivilegesAuthModalCtrl; - this.controllerAs = 'ctrl'; - this.bindToController = true; - this.scope = { - action: '=', - onSuccess: '=', - onCancel: '=', - application: '=' - }; - } -} diff --git a/app/assets/javascripts/directives/views/privilegesManagementModal.ts b/app/assets/javascripts/directives/views/privilegesManagementModal.ts deleted file mode 100644 index 81403b69a0c..00000000000 --- a/app/assets/javascripts/directives/views/privilegesManagementModal.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { WebDirective } from './../../types'; -import { WebApplication } from '@/ui_models/application'; -import template from '%/directives/privileges-management-modal.pug'; -import { PrivilegeCredential, ProtectedAction, SNPrivileges, PrivilegeSessionLength } from '@standardnotes/snjs'; -import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; -import { PrivilegeMutator } from '@standardnotes/snjs'; - -type DisplayInfo = { - label: string - prompt: string -} - -class PrivilegesManagementModalCtrl extends PureViewCtrl { - - hasPasscode = false - hasAccount = false - $element: JQLite - application!: WebApplication - privileges!: SNPrivileges - availableActions!: ProtectedAction[] - availableCredentials!: PrivilegeCredential[] - sessionExpirey!: string - sessionExpired = true - credentialDisplayInfo: Partial> = {} - onCancel!: () => void - - /* @ngInject */ - constructor( - $timeout: ng.ITimeoutService, - $element: JQLite - ) { - super($timeout); - this.$element = $element; - } - - async onAppLaunch() { - super.onAppLaunch(); - this.hasPasscode = this.application.hasPasscode(); - this.hasAccount = !this.application.noAccount(); - this.reloadPrivileges(); - } - - displayInfoForCredential(credential: PrivilegeCredential) { - const info: any = this.application.privilegesService!.displayInfoForCredential(credential); - if (credential === PrivilegeCredential.LocalPasscode) { - info.availability = this.hasPasscode; - } else if (credential === PrivilegeCredential.AccountPassword) { - info.availability = this.hasAccount; - } else { - info.availability = true; - } - return info; - } - - displayInfoForAction(action: ProtectedAction) { - return this.application.privilegesService!.displayInfoForAction(action).label; - } - - isCredentialRequiredForAction(action: ProtectedAction, credential: PrivilegeCredential) { - if (!this.privileges) { - return false; - } - return this.privileges.isCredentialRequiredForAction(action, credential); - } - - async clearSession() { - await this.application.privilegesService!.clearSession(); - this.reloadPrivileges(); - } - - async reloadPrivileges() { - this.availableActions = this.application.privilegesService!.getAvailableActions(); - this.availableCredentials = this.application.privilegesService!.getAvailableCredentials(); - const sessionEndDate = await this.application.privilegesService!.getSessionExpirey(); - this.sessionExpirey = sessionEndDate.toLocaleString(); - this.sessionExpired = new Date() >= sessionEndDate; - for (const cred of this.availableCredentials) { - this.credentialDisplayInfo[cred] = this.displayInfoForCredential(cred); - } - const privs = await this.application.privilegesService!.getPrivileges(); - this.$timeout(() => { - this.privileges = privs; - }); - } - - checkboxValueChanged(action: ProtectedAction, credential: PrivilegeCredential) { - this.application.changeAndSaveItem(this.privileges.uuid, (m) => { - const mutator = m as PrivilegeMutator; - mutator.toggleCredentialForAction(action, credential); - }) - } - - cancel() { - this.dismiss(); - this.onCancel && this.onCancel(); - } - - dismiss() { - const elem = this.$element; - const scope = elem.scope(); - scope.$destroy(); - elem.remove(); - } -} - -export class PrivilegesManagementModal extends WebDirective { - constructor() { - super(); - this.restrict = 'E'; - this.template = template; - this.controller = PrivilegesManagementModalCtrl; - this.controllerAs = 'ctrl'; - this.bindToController = true; - this.scope = { - application: '=' - }; - } -} diff --git a/app/assets/javascripts/directives/views/sessionsModal.tsx b/app/assets/javascripts/directives/views/sessionsModal.tsx index 08f3906e3d3..dd51d4e5117 100644 --- a/app/assets/javascripts/directives/views/sessionsModal.tsx +++ b/app/assets/javascripts/directives/views/sessionsModal.tsx @@ -5,6 +5,7 @@ import { RemoteSession, SessionStrings, UuidString, + isNullOrUndefined, } from '@standardnotes/snjs'; import { autorun, IAutorunOptions, IReactionPublic } from 'mobx'; import { render, FunctionComponent } from 'preact'; @@ -78,7 +79,9 @@ function useSessions( setSessions(sessionsDuringRevoke); const response = await responsePromise; - if ('error' in response) { + if (isNullOrUndefined(response)) { + setSessions(sessionsBeforeRevoke); + } else if ('error' in response) { if (response.error?.message) { setErrorMessage(response.error?.message); } else { diff --git a/app/assets/javascripts/services/archiveManager.ts b/app/assets/javascripts/services/archiveManager.ts index eaa800f3e44..bb2442ac97d 100644 --- a/app/assets/javascripts/services/archiveManager.ts +++ b/app/assets/javascripts/services/archiveManager.ts @@ -1,5 +1,10 @@ import { WebApplication } from '@/ui_models/application'; -import { EncryptionIntent, ProtectedAction, SNItem, ContentType, SNNote, BackupFile } from '@standardnotes/snjs'; +import { + EncryptionIntent, + ContentType, + SNNote, + BackupFile +} from '@standardnotes/snjs'; function zippableTxtName(name: string, suffix = ""): string { const sanitizedName = name @@ -22,41 +27,26 @@ export class ArchiveManager { } public async downloadBackup(encrypted: boolean) { - const run = async () => { - const intent = encrypted - ? EncryptionIntent.FileEncrypted - : EncryptionIntent.FileDecrypted; + const intent = encrypted + ? EncryptionIntent.FileEncrypted + : EncryptionIntent.FileDecrypted; - const data = await this.application.createBackupFile(intent); - if (!data) { - return; - } - const blobData = new Blob( - [JSON.stringify(data, null, 2)], - { type: 'text/json' } + const data = await this.application.createBackupFile(intent); + if (!data) { + return; + } + const blobData = new Blob( + [JSON.stringify(data, null, 2)], + { type: 'text/json' } + ); + if (encrypted) { + this.downloadData( + blobData, + `Standard Notes Encrypted Backup and Import File - ${this.formattedDate()}.txt` ); - if (encrypted) { - this.downloadData( - blobData, - `Standard Notes Encrypted Backup and Import File - ${this.formattedDate()}.txt` - ); - } else { - /** download as zipped plain text files */ - this.downloadZippedDecryptedItems(data); - } - }; - - if ( - await this.application.privilegesService! - .actionRequiresPrivilege(ProtectedAction.ManageBackups) - ) { - this.application.presentPrivilegesModal( - ProtectedAction.ManageBackups, - () => { - run(); - }); } else { - run(); + /** download as zipped plain text files */ + this.downloadZippedDecryptedItems(data); } } diff --git a/app/assets/javascripts/ui_models/app_state.ts b/app/assets/javascripts/ui_models/app_state.ts index e973f48f856..1028099dee6 100644 --- a/app/assets/javascripts/ui_models/app_state.ts +++ b/app/assets/javascripts/ui_models/app_state.ts @@ -217,7 +217,7 @@ export class AppState { } } - async openEditor(noteUuid: string) { + async openEditor(noteUuid: string): Promise { if (this.getActiveEditor()?.note?.uuid === noteUuid) { return; } @@ -226,7 +226,7 @@ export class AppState { if (!note) { console.warn('Tried accessing a non-existant note of UUID ' + noteUuid); return; - }; + } if (await this.application.authorizeNoteAccess(note)) { const activeEditor = this.getActiveEditor(); diff --git a/app/assets/javascripts/views/editor/editor-view.pug b/app/assets/javascripts/views/editor/editor-view.pug index 89c94e83aba..7e8e58c30e2 100644 --- a/app/assets/javascripts/views/editor/editor-view.pug +++ b/app/assets/javascripts/views/editor/editor-view.pug @@ -80,8 +80,7 @@ ) menu-row( action='self.selectedMenuItem(true); self.toggleProtectNote()' - desc=`'Protecting a note will require credentials to view - it (Manage Privileges via Account menu)'`, + desc=`'Protecting a note will require credentials to view it'`, label="self.note.protected ? 'Unprotect' : 'Protect'" ) menu-row( diff --git a/app/assets/stylesheets/_modals.scss b/app/assets/stylesheets/_modals.scss index 77494478d59..966e9ef2421 100644 --- a/app/assets/stylesheets/_modals.scss +++ b/app/assets/stylesheets/_modals.scss @@ -42,55 +42,6 @@ } } -#privileges-modal { - min-width: 400px; - max-width: 700px; - - .sk-panel-header { - position: relative; - } - - .close-button { - cursor: pointer; - position: absolute; - padding: 1.1rem 2rem; - right: 0; - } - - table { - margin-bottom: 12px; - width: 100%; - overflow: auto; - border-collapse: collapse; - border-spacing: 0px; - border-color: var(--sn-stylekit-contrast-border-color); - background-color: var(--sn-stylekit-background-color); - color: var(--sn-stylekit-contrast-foreground-color); - - th, - td { - padding: 6px 13px; - border: 1px solid var(--sn-stylekit-contrast-border-color); - } - - tr:nth-child(2n) { - background-color: var(--sn-stylekit-contrast-background-color); - } - } - - th { - text-align: center; - font-weight: normal; - } - - .priv-header { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - } -} - #item-preview-modal { > .sk-modal-content { width: 800px; diff --git a/app/assets/templates/directives/account-menu.pug b/app/assets/templates/directives/account-menu.pug index 585f1c6fb67..39953a61c58 100644 --- a/app/assets/templates/directives/account-menu.pug +++ b/app/assets/templates/directives/account-menu.pug @@ -222,10 +222,6 @@ | {{option.label}} .sk-p The autolock timer begins when the window or tab loses focus. .sk-panel-row - a.sk-a.info.sk-panel-row.condensed( - ng-click="self.openPrivilegesModal('')", - ng-show='!self.state.user' - ) Manage Privileges a.sk-a.info.sk-panel-row.condensed( ng-click='self.changePasscodePressed()' ) Change Passcode diff --git a/app/assets/templates/directives/privileges-auth-modal.pug b/app/assets/templates/directives/privileges-auth-modal.pug deleted file mode 100644 index 3c578b25695..00000000000 --- a/app/assets/templates/directives/privileges-auth-modal.pug +++ /dev/null @@ -1,37 +0,0 @@ -.sk-modal-background(ng-click="ctrl.cancel()") -#privileges-modal.sk-modal-content - .sn-component - .sk-panel - .sk-panel-header - .sk-panel-header-title Authentication Required - a.close-button.info(ng-click="ctrl.cancel()") Cancel - .sk-panel-content - .sk-panel-section - div(ng-repeat="credential in ctrl.requiredCredentials") - .sk-p.sk-bold.sk-panel-row - strong {{ctrl.promptForCredential(credential)}} - .sk-panel-row - input.sk-input.contrast( - ng-model="ctrl.authParameters[credential]" - should-focus="$index == 0" - sn-autofocus="true" - sn-enter="ctrl.submit()" - type="password" - ) - .sk-panel-row - label.sk-label.danger( - ng-if="ctrl.isCredentialInFailureState(credential)" - ) Invalid authentication. Please try again. - .sk-panel-row - .sk-panel-row - .sk-horizontal-group - .sk-p.sk-bold Remember For - a.sk-a.info( - ng-repeat="option in ctrl.sessionLengthOptions" - ng-class="{'boxed' : option.value == ctrl.selectedSessionLength}" - ng-click="ctrl.selectSessionLength(option.value)" - ) - | {{option.label}} - .sk-panel-footer.extra-padding - .sk-button.info.big.block.bold(ng-click="ctrl.submit()") - .sk-label Submit diff --git a/app/assets/templates/directives/privileges-management-modal.pug b/app/assets/templates/directives/privileges-management-modal.pug deleted file mode 100644 index cc697e901c7..00000000000 --- a/app/assets/templates/directives/privileges-management-modal.pug +++ /dev/null @@ -1,51 +0,0 @@ -.sk-modal-background(ng-click='ctrl.cancel()') -#privileges-modal.sk-modal-content - .sn-component - .sk-panel - .sk-panel-header - .sk-panel-header-title Manage Privileges - a.sk-a.close-button.info(ng-click='ctrl.cancel()') Done - .sk-panel-content - .sk-panel-section - table.sk-table - thead - tr - th - th(ng-repeat='cred in ctrl.availableCredentials') - .priv-header - strong {{ctrl.credentialDisplayInfo[cred].label}} - .sk-p.font-small( - ng-show='!ctrl.credentialDisplayInfo[cred].availability', - style='margin-top: 2px' - ) Not Configured - tbody - tr(ng-repeat='action in ctrl.availableActions') - td - .sk-p {{ctrl.displayInfoForAction(action)}} - th(ng-repeat='credential in ctrl.availableCredentials') - input( - ng-checked='ctrl.isCredentialRequiredForAction(action, credential)', - ng-click='ctrl.checkboxValueChanged(action, credential)', - ng-disabled='!ctrl.credentialDisplayInfo[credential].availability', - type='checkbox' - ) - .sk-panel-section(ng-if='ctrl.sessionExpirey && !ctrl.sessionExpired') - .sk-p.sk-panel-row - | You will not be asked to authenticate until {{ctrl.sessionExpirey}}. - a.sk-a.sk-panel-row.info(ng-click='ctrl.clearSession()') Clear Session - .sk-panel-footer - .sk-h2.sk-bold About Privileges - .sk-panel-section.no-bottom-pad - .sk-panel-row - .text-content - .sk-p - | Privileges represent interface level authentication for accessing - | certain items and features. Note that when your application is unlocked, - | your data exists in temporary memory in an unencrypted state. - | Privileges are meant to protect against unwanted access in the event of - | an unlocked application, but do not affect data encryption state. - p.sk-p - | Privileges sync across your other devices; however, note that if you - | require an "Application Passcode" privilege, and another device does not have - | an application passcode set up, the application passcode requirement will be ignored - | on that device.