feat: clear protection session

This commit is contained in:
Baptiste Grob
2021-02-15 15:36:36 +01:00
parent 42282d1bde
commit badff1568d
5 changed files with 187 additions and 109 deletions

View File

@@ -1,5 +1,5 @@
import { WebDirective } from './../../types';
import { isDesktopApplication, preventRefreshing } from '@/utils';
import { isDesktopApplication, isSameDay, preventRefreshing } from '@/utils';
import template from '%/directives/account-menu.pug';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import {
@@ -18,17 +18,22 @@ import {
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE,
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL,
STRING_UNSUPPORTED_BACKUP_FILE_VERSION,
Strings
Strings,
} from '@/strings';
import { PasswordWizardType } from '@/types';
import { BackupFile, ContentType, Platform } from '@standardnotes/snjs';
import {
ApplicationEvent,
BackupFile,
ContentType,
Platform,
} from '@standardnotes/snjs';
import { confirmDialog, alertDialog } from '@/services/alertService';
import { autorun, IReactionDisposer } from 'mobx';
import { storage, StorageKey } from '@/services/localStorage';
import {
disableErrorReporting,
enableErrorReporting,
errorReportingId
errorReportingId,
} from '@/services/errorReporting';
const ELEMENT_NAME_AUTH_EMAIL = 'email';
@@ -52,7 +57,7 @@ type FormData = {
passcode: string;
confirmPasscode: string;
changingPasscode: boolean;
}
};
type AccountMenuState = {
formData: Partial<FormData>;
@@ -72,21 +77,19 @@ type AccountMenuState = {
showSessions: boolean;
errorReportingId: string | null;
keyStorageInfo: string | null;
}
protectionsDisabledUntil: string | null;
};
class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
public appVersion: string
public appVersion: string;
/** @template */
private closeFunction?: () => void
private removeBetaWarningListener?: IReactionDisposer
private removeSyncObserver?: IReactionDisposer
private closeFunction?: () => void;
private removeBetaWarningListener?: IReactionDisposer;
private removeSyncObserver?: IReactionDisposer;
private removeProtectionLengthObserver?: () => void;
/* @ngInject */
constructor(
$timeout: ng.ITimeoutService,
appVersion: string,
) {
constructor($timeout: ng.ITimeoutService, appVersion: string) {
super($timeout);
this.appVersion = appVersion;
}
@@ -95,7 +98,9 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
getInitialState() {
return {
appVersion: 'v' + ((window as any).electronAppVersion || this.appVersion),
passcodeAutoLockOptions: this.application.getAutolockService().getAutoLockIntervalOptions(),
passcodeAutoLockOptions: this.application
.getAutolockService()
.getAutoLockIntervalOptions(),
user: this.application.getUser(),
formData: {
mergeLocal: true,
@@ -103,12 +108,14 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
},
mutable: {},
showBetaWarning: false,
errorReportingEnabled: storage.get(StorageKey.DisableErrorReporting) === false,
errorReportingEnabled:
storage.get(StorageKey.DisableErrorReporting) === false,
showSessions: false,
errorReportingId: errorReportingId(),
keyStorageInfo: Strings.keyStorageInfo(this.application),
importData: null,
syncInProgress: false,
protectionsDisabledUntil: this.getProtectionsDisabledUntil(),
};
}
@@ -134,14 +141,16 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
user: this.application.getUser(),
canAddPasscode: !this.application.isEphemeralSession(),
hasPasscode: this.application.hasPasscode(),
showPasscodeForm: false
showPasscodeForm: false,
};
}
async $onInit() {
super.$onInit();
this.setState({
showSessions: this.appState.enableUnfinishedFeatures && await this.application.userCanManageSessions()
showSessions:
this.appState.enableUnfinishedFeatures &&
(await this.application.userCanManageSessions()),
});
const sync = this.appState.sync;
@@ -153,14 +162,24 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
});
this.removeBetaWarningListener = autorun(() => {
this.setState({
showBetaWarning: this.appState.showBetaWarning
showBetaWarning: this.appState.showBetaWarning,
});
});
this.removeProtectionLengthObserver = this.application.addEventObserver(
async () => {
this.setState({
protectionsDisabledUntil: this.getProtectionsDisabledUntil(),
});
},
ApplicationEvent.ProtectionSessionExpiryDateChanged
);
}
deinit() {
this.removeSyncObserver?.();
this.removeBetaWarningListener?.();
this.removeProtectionLengthObserver?.();
super.deinit();
}
@@ -170,17 +189,46 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
});
}
private getProtectionsDisabledUntil(): string | null {
const protectionExpiry = this.application.getProtectionSessionExpiryDate();
const now = new Date();
if (protectionExpiry > now) {
let f: Intl.DateTimeFormat;
if (isSameDay(protectionExpiry, now)) {
f = new Intl.DateTimeFormat(undefined, {
hour: 'numeric',
minute: 'numeric',
});
} else {
f = new Intl.DateTimeFormat(undefined, {
weekday: 'long',
day: 'numeric',
month: 'short',
hour: 'numeric',
minute: 'numeric',
});
}
return f.format(protectionExpiry);
}
return null;
}
async loadHost() {
const host = await this.application!.getHost();
const host = await this.application.getHost();
this.setState({
server: host,
formData: {
...this.getState().formData,
url: host
}
url: host,
},
});
}
enableProtections() {
this.application.clearProtectionSession();
}
onHostInputChange() {
const url = this.getState().formData.url!;
this.application!.setHost(url);
@@ -200,8 +248,8 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
encryptionEnabled,
mutable: {
...this.getState().mutable,
backupEncrypted: encryptionEnabled
}
backupEncrypted: encryptionEnabled,
},
});
}
@@ -213,7 +261,7 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
const names = [
ELEMENT_NAME_AUTH_EMAIL,
ELEMENT_NAME_AUTH_PASSWORD,
ELEMENT_NAME_AUTH_PASSWORD_CONF
ELEMENT_NAME_AUTH_PASSWORD_CONF,
];
for (const name of names) {
const element = document.getElementsByName(name)[0];
@@ -224,7 +272,10 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
}
submitAuthForm() {
if (!this.getState().formData.email || !this.getState().formData.user_password) {
if (
!this.getState().formData.email ||
!this.getState().formData.user_password
) {
return;
}
this.blurAuthFields();
@@ -239,15 +290,15 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
return this.setState({
formData: {
...this.getState().formData,
...formData
}
...formData,
},
});
}
async login() {
await this.setFormDataState({
status: STRING_GENERATING_LOGIN_KEYS,
authenticating: true
authenticating: true,
});
const formData = this.getState().formData;
const response = await this.application!.signIn(
@@ -261,7 +312,7 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
if (!error) {
await this.setFormDataState({
authenticating: false,
user_password: undefined
user_password: undefined,
});
this.close();
return;
@@ -269,28 +320,26 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
await this.setFormDataState({
showLogin: true,
status: undefined,
user_password: undefined
user_password: undefined,
});
if (error.message) {
this.application!.alertService!.alert(error.message);
}
await this.setFormDataState({
authenticating: false
authenticating: false,
});
}
async register() {
const confirmation = this.getState().formData.password_conf;
if (confirmation !== this.getState().formData.user_password) {
this.application!.alertService!.alert(
STRING_NON_MATCHING_PASSWORDS
);
this.application!.alertService!.alert(STRING_NON_MATCHING_PASSWORDS);
return;
}
await this.setFormDataState({
confirmPassword: false,
status: STRING_GENERATING_REGISTER_KEYS,
authenticating: true
authenticating: true,
});
const response = await this.application!.register(
this.getState().formData.email!,
@@ -301,14 +350,12 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
const error = response.error;
if (error) {
await this.setFormDataState({
status: undefined
status: undefined,
});
await this.setFormDataState({
authenticating: false
authenticating: false,
});
this.application!.alertService!.alert(
error.message
);
this.application!.alertService!.alert(error.message);
} else {
await this.setFormDataState({ authenticating: false });
this.close();
@@ -320,8 +367,8 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
this.setFormDataState({
mergeLocal: !(await confirmDialog({
text: STRING_ACCOUNT_MENU_UNCHECK_MERGE,
confirmButtonStyle: 'danger'
}))
confirmButtonStyle: 'danger',
})),
});
}
}
@@ -337,17 +384,19 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
}
async destroyLocalData() {
if (await confirmDialog({
if (
await confirmDialog({
text: STRING_SIGN_OUT_CONFIRMATION,
confirmButtonStyle: "danger"
})) {
confirmButtonStyle: 'danger',
})
) {
this.application.signOut();
}
}
showRegister() {
this.setFormDataState({
showRegister: true
showRegister: true,
});
}
@@ -359,9 +408,7 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
const data = JSON.parse(e.target!.result as string);
resolve(data);
} catch (e) {
this.application!.alertService!.alert(
STRING_INVALID_IMPORT_FILE
);
this.application!.alertService!.alert(STRING_INVALID_IMPORT_FILE);
}
};
reader.readAsText(file);
@@ -378,7 +425,8 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
return;
}
if (data.version || data.auth_params || data.keyParams) {
const version = data.version || data.keyParams?.version || data.auth_params?.version;
const version =
data.version || data.keyParams?.version || data.auth_params?.version;
if (
this.application.protocolService.supportedVersions().includes(version)
) {
@@ -396,52 +444,50 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
await this.setState({
importData: {
...this.getState().importData,
loading: true
}
loading: true,
},
});
const result = await this.application.importData(data);
this.setState({
importData: null
importData: null,
});
if (!result) {
return;
} else if ('error' in result) {
void alertDialog({
text: result.error
text: result.error,
});
} else if (result.errorCount) {
void alertDialog({
text: StringImportError(result.errorCount)
text: StringImportError(result.errorCount),
});
} else {
void alertDialog({
text: STRING_IMPORT_SUCCESS
text: STRING_IMPORT_SUCCESS,
});
}
}
async downloadDataArchive() {
this.application.getArchiveService().downloadBackup(this.getState().mutable.backupEncrypted);
this.application
.getArchiveService()
.downloadBackup(this.getState().mutable.backupEncrypted);
}
notesAndTagsCount() {
return this.application.getItems(
[
ContentType.Note,
ContentType.Tag
]
).length;
return this.application.getItems([ContentType.Note, ContentType.Tag])
.length;
}
encryptionStatusForNotes() {
const length = this.notesAndTagsCount();
return length + "/" + length + " notes and tags encrypted";
return length + '/' + length + ' notes and tags encrypted';
}
async reloadAutoLockInterval() {
const interval = await this.application!.getAutolockService().getAutoLockInterval();
this.setState({
selectedAutoLockInterval: interval
selectedAutoLockInterval: interval,
});
}
@@ -458,7 +504,7 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
showLogin: false,
showRegister: false,
user_password: undefined,
password_conf: undefined
password_conf: undefined,
});
}
@@ -468,30 +514,31 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
addPasscodeClicked() {
this.setFormDataState({
showPasscodeForm: true
showPasscodeForm: true,
});
}
async submitPasscodeForm() {
const passcode = this.getState().formData.passcode!;
if (passcode !== this.getState().formData.confirmPasscode!) {
this.application!.alertService!.alert(
STRING_NON_MATCHING_PASSCODES
);
this.application!.alertService!.alert(STRING_NON_MATCHING_PASSCODES);
return;
}
await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE, async () => {
await preventRefreshing(
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE,
async () => {
if (this.application!.hasPasscode()) {
await this.application!.changePasscode(passcode);
} else {
await this.application!.setPasscode(passcode);
}
});
}
);
this.setFormDataState({
passcode: undefined,
confirmPasscode: undefined,
showPasscodeForm: false
showPasscodeForm: false,
});
this.refreshEncryptionStatus();
}
@@ -502,13 +549,18 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
}
async removePasscodePressed() {
await preventRefreshing(STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL, async () => {
await preventRefreshing(
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL,
async () => {
if (await this.application!.removePasscode()) {
await this.application.getAutolockService().deleteAutolockPreference();
await this.application
.getAutolockService()
.deleteAutolockPreference();
await this.reloadAutoLockInterval();
this.refreshEncryptionStatus();
}
});
}
);
}
openErrorReportingDialog() {
@@ -526,7 +578,7 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
anonymized. We use error reports to be alerted when something in our
code is causing unexpected errors and crashes in your application
experience.
`
`,
});
}
@@ -556,7 +608,7 @@ export class AccountMenu extends WebDirective {
this.bindToController = true;
this.scope = {
closeFunction: '&',
application: '='
application: '=',
};
}
}

