From 4676f0595c761f3362aa5e1cd1115f13aa62b76a Mon Sep 17 00:00:00 2001 From: Matt Hill Date: Tue, 11 Jul 2023 17:23:40 -0600 Subject: [PATCH] add reset password to UI (#2341) --- .../server-show/server-show.page.ts | 140 +++++++++++++++++- .../ui/src/app/services/api/api.types.ts | 11 +- .../app/services/api/embassy-api.service.ts | 7 +- .../services/api/embassy-live-api.service.ts | 6 + .../services/api/embassy-mock-api.service.ts | 13 +- 5 files changed, 158 insertions(+), 19 deletions(-) diff --git a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts index 8b8ea4eaf..b9e86bfd5 100644 --- a/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts +++ b/frontend/projects/ui/src/app/pages/server-routes/server-show/server-show.page.ts @@ -24,6 +24,9 @@ import { import { ConfigService } from 'src/app/services/config.service' import { DOCUMENT } from '@angular/common' import { getServerInfo } from 'src/app/util/get-server-info' +import { GenericFormPage } from 'src/app/modals/generic-form/generic-form.page' +import { ConfigSpec } from 'src/app/pkg-config/config-types' +import * as argon2 from '@start9labs/argon2' @Component({ selector: 'server-show', @@ -82,6 +85,102 @@ export class ServerShowPage { await modal.present() } + async presentAlertResetPassword() { + const alert = await this.alertCtrl.create({ + header: 'Warning', + message: + 'You will still need your current password to decrypt existing backups!', + buttons: [ + { + text: 'Cancel', + role: 'cancel', + }, + { + text: 'Continue', + handler: () => this.presentModalResetPassword(), + cssClass: 'enter-click', + }, + ], + cssClass: 'alert-warning-message', + }) + + await alert.present() + } + + async presentModalResetPassword(): Promise { + const modal = await this.modalCtrl.create({ + component: GenericFormPage, + componentProps: { + title: 'Change Master Password', + spec: PasswordSpec, + buttons: [ + { + text: 'Save', + handler: (value: any) => { + return this.resetPassword(value) + }, + isSubmit: true, + }, + ], + }, + }) + await modal.present() + } + + private async resetPassword(value: { + currPass: string + newPass: string + newPass2: string + }): Promise { + let err = '' + + if (value.newPass !== value.newPass2) { + err = 'New passwords do not match' + } else if (value.newPass.length < 12) { + err = 'New password must be 12 characters or greater' + } else if (value.newPass.length > 64) { + err = 'New password must be less than 65 characters' + } + + // confirm current password is correct + const { 'password-hash': passwordHash } = await getServerInfo(this.patch) + try { + argon2.verify(passwordHash, value.currPass) + } catch (e) { + err = 'Current password is invalid' + } + + if (err) { + this.errToast.present(err) + return false + } + + const loader = await this.loadingCtrl.create({ + message: 'Changing master password...', + }) + await loader.present() + + try { + await this.embassyApi.resetPassword({ + 'old-password': value.currPass, + 'new-password': value.newPass, + }) + const toast = await this.toastCtrl.create({ + header: 'Password changed!', + position: 'bottom', + duration: 2000, + }) + + toast.present() + return true + } catch (e: any) { + this.errToast.present(e) + return false + } finally { + loader.dismiss() + } + } + async updateEos(): Promise { const modal = await this.modalCtrl.create({ component: OSUpdatePage, @@ -436,6 +535,14 @@ export class ServerShowPage { detail: true, disabled$: of(false), }, + { + title: 'Change Master Password', + description: `Change your StartOS master password`, + icon: 'key-outline', + action: () => this.presentAlertResetPassword(), + detail: false, + disabled$: of(!this.secure), + }, { title: 'Experimental Features', description: 'Try out new and potentially unstable new features', @@ -530,11 +637,7 @@ export class ServerShowPage { description: 'Get help from the Start9 team and community', icon: 'chatbubbles-outline', action: () => - window.open( - 'https://start9.com/contact', - '_blank', - 'noreferrer', - ), + window.open('https://start9.com/contact', '_blank', 'noreferrer'), detail: true, disabled$: of(false), }, @@ -636,3 +739,30 @@ interface SettingBtn { detail: boolean disabled$: Observable } + +const PasswordSpec: ConfigSpec = { + currPass: { + type: 'string', + name: 'Current Password', + placeholder: 'CurrentPass', + nullable: false, + masked: true, + copyable: false, + }, + newPass: { + type: 'string', + name: 'New Password', + placeholder: 'NewPass', + nullable: false, + masked: true, + copyable: false, + }, + newPass2: { + type: 'string', + name: 'Retype New Password', + placeholder: 'NewPass', + nullable: false, + masked: true, + copyable: false, + }, +} diff --git a/frontend/projects/ui/src/app/services/api/api.types.ts b/frontend/projects/ui/src/app/services/api/api.types.ts index 9a353d109..18f7758af 100644 --- a/frontend/projects/ui/src/app/services/api/api.types.ts +++ b/frontend/projects/ui/src/app/services/api/api.types.ts @@ -30,6 +30,12 @@ export module RR { export type LogoutReq = {} // auth.logout export type LogoutRes = null + export type ResetPasswordReq = { + 'old-password': string + 'new-password': string + } // auth.reset-password + export type ResetPasswordRes = null + // server export type EchoReq = { message: string } // server.echo @@ -84,11 +90,6 @@ export module RR { export type KillSessionsReq = { ids: string[] } // sessions.kill export type KillSessionsRes = null - // password - - export type UpdatePasswordReq = { password: string } // password.set - export type UpdatePasswordRes = null - // notification export type GetNotificationsReq = { diff --git a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts index c37fcc716..2f6dee4de 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-api.service.ts @@ -51,6 +51,10 @@ export abstract class ApiService { abstract killSessions(params: RR.KillSessionsReq): Promise + abstract resetPassword( + params: RR.ResetPasswordReq, + ): Promise + // server abstract echo(params: RR.EchoReq): Promise @@ -126,9 +130,6 @@ export abstract class ApiService { abstract getEos(): Promise - // password - // abstract updatePassword (params: RR.UpdatePasswordReq): Promise - // notification abstract getNotifications( diff --git a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts index 3ad146c69..61590fbbd 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-live-api.service.ts @@ -94,6 +94,12 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'auth.session.kill', params }) } + async resetPassword( + params: RR.ResetPasswordReq, + ): Promise { + return this.rpcRequest({ method: 'auth.reset-password', params }) + } + // server async echo(params: RR.EchoReq): Promise { diff --git a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts index db7810d28..226a081a2 100644 --- a/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/frontend/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -156,6 +156,13 @@ export class MockApiService extends ApiService { return null } + async resetPassword( + params: RR.ResetPasswordReq, + ): Promise { + await pauseFor(2000) + return null + } + // server async echo(params: RR.EchoReq): Promise { @@ -377,12 +384,6 @@ export class MockApiService extends ApiService { return Mock.MarketplaceEos } - // password - // async updatePassword (params: RR.UpdatePasswordReq): Promise { - // await pauseFor(2000) - // return null - // } - // notification async getNotifications(