fix: fix wrong password messaging

This commit is contained in:
waterplea
2024-07-12 11:14:45 +05:00
parent d235ebaac9
commit 0f5cec0a60
9 changed files with 170 additions and 115 deletions

94
web/package-lock.json generated
View File

@@ -28,12 +28,12 @@
"@start9labs/argon2": "^0.2.2",
"@start9labs/emver": "^0.1.5",
"@start9labs/start-sdk": "file:../sdk/dist",
"@taiga-ui/addon-charts": "3.84.0",
"@taiga-ui/cdk": "3.84.0",
"@taiga-ui/core": "3.84.0",
"@taiga-ui/experimental": "3.84.0",
"@taiga-ui/icons": "3.84.0",
"@taiga-ui/kit": "3.84.0",
"@taiga-ui/addon-charts": "3.86.0",
"@taiga-ui/cdk": "3.86.0",
"@taiga-ui/core": "3.86.0",
"@taiga-ui/experimental": "3.86.0",
"@taiga-ui/icons": "3.86.0",
"@taiga-ui/kit": "3.86.0",
"@tinkoff/ng-dompurify": "4.0.0",
"@tinkoff/ng-event-plugins": "3.2.0",
"angular-svg-round-progressbar": "^9.0.0",
@@ -4128,9 +4128,9 @@
}
},
"node_modules/@taiga-ui/addon-charts": {
"version": "3.84.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.84.0.tgz",
"integrity": "sha512-XR7UFywnrv4NRLHOCbba63gXDYYDL4Rt0MbjnF54p5U2EXnbt2of7VbjlB6cPx40XkQqfqa3CNayYxWZP82Ijg==",
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.86.0.tgz",
"integrity": "sha512-Du/85qqaj8hpFSI6hPuFeIhtE93Z6WSkYZLt0gvnsaCb2qSAg8D4oHSogrtF1rsWGGoM+fvXjD7UEUw9GzFIPg==",
"dependencies": {
"tslib": "^2.6.2"
},
@@ -4138,15 +4138,15 @@
"@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0",
"@ng-web-apis/common": "^3.0.6",
"@taiga-ui/cdk": "^3.84.0",
"@taiga-ui/core": "^3.84.0",
"@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0"
}
},
"node_modules/@taiga-ui/addon-commerce": {
"version": "3.84.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.84.0.tgz",
"integrity": "sha512-1zqLwnZLAYYcHvjH89d7JmtV2+QeZ2YnSJ3YWEMNLjGPzpev4RvQXtDfglIyu0LCyTxqpXmuzes9v/cgq2P5TQ==",
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.86.0.tgz",
"integrity": "sha512-8QSB490ckI4jnU+1sQ3x8os2GVE162hbvzPVYIZ0TruoeXl076dAz6PT2WRaFwjcaCAIGsuaQgQ4Cv02NjkiYQ==",
"peer": true,
"dependencies": {
"tslib": "^2.6.2"
@@ -4159,18 +4159,18 @@
"@maskito/core": "^1.9.0",
"@maskito/kit": "^1.9.0",
"@ng-web-apis/common": "^3.0.6",
"@taiga-ui/cdk": "^3.84.0",
"@taiga-ui/core": "^3.84.0",
"@taiga-ui/i18n": "^3.84.0",
"@taiga-ui/kit": "^3.84.0",
"@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.86.0",
"@taiga-ui/i18n": "^3.86.0",
"@taiga-ui/kit": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/cdk": {
"version": "3.84.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.84.0.tgz",
"integrity": "sha512-0umw/CUmYNEYOCUNQVTQS53zXzxZsH/6+lj1mFVzocvfJFJWAUT6ltCH9QvxYmxSDDGWwNGg16AaVo2K+aGL0w==",
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.86.0.tgz",
"integrity": "sha512-aVbnW01Oh0Er1sHKVGHP8W05mOSKxjSzFE3Qx4iF4T6KW7Rlz9HZoNx5ADMg0TATYChtWh9Kwjo8I4LSVj2ZUw==",
"dependencies": {
"@ng-web-apis/common": "3.0.6",
"@ng-web-apis/mutation-observer": "3.1.0",
@@ -4221,11 +4221,11 @@
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/@taiga-ui/core": {
"version": "3.84.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.84.0.tgz",
"integrity": "sha512-FZy77z0E4qjYcszVcp+qPFkPwJPl8qXZb7t2P+juUtJvSmSn2foQHHdyhbIYN808H26tqCdgkTMG1BWQxVuDSg==",
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.86.0.tgz",
"integrity": "sha512-diQKOnPtDDfxPOMk6wLRq8nyDVfNSPSNy+1TeyqzUgOvJ6XAjfaBXGsL3iuR7AN8+sz/b3rJmBce+vdw6FjMLQ==",
"dependencies": {
"@taiga-ui/i18n": "^3.84.0",
"@taiga-ui/i18n": "^3.86.0",
"tslib": "^2.6.2"
},
"peerDependencies": {
@@ -4237,35 +4237,35 @@
"@angular/router": ">=12.0.0",
"@ng-web-apis/common": "^3.0.6",
"@ng-web-apis/mutation-observer": "^3.1.0",
"@taiga-ui/cdk": "^3.84.0",
"@taiga-ui/i18n": "^3.84.0",
"@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/i18n": "^3.86.0",
"@tinkoff/ng-event-plugins": "^3.2.0",
"@tinkoff/ng-polymorpheus": "^4.3.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/experimental": {
"version": "3.84.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.84.0.tgz",
"integrity": "sha512-q0hNVy+EmywCG8hpZlg/+haKIFhnmxicQiSeV/D1P7CHO10safjGo0ptT6e1hYMFa5/cJZOM4OwDPen2xs17Wg==",
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.86.0.tgz",
"integrity": "sha512-ACjoRVeX5MgsNJsiu2ukliXLD2mfEWm8Vtmk78vqcnkyPUmy1ZWK4sG3p5ybFN8AdIMHkblVq0l+x2qAwr/+LQ==",
"dependencies": {
"tslib": "^2.6.2"
},
"peerDependencies": {
"@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0",
"@taiga-ui/addon-commerce": "^3.84.0",
"@taiga-ui/cdk": "^3.84.0",
"@taiga-ui/core": "^3.84.0",
"@taiga-ui/kit": "^3.84.0",
"@taiga-ui/addon-commerce": "^3.86.0",
"@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.86.0",
"@taiga-ui/kit": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/i18n": {
"version": "3.85.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.85.0.tgz",
"integrity": "sha512-CGoxfq9WY+psX5ZOfWmuQZ6OA/0CAPYJTlbHkw5sRKAyhEQ3NM/Wbx3xcwrcYRRJDnt9yOlfibz+3a+WDF2bFA==",
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.86.0.tgz",
"integrity": "sha512-8zkNhMo/QtxZ2Zp6EP/nxo4SOLwaIrX+P3X/Wt+1cjFNZUYWWfdvfHLLdNviKFPVl4RAOxvkhDfza/wkrwv+iQ==",
"dependencies": {
"tslib": "^2.6.2"
},
@@ -4276,20 +4276,20 @@
}
},
"node_modules/@taiga-ui/icons": {
"version": "3.84.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.84.0.tgz",
"integrity": "sha512-KiH7BJRZ6wbkOHlJAS0XHq2gYnQTpRgdEogKW+GoD0da/4trCdM66vhDk2j0DwDFdBGq5U0inHJCjnskBI1nSQ==",
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.86.0.tgz",
"integrity": "sha512-jVBEbvE/r9JG+knmXMTn/l/js3JjYi8nSGbrLCryJZZoS2izRnQARN2txABieUJm8H463CoF0rcdXlHKRuA4Ew==",
"dependencies": {
"tslib": "^2.6.2"
},
"peerDependencies": {
"@taiga-ui/cdk": "^3.84.0"
"@taiga-ui/cdk": "^3.86.0"
}
},
"node_modules/@taiga-ui/kit": {
"version": "3.84.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.84.0.tgz",
"integrity": "sha512-lSUPDco5FeBYK3ESnXeEPLCdMCmNXwcdHNK/we+0ZoH4VPx/OGg2hpEP0Fej7jfGHwXFTzDbufQD0hT6WlfTAw==",
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.86.0.tgz",
"integrity": "sha512-naAy4pyhCaQ9+vWxqSMjbV+9KwnMxT5ybrw+MAJgMn2evzRq0FjqzyFZFog7oiRbRvgVdoWPQfBNKaaLhJcpsw==",
"dependencies": {
"@maskito/angular": "1.9.0",
"@maskito/core": "1.9.0",
@@ -4306,9 +4306,9 @@
"@ng-web-apis/common": "3.0.6",
"@ng-web-apis/mutation-observer": "^3.1.0",
"@ng-web-apis/resize-observer": "^3.0.6",
"@taiga-ui/cdk": "^3.84.0",
"@taiga-ui/core": "^3.84.0",
"@taiga-ui/i18n": "^3.84.0",
"@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.86.0",
"@taiga-ui/i18n": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0",
"rxjs": ">=6.0.0"
}

View File

@@ -51,12 +51,12 @@
"@start9labs/argon2": "^0.2.2",
"@start9labs/emver": "^0.1.5",
"@start9labs/start-sdk": "file:../sdk/dist",
"@taiga-ui/addon-charts": "3.84.0",
"@taiga-ui/cdk": "3.84.0",
"@taiga-ui/core": "3.84.0",
"@taiga-ui/experimental": "3.84.0",
"@taiga-ui/icons": "3.84.0",
"@taiga-ui/kit": "3.84.0",
"@taiga-ui/addon-charts": "3.86.0",
"@taiga-ui/cdk": "3.86.0",
"@taiga-ui/core": "3.86.0",
"@taiga-ui/experimental": "3.86.0",
"@taiga-ui/icons": "3.86.0",
"@taiga-ui/kit": "3.86.0",
"@tinkoff/ng-dompurify": "4.0.0",
"@tinkoff/ng-event-plugins": "3.2.0",
"angular-svg-round-progressbar": "^9.0.0",

View File

@@ -15,7 +15,6 @@ export class ErrorService extends ErrorHandler {
this.alerts
.open(getErrorMessage(error, link), {
label: 'Error',
autoClose: false,
status: TuiNotification.Error,
})
.subscribe()

View File

@@ -32,7 +32,7 @@ export class BadgeMenuComponent {
constructor(
private readonly splitPane: SplitPaneTracker,
private readonly patch: PatchDB<DataModel>,
private readonly dialog: TuiDialogService,
private readonly dialogs: TuiDialogService,
private readonly clientStorageService: ClientStorageService,
) {}
@@ -44,6 +44,6 @@ export class BadgeMenuComponent {
}
onWidgets() {
this.dialog.open(WIDGETS_COMPONENT, { label: 'Widgets' }).subscribe()
this.dialogs.open(WIDGETS_COMPONENT, { label: 'Widgets' }).subscribe()
}
}

View File

@@ -6,6 +6,10 @@ import {
LoadingService,
StartOSDiskInfo,
} from '@start9labs/shared'
import {
PasswordPromptComponent,
PromptOptions,
} from 'src/app/modals/password-prompt.component'
import {
BackupInfo,
CifsBackupTarget,
@@ -14,7 +18,6 @@ import {
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
import { AppRecoverSelectPage } from '../app-recover-select/app-recover-select.page'
import { PasswordPromptModal } from './password-prompt.modal'
@Component({
selector: 'backup-server-select',
@@ -38,24 +41,35 @@ export class BackupServerSelectModal {
async presentModalPassword(
serverId: string,
server: StartOSDiskInfo,
{ passwordHash }: StartOSDiskInfo,
): Promise<void> {
const options: PromptOptions = {
title: 'Password Required',
message:
'Enter the password that was used to encrypt this backup. On the next screen, you will select the individual services you want to restore.',
label: 'Decrypt Backup',
placeholder: 'Enter password',
buttonText: 'Next',
}
const modal = await this.modalCtrl.create({
component: PasswordPromptModal,
component: PasswordPromptComponent,
componentProps: { options },
canDismiss: async password => {
if (password === null) {
return true
}
try {
argon2.verify(passwordHash!, password)
await this.restoreFromBackup(serverId, password)
return true
} catch (e: any) {
this.errorService.handleError(e)
return false
}
},
})
modal.present()
const { data, role } = await modal.onWillDismiss()
if (role === 'confirm') {
try {
// @TODO Alex if invalid password, we should tell the user "Invalid password" and halt execution of this function. The modal should remain so the user can try again. Correct password is asdfasdf
argon2.verify(server.passwordHash!, data)
await this.restoreFromBackup(serverId, data)
} catch (e: any) {
this.errorService.handleError(e)
}
}
}
private async restoreFromBackup(

View File

@@ -1,14 +1,29 @@
import { Component } from '@angular/core'
import {
AfterViewInit,
Component,
ElementRef,
Input,
ViewChild,
} from '@angular/core'
import { FormsModule } from '@angular/forms'
import { IonicModule, ModalController } from '@ionic/angular'
import { TuiTextfieldComponent } from '@taiga-ui/core'
import { TuiInputPasswordModule } from '@taiga-ui/kit'
export interface PromptOptions {
title: string
message: string
label: string
placeholder: string
buttonText: string
}
@Component({
standalone: true,
template: `
<ion-header>
<ion-toolbar>
<ion-title>Decrypt Backup</ion-title>
<ion-title>{{ options.title }}</ion-title>
<ion-buttons slot="end">
<ion-button (click)="cancel()">
<ion-icon slot="icon-only" name="close"></ion-icon>
@@ -18,13 +33,11 @@ import { TuiInputPasswordModule } from '@taiga-ui/kit'
</ion-header>
<ion-content class="ion-padding">
<p>{{ options.message }}</p>
<p>
Enter the password that was used to encrypt this backup. On the next
screen, you will select the individual services you want to restore.
</p>
<p>
<tui-input-password [(ngModel)]="password">
Enter password
<tui-input-password [(ngModel)]="password" (keydown.enter)="confirm()">
{{ options.label }}
<input tuiTextfield [placeholder]="options.placeholder" />
</tui-input-password>
</p>
</ion-content>
@@ -47,18 +60,30 @@ import { TuiInputPasswordModule } from '@taiga-ui/kit'
[disabled]="!password"
(click)="confirm()"
>
Next
{{ options.buttonText }}
</ion-button>
</ion-toolbar>
</ion-footer>
`,
imports: [IonicModule, FormsModule, TuiInputPasswordModule],
})
export class PasswordPromptModal {
export class PasswordPromptComponent implements AfterViewInit {
@ViewChild(TuiTextfieldComponent, { read: ElementRef })
input?: ElementRef<HTMLInputElement>
@Input()
options!: PromptOptions
password = ''
constructor(private modalCtrl: ModalController) {}
ngAfterViewInit() {
setTimeout(() => {
this.input?.nativeElement.focus({ preventScroll: true })
}, 300)
}
cancel() {
return this.modalCtrl.dismiss(null, 'cancel')
}

View File

@@ -1,11 +1,13 @@
import { Component } from '@angular/core'
import { ModalController, NavController } from '@ionic/angular'
import { LoadingService } from '@start9labs/shared'
import { TuiDialogService } from '@taiga-ui/core'
import { PROMPT, PromptOptions } from 'src/app/modals/prompt.component'
import { ErrorService, LoadingService } from '@start9labs/shared'
import {
PasswordPromptComponent,
PromptOptions,
} from 'src/app/modals/password-prompt.component'
import { ApiService } from 'src/app/services/api/embassy-api.service'
import { PatchDB } from 'patch-db-client'
import { skip, take, takeUntil } from 'rxjs/operators'
import { skip, takeUntil } from 'rxjs/operators'
import { MappedBackupTarget } from 'src/app/types/mapped-backup-target'
import * as argon2 from '@start9labs/argon2'
import { TuiDestroyService } from '@taiga-ui/cdk'
@@ -22,7 +24,6 @@ import { BackupService } from 'src/app/components/backup-drives/backup.service'
@Component({
selector: 'server-backup',
templateUrl: './server-backup.page.html',
styleUrls: ['./server-backup.page.scss'],
providers: [TuiDestroyService],
})
export class ServerBackupPage {
@@ -31,8 +32,8 @@ export class ServerBackupPage {
readonly backingUp$ = this.eosService.backingUp$
constructor(
private readonly errorService: ErrorService,
private readonly loader: LoadingService,
private readonly dialogs: TuiDialogService,
private readonly modalCtrl: ModalController,
private readonly embassyApi: ApiService,
private readonly navCtrl: NavController,
@@ -74,29 +75,35 @@ export class ServerBackupPage {
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
): Promise<void> {
const options: PromptOptions = {
title: 'Master Password Needed',
message: 'Enter your master password to encrypt this backup.',
label: 'Master Password',
placeholder: 'Enter master password',
useMask: true,
buttonText: 'Create Backup',
}
this.dialogs
.open<string>(PROMPT, {
label: 'Master Password Needed',
data: options,
})
.pipe(take(1))
.subscribe(async (password: string) => {
const modal = await this.modalCtrl.create({
component: PasswordPromptComponent,
componentProps: { options },
canDismiss: async password => {
if (password === null) {
return true
}
const { passwordHash, id } = await getServerInfo(this.patch)
// @TODO Alex if invalid password, we should tell the user "Invalid password" and halt execution of this function. The modal should remain so the user can try again. Correct password is asdfasdf
// confirm password matches current master password
argon2.verify(passwordHash, password)
try {
argon2.verify(passwordHash, password)
} catch (e: any) {
this.errorService.handleError(e)
return false
}
// first time backup
if (!this.backupService.hasThisBackup(target.entry, id)) {
await this.createBackup(target, password)
this.createBackup(target, password)
return true
// existing backup
} else {
try {
@@ -106,39 +113,49 @@ export class ServerBackupPage {
() => this.presentModalOldPassword(target, password),
250,
)
return
return true
}
await this.createBackup(target, password)
return true
}
})
},
})
modal.present()
}
private async presentModalOldPassword(
target: MappedBackupTarget<CifsBackupTarget | DiskBackupTarget>,
password: string,
): Promise<void> {
const { id } = await getServerInfo(this.patch)
const options: PromptOptions = {
title: 'Original Password Needed',
message:
'This backup was created with a different password. Enter the ORIGINAL password that was used to encrypt this backup.',
label: 'Original Password',
placeholder: 'Enter original password',
useMask: true,
buttonText: 'Create Backup',
}
const { id } = await getServerInfo(this.patch)
const modal = await this.modalCtrl.create({
component: PasswordPromptComponent,
componentProps: { options },
canDismiss: async oldPassword => {
if (oldPassword === null) {
return true
}
this.dialogs
.open<string>(PROMPT, {
label: 'Original Password Needed',
data: options,
})
.pipe(take(1))
.subscribe(async (oldPassword: string) => {
// @TODO Alex if invalid password, we should tell the user "Invalid password" and halt execution of this function. The modal should remain so the user can try again. Correct password is asdfasdf
argon2.verify(target.entry.startOs[id].passwordHash!, oldPassword)
await this.createBackup(target, password, oldPassword)
})
try {
argon2.verify(target.entry.startOs[id].passwordHash!, oldPassword)
await this.createBackup(target, password, oldPassword)
return true
} catch (e: any) {
this.errorService.handleError(e)
return false
}
},
})
modal.present()
}
private async createBackup(

View File

@@ -56,7 +56,7 @@ export class WidgetsPage {
@Optional()
@Inject(POLYMORPHEUS_CONTEXT)
readonly context: TuiDialogContext | null,
private readonly dialog: TuiDialogService,
private readonly dialogs: TuiDialogService,
private readonly patch: PatchDB<DataModel>,
private readonly cdr: ChangeDetectorRef,
private readonly api: ApiService,
@@ -83,7 +83,7 @@ export class WidgetsPage {
}
add() {
this.dialog.open(ADD_WIDGET, { label: 'Add widget' }).subscribe(widget => {
this.dialogs.open(ADD_WIDGET, { label: 'Add widget' }).subscribe(widget => {
this.addWidget(widget!)
})
}