View File

@@ -1,9 +1,9 @@
import { Platform, platformFromString } from "@standardnotes/snjs";
import { Platform, platformFromString } from '@standardnotes/snjs';
declare const process : {
declare const process: {
env: {
NODE_ENV: string | null | undefined
}
NODE_ENV: string | null | undefined;
};
};
export const isDev = process.env.NODE_ENV === 'development';
@@ -36,11 +36,10 @@ let sharedDateFormatter: Intl.DateTimeFormat;
export function dateToLocalizedString(date: Date) {
if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
if (!sharedDateFormatter) {
const locale = (
(navigator.languages && navigator.languages.length)
const locale =
navigator.languages && navigator.languages.length
? navigator.languages[0]
: navigator.language
);
: navigator.language;
sharedDateFormatter = new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'numeric',
@@ -58,8 +57,21 @@ export function dateToLocalizedString(date: Date) {
}
}
export function isSameDay(dateA: Date, dateB: Date): boolean {
return (
dateA.getFullYear() === dateB.getFullYear() &&
dateA.getMonth() === dateB.getMonth() &&
dateA.getDate() === dateB.getDate()
);
}
/** Via https://davidwalsh.name/javascript-debounce-function */
export function debounce(this: any, func: any, wait: number, immediate = false) {
export function debounce(
this: any,
func: any,
wait: number,
immediate = false
) {
let timeout: any;
return () => {
// eslint-disable-next-line @typescript-eslint/no-this-alias
@@ -131,7 +143,7 @@ if (!Array.prototype.includes) {
// 8. Return false
return false;
}
},
});
}
@@ -153,7 +165,9 @@ declare const __WEB__: boolean;
declare const __DESKTOP__: boolean;
if (!__WEB__ && !__DESKTOP__) {
throw Error('Neither __WEB__ nor __DESKTOP__ is true. Check your configuration files.');
throw Error(
'Neither __WEB__ nor __DESKTOP__ is true. Check your configuration files.'
);
}
export function isDesktopApplication() {

View File

@@ -164,6 +164,19 @@
| {{self.encryptionStatusForNotes()}}
p.sk-p
| {{self.state.encryptionStatusString}}
.sk-panel-section
.sk-panel-section-title Protections
.sk-panel-section-subtitle.info(ng-if="self.state.protectionsDisabledUntil")
| Protections are disabled until {{self.state.protectionsDisabledUntil}}
.sk-panel-section-subtitle.info(ng-if="!self.state.protectionsDisabledUntil")
| Protections are enabled
p.sk-p
| Actions like viewing protected notes, exporting decrypted backups,
| or revoking an active session, require additional authentication
| like entering your account password or application passcode.
.sk-panel-row(ng-if="self.state.protectionsDisabledUntil")
button.sk-button.info(ng-click="self.enableProtections()")
span.sk-label Enable protections
.sk-panel-section
.sk-panel-section-title Passcode Lock
div(ng-if='!self.state.hasPasscode')
@@ -206,8 +219,7 @@
ng-click='self.state.formData.showPasscodeForm = false'
) Cancel
div(ng-if='self.state.hasPasscode && !self.state.formData.showPasscodeForm')
.sk-p
| Passcode lock is enabled.
.sk-panel-section-subtitle.info Passcode lock is enabled
.sk-notification.contrast
.sk-notification-title Options
.sk-notification-text
@@ -273,7 +285,7 @@
.sk-panel-section
.sk-panel-section-title Error Reporting
.sk-panel-section-subtitle.info
| Automatic error reporting is {{ self.state.errorReportingEnabled ? 'enabled.' : 'disabled.' }}
| Automatic error reporting is {{ self.state.errorReportingEnabled ? 'enabled' : 'disabled' }}
p.sk-p
| Help us improve Standard Notes by automatically submitting
| anonymized error reports.

