import { WebApplication } from '@/ui_models/application'; import { PasswordWizardScope, PasswordWizardType, WebDirective, } from './../../types'; import template from '%/directives/password-wizard.pug'; import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl'; const DEFAULT_CONTINUE_TITLE = 'Continue'; enum Steps { PasswordStep = 1, FinishStep = 2, } type FormData = { currentPassword?: string; newPassword?: string; newPasswordConfirmation?: string; status?: string; }; type State = { lockContinue: boolean; formData: FormData; continueTitle: string; step: Steps; title: string; showSpinner: boolean; processing: boolean; }; type Props = { type: PasswordWizardType; changePassword: boolean; securityUpdate: boolean; }; class PasswordWizardCtrl extends PureViewCtrl implements PasswordWizardScope { $element: JQLite; application!: WebApplication; type!: PasswordWizardType; isContinuing = false; /* @ngInject */ constructor($element: JQLite, $timeout: ng.ITimeoutService) { super($timeout); this.$element = $element; this.registerWindowUnloadStopper(); } $onInit() { super.$onInit(); this.initProps({ type: this.type, changePassword: this.type === PasswordWizardType.ChangePassword, securityUpdate: this.type === PasswordWizardType.AccountUpgrade, }); this.setState({ formData: {}, continueTitle: DEFAULT_CONTINUE_TITLE, step: Steps.PasswordStep, title: this.props.changePassword ? 'Change Password' : 'Account Update', }); } $onDestroy() { super.$onDestroy(); window.onbeforeunload = null; } /** Confirms with user before closing tab */ registerWindowUnloadStopper() { window.onbeforeunload = () => { return true; }; } resetContinueState() { this.setState({ showSpinner: false, continueTitle: DEFAULT_CONTINUE_TITLE, }); this.isContinuing = false; } async nextStep() { if (this.state.lockContinue || this.isContinuing) { return; } if (this.state.step === Steps.FinishStep) { this.dismiss(); return; } this.isContinuing = true; await this.setState({ showSpinner: true, continueTitle: 'Generating Keys...', }); const valid = await this.validateCurrentPassword(); if (!valid) { this.resetContinueState(); return; } const success = await this.processPasswordChange(); if (!success) { this.resetContinueState(); return; } this.isContinuing = false; this.setState({ showSpinner: false, continueTitle: 'Finish', step: Steps.FinishStep, }); } async setFormDataState(formData: Partial) { return this.setState({ formData: { ...this.state.formData, ...formData, }, }); } async validateCurrentPassword() { const currentPassword = this.state.formData.currentPassword; const newPass = this.props.securityUpdate ? currentPassword : this.state.formData.newPassword; if (!currentPassword || currentPassword.length === 0) { this.application.alertService!.alert( 'Please enter your current password.' ); return false; } if (this.props.changePassword) { if (!newPass || newPass.length === 0) { this.application.alertService!.alert('Please enter a new password.'); return false; } if (newPass !== this.state.formData.newPasswordConfirmation) { this.application.alertService!.alert( 'Your new password does not match its confirmation.' ); this.setFormDataState({ status: undefined, }); return false; } } if (!this.application.getUser()?.email) { this.application.alertService!.alert( "We don't have your email stored. Please sign out then log back in to fix this issue." ); this.setFormDataState({ status: undefined, }); return false; } /** Validate current password */ const success = await this.application.validateAccountPassword( this.state.formData.currentPassword! ); if (!success) { this.application.alertService!.alert( 'The current password you entered is not correct. Please try again.' ); } return success; } async processPasswordChange() { await this.application.downloadBackup(); await this.setState({ lockContinue: true, processing: true, }); await this.setFormDataState({ status: 'Processing encryption keys…', }); const newPassword = this.props.securityUpdate ? this.state.formData.currentPassword : this.state.formData.newPassword; const response = await this.application.changePassword( this.state.formData.currentPassword!, newPassword! ); const success = !response.error; await this.setState({ processing: false, lockContinue: false, }); if (!success) { this.setFormDataState({ status: 'Unable to process your password. Please try again.', }); } else { this.setState({ formData: { ...this.state.formData, status: this.props.changePassword ? 'Successfully changed password.' : 'Successfully performed account update.', }, }); } return success; } dismiss() { if (this.state.lockContinue) { this.application.alertService!.alert( 'Cannot close window until pending tasks are complete.' ); } else { const elem = this.$element; const scope = elem.scope(); scope.$destroy(); elem.remove(); } } } export class PasswordWizard extends WebDirective { constructor() { super(); this.restrict = 'E'; this.template = template; this.controller = PasswordWizardCtrl; this.controllerAs = 'ctrl'; this.bindToController = true; this.scope = { type: '=', application: '=', }; } }