From 252183a7c52bbf27886f17afa5a4b80c481db0ef Mon Sep 17 00:00:00 2001 From: Baptiste Grob <60621355+baptiste-grob@users.noreply.github.com> Date: Tue, 5 Jan 2021 12:58:07 +0100 Subject: [PATCH 1/4] feat: (wip) authorize note access --- app/assets/javascripts/ui_models/app_state.ts | 31 +++++++------------ .../javascripts/views/editor/editor_view.ts | 8 ----- 2 files changed, 11 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/ui_models/app_state.ts b/app/assets/javascripts/ui_models/app_state.ts index f64d2f8c8..0fecb118f 100644 --- a/app/assets/javascripts/ui_models/app_state.ts +++ b/app/assets/javascripts/ui_models/app_state.ts @@ -222,9 +222,18 @@ export class AppState { } async openEditor(noteUuid: string) { + if (this.getActiveEditor()?.note?.uuid === noteUuid) { + return; + } + const note = this.application.findItem(noteUuid) as SNNote; - if (this.getActiveEditor()?.note?.uuid === noteUuid) return; - const run = async () => { + if (!note) { + console.warn('Tried accessing a non-existant note of UUID ' + noteUuid); + return; + }; + + const approved = this.application.authorizeNoteAccess(note); + if (approved === true || await approved) { const activeEditor = this.getActiveEditor(); if (!activeEditor || this.multiEditorEnabled) { this.application.editorGroup.createEditor(noteUuid); @@ -232,24 +241,6 @@ export class AppState { activeEditor.setNote(note); } await this.notifyEvent(AppStateEvent.ActiveEditorChanged); - }; - if ( - note && - note.safeContent.protected && - (await this.application.privilegesService!.actionRequiresPrivilege( - ProtectedAction.ViewProtectedNotes - )) - ) { - return new Promise((resolve) => { - this.application.presentPrivilegesModal( - ProtectedAction.ViewProtectedNotes, - () => { - run().then(resolve); - } - ); - }); - } else { - return run(); } } diff --git a/app/assets/javascripts/views/editor/editor_view.ts b/app/assets/javascripts/views/editor/editor_view.ts index 1379dccd2..b1c1d3c6c 100644 --- a/app/assets/javascripts/views/editor/editor_view.ts +++ b/app/assets/javascripts/views/editor/editor_view.ts @@ -740,14 +740,6 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> { mutator.protected = !this.note.protected } ); - /** Show privileges manager if protection is not yet set up */ - this.application.privilegesService!.actionHasPrivilegesConfigured( - ProtectedAction.ViewProtectedNotes - ).then((configured) => { - if (!configured) { - this.application.presentPrivilegesManagementModal(); - } - }); } toggleNotePreview() { From f7f3b062fbf59baad164ac99cc2536cc5566e252 Mon Sep 17 00:00:00 2001 From: Baptiste Grob <60621355+baptiste-grob@users.noreply.github.com> Date: Tue, 5 Jan 2021 13:02:03 +0100 Subject: [PATCH 2/4] fix: remove multiEditorEnabled --- app/assets/javascripts/ui_models/app_state.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/ui_models/app_state.ts b/app/assets/javascripts/ui_models/app_state.ts index 0fecb118f..b15105d20 100644 --- a/app/assets/javascripts/ui_models/app_state.ts +++ b/app/assets/javascripts/ui_models/app_state.ts @@ -106,7 +106,6 @@ export class AppState { rootScopeCleanup2: any; onVisibilityChange: any; selectedTag?: SNTag; - multiEditorEnabled = false; showBetaWarning = false; readonly actionsMenu = new ActionsMenuState(); readonly sync = new SyncState(); @@ -210,7 +209,7 @@ export class AppState { : this.selectedTag.uuid : undefined; - if (!activeEditor || this.multiEditorEnabled) { + if (!activeEditor) { this.application.editorGroup.createEditor( undefined, title, @@ -235,7 +234,7 @@ export class AppState { const approved = this.application.authorizeNoteAccess(note); if (approved === true || await approved) { const activeEditor = this.getActiveEditor(); - if (!activeEditor || this.multiEditorEnabled) { + if (!activeEditor) { this.application.editorGroup.createEditor(noteUuid); } else { activeEditor.setNote(note); From 27a68accdd81a226d5cc800c004ef2a3325c10eb Mon Sep 17 00:00:00 2001 From: Baptiste Grob <60621355+baptiste-grob@users.noreply.github.com> Date: Mon, 18 Jan 2021 13:06:01 +0100 Subject: [PATCH 3/4] refactor: update SNJS + eslint --- .eslintrc | 11 +- .../directives/views/accountMenu.ts | 194 +++++------------- .../javascripts/services/desktopManager.ts | 15 +- app/assets/javascripts/ui_models/app_state.ts | 6 +- .../javascripts/ui_models/application.ts | 70 ++----- .../views/challenge_modal/challenge-modal.pug | 16 +- .../views/challenge_modal/challenge_modal.ts | 152 +++++++------- .../javascripts/views/editor/editor_view.ts | 81 +++----- .../javascripts/views/footer/footer_view.ts | 31 +-- .../templates/directives/account-menu.pug | 4 - package.json | 9 +- yarn.lock | 67 +++--- 12 files changed, 272 insertions(+), 384 deletions(-) diff --git a/.eslintrc b/.eslintrc index 657cd2c81..0f893399a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,11 +1,10 @@ { - "extends": ["eslint:recommended", "semistandard", "prettier"], - "parser": "babel-eslint", + "extends": ["eslint:recommended", "prettier"], + "parser": "@typescript-eslint/parser", "rules": { - "standard/no-callback-literal": 0, // Disable this as we have too many callbacks relying on literals - "no-throw-literal": 0, - // "no-console": "error", - "semi": 1 + "standard/no-callback-literal": "off", // Disable this as we have too many callbacks relying on literals + "no-throw-literal": "off", + "camelcase": "off" }, "env": { "browser": true diff --git a/app/assets/javascripts/directives/views/accountMenu.ts b/app/assets/javascripts/directives/views/accountMenu.ts index 21ed1a884..17ddd2cf0 100644 --- a/app/assets/javascripts/directives/views/accountMenu.ts +++ b/app/assets/javascripts/directives/views/accountMenu.ts @@ -1,7 +1,6 @@ import { WebDirective } from './../../types'; import { isDesktopApplication, preventRefreshing } from '@/utils'; import template from '%/directives/account-menu.pug'; -import { ProtectedAction, ContentType } from '@standardnotes/snjs'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { STRING_ACCOUNT_MENU_UNCHECK_MERGE, @@ -10,8 +9,6 @@ import { STRING_LOCAL_ENC_ENABLED, STRING_ENC_NOT_ENABLED, STRING_IMPORT_SUCCESS, - STRING_REMOVE_PASSCODE_CONFIRMATION, - STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM, STRING_NON_MATCHING_PASSCODES, STRING_NON_MATCHING_PASSWORDS, STRING_INVALID_IMPORT_FILE, @@ -23,7 +20,7 @@ import { STRING_UNSUPPORTED_BACKUP_FILE_VERSION } from '@/strings'; import { PasswordWizardType } from '@/types'; -import { BackupFile } from '@standardnotes/snjs'; +import { BackupFile, ContentType } from '@standardnotes/snjs'; import { confirmDialog, alertDialog } from '@/services/alertService'; import { autorun, IReactionDisposer } from 'mobx'; import { storage, StorageKey } from '@/services/localStorage'; @@ -35,22 +32,22 @@ const ELEMENT_NAME_AUTH_PASSWORD = 'password'; const ELEMENT_NAME_AUTH_PASSWORD_CONF = 'password_conf'; type FormData = { - email: string - user_password: string - password_conf: string - confirmPassword: boolean - showLogin: boolean - showRegister: boolean - showPasscodeForm: boolean - strictSignin?: boolean - ephemeral: boolean - mergeLocal?: boolean - url: string - authenticating: boolean - status: string - passcode: string - confirmPasscode: string - changingPasscode: boolean + email: string; + user_password: string; + password_conf: string; + confirmPassword: boolean; + showLogin: boolean; + showRegister: boolean; + showPasscodeForm: boolean; + strictSignin?: boolean; + ephemeral: boolean; + mergeLocal?: boolean; + url: string; + authenticating: boolean; + status: string; + passcode: string; + confirmPasscode: string; + changingPasscode: boolean; } type AccountMenuState = { @@ -72,7 +69,7 @@ type AccountMenuState = { showSessions: boolean; } -class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> { +class AccountMenuCtrl extends PureViewCtrl { public appVersion: string /** @template */ @@ -331,26 +328,6 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> { this.appState.openSessionsModal(); } - async openPrivilegesModal() { - const run = () => { - this.application!.presentPrivilegesManagementModal(); - this.close(); - }; - const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege( - ProtectedAction.ManagePrivileges - ); - if (needsPrivilege) { - this.application!.presentPrivilegesModal( - ProtectedAction.ManagePrivileges, - () => { - run(); - } - ); - } else { - run(); - } - } - async destroyLocalData() { if (await confirmDialog({ text: STRING_SIGN_OUT_CONFIRMATION, @@ -394,56 +371,46 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> { * @template */ async importFileSelected(files: File[]) { - const run = async () => { - const file = files[0]; - const data = await this.readFile(file); - if (!data) { + const file = files[0]; + const data = await this.readFile(file); + if (!data) { + return; + } + if (data.version || data.auth_params || data.keyParams) { + const version = data.version || data.keyParams?.version || data.auth_params?.version; + if ( + !this.application!.protocolService!.supportedVersions().includes(version) + ) { + await this.setState({ importData: null }); + alertDialog({ text: STRING_UNSUPPORTED_BACKUP_FILE_VERSION }); return; } - if (data.version || data.auth_params || data.keyParams) { - const version = data.version || data.keyParams?.version || data.auth_params?.version; - if ( - !this.application!.protocolService!.supportedVersions().includes(version) - ) { - await this.setState({ importData: null }); - alertDialog({ text: STRING_UNSUPPORTED_BACKUP_FILE_VERSION }); - return; - } - if (data.keyParams || data.auth_params) { - await this.setState({ - importData: { - ...this.getState().importData, - requestPassword: true, - data, - } - }); - const element = document.getElementById( - ELEMENT_ID_IMPORT_PASSWORD_INPUT - ); - if (element) { - element.scrollIntoView(false); + if (data.keyParams || data.auth_params) { + await this.setState({ + importData: { + ...this.getState().importData, + requestPassword: true, + data, } - } else { - await this.performImport(data, undefined); + }); + const element = document.getElementById( + ELEMENT_ID_IMPORT_PASSWORD_INPUT + ); + if (element) { + element.scrollIntoView(false); } } else { await this.performImport(data, undefined); } - }; - const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege( - ProtectedAction.ManageBackups - ); - if (needsPrivilege) { - this.application!.presentPrivilegesModal( - ProtectedAction.ManageBackups, - run - ); } else { - run(); + await this.performImport(data, undefined); } } async performImport(data: BackupFile, password?: string) { + if (!(await this.application.authorizeFileImport())) { + return; + } await this.setState({ importData: { ...this.getState().importData, @@ -499,23 +466,11 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> { } async selectAutoLockInterval(interval: number) { - const run = async () => { - await this.application!.getAutolockService().setAutoLockInterval(interval); - this.reloadAutoLockInterval(); - }; - const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege( - ProtectedAction.ManagePasscode - ); - if (needsPrivilege) { - this.application!.presentPrivilegesModal( - ProtectedAction.ManagePasscode, - () => { - run(); - } - ); - } else { - run(); + if (!(await this.application.authorizeAutolockIntervalChange())) { + return; } + await this.application!.getAutolockService().setAutoLockInterval(interval); + this.reloadAutoLockInterval(); } hidePasswordForm() { @@ -562,53 +517,18 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> { } async changePasscodePressed() { - const run = () => { - this.getState().formData.changingPasscode = true; - this.addPasscodeClicked(); - }; - const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege( - ProtectedAction.ManagePasscode - ); - if (needsPrivilege) { - this.application!.presentPrivilegesModal( - ProtectedAction.ManagePasscode, - run - ); - } else { - run(); - } + this.getState().formData.changingPasscode = true; + this.addPasscodeClicked(); } async removePasscodePressed() { - const run = async () => { - const signedIn = this.application!.hasAccount(); - let message = STRING_REMOVE_PASSCODE_CONFIRMATION; - if (!signedIn) { - message += STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM; - } - if (await confirmDialog({ - text: message, - confirmButtonStyle: 'danger' - })) { - await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL, async () => { - await this.application.getAutolockService().deleteAutolockPreference(); - await this.application!.removePasscode(); - await this.reloadAutoLockInterval(); - }); + await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL, async () => { + if (await this.application!.removePasscode()) { + await this.application.getAutolockService().deleteAutolockPreference(); + await this.reloadAutoLockInterval(); this.refreshEncryptionStatus(); } - }; - const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege( - ProtectedAction.ManagePasscode - ); - if (needsPrivilege) { - this.application!.presentPrivilegesModal( - ProtectedAction.ManagePasscode, - run - ); - } else { - run(); - } + }); } openErrorReportingDialog() { diff --git a/app/assets/javascripts/services/desktopManager.ts b/app/assets/javascripts/services/desktopManager.ts index baae0aca2..057c20c3c 100644 --- a/app/assets/javascripts/services/desktopManager.ts +++ b/app/assets/javascripts/services/desktopManager.ts @@ -1,9 +1,17 @@ -import { SNComponent, PurePayload, ComponentMutator, AppDataField, ContentType } from '@standardnotes/snjs'; +import { + SNComponent, + PurePayload, + ComponentMutator, + AppDataField, + EncryptionIntent, + ApplicationService, + ApplicationEvent, + removeFromArray, +} from '@standardnotes/snjs'; /* eslint-disable camelcase */ import { WebApplication } from '@/ui_models/application'; // An interface used by the Desktop app to interact with SN import { isDesktopApplication } from '@/utils'; -import { EncryptionIntent, ApplicationService, ApplicationEvent, removeFromArray } from '@standardnotes/snjs'; import { Bridge } from './bridge'; type UpdateObserverCallback = (component: SNComponent) => void @@ -20,7 +28,8 @@ export class DesktopManager extends ApplicationService { componentActivationObservers: ComponentActivationObserver[] = [] updateObservers: { callback: UpdateObserverCallback - }[] = [] + }[] = []; + isDesktop = isDesktopApplication(); dataLoaded = false diff --git a/app/assets/javascripts/ui_models/app_state.ts b/app/assets/javascripts/ui_models/app_state.ts index b15105d20..e973f48f8 100644 --- a/app/assets/javascripts/ui_models/app_state.ts +++ b/app/assets/javascripts/ui_models/app_state.ts @@ -1,13 +1,10 @@ import { isDesktopApplication, isDev } from '@/utils'; import pull from 'lodash/pull'; import { - ProtectedAction, ApplicationEvent, SNTag, SNNote, - SNUserPrefs, ContentType, - SNSmartTag, PayloadSource, DeinitSource, UuidString, @@ -231,8 +228,7 @@ export class AppState { return; }; - const approved = this.application.authorizeNoteAccess(note); - if (approved === true || await approved) { + if (await this.application.authorizeNoteAccess(note)) { const activeEditor = this.getActiveEditor(); if (!activeEditor) { this.application.editorGroup.createEditor(noteUuid); diff --git a/app/assets/javascripts/ui_models/application.ts b/app/assets/javascripts/ui_models/application.ts index 79f628799..76dadcc68 100644 --- a/app/assets/javascripts/ui_models/application.ts +++ b/app/assets/javascripts/ui_models/application.ts @@ -1,4 +1,3 @@ -import { PermissionDialog } from '@standardnotes/snjs'; import { ComponentModalScope } from './../directives/views/componentModal'; import { AccountSwitcherScope, PermissionsModalScope } from './../types'; import { ComponentGroup } from './component_group'; @@ -9,7 +8,9 @@ import { SNApplication, platformFromString, Challenge, - ProtectedAction, SNComponent + SNComponent, + PermissionDialog, + DeinitSource, } from '@standardnotes/snjs'; import angular from 'angular'; import { getPlatformString } from '@/utils'; @@ -27,17 +28,16 @@ import { import { AppState } from '@/ui_models/app_state'; import { SNWebCrypto } from '@standardnotes/sncrypto-web'; import { Bridge } from '@/services/bridge'; -import { DeinitSource } from '@standardnotes/snjs'; type WebServices = { - appState: AppState - desktopService: DesktopManager - autolockService: AutolockService - archiveService: ArchiveManager - nativeExtService: NativeExtManager - statusManager: StatusManager - themeService: ThemeManager - keyboardService: KeyboardManager + appState: AppState; + desktopService: DesktopManager; + autolockService: AutolockService; + archiveService: ArchiveManager; + nativeExtService: NativeExtManager; + statusManager: StatusManager; + themeService: ThemeManager; + keyboardService: KeyboardManager; } export class WebApplication extends SNApplication { @@ -78,7 +78,7 @@ export class WebApplication extends SNApplication { } /** @override */ - deinit(source: DeinitSource) { + deinit(source: DeinitSource): void { for (const service of Object.values(this.webServices)) { if ('deinit' in service) { service.deinit?.(source); @@ -107,15 +107,15 @@ export class WebApplication extends SNApplication { this.componentManager!.presentPermissionsDialog = this.presentPermissionsDialog; } - setWebServices(services: WebServices) { + setWebServices(services: WebServices): void { this.webServices = services; } - public getAppState() { + public getAppState(): AppState { return this.webServices.appState; } - public getDesktopService() { + public getDesktopService(): DesktopManager { return this.webServices.desktopService; } @@ -170,46 +170,6 @@ export class WebApplication extends SNApplication { this.applicationElement.append(el); } - async presentPrivilegesModal( - action: ProtectedAction, - onSuccess?: any, - onCancel?: any - ) { - if (this.authenticationInProgress()) { - onCancel && onCancel(); - return; - } - - const customSuccess = async () => { - onSuccess && await onSuccess(); - this.currentAuthenticationElement = undefined; - }; - const customCancel = async () => { - onCancel && await onCancel(); - this.currentAuthenticationElement = undefined; - }; - - const scope: any = this.scope!.$new(true); - scope.action = action; - scope.onSuccess = customSuccess; - scope.onCancel = customCancel; - scope.application = this; - const el = this.$compile!(` - - `)(scope); - this.applicationElement.append(el); - - this.currentAuthenticationElement = el; - } - - presentPrivilegesManagementModal() { - const scope: any = this.scope!.$new(true); - scope.application = this; - const el = this.$compile!("")(scope); - this.applicationElement.append(el); - } - authenticationInProgress() { return this.currentAuthenticationElement != null; } diff --git a/app/assets/javascripts/views/challenge_modal/challenge-modal.pug b/app/assets/javascripts/views/challenge_modal/challenge-modal.pug index bb44ffbf9..6277aef49 100644 --- a/app/assets/javascripts/views/challenge_modal/challenge-modal.pug +++ b/app/assets/javascripts/views/challenge_modal/challenge-modal.pug @@ -12,7 +12,9 @@ | {{ctrl.challenge.subheading}} .sk-panel-section div(ng-repeat="prompt in ctrl.state.prompts track by prompt.id") - .sk-panel-row + .sk-panel-row( + ng-if="prompt.validation != ctrl.protectionsSessionValidation" + ) input.sk-input.contrast( ng-model="ctrl.state.values[prompt.id].value" should-focus="$index == 0" @@ -21,7 +23,17 @@ ng-change="ctrl.onTextValueChange(prompt)" ng-attr-type="{{prompt.secureTextEntry ? 'password' : 'text'}}", ng-attr-placeholder="{{prompt.title}}" - ) + ) + .sk-horizontal-group( + ng-if="prompt.validation == ctrl.protectionsSessionValidation" + ) + .sk-p.sk-bold Remember For + a.sk-a.info( + ng-repeat="option in ctrl.protectionsSessionDurations" + ng-class="{'boxed' : option.valueInSeconds == ctrl.state.values[prompt.id].value}" + ng-click="ctrl.onValueChange(prompt, option.valueInSeconds);" + ) + | {{option.label}} .sk-panel-row.centered label.sk-label.danger( ng-if="ctrl.state.values[prompt.id].invalid" diff --git a/app/assets/javascripts/views/challenge_modal/challenge_modal.ts b/app/assets/javascripts/views/challenge_modal/challenge_modal.ts index a605dac9f..a69f88b33 100644 --- a/app/assets/javascripts/views/challenge_modal/challenge_modal.ts +++ b/app/assets/javascripts/views/challenge_modal/challenge_modal.ts @@ -5,45 +5,46 @@ import { removeFromArray, Challenge, ChallengeReason, - ChallengePrompt + ChallengePrompt, + ChallengeValidation, + ProtectionSessionDurations, } from '@standardnotes/snjs'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { WebDirective } from '@/types'; import { confirmDialog } from '@/services/alertService'; -import { - STRING_SIGN_OUT_CONFIRMATION, -} from '@/strings'; +import { STRING_SIGN_OUT_CONFIRMATION } from '@/strings'; type InputValue = { - prompt: ChallengePrompt - value: string - invalid: boolean -} + prompt: ChallengePrompt; + value: string | number | boolean; + invalid: boolean; +}; -type Values = Record +type Values = Record; type ChallengeModalState = { - prompts: ChallengePrompt[] - values: Partial - processing: boolean, - forgotPasscode: boolean, - showForgotPasscodeLink: boolean, - processingPrompts: ChallengePrompt[], - hasAccount: boolean, -} + prompts: ChallengePrompt[]; + values: Partial; + processing: boolean; + forgotPasscode: boolean; + showForgotPasscodeLink: boolean; + processingPrompts: ChallengePrompt[]; + hasAccount: boolean; + protectedNoteAccessDuration: number; +}; -class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> { - private $element: JQLite - application!: WebApplication - challenge!: Challenge +class ChallengeModalCtrl extends PureViewCtrl { + application!: WebApplication; + challenge!: Challenge; + + /** @template */ + protectionsSessionDurations = ProtectionSessionDurations; + protectionsSessionValidation = + ChallengeValidation.ProtectionSessionDuration; /* @ngInject */ - constructor( - $element: JQLite, - $timeout: ng.ITimeoutService - ) { + constructor(private $element: JQLite, $timeout: ng.ITimeoutService) { super($timeout); - this.$element = $element; } getState() { @@ -57,13 +58,13 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> { for (const prompt of prompts) { values[prompt.id] = { prompt, - value: '', - invalid: false + value: prompt.initialValue ?? '', + invalid: false, }; } const showForgotPasscodeLink = [ ChallengeReason.ApplicationUnlock, - ChallengeReason.Migration + ChallengeReason.Migration, ].includes(this.challenge.reason); this.setState({ prompts, @@ -72,34 +73,32 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> { forgotPasscode: false, showForgotPasscodeLink, hasAccount: this.application.hasAccount(), - processingPrompts: [] + processingPrompts: [], + protectedNoteAccessDuration: ProtectionSessionDurations[0].valueInSeconds, }); - this.application.addChallengeObserver( - this.challenge, - { - onValidValue: (value) => { - this.getState().values[value.prompt.id]!.invalid = false; + this.application.addChallengeObserver(this.challenge, { + onValidValue: (value) => { + this.getState().values[value.prompt.id]!.invalid = false; + removeFromArray(this.state.processingPrompts, value.prompt); + this.reloadProcessingStatus(); + }, + onInvalidValue: (value) => { + this.getState().values[value.prompt.id]!.invalid = true; + /** If custom validation, treat all values together and not individually */ + if (!value.prompt.validates) { + this.setState({ processingPrompts: [], processing: false }); + } else { removeFromArray(this.state.processingPrompts, value.prompt); this.reloadProcessingStatus(); - }, - onInvalidValue: (value) => { - this.getState().values[value.prompt.id]!.invalid = true; - /** If custom validation, treat all values together and not individually */ - if (!value.prompt.validates) { - this.setState({ processingPrompts: [], processing: false }); - } else { - removeFromArray(this.state.processingPrompts, value.prompt); - this.reloadProcessingStatus(); - } - }, - onComplete: () => { - this.dismiss(); - }, - onCancel: () => { - this.dismiss(); - }, - } - ); + } + }, + onComplete: () => { + this.dismiss(); + }, + onCancel: () => { + this.dismiss(); + }, + }); } deinit() { @@ -110,18 +109,20 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> { reloadProcessingStatus() { return this.setState({ - processing: this.state.processingPrompts.length > 0 + processing: this.state.processingPrompts.length > 0, }); } async destroyLocalData() { - if (await confirmDialog({ - text: STRING_SIGN_OUT_CONFIRMATION, - confirmButtonStyle: "danger" - })) { + if ( + await confirmDialog({ + text: STRING_SIGN_OUT_CONFIRMATION, + confirmButtonStyle: 'danger', + }) + ) { await this.application.signOut(); this.dismiss(); - }; + } } /** @template */ @@ -133,7 +134,7 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> { onForgotPasscodeClick() { this.setState({ - forgotPasscode: true + forgotPasscode: true, }); } @@ -143,15 +144,22 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> { this.setState({ values }); } + onValueChange(prompt: ChallengePrompt, value: number) { + const values = this.state.values; + values[prompt.id]!.invalid = false; + values[prompt.id]!.value = value; + } + validate() { - const failed = []; - for (const prompt of this.getState().prompts) { - const value = this.getState().values[prompt.id]; - if (!value || value.value.length === 0) { - this.getState().values[prompt.id]!.invalid = true; + let failed = 0; + for (const prompt of this.state.prompts) { + const value = this.state.values[prompt.id]!; + if (typeof value.value === 'string' && value.value.length === 0) { + this.state.values[prompt.id]!.invalid = true; + failed++; } } - return failed.length === 0; + return failed === 0; } async submit() { @@ -161,15 +169,15 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> { await this.setState({ processing: true }); const values: ChallengeValue[] = []; for (const inputValue of Object.values(this.getState().values)) { - const rawValue = inputValue!!.value; + const rawValue = inputValue!.value; const value = new ChallengeValue(inputValue!.prompt, rawValue); values.push(value); } const processingPrompts = values.map((v) => v.prompt); await this.setState({ processingPrompts: processingPrompts, - processing: processingPrompts.length > 0 - }) + processing: processingPrompts.length > 0, + }); /** * Unfortunately neccessary to wait 50ms so that the above setState call completely * updates the UI to change processing state, before we enter into UI blocking operation @@ -181,7 +189,7 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> { } else { this.setState({ processing: false }); } - }, 50) + }, 50); } dismiss() { @@ -202,7 +210,7 @@ export class ChallengeModal extends WebDirective { this.bindToController = true; this.scope = { challenge: '=', - application: '=' + application: '=', }; } } diff --git a/app/assets/javascripts/views/editor/editor_view.ts b/app/assets/javascripts/views/editor/editor_view.ts index b1c1d3c6c..d6ff57ee5 100644 --- a/app/assets/javascripts/views/editor/editor_view.ts +++ b/app/assets/javascripts/views/editor/editor_view.ts @@ -8,7 +8,6 @@ import { isPayloadSourceRetrieved, isPayloadSourceInternalChange, ContentType, - ProtectedAction, SNComponent, SNNote, SNTag, @@ -24,7 +23,7 @@ import { isDesktopApplication } from '@/utils'; import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager'; import template from './editor-view.pug'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; -import { AppStateEvent, EventSource } from '@/ui_models/app_state'; +import { EventSource } from '@/ui_models/app_state'; import { STRING_DELETED_NOTE, STRING_INVALID_NOTE, @@ -85,7 +84,7 @@ type EditorState = { * then re-initialized. Used when reloading spellcheck status. */ textareaUnloading: boolean /** Fields that can be directly mutated by the template */ - mutable: {} + mutable: any } type EditorValues = { @@ -98,7 +97,7 @@ function sortAlphabetically(array: SNComponent[]): SNComponent[] { return array.sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1); } -class EditorViewCtrl extends PureViewCtrl<{}, EditorState> { +class EditorViewCtrl extends PureViewCtrl { /** Passed through template */ readonly application!: WebApplication readonly editor!: Editor @@ -248,7 +247,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> { case ApplicationEvent.HighLatencySync: this.setState({ syncTakingTooLong: true }); break; - case ApplicationEvent.CompletedFullSync: + case ApplicationEvent.CompletedFullSync: { this.setState({ syncTakingTooLong: false }); const isInErrorState = this.state.saveError; /** if we're still dirty, don't change status, a sync is likely upcoming. */ @@ -256,6 +255,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> { this.showAllChangesSavedStatus(); } break; + } case ApplicationEvent.FailedSync: /** * Only show error status in editor if the note is dirty. @@ -601,10 +601,12 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> { this.setMenuState('showOptionsMenu', false); } + // eslint-disable-next-line @typescript-eslint/no-empty-function onTitleFocus() { } + // eslint-disable-next-line @typescript-eslint/no-empty-function onTitleBlur() { } @@ -627,50 +629,35 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> { ); return; } - const run = async () => { - if (this.note.locked) { - this.application.alertService!.alert( - STRING_DELETE_LOCKED_ATTEMPT - ); - return; - } - const title = this.note.safeTitle().length - ? `'${this.note.title}'` - : "this note"; - const text = StringDeleteNote( - title, - permanently + if (this.note.locked) { + this.application.alertService!.alert( + STRING_DELETE_LOCKED_ATTEMPT ); - if (await confirmDialog({ - text, - confirmButtonStyle: 'danger' - })) { - if (permanently) { - this.performNoteDeletion(this.note); - } else { - this.saveNote( - true, - false, - true, - (mutator) => { - mutator.trashed = true; - } - ); - } - }; - }; - const requiresPrivilege = await this.application.privilegesService!.actionRequiresPrivilege( - ProtectedAction.DeleteNote + return; + } + const title = this.note.safeTitle().length + ? `'${this.note.title}'` + : "this note"; + const text = StringDeleteNote( + title, + permanently ); - if (requiresPrivilege) { - this.application.presentPrivilegesModal( - ProtectedAction.DeleteNote, - () => { - run(); - } - ); - } else { - run(); + if (await confirmDialog({ + text, + confirmButtonStyle: 'danger' + })) { + if (permanently) { + this.performNoteDeletion(this.note); + } else { + this.saveNote( + true, + false, + true, + (mutator) => { + mutator.trashed = true; + } + ); + } } } diff --git a/app/assets/javascripts/views/footer/footer_view.ts b/app/assets/javascripts/views/footer/footer_view.ts index b6491d070..1e029617a 100644 --- a/app/assets/javascripts/views/footer/footer_view.ts +++ b/app/assets/javascripts/views/footer/footer_view.ts @@ -5,7 +5,6 @@ import { dateToLocalizedString, preventRefreshing } from '@/utils'; import { ApplicationEvent, SyncQueueStrategy, - ProtectedAction, ContentType, SNComponent, SNTheme, @@ -44,7 +43,7 @@ type DockShortcut = { } } -class FooterViewCtrl extends PureViewCtrl<{}, { +class FooterViewCtrl extends PureViewCtrl { - this.$timeout(() => { - this.roomShowState[room.uuid] = !this.roomShowState[room.uuid]; - }); - }; - - if (!this.roomShowState[room.uuid]) { - const requiresPrivilege = await this.application.privilegesService! - .actionRequiresPrivilege( - ProtectedAction.ManageExtensions - ); - if (requiresPrivilege) { - this.application.presentPrivilegesModal( - ProtectedAction.ManageExtensions, - run - ); - } else { - run(); - } - } else { - run(); - } + this.$timeout(() => { + this.roomShowState[room.uuid] = !this.roomShowState[room.uuid]; + }); } displayBetaDialog() { diff --git a/app/assets/templates/directives/account-menu.pug b/app/assets/templates/directives/account-menu.pug index 6c3572309..1cc5892d0 100644 --- a/app/assets/templates/directives/account-menu.pug +++ b/app/assets/templates/directives/account-menu.pug @@ -168,10 +168,6 @@ ng-click="self.openSessionsModal()" ng-if="self.state.showSessions" ) Manage Sessions - a.sk-a.info.sk-panel-row.condensed( - ng-click="self.openPrivilegesModal('')", - ng-show='self.state.user' - ) Manage Privileges .sk-panel-section .sk-panel-section-title Encryption .sk-panel-section-subtitle.info(ng-if='self.state.encryptionEnabled') diff --git a/package.json b/package.json index 1659fd071..805665532 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "@types/mocha": "^7.0.2", "@types/pug": "^2.0.4", "@types/react": "^17.0.0", - "@typescript-eslint/eslint-plugin": "^2.23.0", - "@typescript-eslint/parser": "^2.23.0", + "@typescript-eslint/eslint-plugin": "^3.10.1", + "@typescript-eslint/parser": "^3.10.1", "angular": "^1.8.2", "apply-loader": "^2.0.0", "babel-eslint": "^10.1.0", @@ -71,9 +71,12 @@ "@reach/alert-dialog": "^0.12.1", "@reach/dialog": "^0.12.1", "@standardnotes/sncrypto-web": "^1.2.9", - "@standardnotes/snjs": "^2.0.35", + "@standardnotes/snjs": "^2.0.41", "babel-loader": "^8.2.2", "mobx": "^6.0.4", "preact": "^10.5.7" + }, + "peerDependencies": { + "react": "^16.8.0" } } diff --git a/yarn.lock b/yarn.lock index 556ffb807..af8099b2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1045,10 +1045,10 @@ "@standardnotes/sncrypto-common" "^1.2.7" libsodium-wrappers "^0.7.8" -"@standardnotes/snjs@^2.0.35": - version "2.0.35" - resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.35.tgz#9e9c3058ebbfc9af7a5fc3ae18497f02a9b1e71b" - integrity sha512-uA4HXorgiV8yFGN1dtO52istUudnc3ZzEQiFLgf0bkKvA0wH3Xt+R9bBviYAdIBkTBKHCyGsBdsCiKr1QS/X9g== +"@standardnotes/snjs@^2.0.41": + version "2.0.41" + resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.41.tgz#131f3a206b220be5359c43963c47b1929b92f037" + integrity sha512-/6hoBcb/Ib8voqwk1YLVTAM6L2ZJV/AZDXJz/oEKY9cjO0onFMbl0T+AAWEal1d6zojyjC4Wv6Pc9fkNInvBSQ== dependencies: "@standardnotes/sncrypto-common" "^1.2.9" @@ -1188,49 +1188,66 @@ "@types/webpack-sources" "*" source-map "^0.6.0" -"@typescript-eslint/eslint-plugin@^2.23.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" - integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== +"@typescript-eslint/eslint-plugin@^3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f" + integrity sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ== dependencies: - "@typescript-eslint/experimental-utils" "2.34.0" + "@typescript-eslint/experimental-utils" "3.10.1" + debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" + semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" - integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== +"@typescript-eslint/experimental-utils@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686" + integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.34.0" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/typescript-estree" "3.10.1" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^2.23.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" - integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== +"@typescript-eslint/parser@^3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467" + integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.34.0" - "@typescript-eslint/typescript-estree" "2.34.0" + "@typescript-eslint/experimental-utils" "3.10.1" + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/typescript-estree" "3.10.1" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" - integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== +"@typescript-eslint/types@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" + integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== + +"@typescript-eslint/typescript-estree@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz#fd0061cc38add4fad45136d654408569f365b853" + integrity sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w== dependencies: + "@typescript-eslint/types" "3.10.1" + "@typescript-eslint/visitor-keys" "3.10.1" debug "^4.1.1" - eslint-visitor-keys "^1.1.0" glob "^7.1.6" is-glob "^4.0.1" lodash "^4.17.15" semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/visitor-keys@3.10.1": + version "3.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz#cd4274773e3eb63b2e870ac602274487ecd1e931" + integrity sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ== + dependencies: + eslint-visitor-keys "^1.1.0" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" 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 4/4] 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 4051a7883..1d414d8df 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 adee8d90c..99931e2bb 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 e715ee9c4..000000000 --- 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 81403b69a..000000000 --- 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 08f3906e3..dd51d4e51 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 eaa800f3e..bb2442ac9 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 e973f48f8..1028099de 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 89c94e83a..7e8e58c30 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 77494478d..966e9ef24 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 585f1c6fb..39953a61c 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 3c578b256..000000000 --- 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 cc697e901..000000000 --- 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.