View File

@@ -71,7 +71,7 @@
"@reach/alert-dialog": "^0.13.0",
"@reach/dialog": "^0.13.0",
"@standardnotes/sncrypto-web": "^1.2.10",
"@standardnotes/snjs": "^2.0.53",
"@standardnotes/snjs": "^2.0.54",
"mobx": "^6.1.6",
"preact": "^10.5.12"
}

View File

@@ -1845,10 +1845,10 @@
"@standardnotes/sncrypto-common" "^1.2.7"
libsodium-wrappers "^0.7.8"
"@standardnotes/snjs@^2.0.53":
version "2.0.53"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.53.tgz#ec89668fef57bf154dc0e145e4bc883cbe6be241"
integrity sha512-9mlSitWXCBnQtMwhHMIV6/BaLIUXzWuQIMKkqWn43XYojnF33avEJteu0ciffZMAW9A2S7ORerRBVBbJxlqtdg==
"@standardnotes/snjs@^2.0.54":
version "2.0.54"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.54.tgz#dab1dbf0405c2671aa73e4dbb944863bd434f629"
integrity sha512-q1FErsVthiLpOarpKohoZhvXb8BGvgCBpXzbDZ64/15L2lErFUTYbXxyklBTy5DCbULwUh7wBvkm74JF10Ghlg==
dependencies:
"@standardnotes/sncrypto-common" "^1.2.9"