Merge branch 'feature/privileges-simplification' into develop

This commit is contained in:
Baptiste Grob
2021-01-18 14:59:38 +01:00
21 changed files with 303 additions and 837 deletions

View File

@@ -44,8 +44,6 @@ import {
PanelResizer, PanelResizer,
PasswordWizard, PasswordWizard,
PermissionsModal, PermissionsModal,
PrivilegesAuthModal,
PrivilegesManagementModal,
RevisionPreviewModal, RevisionPreviewModal,
HistoryMenu, HistoryMenu,
SyncResolutionMenu, SyncResolutionMenu,
@@ -140,11 +138,6 @@ const startApplication: StartApplication = async function startApplication(
.directive('panelResizer', () => new PanelResizer()) .directive('panelResizer', () => new PanelResizer())
.directive('passwordWizard', () => new PasswordWizard()) .directive('passwordWizard', () => new PasswordWizard())
.directive('permissionsModal', () => new PermissionsModal()) .directive('permissionsModal', () => new PermissionsModal())
.directive('privilegesAuthModal', () => new PrivilegesAuthModal())
.directive(
'privilegesManagementModal',
() => new PrivilegesManagementModal()
)
.directive('revisionPreviewModal', () => new RevisionPreviewModal()) .directive('revisionPreviewModal', () => new RevisionPreviewModal())
.directive('historyMenu', () => new HistoryMenu()) .directive('historyMenu', () => new HistoryMenu())
.directive('syncResolutionMenu', () => new SyncResolutionMenu()) .directive('syncResolutionMenu', () => new SyncResolutionMenu())

View File

@@ -1,7 +1,6 @@
import { WebDirective } from './../../types'; import { WebDirective } from './../../types';
import { isDesktopApplication, preventRefreshing } from '@/utils'; import { isDesktopApplication, preventRefreshing } from '@/utils';
import template from '%/directives/account-menu.pug'; import template from '%/directives/account-menu.pug';
import { ProtectedAction, ContentType } from '@standardnotes/snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { import {
STRING_ACCOUNT_MENU_UNCHECK_MERGE, STRING_ACCOUNT_MENU_UNCHECK_MERGE,
@@ -10,8 +9,6 @@ import {
STRING_LOCAL_ENC_ENABLED, STRING_LOCAL_ENC_ENABLED,
STRING_ENC_NOT_ENABLED, STRING_ENC_NOT_ENABLED,
STRING_IMPORT_SUCCESS, STRING_IMPORT_SUCCESS,
STRING_REMOVE_PASSCODE_CONFIRMATION,
STRING_REMOVE_PASSCODE_OFFLINE_ADDENDUM,
STRING_NON_MATCHING_PASSCODES, STRING_NON_MATCHING_PASSCODES,
STRING_NON_MATCHING_PASSWORDS, STRING_NON_MATCHING_PASSWORDS,
STRING_INVALID_IMPORT_FILE, STRING_INVALID_IMPORT_FILE,
@@ -23,7 +20,7 @@ import {
STRING_UNSUPPORTED_BACKUP_FILE_VERSION STRING_UNSUPPORTED_BACKUP_FILE_VERSION
} from '@/strings'; } from '@/strings';
import { PasswordWizardType } from '@/types'; import { PasswordWizardType } from '@/types';
import { BackupFile } from '@standardnotes/snjs'; import { BackupFile, ContentType } from '@standardnotes/snjs';
import { confirmDialog, alertDialog } from '@/services/alertService'; import { confirmDialog, alertDialog } from '@/services/alertService';
import { autorun, IReactionDisposer } from 'mobx'; import { autorun, IReactionDisposer } from 'mobx';
import { storage, StorageKey } from '@/services/localStorage'; import { storage, StorageKey } from '@/services/localStorage';
@@ -35,22 +32,22 @@ const ELEMENT_NAME_AUTH_PASSWORD = 'password';
const ELEMENT_NAME_AUTH_PASSWORD_CONF = 'password_conf'; const ELEMENT_NAME_AUTH_PASSWORD_CONF = 'password_conf';
type FormData = { type FormData = {
email: string email: string;
user_password: string user_password: string;
password_conf: string password_conf: string;
confirmPassword: boolean confirmPassword: boolean;
showLogin: boolean showLogin: boolean;
showRegister: boolean showRegister: boolean;
showPasscodeForm: boolean showPasscodeForm: boolean;
strictSignin?: boolean strictSignin?: boolean;
ephemeral: boolean ephemeral: boolean;
mergeLocal?: boolean mergeLocal?: boolean;
url: string url: string;
authenticating: boolean authenticating: boolean;
status: string status: string;
passcode: string passcode: string;
confirmPasscode: string confirmPasscode: string;
changingPasscode: boolean changingPasscode: boolean;
} }
type AccountMenuState = { type AccountMenuState = {
@@ -71,7 +68,7 @@ type AccountMenuState = {
showSessions: boolean; showSessions: boolean;
} }
class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> { class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
public appVersion: string public appVersion: string
/** @template */ /** @template */
@@ -329,26 +326,6 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
this.appState.openSessionsModal(); 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() { async destroyLocalData() {
if (await confirmDialog({ if (await confirmDialog({
text: STRING_SIGN_OUT_CONFIRMATION, text: STRING_SIGN_OUT_CONFIRMATION,
@@ -392,56 +369,46 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
* @template * @template
*/ */
async importFileSelected(files: File[]) { async importFileSelected(files: File[]) {
const run = async () => { const file = files[0];
const file = files[0]; const data = await this.readFile(file);
const data = await this.readFile(file); if (!data) {
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; return;
} }
if (data.version || data.auth_params || data.keyParams) { if (data.keyParams || data.auth_params) {
const version = data.version || data.keyParams?.version || data.auth_params?.version; await this.setState({
if ( importData: {
!this.application!.protocolService!.supportedVersions().includes(version) ...this.getState().importData,
) { requestPassword: true,
await this.setState({ importData: null }); data,
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);
} }
} else { });
await this.performImport(data, undefined); const element = document.getElementById(
ELEMENT_ID_IMPORT_PASSWORD_INPUT
);
if (element) {
element.scrollIntoView(false);
} }
} else { } else {
await this.performImport(data, undefined); await this.performImport(data, undefined);
} }
};
const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege(
ProtectedAction.ManageBackups
);
if (needsPrivilege) {
this.application!.presentPrivilegesModal(
ProtectedAction.ManageBackups,
run
);
} else { } else {
run(); await this.performImport(data, undefined);
} }
} }
async performImport(data: BackupFile, password?: string) { async performImport(data: BackupFile, password?: string) {
if (!(await this.application.authorizeFileImport())) {
return;
}
await this.setState({ await this.setState({
importData: { importData: {
...this.getState().importData, ...this.getState().importData,
@@ -497,23 +464,11 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
} }
async selectAutoLockInterval(interval: number) { async selectAutoLockInterval(interval: number) {
const run = async () => { if (!(await this.application.authorizeAutolockIntervalChange())) {
await this.application!.getAutolockService().setAutoLockInterval(interval); return;
this.reloadAutoLockInterval();
};
const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege(
ProtectedAction.ManagePasscode
);
if (needsPrivilege) {
this.application!.presentPrivilegesModal(
ProtectedAction.ManagePasscode,
() => {
run();
}
);
} else {
run();
} }
await this.application!.getAutolockService().setAutoLockInterval(interval);
this.reloadAutoLockInterval();
} }
hidePasswordForm() { hidePasswordForm() {
@@ -560,53 +515,18 @@ class AccountMenuCtrl extends PureViewCtrl<{}, AccountMenuState> {
} }
async changePasscodePressed() { async changePasscodePressed() {
const run = () => { this.getState().formData.changingPasscode = true;
this.getState().formData.changingPasscode = true; this.addPasscodeClicked();
this.addPasscodeClicked();
};
const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege(
ProtectedAction.ManagePasscode
);
if (needsPrivilege) {
this.application!.presentPrivilegesModal(
ProtectedAction.ManagePasscode,
run
);
} else {
run();
}
} }
async removePasscodePressed() { async removePasscodePressed() {
const run = async () => { await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL, async () => {
const signedIn = this.application!.hasAccount(); if (await this.application!.removePasscode()) {
let message = STRING_REMOVE_PASSCODE_CONFIRMATION; await this.application.getAutolockService().deleteAutolockPreference();
if (!signedIn) { await this.reloadAutoLockInterval();
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();
});
this.refreshEncryptionStatus(); this.refreshEncryptionStatus();
} }
}; });
const needsPrivilege = await this.application!.privilegesService!.actionRequiresPrivilege(
ProtectedAction.ManagePasscode
);
if (needsPrivilege) {
this.application!.presentPrivilegesModal(
ProtectedAction.ManagePasscode,
run
);
} else {
run();
}
} }
openErrorReportingDialog() { openErrorReportingDialog() {

View File

@@ -8,8 +8,6 @@ export { MenuRow } from './menuRow';
export { PanelResizer } from './panelResizer'; export { PanelResizer } from './panelResizer';
export { PasswordWizard } from './passwordWizard'; export { PasswordWizard } from './passwordWizard';
export { PermissionsModal } from './permissionsModal'; export { PermissionsModal } from './permissionsModal';
export { PrivilegesAuthModal } from './privilegesAuthModal';
export { PrivilegesManagementModal } from './privilegesManagementModal';
export { RevisionPreviewModal } from './revisionPreviewModal'; export { RevisionPreviewModal } from './revisionPreviewModal';
export { HistoryMenu } from './historyMenu'; export { HistoryMenu } from './historyMenu';
export { SyncResolutionMenu } from './syncResolutionMenu'; export { SyncResolutionMenu } from './syncResolutionMenu';

View File

@@ -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<Record<PrivilegeCredential, string>> = {}
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: '='
};
}
}

View File

@@ -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<Record<PrivilegeCredential, DisplayInfo>> = {}
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: '='
};
}
}

View File

@@ -5,6 +5,7 @@ import {
RemoteSession, RemoteSession,
SessionStrings, SessionStrings,
UuidString, UuidString,
isNullOrUndefined,
} from '@standardnotes/snjs'; } from '@standardnotes/snjs';
import { autorun, IAutorunOptions, IReactionPublic } from 'mobx'; import { autorun, IAutorunOptions, IReactionPublic } from 'mobx';
import { render, FunctionComponent } from 'preact'; import { render, FunctionComponent } from 'preact';
@@ -78,7 +79,9 @@ function useSessions(
setSessions(sessionsDuringRevoke); setSessions(sessionsDuringRevoke);
const response = await responsePromise; const response = await responsePromise;
if ('error' in response) { if (isNullOrUndefined(response)) {
setSessions(sessionsBeforeRevoke);
} else if ('error' in response) {
if (response.error?.message) { if (response.error?.message) {
setErrorMessage(response.error?.message); setErrorMessage(response.error?.message);
} else { } else {

View File

@@ -1,5 +1,10 @@
import { WebApplication } from '@/ui_models/application'; 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 { function zippableTxtName(name: string, suffix = ""): string {
const sanitizedName = name const sanitizedName = name
@@ -22,41 +27,26 @@ export class ArchiveManager {
} }
public async downloadBackup(encrypted: boolean) { public async downloadBackup(encrypted: boolean) {
const run = async () => { const intent = encrypted
const intent = encrypted ? EncryptionIntent.FileEncrypted
? EncryptionIntent.FileEncrypted : EncryptionIntent.FileDecrypted;
: EncryptionIntent.FileDecrypted;
const data = await this.application.createBackupFile(intent); const data = await this.application.createBackupFile(intent);
if (!data) { if (!data) {
return; return;
} }
const blobData = new Blob( const blobData = new Blob(
[JSON.stringify(data, null, 2)], [JSON.stringify(data, null, 2)],
{ type: 'text/json' } { 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 { } else {
run(); /** download as zipped plain text files */
this.downloadZippedDecryptedItems(data);
} }
} }

View File

@@ -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 */ /* eslint-disable camelcase */
import { WebApplication } from '@/ui_models/application'; import { WebApplication } from '@/ui_models/application';
// An interface used by the Desktop app to interact with SN // An interface used by the Desktop app to interact with SN
import { isDesktopApplication } from '@/utils'; import { isDesktopApplication } from '@/utils';
import { EncryptionIntent, ApplicationService, ApplicationEvent, removeFromArray } from '@standardnotes/snjs';
import { Bridge } from './bridge'; import { Bridge } from './bridge';
type UpdateObserverCallback = (component: SNComponent) => void type UpdateObserverCallback = (component: SNComponent) => void

View File

@@ -1,13 +1,10 @@
import { isDesktopApplication, isDev } from '@/utils'; import { isDesktopApplication, isDev } from '@/utils';
import pull from 'lodash/pull'; import pull from 'lodash/pull';
import { import {
ProtectedAction,
ApplicationEvent, ApplicationEvent,
SNTag, SNTag,
SNNote, SNNote,
SNUserPrefs,
ContentType, ContentType,
SNSmartTag,
PayloadSource, PayloadSource,
DeinitSource, DeinitSource,
UuidString, UuidString,
@@ -106,7 +103,6 @@ export class AppState {
rootScopeCleanup2: any; rootScopeCleanup2: any;
onVisibilityChange: any; onVisibilityChange: any;
selectedTag?: SNTag; selectedTag?: SNTag;
multiEditorEnabled = false;
showBetaWarning = false; showBetaWarning = false;
readonly actionsMenu = new ActionsMenuState(); readonly actionsMenu = new ActionsMenuState();
readonly sync = new SyncState(); readonly sync = new SyncState();
@@ -210,7 +206,7 @@ export class AppState {
: this.selectedTag.uuid : this.selectedTag.uuid
: undefined; : undefined;
if (!activeEditor || this.multiEditorEnabled) { if (!activeEditor) {
this.application.editorGroup.createEditor( this.application.editorGroup.createEditor(
undefined, undefined,
title, title,
@@ -221,35 +217,25 @@ export class AppState {
} }
} }
async openEditor(noteUuid: string) { async openEditor(noteUuid: string): Promise<void> {
if (this.getActiveEditor()?.note?.uuid === noteUuid) {
return;
}
const note = this.application.findItem(noteUuid) as SNNote; const note = this.application.findItem(noteUuid) as SNNote;
if (this.getActiveEditor()?.note?.uuid === noteUuid) return; if (!note) {
const run = async () => { console.warn('Tried accessing a non-existant note of UUID ' + noteUuid);
return;
}
if (await this.application.authorizeNoteAccess(note)) {
const activeEditor = this.getActiveEditor(); const activeEditor = this.getActiveEditor();
if (!activeEditor || this.multiEditorEnabled) { if (!activeEditor) {
this.application.editorGroup.createEditor(noteUuid); this.application.editorGroup.createEditor(noteUuid);
} else { } else {
activeEditor.setNote(note); activeEditor.setNote(note);
} }
await this.notifyEvent(AppStateEvent.ActiveEditorChanged); 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();
} }
} }

View File

@@ -1,4 +1,3 @@
import { PermissionDialog } from '@standardnotes/snjs';
import { ComponentModalScope } from './../directives/views/componentModal'; import { ComponentModalScope } from './../directives/views/componentModal';
import { AccountSwitcherScope, PermissionsModalScope } from './../types'; import { AccountSwitcherScope, PermissionsModalScope } from './../types';
import { ComponentGroup } from './component_group'; import { ComponentGroup } from './component_group';
@@ -9,7 +8,9 @@ import {
SNApplication, SNApplication,
platformFromString, platformFromString,
Challenge, Challenge,
ProtectedAction, SNComponent SNComponent,
PermissionDialog,
DeinitSource,
} from '@standardnotes/snjs'; } from '@standardnotes/snjs';
import angular from 'angular'; import angular from 'angular';
import { getPlatformString } from '@/utils'; import { getPlatformString } from '@/utils';
@@ -27,17 +28,16 @@ import {
import { AppState } from '@/ui_models/app_state'; import { AppState } from '@/ui_models/app_state';
import { SNWebCrypto } from '@standardnotes/sncrypto-web'; import { SNWebCrypto } from '@standardnotes/sncrypto-web';
import { Bridge } from '@/services/bridge'; import { Bridge } from '@/services/bridge';
import { DeinitSource } from '@standardnotes/snjs';
type WebServices = { type WebServices = {
appState: AppState appState: AppState;
desktopService: DesktopManager desktopService: DesktopManager;
autolockService: AutolockService autolockService: AutolockService;
archiveService: ArchiveManager archiveService: ArchiveManager;
nativeExtService: NativeExtManager nativeExtService: NativeExtManager;
statusManager: StatusManager statusManager: StatusManager;
themeService: ThemeManager themeService: ThemeManager;
keyboardService: KeyboardManager keyboardService: KeyboardManager;
} }
export class WebApplication extends SNApplication { export class WebApplication extends SNApplication {
@@ -78,7 +78,7 @@ export class WebApplication extends SNApplication {
} }
/** @override */ /** @override */
deinit(source: DeinitSource) { deinit(source: DeinitSource): void {
for (const service of Object.values(this.webServices)) { for (const service of Object.values(this.webServices)) {
if ('deinit' in service) { if ('deinit' in service) {
service.deinit?.(source); service.deinit?.(source);
@@ -107,15 +107,15 @@ export class WebApplication extends SNApplication {
this.componentManager!.presentPermissionsDialog = this.presentPermissionsDialog; this.componentManager!.presentPermissionsDialog = this.presentPermissionsDialog;
} }
setWebServices(services: WebServices) { setWebServices(services: WebServices): void {
this.webServices = services; this.webServices = services;
} }
public getAppState() { public getAppState(): AppState {
return this.webServices.appState; return this.webServices.appState;
} }
public getDesktopService() { public getDesktopService(): DesktopManager {
return this.webServices.desktopService; return this.webServices.desktopService;
} }
@@ -170,46 +170,6 @@ export class WebApplication extends SNApplication {
this.applicationElement.append(el); 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!(`
<privileges-auth-modal application='application' action='action' on-success='onSuccess'
on-cancel='onCancel' class='sk-modal'></privileges-auth-modal>
`)(scope);
this.applicationElement.append(el);
this.currentAuthenticationElement = el;
}
presentPrivilegesManagementModal() {
const scope: any = this.scope!.$new(true);
scope.application = this;
const el = this.$compile!("<privileges-management-modal application='application' class='sk-modal'></privileges-management-modal>")(scope);
this.applicationElement.append(el);
}
authenticationInProgress() { authenticationInProgress() {
return this.currentAuthenticationElement != null; return this.currentAuthenticationElement != null;
} }

View File

@@ -12,7 +12,9 @@
| {{ctrl.challenge.subheading}} | {{ctrl.challenge.subheading}}
.sk-panel-section .sk-panel-section
div(ng-repeat="prompt in ctrl.state.prompts track by prompt.id") 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( input.sk-input.contrast(
ng-model="ctrl.state.values[prompt.id].value" ng-model="ctrl.state.values[prompt.id].value"
should-focus="$index == 0" should-focus="$index == 0"
@@ -21,7 +23,17 @@
ng-change="ctrl.onTextValueChange(prompt)" ng-change="ctrl.onTextValueChange(prompt)"
ng-attr-type="{{prompt.secureTextEntry ? 'password' : 'text'}}", ng-attr-type="{{prompt.secureTextEntry ? 'password' : 'text'}}",
ng-attr-placeholder="{{prompt.title}}" 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 .sk-panel-row.centered
label.sk-label.danger( label.sk-label.danger(
ng-if="ctrl.state.values[prompt.id].invalid" ng-if="ctrl.state.values[prompt.id].invalid"

View File

@@ -5,45 +5,46 @@ import {
removeFromArray, removeFromArray,
Challenge, Challenge,
ChallengeReason, ChallengeReason,
ChallengePrompt ChallengePrompt,
ChallengeValidation,
ProtectionSessionDurations,
} from '@standardnotes/snjs'; } from '@standardnotes/snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { WebDirective } from '@/types'; import { WebDirective } from '@/types';
import { confirmDialog } from '@/services/alertService'; import { confirmDialog } from '@/services/alertService';
import { import { STRING_SIGN_OUT_CONFIRMATION } from '@/strings';
STRING_SIGN_OUT_CONFIRMATION,
} from '@/strings';
type InputValue = { type InputValue = {
prompt: ChallengePrompt; prompt: ChallengePrompt;
value: string; value: string | number | boolean;
invalid: boolean; invalid: boolean;
} };
type Values = Record<number, InputValue> type Values = Record<number, InputValue>;
type ChallengeModalState = { type ChallengeModalState = {
prompts: ChallengePrompt[] prompts: ChallengePrompt[];
values: Partial<Values> values: Partial<Values>;
processing: boolean, processing: boolean;
forgotPasscode: boolean, forgotPasscode: boolean;
showForgotPasscodeLink: boolean, showForgotPasscodeLink: boolean;
processingPrompts: ChallengePrompt[], processingPrompts: ChallengePrompt[];
hasAccount: boolean, hasAccount: boolean;
} protectedNoteAccessDuration: number;
};
class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> { class ChallengeModalCtrl extends PureViewCtrl<unknown, ChallengeModalState> {
private $element: JQLite application!: WebApplication;
application!: WebApplication challenge!: Challenge;
challenge!: Challenge
/** @template */
protectionsSessionDurations = ProtectionSessionDurations;
protectionsSessionValidation =
ChallengeValidation.ProtectionSessionDuration;
/* @ngInject */ /* @ngInject */
constructor( constructor(private $element: JQLite, $timeout: ng.ITimeoutService) {
$element: JQLite,
$timeout: ng.ITimeoutService
) {
super($timeout); super($timeout);
this.$element = $element;
} }
getState() { getState() {
@@ -57,13 +58,13 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> {
for (const prompt of prompts) { for (const prompt of prompts) {
values[prompt.id] = { values[prompt.id] = {
prompt, prompt,
value: '', value: prompt.initialValue ?? '',
invalid: false invalid: false,
}; };
} }
const showForgotPasscodeLink = [ const showForgotPasscodeLink = [
ChallengeReason.ApplicationUnlock, ChallengeReason.ApplicationUnlock,
ChallengeReason.Migration ChallengeReason.Migration,
].includes(this.challenge.reason); ].includes(this.challenge.reason);
this.setState({ this.setState({
prompts, prompts,
@@ -72,34 +73,32 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> {
forgotPasscode: false, forgotPasscode: false,
showForgotPasscodeLink, showForgotPasscodeLink,
hasAccount: this.application.hasAccount(), hasAccount: this.application.hasAccount(),
processingPrompts: [] processingPrompts: [],
protectedNoteAccessDuration: ProtectionSessionDurations[0].valueInSeconds,
}); });
this.application.addChallengeObserver( this.application.addChallengeObserver(this.challenge, {
this.challenge, onValidValue: (value) => {
{ this.getState().values[value.prompt.id]!.invalid = false;
onValidValue: (value) => { removeFromArray(this.state.processingPrompts, value.prompt);
this.getState().values[value.prompt.id]!.invalid = false; 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); removeFromArray(this.state.processingPrompts, value.prompt);
this.reloadProcessingStatus(); this.reloadProcessingStatus();
}, }
onInvalidValue: (value) => { },
this.getState().values[value.prompt.id]!.invalid = true; onComplete: () => {
/** If custom validation, treat all values together and not individually */ this.dismiss();
if (!value.prompt.validates) { },
this.setState({ processingPrompts: [], processing: false }); onCancel: () => {
} else { this.dismiss();
removeFromArray(this.state.processingPrompts, value.prompt); },
this.reloadProcessingStatus(); });
}
},
onComplete: () => {
this.dismiss();
},
onCancel: () => {
this.dismiss();
},
}
);
} }
deinit() { deinit() {
@@ -110,18 +109,20 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> {
reloadProcessingStatus() { reloadProcessingStatus() {
return this.setState({ return this.setState({
processing: this.state.processingPrompts.length > 0 processing: this.state.processingPrompts.length > 0,
}); });
} }
async destroyLocalData() { async destroyLocalData() {
if (await confirmDialog({ if (
text: STRING_SIGN_OUT_CONFIRMATION, await confirmDialog({
confirmButtonStyle: "danger" text: STRING_SIGN_OUT_CONFIRMATION,
})) { confirmButtonStyle: 'danger',
})
) {
await this.application.signOut(); await this.application.signOut();
this.dismiss(); this.dismiss();
}; }
} }
/** @template */ /** @template */
@@ -133,7 +134,7 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> {
onForgotPasscodeClick() { onForgotPasscodeClick() {
this.setState({ this.setState({
forgotPasscode: true forgotPasscode: true,
}); });
} }
@@ -143,15 +144,22 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> {
this.setState({ values }); this.setState({ values });
} }
onValueChange(prompt: ChallengePrompt, value: number) {
const values = this.state.values;
values[prompt.id]!.invalid = false;
values[prompt.id]!.value = value;
}
validate() { validate() {
const failed = []; let failed = 0;
for (const prompt of this.getState().prompts) { for (const prompt of this.state.prompts) {
const value = this.getState().values[prompt.id]; const value = this.state.values[prompt.id]!;
if (!value || value.value.length === 0) { if (typeof value.value === 'string' && value.value.length === 0) {
this.getState().values[prompt.id]!.invalid = true; this.state.values[prompt.id]!.invalid = true;
failed++;
} }
} }
return failed.length === 0; return failed === 0;
} }
async submit() { async submit() {
@@ -161,15 +169,15 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> {
await this.setState({ processing: true }); await this.setState({ processing: true });
const values: ChallengeValue[] = []; const values: ChallengeValue[] = [];
for (const inputValue of Object.values(this.getState().values)) { for (const inputValue of Object.values(this.getState().values)) {
const rawValue = inputValue!!.value; const rawValue = inputValue!.value;
const value = new ChallengeValue(inputValue!.prompt, rawValue); const value = new ChallengeValue(inputValue!.prompt, rawValue);
values.push(value); values.push(value);
} }
const processingPrompts = values.map((v) => v.prompt); const processingPrompts = values.map((v) => v.prompt);
await this.setState({ await this.setState({
processingPrompts: processingPrompts, processingPrompts: processingPrompts,
processing: processingPrompts.length > 0 processing: processingPrompts.length > 0,
}) });
/** /**
* Unfortunately neccessary to wait 50ms so that the above setState call completely * 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 * updates the UI to change processing state, before we enter into UI blocking operation
@@ -181,7 +189,7 @@ class ChallengeModalCtrl extends PureViewCtrl<{}, ChallengeModalState> {
} else { } else {
this.setState({ processing: false }); this.setState({ processing: false });
} }
}, 50) }, 50);
} }
dismiss() { dismiss() {
@@ -202,7 +210,7 @@ export class ChallengeModal extends WebDirective {
this.bindToController = true; this.bindToController = true;
this.scope = { this.scope = {
challenge: '=', challenge: '=',
application: '=' application: '=',
}; };
} }
} }

View File

@@ -80,8 +80,7 @@
) )
menu-row( menu-row(
action='self.selectedMenuItem(true); self.toggleProtectNote()' action='self.selectedMenuItem(true); self.toggleProtectNote()'
desc=`'Protecting a note will require credentials to view desc=`'Protecting a note will require credentials to view it'`,
it (Manage Privileges via Account menu)'`,
label="self.note.protected ? 'Unprotect' : 'Protect'" label="self.note.protected ? 'Unprotect' : 'Protect'"
) )
menu-row( menu-row(

View File

@@ -8,7 +8,6 @@ import {
isPayloadSourceRetrieved, isPayloadSourceRetrieved,
isPayloadSourceInternalChange, isPayloadSourceInternalChange,
ContentType, ContentType,
ProtectedAction,
SNComponent, SNComponent,
SNNote, SNNote,
SNTag, SNTag,
@@ -24,7 +23,7 @@ import { isDesktopApplication } from '@/utils';
import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager'; import { KeyboardModifier, KeyboardKey } from '@/services/keyboardManager';
import template from './editor-view.pug'; import template from './editor-view.pug';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { AppStateEvent, EventSource } from '@/ui_models/app_state'; import { EventSource } from '@/ui_models/app_state';
import { import {
STRING_DELETED_NOTE, STRING_DELETED_NOTE,
STRING_INVALID_NOTE, STRING_INVALID_NOTE,
@@ -85,7 +84,7 @@ type EditorState = {
* then re-initialized. Used when reloading spellcheck status. */ * then re-initialized. Used when reloading spellcheck status. */
textareaUnloading: boolean textareaUnloading: boolean
/** Fields that can be directly mutated by the template */ /** Fields that can be directly mutated by the template */
mutable: {} mutable: any
} }
type EditorValues = { type EditorValues = {
@@ -98,7 +97,7 @@ function sortAlphabetically(array: SNComponent[]): SNComponent[] {
return array.sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1); return array.sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1);
} }
class EditorViewCtrl extends PureViewCtrl<{}, EditorState> { class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
/** Passed through template */ /** Passed through template */
readonly application!: WebApplication readonly application!: WebApplication
readonly editor!: Editor readonly editor!: Editor
@@ -248,7 +247,7 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
case ApplicationEvent.HighLatencySync: case ApplicationEvent.HighLatencySync:
this.setState({ syncTakingTooLong: true }); this.setState({ syncTakingTooLong: true });
break; break;
case ApplicationEvent.CompletedFullSync: case ApplicationEvent.CompletedFullSync: {
this.setState({ syncTakingTooLong: false }); this.setState({ syncTakingTooLong: false });
const isInErrorState = this.state.saveError; const isInErrorState = this.state.saveError;
/** if we're still dirty, don't change status, a sync is likely upcoming. */ /** 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(); this.showAllChangesSavedStatus();
} }
break; break;
}
case ApplicationEvent.FailedSync: case ApplicationEvent.FailedSync:
/** /**
* Only show error status in editor if the note is dirty. * Only show error status in editor if the note is dirty.
@@ -601,10 +601,12 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
this.setMenuState('showOptionsMenu', false); this.setMenuState('showOptionsMenu', false);
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function
onTitleFocus() { onTitleFocus() {
} }
// eslint-disable-next-line @typescript-eslint/no-empty-function
onTitleBlur() { onTitleBlur() {
} }
@@ -627,50 +629,35 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
); );
return; return;
} }
const run = async () => { if (this.note.locked) {
if (this.note.locked) { this.application.alertService!.alert(
this.application.alertService!.alert( STRING_DELETE_LOCKED_ATTEMPT
STRING_DELETE_LOCKED_ATTEMPT
);
return;
}
const title = this.note.safeTitle().length
? `'${this.note.title}'`
: "this note";
const text = StringDeleteNote(
title,
permanently
); );
if (await confirmDialog({ return;
text, }
confirmButtonStyle: 'danger' const title = this.note.safeTitle().length
})) { ? `'${this.note.title}'`
if (permanently) { : "this note";
this.performNoteDeletion(this.note); const text = StringDeleteNote(
} else { title,
this.saveNote( permanently
true,
false,
true,
(mutator) => {
mutator.trashed = true;
}
);
}
};
};
const requiresPrivilege = await this.application.privilegesService!.actionRequiresPrivilege(
ProtectedAction.DeleteNote
); );
if (requiresPrivilege) { if (await confirmDialog({
this.application.presentPrivilegesModal( text,
ProtectedAction.DeleteNote, confirmButtonStyle: 'danger'
() => { })) {
run(); if (permanently) {
} this.performNoteDeletion(this.note);
); } else {
} else { this.saveNote(
run(); true,
false,
true,
(mutator) => {
mutator.trashed = true;
}
);
}
} }
} }
@@ -740,14 +727,6 @@ class EditorViewCtrl extends PureViewCtrl<{}, EditorState> {
mutator.protected = !this.note.protected 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() { toggleNotePreview() {

View File

@@ -5,7 +5,6 @@ import { dateToLocalizedString, preventRefreshing } from '@/utils';
import { import {
ApplicationEvent, ApplicationEvent,
SyncQueueStrategy, SyncQueueStrategy,
ProtectedAction,
ContentType, ContentType,
SNComponent, SNComponent,
SNTheme, SNTheme,
@@ -44,7 +43,7 @@ type DockShortcut = {
} }
} }
class FooterViewCtrl extends PureViewCtrl<{}, { class FooterViewCtrl extends PureViewCtrl<unknown, {
outOfSync: boolean; outOfSync: boolean;
hasPasscode: boolean; hasPasscode: boolean;
dataUpgradeAvailable: boolean; dataUpgradeAvailable: boolean;
@@ -207,7 +206,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
case AppStateEvent.BeganBackupDownload: case AppStateEvent.BeganBackupDownload:
statusService.setMessage("Saving local backup…"); statusService.setMessage("Saving local backup…");
break; break;
case AppStateEvent.EndedBackupDownload: case AppStateEvent.EndedBackupDownload: {
const successMessage = "Successfully saved backup."; const successMessage = "Successfully saved backup.";
const errorMessage = "Unable to save local backup." const errorMessage = "Unable to save local backup."
statusService.setMessage(data.success ? successMessage : errorMessage); statusService.setMessage(data.success ? successMessage : errorMessage);
@@ -222,6 +221,7 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} }
}, twoSeconds); }, twoSeconds);
break; break;
}
} }
} }
@@ -544,28 +544,9 @@ class FooterViewCtrl extends PureViewCtrl<{}, {
} }
async selectRoom(room: SNComponent) { async selectRoom(room: SNComponent) {
const run = () => { this.$timeout(() => {
this.$timeout(() => { this.roomShowState[room.uuid] = !this.roomShowState[room.uuid];
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();
}
} }
displayBetaDialog() { displayBetaDialog() {

View File

@@ -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 { #item-preview-modal {
> .sk-modal-content { > .sk-modal-content {
width: 800px; width: 800px;

View File

@@ -158,10 +158,6 @@
ng-click="self.openSessionsModal()" ng-click="self.openSessionsModal()"
ng-if="self.state.showSessions" ng-if="self.state.showSessions"
) Manage Sessions ) 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
.sk-panel-section-title Encryption .sk-panel-section-title Encryption
.sk-panel-section-subtitle.info(ng-if='self.state.encryptionEnabled') .sk-panel-section-subtitle.info(ng-if='self.state.encryptionEnabled')
@@ -226,10 +222,6 @@
| {{option.label}} | {{option.label}}
.sk-p The autolock timer begins when the window or tab loses focus. .sk-p The autolock timer begins when the window or tab loses focus.
.sk-panel-row .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( a.sk-a.info.sk-panel-row.condensed(
ng-click='self.changePasscodePressed()' ng-click='self.changePasscodePressed()'
) Change Passcode ) Change Passcode

View File

@@ -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

View File

@@ -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.

View File

@@ -30,8 +30,8 @@
"@types/mocha": "^7.0.2", "@types/mocha": "^7.0.2",
"@types/pug": "^2.0.4", "@types/pug": "^2.0.4",
"@types/react": "^17.0.0", "@types/react": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^2.23.0", "@typescript-eslint/eslint-plugin": "^3.10.1",
"@typescript-eslint/parser": "^2.23.0", "@typescript-eslint/parser": "^3.10.1",
"angular": "^1.8.2", "angular": "^1.8.2",
"apply-loader": "^2.0.0", "apply-loader": "^2.0.0",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
@@ -71,9 +71,12 @@
"@reach/alert-dialog": "^0.12.1", "@reach/alert-dialog": "^0.12.1",
"@reach/dialog": "^0.12.1", "@reach/dialog": "^0.12.1",
"@standardnotes/sncrypto-web": "^1.2.9", "@standardnotes/sncrypto-web": "^1.2.9",
"@standardnotes/snjs": "^2.0.38", "@standardnotes/snjs": "^2.0.41",
"babel-loader": "^8.2.2", "babel-loader": "^8.2.2",
"mobx": "^6.0.4", "mobx": "^6.0.4",
"preact": "^10.5.7" "preact": "^10.5.7"
},
"peerDependencies": {
"react": "^16.8.0"
} }
} }

View File

@@ -1045,10 +1045,10 @@
"@standardnotes/sncrypto-common" "^1.2.7" "@standardnotes/sncrypto-common" "^1.2.7"
libsodium-wrappers "^0.7.8" libsodium-wrappers "^0.7.8"
"@standardnotes/snjs@^2.0.38": "@standardnotes/snjs@^2.0.41":
version "2.0.38" version "2.0.41"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.38.tgz#3edbdc6c04926f2f4940aea8ebb6c7007f6d4235" resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.41.tgz#131f3a206b220be5359c43963c47b1929b92f037"
integrity sha512-cr+jdtDOyrwKlad/SRCy/D/cIsEQdShEsuazSIajpvXhwo9ixkj/wv/o0r3GrHug6CgXLOQRgzuZ9eZvi0PM9g== integrity sha512-/6hoBcb/Ib8voqwk1YLVTAM6L2ZJV/AZDXJz/oEKY9cjO0onFMbl0T+AAWEal1d6zojyjC4Wv6Pc9fkNInvBSQ==
dependencies: dependencies:
"@standardnotes/sncrypto-common" "^1.2.9" "@standardnotes/sncrypto-common" "^1.2.9"
@@ -1188,49 +1188,66 @@
"@types/webpack-sources" "*" "@types/webpack-sources" "*"
source-map "^0.6.0" source-map "^0.6.0"
"@typescript-eslint/eslint-plugin@^2.23.0": "@typescript-eslint/eslint-plugin@^3.10.1":
version "2.34.0" version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz#7e061338a1383f59edc204c605899f93dc2e2c8f"
integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== integrity sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ==
dependencies: 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" functional-red-black-tree "^1.0.1"
regexpp "^3.0.0" regexpp "^3.0.0"
semver "^7.3.2"
tsutils "^3.17.1" tsutils "^3.17.1"
"@typescript-eslint/experimental-utils@2.34.0": "@typescript-eslint/experimental-utils@3.10.1":
version "2.34.0" version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz#e179ffc81a80ebcae2ea04e0332f8b251345a686"
integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== integrity sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==
dependencies: dependencies:
"@types/json-schema" "^7.0.3" "@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-scope "^5.0.0"
eslint-utils "^2.0.0" eslint-utils "^2.0.0"
"@typescript-eslint/parser@^2.23.0": "@typescript-eslint/parser@^3.10.1":
version "2.34.0" version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-3.10.1.tgz#1883858e83e8b442627e1ac6f408925211155467"
integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== integrity sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==
dependencies: dependencies:
"@types/eslint-visitor-keys" "^1.0.0" "@types/eslint-visitor-keys" "^1.0.0"
"@typescript-eslint/experimental-utils" "2.34.0" "@typescript-eslint/experimental-utils" "3.10.1"
"@typescript-eslint/typescript-estree" "2.34.0" "@typescript-eslint/types" "3.10.1"
"@typescript-eslint/typescript-estree" "3.10.1"
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
"@typescript-eslint/typescript-estree@2.34.0": "@typescript-eslint/types@3.10.1":
version "2.34.0" version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== 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: dependencies:
"@typescript-eslint/types" "3.10.1"
"@typescript-eslint/visitor-keys" "3.10.1"
debug "^4.1.1" debug "^4.1.1"
eslint-visitor-keys "^1.1.0"
glob "^7.1.6" glob "^7.1.6"
is-glob "^4.0.1" is-glob "^4.0.1"
lodash "^4.17.15" lodash "^4.17.15"
semver "^7.3.2" semver "^7.3.2"
tsutils "^3.17.1" 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": "@webassemblyjs/ast@1.9.0":
version "1.9.0" version "1.9.0"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"