feat: updated settings password modal

pull/2522/head
William Grant 3 years ago
parent d95bd47438
commit 0d10667d6e

@ -536,29 +536,6 @@ label {
display: flex;
flex-direction: column;
background-color: var(--background-secondary-color);
&__password-lock {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 1;
&-box {
padding: 45px 60px;
display: flex;
flex-direction: column;
align-items: center;
max-width: 90%;
min-width: 400px;
background: var(--background-primary-color);
color: var(--text-primary-color);
border: 1px solid var(--border-color);
border-radius: 5px;
}
}
}
#qr svg,

@ -42,5 +42,3 @@
@import 'session_slider';
@import 'session_conversation';
@import '_session_password';

@ -3,7 +3,7 @@ import React from 'react';
import { missingCaseError } from '../../util';
import { ToastUtils } from '../../session/utils';
import { Data } from '../../data/data';
import { SpacerLG, SpacerSM } from '../basic/Text';
import { SpacerSM } from '../basic/Text';
import autoBind from 'auto-bind';
import { sessionPassword } from '../../state/ducks/modalDialog';
import { LocalizerKeys } from '../../types/LocalizerKeys';
@ -11,7 +11,7 @@ import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/S
import { SessionWrapperModal } from '../SessionWrapperModal';
import { matchesHash, validatePassword } from '../../util/passwordUtils';
export type PasswordAction = 'set' | 'change' | 'remove';
export type PasswordAction = 'set' | 'change' | 'remove' | 'enter';
interface Props {
passwordAction: PasswordAction;
@ -62,6 +62,9 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
case 'remove':
placeholders = [window.i18n('enterPassword')];
break;
case 'enter':
placeholders = [window.i18n('enterPassword')];
break;
default:
placeholders = [window.i18n('createPassword'), window.i18n('confirmPassword')];
}
@ -72,6 +75,8 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
? 'changePassword'
: passwordAction === 'remove'
? 'removePassword'
: passwordAction === 'enter'
? 'passwordViewTitle'
: 'setPassword';
return (
@ -89,7 +94,7 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
onKeyUp={this.onPasswordInput}
data-testid="password-input"
/>
{passwordAction !== 'remove' && (
{passwordAction !== 'enter' && passwordAction !== 'remove' && (
<input
type="password"
id="password-modal-input-confirm"
@ -110,7 +115,6 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
</div>
<SpacerSM />
{this.showError()}
<div className="session-modal__button-group">
<SessionButton
@ -119,12 +123,14 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
buttonType={SessionButtonType.Simple}
onClick={this.setPassword}
/>
<SessionButton
text={window.i18n('cancel')}
buttonColor={passwordAction !== 'remove' ? SessionButtonColor.Danger : undefined}
buttonType={SessionButtonType.Simple}
onClick={this.closeDialog}
/>
{passwordAction !== 'enter' && (
<SessionButton
text={window.i18n('cancel')}
buttonColor={passwordAction !== 'remove' ? SessionButtonColor.Danger : undefined}
buttonType={SessionButtonType.Simple}
onClick={this.closeDialog}
/>
)}
</div>
</SessionWrapperModal>
);
@ -141,18 +147,9 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
}
private showError() {
const message = this.state.error;
return (
<>
{message && (
<>
<div className="session-label danger">{message}</div>
<SpacerLG />
</>
)}
</>
);
if (this.state.error) {
ToastUtils.pushToastError('enterPasswordErrorToast', this.state.error);
}
}
/**
@ -166,6 +163,7 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
this.setState({
error: errorFirstInput,
});
this.showError();
return false;
}
return true;
@ -182,6 +180,7 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
this.setState({
error: window.i18n('setPasswordInvalid'),
});
this.showError();
return;
}
await window.setPassword(enteredPassword, null);
@ -211,6 +210,7 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
this.setState({
error: window.i18n('passwordsDoNotMatch'),
});
this.showError();
return;
}
@ -219,6 +219,7 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
this.setState({
error: window.i18n('changePasswordInvalid'),
});
this.showError();
return;
}
await window.setPassword(newPassword, oldPassword);
@ -240,6 +241,7 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
this.setState({
error: window.i18n('removePasswordInvalid'),
});
this.showError();
return;
}
await window.setPassword(null, oldPassword);
@ -254,6 +256,25 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
this.closeDialog();
}
private async handleActionEnter(enteredPassword: string) {
// be sure the password is valid
if (!this.validatePassword(enteredPassword)) {
return;
}
const isValidWithStoredInDB = Boolean(await this.validatePasswordHash(enteredPassword));
if (!isValidWithStoredInDB) {
this.setState({
error: window.i18n('invalidPassword'),
});
this.showError();
return;
}
this.props.onOk();
this.closeDialog();
}
// tslint:disable-next-line: cyclomatic-complexity
private async setPassword() {
const { passwordAction } = this.props;
@ -285,6 +306,10 @@ export class SessionPasswordDialog extends React.Component<Props, State> {
await this.handleActionRemove(firstPasswordEntered);
return;
}
case 'enter': {
await this.handleActionEnter(firstPasswordEntered);
return;
}
default:
throw missingCaseError(passwordAction);
}

@ -9,13 +9,26 @@ import { SessionNotificationGroupSettings } from './SessionNotificationGroupSett
import { CategoryConversations } from './section/CategoryConversations';
import { SettingsCategoryPrivacy } from './section/CategoryPrivacy';
import { SettingsCategoryAppearance } from './section/CategoryAppearance';
import { SessionButton, SessionButtonType } from '../basic/SessionButton';
import { Data } from '../../data/data';
import { matchesHash } from '../../util/passwordUtils';
import { SettingsCategoryPermissions } from './section/CategoryPermissions';
import { SettingsCategoryHelp } from './section/CategoryHelp';
import styled from 'styled-components';
import { ToastUtils } from '../../session/utils';
import { sessionPassword } from '../../state/ducks/modalDialog';
import { PasswordAction } from '../dialog/SessionPasswordDialog';
export function displayPasswordModal(
passwordAction: PasswordAction,
onPasswordUpdated: (action: string) => void
) {
window.inboxStore?.dispatch(
sessionPassword({
passwordAction,
onOk: () => {
onPasswordUpdated(passwordAction);
},
})
);
}
export function getMediaPermissionsSettings() {
return window.getSettingValue('media-permissions');
@ -92,58 +105,6 @@ const SessionInfo = () => {
);
};
const StyledPasswordInput = styled.input`
width: 100%;
background: var(--text-box-background-color);
color: var(--text-box-text-user-color);
padding: var(--margins-xs) var(--margins-md);
margin-bottom: var(--margins-lg);
outline: none;
border: 1px solid var(--border-color);
border-radius: 7px;
text-align: center;
font-size: 16px;
font-family: var(--font-default);
::placeholder {
color: var(--text-box-text-control-color);
}
`;
const StyledH3 = styled.h3`
padding: 0px;
margin-bottom: var(--margins-lg);
`;
const PasswordLock = ({
validatePasswordLock,
}: {
validatePasswordLock: () => Promise<boolean>;
}) => {
return (
<div className="session-settings__password-lock">
<div className="session-settings__password-lock-box">
<StyledH3>{window.i18n('passwordViewTitle')}</StyledH3>
<StyledPasswordInput
type="password"
id="password-lock-input"
defaultValue=""
placeholder={window.i18n('enterPassword')}
data-testid="password-lock-input"
autoFocus={true}
/>
<SessionButton
buttonType={SessionButtonType.Simple}
text={window.i18n('done')}
onClick={validatePasswordLock}
/>
</div>
</div>
);
};
const SettingInCategory = (props: {
category: SessionSettingCategory;
hasPassword: boolean;
@ -216,38 +177,23 @@ export class SessionSettingsView extends React.Component<SettingsViewProps, Stat
});
}
public componentDidMount() {
window.addEventListener('keyup', this.onKeyUp);
}
public componentWillUnmount() {
window.removeEventListener('keyup', this.onKeyUp);
}
public async validatePasswordLock() {
const enteredPassword = String(
(document.getElementById('password-lock-input') as HTMLInputElement)?.value
);
if (!enteredPassword) {
ToastUtils.pushToastError('validatePassword', window.i18n('noGivenPassword'));
return false;
}
// Check if the password matches the hash we have stored
const hash = await Data.getPasswordHash();
if (hash && !matchesHash(enteredPassword, hash)) {
ToastUtils.pushToastError('validatePassword', window.i18n('invalidPassword'));
return false;
public componentDidUpdate(_: SettingsViewProps, _prevState: State) {
const oldShouldRenderPasswordLock = _prevState.shouldLockSettings && _prevState.hasPassword;
const newShouldRenderPasswordLock = this.state.shouldLockSettings && this.state.hasPassword;
if (
newShouldRenderPasswordLock &&
newShouldRenderPasswordLock !== oldShouldRenderPasswordLock
) {
displayPasswordModal('enter', action => {
if (action === 'enter') {
// Unlocked settings
this.setState({
shouldLockSettings: false,
});
}
});
}
// Unlocked settings
this.setState({
shouldLockSettings: false,
});
return true;
}
public render() {
@ -256,22 +202,23 @@ export class SessionSettingsView extends React.Component<SettingsViewProps, Stat
return (
<div className="session-settings">
<SettingsHeader category={category} />
<StyledSettingsView>
{shouldRenderPasswordLock ? (
<PasswordLock validatePasswordLock={this.validatePasswordLock} />
) : (
<StyledSettingsList ref={this.settingsViewRef}>
<SettingInCategory
category={category}
onPasswordUpdated={this.onPasswordUpdated}
hasPassword={Boolean(this.state.hasPassword)}
/>
</StyledSettingsList>
)}
<SessionInfo />
</StyledSettingsView>
{shouldRenderPasswordLock ? (
<></>
) : (
<>
<SettingsHeader category={category} />
<StyledSettingsView>
<StyledSettingsList ref={this.settingsViewRef}>
<SettingInCategory
category={category}
onPasswordUpdated={this.onPasswordUpdated}
hasPassword={Boolean(this.state.hasPassword)}
/>
</StyledSettingsList>
<SessionInfo />
</StyledSettingsView>
</>
)}
</div>
);
}
@ -290,14 +237,4 @@ export class SessionSettingsView extends React.Component<SettingsViewProps, Stat
});
}
}
private async onKeyUp(event: any) {
const lockPasswordVisible = Boolean(document.getElementById('password-lock-input'));
if (event.key === 'Enter' && lockPasswordVisible) {
await this.validatePasswordLock();
}
event.preventDefault();
}
}

@ -4,27 +4,13 @@ import useUpdate from 'react-use/lib/useUpdate';
import { Data, hasLinkPreviewPopupBeenDisplayed } from '../../../data/data';
import { SettingsKey } from '../../../data/settings-key';
import { ConversationTypeEnum } from '../../../models/conversationAttributes';
import { sessionPassword, updateConfirmModal } from '../../../state/ducks/modalDialog';
import { updateConfirmModal } from '../../../state/ducks/modalDialog';
import { SessionButtonColor } from '../../basic/SessionButton';
import { SpacerLG } from '../../basic/Text';
import { TypingBubble } from '../../conversation/TypingBubble';
import { PasswordAction } from '../../dialog/SessionPasswordDialog';
import { SessionSettingButtonItem, SessionToggleWithDescription } from '../SessionSettingListItem';
function displayPasswordModal(
passwordAction: PasswordAction,
onPasswordUpdated: (action: string) => void
) {
window.inboxStore?.dispatch(
sessionPassword({
passwordAction,
onOk: () => {
onPasswordUpdated(passwordAction);
},
})
);
}
import { displayPasswordModal } from '../SessionSettings';
async function toggleLinkPreviews() {
const newValue = !window.getSettingValue(SettingsKey.settingsLinkPreview);

@ -51,7 +51,7 @@ test.describe('Password checks', () => {
);
// Type password into input field
await typeIntoInput(window, 'password-lock-input', testPassword);
await typeIntoInput(window, 'password-input', testPassword);
// Click OK
await clickOnMatchingText(window, 'OK');
// Change password
@ -99,7 +99,7 @@ test.describe('Password checks', () => {
// Click OK
await window.keyboard.press('Enter');
// Type password into input field
await typeIntoInput(window, 'password-lock-input', testPassword);
await typeIntoInput(window, 'password-input', testPassword);
await window.keyboard.press('Delete');
// Click OK
await clickOnMatchingText(window, 'OK');
@ -108,7 +108,7 @@ test.describe('Password checks', () => {
// // Click on settings tab
await clickOnTestIdWithText(window, 'settings-section');
// // Try with incorrect password
await typeIntoInput(window, 'password-lock-input', '0000');
await typeIntoInput(window, 'password-input', '0000');
await window.keyboard.press('Delete');
// Confirm
await clickOnMatchingText(window, 'OK');

Loading…
Cancel
Save