feat: implement SNJS backup file password retrieval

This commit is contained in:
Baptiste Grob
2021-01-28 15:28:55 +01:00
parent 1576da01c4
commit d4f02a124d
7 changed files with 74 additions and 113 deletions

View File

@@ -342,13 +342,6 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
}
}
async submitImportPassword() {
await this.performImport(
this.getState().importData.data,
this.getState().importData.password
);
}
showRegister() {
this.setFormDataState({
showRegister: true
@@ -384,73 +377,52 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
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)
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);
}
await this.performImport(data);
} else {
await this.performImport(data, undefined);
await this.setState({ importData: null });
void alertDialog({ text: STRING_UNSUPPORTED_BACKUP_FILE_VERSION });
}
} else {
await this.performImport(data, undefined);
await this.performImport(data);
}
}
async performImport(data: BackupFile, password?: string) {
if (!(await this.application.authorizeFileImport())) {
return;
}
async performImport(data: BackupFile) {
await this.setState({
importData: {
...this.getState().importData,
loading: true
}
});
const result = await this.application!.importData(
data,
password
);
const result = await this.application.importData(data);
this.setState({
importData: null
});
if ('error' in result) {
this.application!.alertService!.alert(
result.error
);
if (!result) {
return;
} else if ('error' in result) {
void alertDialog({
text: result.error
});
} else if (result.errorCount) {
const message = StringImportError(result.errorCount);
this.application!.alertService!.alert(
message
);
void alertDialog({
text: StringImportError(result.errorCount)
});
} else {
this.application!.alertService!.alert(
STRING_IMPORT_SUCCESS
);
void alertDialog({
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(
return this.application.getItems(
[
ContentType.Note,
ContentType.Tag

View File

@@ -10,7 +10,6 @@ import {
UuidString,
SyncOpStatus,
PrefKey,
Challenge,
} from '@standardnotes/snjs';
import { WebApplication } from '@/ui_models/application';
import { Editor } from '@/ui_models/editor';
@@ -28,6 +27,11 @@ export enum AppStateEvent {
WindowDidBlur,
}
export type PanelResizedData = {
panel: string;
collapsed: boolean;
}
export enum EventSource {
UserInteraction,
Script,
@@ -392,10 +396,11 @@ export class AppState {
}
panelDidResize(name: string, collapsed: boolean) {
this.notifyEvent(AppStateEvent.PanelResized, {
const data: PanelResizedData = {
panel: name,
collapsed: collapsed,
});
};
this.notifyEvent(AppStateEvent.PanelResized, data);
}
editorDidFocus(eventSource: EventSource) {

View File

@@ -28,7 +28,7 @@
application='self.application'
)
challenge-modal(
ng-repeat="challenge in self.state.challenges track by challenge.id"
ng-repeat="challenge in self.challenges track by challenge.id"
class="sk-modal"
application="self.application"
challenge="challenge"

View File

@@ -2,8 +2,8 @@ import { RootScopeMessages } from './../../messages';
import { WebDirective } from '@/types';
import { getPlatformString } from '@/utils';
import template from './application-view.pug';
import { AppStateEvent } from '@/ui_models/app_state';
import { ApplicationEvent, Challenge } from '@standardnotes/snjs';
import { AppStateEvent, PanelResizedData } from '@/ui_models/app_state';
import { ApplicationEvent, Challenge, removeFromArray } from '@standardnotes/snjs';
import {
PANEL_NAME_NOTES,
PANEL_NAME_TAGS
@@ -18,18 +18,20 @@ class ApplicationViewCtrl extends PureViewCtrl<unknown, {
ready?: boolean,
needsUnlock?: boolean,
appClass: string,
challenges: Challenge[]
}> {
private $location?: ng.ILocationService
private $rootScope?: ng.IRootScopeService
public platformString: string
private notesCollapsed = false
private tagsCollapsed = false
/**
* To prevent stale state reads (setState is async),
* challenges is a mutable array
*/
private challenges: Challenge[] = [];
/* @ngInject */
constructor(
$location: ng.ILocationService,
$rootScope: ng.IRootScopeService,
private $location: ng.ILocationService,
private $rootScope: ng.IRootScopeService,
$timeout: ng.ITimeoutService
) {
super($timeout);
@@ -43,8 +45,8 @@ class ApplicationViewCtrl extends PureViewCtrl<unknown, {
}
deinit() {
this.$location = undefined;
this.$rootScope = undefined;
(this.$location as unknown) = undefined;
(this.$rootScope as unknown) = undefined;
(this.application as unknown) = undefined;
window.removeEventListener('dragover', this.onDragOver, true);
window.removeEventListener('drop', this.onDragDrop, true);
@@ -66,23 +68,22 @@ class ApplicationViewCtrl extends PureViewCtrl<unknown, {
}
async loadApplication() {
this.application!.componentManager!.setDesktopManager(
this.application!.getDesktopService()
this.application.componentManager.setDesktopManager(
this.application.getDesktopService()
);
await this.application!.prepareForLaunch({
await this.application.prepareForLaunch({
receiveChallenge: async (challenge) => {
this.setState({
challenges: this.state.challenges.concat(challenge)
this.$timeout(() => {
this.challenges.push(challenge);
});
}
});
await this.application!.launch();
await this.application.launch();
}
public removeChallenge(challenge: Challenge) {
this.setState({
challenges: this.state.challenges.filter(c => c.id !== challenge.id)
public async removeChallenge(challenge: Challenge) {
this.$timeout(() => {
removeFromArray(this.challenges, challenge);
});
}
@@ -90,7 +91,7 @@ class ApplicationViewCtrl extends PureViewCtrl<unknown, {
super.onAppStart();
this.setState({
ready: true,
needsUnlock: this.application!.hasPasscode()
needsUnlock: this.application.hasPasscode()
});
}
@@ -101,7 +102,7 @@ class ApplicationViewCtrl extends PureViewCtrl<unknown, {
}
onUpdateAvailable() {
this.$rootScope!.$broadcast(RootScopeMessages.NewUpdateAvailable);
this.$rootScope.$broadcast(RootScopeMessages.NewUpdateAvailable);
}
/** @override */
@@ -119,21 +120,22 @@ class ApplicationViewCtrl extends PureViewCtrl<unknown, {
}
/** @override */
async onAppStateEvent(eventName: AppStateEvent, data?: any) {
async onAppStateEvent(eventName: AppStateEvent, data?: unknown) {
if (eventName === AppStateEvent.PanelResized) {
if (data.panel === PANEL_NAME_NOTES) {
this.notesCollapsed = data.collapsed;
const { panel, collapsed } = data as PanelResizedData;
if (panel === PANEL_NAME_NOTES) {
this.notesCollapsed = collapsed;
}
if (data.panel === PANEL_NAME_TAGS) {
this.tagsCollapsed = data.collapsed;
if (panel === PANEL_NAME_TAGS) {
this.tagsCollapsed = collapsed;
}
let appClass = "";
if (this.notesCollapsed) { appClass += "collapsed-notes"; }
if (this.tagsCollapsed) { appClass += " collapsed-tags"; }
this.setState({ appClass });
} else if (eventName === AppStateEvent.WindowDidFocus) {
if (!(await this.application!.isLocked())) {
this.application!.sync();
if (!(await this.application.isLocked())) {
this.application.sync();
}
}
}
@@ -149,29 +151,29 @@ class ApplicationViewCtrl extends PureViewCtrl<unknown, {
}
onDragOver(event: DragEvent) {
if (event.dataTransfer!.files.length > 0) {
if (event.dataTransfer?.files.length) {
event.preventDefault();
}
}
onDragDrop(event: DragEvent) {
if (event.dataTransfer!.files.length > 0) {
if (event.dataTransfer?.files.length) {
event.preventDefault();
this.application!.alertService!.alert(
STRING_DEFAULT_FILE_ERROR
);
void alertDialog({
text: STRING_DEFAULT_FILE_ERROR
});
}
}
async handleDemoSignInFromParams() {
if (
this.$location!.search().demo === 'true' &&
this.$location.search().demo === 'true' &&
!this.application.hasAccount()
) {
await this.application!.setHost(
await this.application.setHost(
'https://syncing-server-demo.standardnotes.org'
);
this.application!.signIn(
this.application.signIn(
'demo@standardnotes.org',
'password',
);

View File

@@ -266,24 +266,6 @@
span(ng-if='self.isDesktopApplication()')
| Backups are automatically created on desktop and can be managed
| via the "Backups" top-level menu.
#import-password-request(ng-if='self.state.importData.requestPassword')
form.sk-panel-form.stretch(ng-submit='self.submitImportPassword()')
p Enter the account password associated with the import file.
input.sk-input.contrast.mt-5(
autofocus='true',
ng-model='self.state.importData.password',
placeholder='Enter File Account Password',
type='password'
)
.sk-button-group.stretch.sk-panel-row.form-submit
button.sk-button.info(type='submit')
.sk-label Decrypt & Import
p
| Importing from backup will not overwrite existing data,
| but instead create a duplicate of any differing data.
p
| If you'd like to import only a selection of items instead of
| the whole file, please use the Batch Manager extension.
.sk-panel-row
.sk-spinner.small.info(ng-if='self.state.importData.loading')
.sk-panel-section

View File

@@ -34,6 +34,7 @@
"angular": "^1.8.2",
"apply-loader": "^2.0.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
"babel-plugin-angularjs-annotate": "^0.10.0",
"chai": "^4.2.0",
"connect": "^3.7.0",
@@ -69,8 +70,7 @@
"@reach/alert-dialog": "^0.12.1",
"@reach/dialog": "^0.12.1",
"@standardnotes/sncrypto-web": "^1.2.10",
"@standardnotes/snjs": "^2.0.47",
"babel-loader": "^8.2.2",
"@standardnotes/snjs": "^2.0.48",
"mobx": "^6.0.4",
"preact": "^10.5.11"
}

View File

@@ -1085,10 +1085,10 @@
"@standardnotes/sncrypto-common" "^1.2.7"
libsodium-wrappers "^0.7.8"
"@standardnotes/snjs@^2.0.47":
version "2.0.47"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.47.tgz#b6235faed52369168d383d6bf1ba45302f9b9cea"
integrity sha512-2d4CKdcKXg0dKYMdPPqQrLQE1NS62IWusfD2OZ0zRNbzhOHUhap/eRIURCDa0UQphKTaDaGa70x2rJl3PW4J6Q==
"@standardnotes/snjs@^2.0.48":
version "2.0.48"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.48.tgz#0e61b21e9a3d70d02867b86d2ced5dd1e8c861a1"
integrity sha512-OnlG6D0QNTu+jePJqUpyqeC81VMxKwyu3yNfGQyC6/p5j/nFcBC6W0aU6xBTeVsE2zEOX+Dltj6olY6dN4vgdQ==
dependencies:
"@standardnotes/sncrypto-common" "^1.2.9"