import React from 'react'; import { SessionModal } from './SessionModal'; import { SessionButton, SessionButtonColor } from './SessionButton'; export enum PasswordAction { Set = 'set', Change = 'change', Remove = 'remove', } interface Props { action: PasswordAction; onOk: any; onClose: any; } interface State { error: string | null; currentPasswordEntered: string | null; currentPasswordConfirmEntered: string | null; } export class SessionPasswordModal extends React.Component { private passportInput: HTMLInputElement | null = null; constructor(props: any) { super(props); this.state = { error: null, currentPasswordEntered: null, currentPasswordConfirmEntered: null, }; this.showError = this.showError.bind(this); this.setPassword = this.setPassword.bind(this); this.closeDialog = this.closeDialog.bind(this); this.onPasswordInput = this.onPasswordInput.bind(this); this.onPasswordConfirmInput = this.onPasswordConfirmInput.bind(this); this.onPaste = this.onPaste.bind(this); } public componentDidMount() { setTimeout(() => { // tslint:disable-next-line: no-unused-expression this.passportInput && this.passportInput.focus(); }, 1); } public render() { const { action, onOk } = this.props; const placeholders = action === PasswordAction.Change ? [window.i18n('typeInOldPassword'), window.i18n('enterPassword')] : [window.i18n('enterPassword'), window.i18n('confirmPassword')]; const confirmButtonColor = action === PasswordAction.Remove ? SessionButtonColor.Danger : SessionButtonColor.Primary; return ( null} onClose={this.closeDialog} >
{ this.passportInput = input; }} placeholder={placeholders[0]} onKeyUp={this.onPasswordInput} maxLength={window.CONSTANTS.MAX_PASSWORD_LENGTH} onPaste={this.onPaste} /> {action !== PasswordAction.Remove && ( )}
{this.showError()}
this.setPassword(onOk)} />
); } public async validatePasswordHash(password: string | null) { // Check if the password matches the hash we have stored const hash = await window.Signal.Data.getPasswordHash(); if (hash && !window.passwordUtil.matchesHash(password, hash)) { return false; } return true; } private showError() { const message = this.state.error; return ( <> {message && ( <>
{message}
)} ); } // tslint:disable-next-line: cyclomatic-complexity private async setPassword(onSuccess?: any) { const { action } = this.props; const { currentPasswordEntered, currentPasswordConfirmEntered, } = this.state; const { Set, Remove, Change } = PasswordAction; // Trim leading / trailing whitespace for UX const enteredPassword = (currentPasswordEntered || '').trim(); const enteredPasswordConfirm = (currentPasswordConfirmEntered || '').trim(); // if user did not fill the first password field, we can't do anything const errorFirstInput = window.passwordUtil.validatePassword( enteredPassword, window.i18n ); if (errorFirstInput !== null) { this.setState({ error: errorFirstInput, }); return; } // if action is Set or Change, we need a valid ConfirmPassword if (action === Set || action === Change) { const errorSecondInput = window.passwordUtil.validatePassword( enteredPasswordConfirm, window.i18n ); if (errorSecondInput !== null) { this.setState({ error: errorSecondInput, }); return; } } // Passwords match or remove password successful const newPassword = action === Remove ? null : enteredPasswordConfirm; const oldPassword = action === Set ? null : enteredPassword; // Check if password match, when setting, changing or removing let valid; if (action === Set) { valid = enteredPassword === enteredPasswordConfirm; } else { valid = Boolean(await this.validatePasswordHash(oldPassword)); } if (!valid) { this.setState({ error: window.i18n(`${action}PasswordInvalid`), }); return; } await window.setPassword(newPassword, oldPassword); const toastParams = { title: window.i18n(`${action}PasswordTitle`), description: window.i18n(`${action}PasswordToastDescription`), type: action !== Remove ? 'success' : 'warning', icon: action !== Remove ? 'lock' : undefined, }; window.pushToast({ id: 'set-password-success-toast', ...toastParams, }); onSuccess(this.props.action); this.closeDialog(); } private closeDialog() { if (this.props.onClose) { this.props.onClose(); } } private onPaste(event: any) { const clipboard = event.clipboardData.getData('text'); if (clipboard.length > window.CONSTANTS.MAX_PASSWORD_LENGTH) { const title = String( window.i18n( 'pasteLongPasswordToastTitle', window.CONSTANTS.MAX_PASSWORD_LENGTH ) ); window.pushToast({ title, type: 'warning', }); } // Prevent pating into input return false; } private async onPasswordInput(event: any) { if (event.key === 'Enter') { return this.setPassword(this.props.onOk); } this.setState({ currentPasswordEntered: event.target.value }); } private async onPasswordConfirmInput(event: any) { if (event.key === 'Enter') { return this.setPassword(this.props.onOk); } this.setState({ currentPasswordConfirmEntered: event.target.value }); } }