feat: implement SNJS backup file password retrieval
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user