modernize reset password flow for TUI

This commit is contained in:
Matt Hill
2023-07-26 11:53:29 -06:00
4 changed files with 60 additions and 11186 deletions

11099
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,5 @@
import { Component, Inject } from '@angular/core' import { Component, Inject } from '@angular/core'
import { import { NavController } from '@ionic/angular'
AlertController,
LoadingController,
ModalController,
NavController,
ToastController,
} from '@ionic/angular'
import { ApiService } from 'src/app/services/api/embassy-api.service' import { ApiService } from 'src/app/services/api/embassy-api.service'
import { ActivatedRoute } from '@angular/router' import { ActivatedRoute } from '@angular/router'
import { PatchDB } from 'patch-db-client' import { PatchDB } from 'patch-db-client'
@@ -17,6 +11,11 @@ import { OSUpdatePage } from './os-update/os-update.page'
import { getAllPackages } from 'src/app/util/get-package-data' import { getAllPackages } from 'src/app/util/get-package-data'
import { AuthService } from 'src/app/services/auth.service' import { AuthService } from 'src/app/services/auth.service'
import { DataModel } from 'src/app/services/patch-db/data-model' import { DataModel } from 'src/app/services/patch-db/data-model'
import { FormDialogService } from 'src/app/services/form-dialog.service'
import { FormPage } from '../../../modals/form/form.page'
import { Config } from '@start9labs/start-sdk/lib/config/builder/config'
import { Value } from '@start9labs/start-sdk/lib/config/builder/value'
import { configBuilderToSpec } from 'src/app/util/configBuilderToSpec'
import { ConfigService } from 'src/app/services/config.service' import { ConfigService } from 'src/app/services/config.service'
import { TuiAlertService, TuiDialogService } from '@taiga-ui/core' import { TuiAlertService, TuiDialogService } from '@taiga-ui/core'
import { PROMPT } from 'src/app/apps/ui/modals/prompt/prompt.component' import { PROMPT } from 'src/app/apps/ui/modals/prompt/prompt.component'
@@ -56,6 +55,7 @@ export class ServerShowPage {
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly alerts: TuiAlertService, private readonly alerts: TuiAlertService,
private readonly config: ConfigService, private readonly config: ConfigService,
private readonly formDialog: FormDialogService,
@Inject(DOCUMENT) private readonly document: Document, @Inject(DOCUMENT) private readonly document: Document,
) {} ) {}
@@ -98,109 +98,80 @@ export class ServerShowPage {
this.dialogs.open(new PolymorpheusComponent(OSUpdatePage)).subscribe() this.dialogs.open(new PolymorpheusComponent(OSUpdatePage)).subscribe()
} }
async presentAlertResetPassword() { private presentAlertResetPassword() {
const alert = await this.alertCtrl.create({ this.dialogs
header: 'Warning', .open(TUI_PROMPT, {
message: label: 'Warning',
'You will still need your current password to decrypt existing backups!', size: 's',
buttons: [ data: {
{ content:
text: 'Cancel', 'You will still need your current password to decrypt existing backups!',
role: 'cancel', yes: 'Continue',
no: 'Cancel',
}, },
{ })
text: 'Continue', .pipe(filter(Boolean))
handler: () => this.presentModalResetPassword(), .subscribe(() => this.presentModalResetPassword())
cssClass: 'enter-click',
},
],
cssClass: 'alert-warning-message',
})
await alert.present()
} }
async presentModalResetPassword(): Promise<void> { async presentModalResetPassword(): Promise<void> {
const modal = await this.modalCtrl.create({ this.formDialog.open(FormPage, {
component: GenericFormPage, label: 'Change Master Password',
componentProps: { data: {
title: 'Change Master Password', spec: await configBuilderToSpec(passwordSpec),
spec: PasswordSpec,
buttons: [ buttons: [
{ {
text: 'Save', text: 'Save',
handler: (value: any) => { handler: (value: PasswordSpec) => this.resetPassword(value),
return this.resetPassword(value)
},
isSubmit: true,
}, },
], ],
}, },
}) })
await modal.present()
} }
private async resetPassword(value: { private async resetPassword(value: PasswordSpec): Promise<boolean> {
currPass: string
newPass: string
newPass2: string
}): Promise<boolean> {
let err = '' let err = ''
if (value.newPass !== value.newPass2) { if (value.newPassword1 !== value.newPassword2) {
err = 'New passwords do not match' err = 'New passwords do not match'
} else if (value.newPass.length < 12) { } else if (value.newPassword1.length < 12) {
err = 'New password must be 12 characters or greater' err = 'New password must be 12 characters or greater'
} else if (value.newPass.length > 64) { } else if (value.newPassword1.length > 64) {
err = 'New password must be less than 65 characters' err = 'New password must be less than 65 characters'
} }
// confirm current password is correct // confirm current password is correct
const { 'password-hash': passwordHash } = await getServerInfo(this.patch) const { 'password-hash': passwordHash } = await getServerInfo(this.patch)
try { try {
argon2.verify(passwordHash, value.currPass) argon2.verify(passwordHash, value.currentPassword)
} catch (e) { } catch (e) {
err = 'Current password is invalid' err = 'Current password is invalid'
} }
if (err) { if (err) {
this.errToast.present(err) this.errorService.handleError(err)
return false return false
} }
const loader = await this.loadingCtrl.create({ const loader = this.loader.open('Saving...').subscribe()
message: 'Changing master password...',
})
await loader.present()
try { try {
await this.embassyApi.resetPassword({ await this.embassyApi.resetPassword({
'old-password': value.currPass, 'old-password': value.currentPassword,
'new-password': value.newPass, 'new-password': value.newPassword1,
})
const toast = await this.toastCtrl.create({
header: 'Password changed!',
position: 'bottom',
duration: 2000,
}) })
toast.present() this.alerts.open('Password changed!').subscribe()
return true return true
} catch (e: any) { } catch (e: any) {
this.errToast.present(e) this.errorService.handleError(e)
return false return false
} finally { } finally {
loader.dismiss() loader.unsubscribe()
} }
} }
async updateEos(): Promise<void> {
const modal = await this.modalCtrl.create({
component: OSUpdatePage,
})
modal.present()
}
private presentAlertLogout() { private presentAlertLogout() {
this.dialogs this.dialogs
.open(TUI_PROMPT, { .open(TUI_PROMPT, {
@@ -685,29 +656,28 @@ interface SettingBtn {
disabled$: Observable<boolean> disabled$: Observable<boolean>
} }
const PasswordSpec: ConfigSpec = { export const passwordSpec = Config.of({
currPass: { currentPassword: Value.text({
type: 'string',
name: 'Current Password', name: 'Current Password',
placeholder: 'CurrentPass', required: {
nullable: false, default: null,
},
masked: true, masked: true,
copyable: false, }),
}, newPassword1: Value.text({
newPass: {
type: 'string',
name: 'New Password', name: 'New Password',
placeholder: 'NewPass', required: {
nullable: false, default: null,
},
masked: true, masked: true,
copyable: false, }),
}, newPassword2: Value.text({
newPass2: {
type: 'string',
name: 'Retype New Password', name: 'Retype New Password',
placeholder: 'NewPass', required: {
nullable: false, default: null,
},
masked: true, masked: true,
copyable: false, }),
}, })
}
type PasswordSpec = typeof passwordSpec.validator._TYPE

View File

@@ -106,6 +106,8 @@ export const mockPatchData: DataModel = {
password: '', password: '',
tls: true, tls: true,
}, },
'password-hash':
'$argon2d$v=19$m=1024,t=1,p=1$YXNkZmFzZGZhc2RmYXNkZg$Ceev1I901G6UwU+hY0sHrFZ56D+o+LNJ',
}, },
'package-data': { 'package-data': {
bitcoind: Mock.bitcoind, bitcoind: Mock.bitcoind,

View File

@@ -66,6 +66,7 @@ export interface ServerInfo {
'system-start-time': string 'system-start-time': string
zram: boolean zram: boolean
smtp: typeof customSmtp.validator._TYPE smtp: typeof customSmtp.validator._TYPE
'password-hash': string
} }
export type StartOsUiInfo = { export type StartOsUiInfo = {