import { WebApplication } from '@/UIModels/Application' import { createRef, JSX } from 'preact' import { PureComponent } from '@/Components/Abstract/PureComponent' interface Props { application: WebApplication } type State = { continueTitle: string formData: FormData isContinuing?: boolean lockContinue?: boolean processing?: boolean showSpinner?: boolean step: Steps title: string } const DEFAULT_CONTINUE_TITLE = 'Continue' enum Steps { PasswordStep = 1, FinishStep = 2, } type FormData = { currentPassword?: string newPassword?: string newPasswordConfirmation?: string status?: string } export class PasswordWizard extends PureComponent { private currentPasswordInput = createRef() constructor(props: Props) { super(props, props.application) this.registerWindowUnloadStopper() this.state = { formData: {}, continueTitle: DEFAULT_CONTINUE_TITLE, step: Steps.PasswordStep, title: 'Change Password', } } override componentDidMount(): void { super.componentDidMount() this.currentPasswordInput.current?.focus() } override componentWillUnmount(): void { super.componentWillUnmount() window.onbeforeunload = null } registerWindowUnloadStopper() { window.onbeforeunload = () => { return true } } resetContinueState() { this.setState({ showSpinner: false, continueTitle: DEFAULT_CONTINUE_TITLE, isContinuing: false, }) } nextStep = async () => { if (this.state.lockContinue || this.state.isContinuing) { return } if (this.state.step === Steps.FinishStep) { this.dismiss() return } this.setState({ isContinuing: true, 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.setState({ isContinuing: false, showSpinner: false, continueTitle: 'Finish', step: Steps.FinishStep, }) } async validateCurrentPassword() { const currentPassword = this.state.formData.currentPassword const newPass = this.state.formData.newPassword if (!currentPassword || currentPassword.length === 0) { this.application.alertService.alert('Please enter your current password.').catch(console.error) return false } if (!newPass || newPass.length === 0) { this.application.alertService.alert('Please enter a new password.').catch(console.error) return false } if (newPass !== this.state.formData.newPasswordConfirmation) { this.application.alertService.alert('Your new password does not match its confirmation.').catch(console.error) this.setFormDataState({ status: undefined, }).catch(console.error) 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.") .catch(console.error) this.setFormDataState({ status: undefined, }).catch(console.error) return false } /** Validate current password */ const success = await this.application.validateAccountPassword(this.state.formData.currentPassword as string) if (!success) { this.application.alertService .alert('The current password you entered is not correct. Please try again.') .catch(console.error) } return success } async processPasswordChange() { await this.application.downloadBackup() this.setState({ lockContinue: true, processing: true, }) await this.setFormDataState({ status: 'Processing encryption keys…', }) const newPassword = this.state.formData.newPassword const response = await this.application.changePassword( this.state.formData.currentPassword as string, newPassword as string, ) const success = !response.error this.setState({ processing: false, lockContinue: false, }) if (!success) { this.setFormDataState({ status: 'Unable to process your password. Please try again.', }).catch(console.error) } else { this.setState({ formData: { ...this.state.formData, status: 'Successfully changed password.', }, }) } return success } dismiss = () => { if (this.state.lockContinue) { this.application.alertService.alert('Cannot close window until pending tasks are complete.').catch(console.error) } else { this.dismissModal() } } async setFormDataState(formData: Partial) { return this.setState({ formData: { ...this.state.formData, ...formData, }, }) } handleCurrentPasswordInputChange = ({ currentTarget }: JSX.TargetedEvent) => { this.setFormDataState({ currentPassword: currentTarget.value, }).catch(console.error) } handleNewPasswordInputChange = ({ currentTarget }: JSX.TargetedEvent) => { this.setFormDataState({ newPassword: currentTarget.value, }).catch(console.error) } handleNewPasswordConfirmationInputChange = ({ currentTarget }: JSX.TargetedEvent) => { this.setFormDataState({ newPasswordConfirmation: currentTarget.value, }).catch(console.error) } override render() { return (
{this.state.title}
Close
{this.state.step === Steps.PasswordStep && (
)} {this.state.step === Steps.FinishStep && (
Your password has been successfully changed.

Please ensure you are running the latest version of Standard Notes on all platforms to ensure maximum compatibility.

)}
) } }