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

View File

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

View File

@@ -28,7 +28,7 @@
application='self.application' application='self.application'
) )
challenge-modal( 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" class="sk-modal"
application="self.application" application="self.application"
challenge="challenge" challenge="challenge"

View File

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

View File

@@ -266,24 +266,6 @@
span(ng-if='self.isDesktopApplication()') span(ng-if='self.isDesktopApplication()')
| Backups are automatically created on desktop and can be managed | Backups are automatically created on desktop and can be managed
| via the "Backups" top-level menu. | 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-panel-row
.sk-spinner.small.info(ng-if='self.state.importData.loading') .sk-spinner.small.info(ng-if='self.state.importData.loading')
.sk-panel-section .sk-panel-section

View File

@@ -34,6 +34,7 @@
"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",
"babel-loader": "^8.2.2",
"babel-plugin-angularjs-annotate": "^0.10.0", "babel-plugin-angularjs-annotate": "^0.10.0",
"chai": "^4.2.0", "chai": "^4.2.0",
"connect": "^3.7.0", "connect": "^3.7.0",
@@ -69,8 +70,7 @@
"@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.10", "@standardnotes/sncrypto-web": "^1.2.10",
"@standardnotes/snjs": "^2.0.47", "@standardnotes/snjs": "^2.0.48",
"babel-loader": "^8.2.2",
"mobx": "^6.0.4", "mobx": "^6.0.4",
"preact": "^10.5.11" "preact": "^10.5.11"
} }

View File

@@ -1085,10 +1085,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.47": "@standardnotes/snjs@^2.0.48":
version "2.0.47" version "2.0.48"
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.47.tgz#b6235faed52369168d383d6bf1ba45302f9b9cea" resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.0.48.tgz#0e61b21e9a3d70d02867b86d2ced5dd1e8c861a1"
integrity sha512-2d4CKdcKXg0dKYMdPPqQrLQE1NS62IWusfD2OZ0zRNbzhOHUhap/eRIURCDa0UQphKTaDaGa70x2rJl3PW4J6Q== integrity sha512-OnlG6D0QNTu+jePJqUpyqeC81VMxKwyu3yNfGQyC6/p5j/nFcBC6W0aU6xBTeVsE2zEOX+Dltj6olY6dN4vgdQ==
dependencies: dependencies:
"@standardnotes/sncrypto-common" "^1.2.9" "@standardnotes/sncrypto-common" "^1.2.9"