Skip to content

Commit 4e913f1

Browse files
committed
fix: displayName allowed length based on bytes rather than char
1 parent e2c3cce commit 4e913f1

File tree

9 files changed

+82
-36
lines changed

9 files changed

+82
-36
lines changed

_locales/en/messages.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@
360360
"notificationPreview": "Preview",
361361
"recoveryPhraseEmpty": "Enter your recovery phrase",
362362
"displayNameEmpty": "Please enter a display name",
363+
"displayNameTooLong": "Display name is too long",
363364
"members": "$count$ members",
364365
"join": "Join",
365366
"joinOpenGroup": "Join Community",

ts/components/dialog/EditProfileDialog.tsx

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import { uploadOurAvatar } from '../../interactions/conversationInteractions';
1616
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
1717
import { SessionSpinner } from '../basic/SessionSpinner';
1818
import { SessionIconButton } from '../icon';
19-
import { MAX_USERNAME_LENGTH } from '../registration/RegistrationStages';
2019
import { SessionWrapperModal } from '../SessionWrapperModal';
2120
import { pickFileForAvatar } from '../../types/attachments/VisualAttachment';
2221
import { sanitizeSessionUsername } from '../../session/utils/String';
2322
import { setLastProfileUpdateTimestamp } from '../../util/storage';
2423
import { ConversationTypeEnum } from '../../models/conversationAttributes';
24+
import { MAX_USERNAME_BYTES } from '../../session/constants';
2525

2626
interface State {
2727
profileName: string;
@@ -214,7 +214,7 @@ export class EditProfileDialog extends React.Component<{}, State> {
214214
value={this.state.profileName}
215215
placeholder={placeholderText}
216216
onChange={this.onNameEdited}
217-
maxLength={MAX_USERNAME_LENGTH}
217+
maxLength={MAX_USERNAME_BYTES}
218218
tabIndex={0}
219219
required={true}
220220
aria-required={true}
@@ -240,10 +240,18 @@ export class EditProfileDialog extends React.Component<{}, State> {
240240
}
241241

242242
private onNameEdited(event: ChangeEvent<HTMLInputElement>) {
243-
const newName = sanitizeSessionUsername(event.target.value);
244-
this.setState({
245-
profileName: newName,
246-
});
243+
const displayName = event.target.value;
244+
try {
245+
const newName = sanitizeSessionUsername(displayName);
246+
this.setState({
247+
profileName: newName,
248+
});
249+
} catch (e) {
250+
this.setState({
251+
profileName: displayName,
252+
});
253+
ToastUtils.pushToastError('nameTooLong', window.i18n('displayNameTooLong'));
254+
}
247255
}
248256

249257
private onKeyUp(event: any) {
@@ -266,26 +274,37 @@ export class EditProfileDialog extends React.Component<{}, State> {
266274
*/
267275
private onClickOK() {
268276
const { newAvatarObjectUrl, profileName } = this.state;
269-
const newName = profileName ? profileName.trim() : '';
277+
try {
278+
const newName = profileName ? profileName.trim() : '';
279+
280+
if (newName.length === 0 || newName.length > MAX_USERNAME_BYTES) {
281+
return;
282+
}
283+
284+
// this throw if the length in bytes is too long
285+
const sanitizedName = sanitizeSessionUsername(newName);
286+
const trimName = sanitizedName.trim();
287+
288+
this.setState(
289+
{
290+
profileName: trimName,
291+
loading: true,
292+
},
293+
async () => {
294+
await commitProfileEdits(newName, newAvatarObjectUrl);
295+
this.setState({
296+
loading: false,
297+
298+
mode: 'default',
299+
updatedProfileName: this.state.profileName,
300+
});
301+
}
302+
);
303+
} catch (e) {
304+
ToastUtils.pushToastError('nameTooLong', window.i18n('displayNameTooLong'));
270305

271-
if (newName.length === 0 || newName.length > MAX_USERNAME_LENGTH) {
272306
return;
273307
}
274-
275-
this.setState(
276-
{
277-
loading: true,
278-
},
279-
async () => {
280-
await commitProfileEdits(newName, newAvatarObjectUrl);
281-
this.setState({
282-
loading: false,
283-
284-
mode: 'default',
285-
updatedProfileName: this.state.profileName,
286-
});
287-
}
288-
);
289308
}
290309

291310
private closeDialog() {

ts/components/registration/RegistrationStages.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
import { fromHex } from '../../session/utils/String';
1818
import { setSignInByLinking, setSignWithRecoveryPhrase, Storage } from '../../util/storage';
1919

20-
export const MAX_USERNAME_LENGTH = 26;
2120
// tslint:disable: use-simple-attributes
2221

2322
export async function resetRegistration() {

ts/components/registration/RegistrationUserDetails.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import classNames from 'classnames';
22
import React from 'react';
3+
import { MAX_USERNAME_BYTES } from '../../session/constants';
34
import { SessionInput } from '../basic/SessionInput';
4-
import { MAX_USERNAME_LENGTH } from './RegistrationStages';
55

66
const DisplayNameInput = (props: {
77
stealAutoFocus?: boolean;
@@ -17,7 +17,7 @@ const DisplayNameInput = (props: {
1717
type="text"
1818
placeholder={window.i18n('enterDisplayName')}
1919
value={props.displayName}
20-
maxLength={MAX_USERNAME_LENGTH}
20+
maxLength={MAX_USERNAME_BYTES}
2121
onValueChanged={props.onDisplayNameChanged}
2222
onEnterPressed={props.handlePressEnter}
2323
inputDataTestId="display-name-input"

ts/components/registration/SignInTab.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useContext, useState } from 'react';
2+
import { ToastUtils } from '../../session/utils';
23
import { sanitizeSessionUsername } from '../../session/utils/String';
34
import { Flex } from '../basic/Flex';
45
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
@@ -148,10 +149,16 @@ export const SignInTab = () => {
148149
displayName={displayName}
149150
handlePressEnter={continueYourSession}
150151
onDisplayNameChanged={(name: string) => {
151-
const sanitizedName = sanitizeSessionUsername(name);
152-
const trimName = sanitizedName.trim();
153-
setDisplayName(sanitizedName);
154-
setDisplayNameError(!trimName ? window.i18n('displayNameEmpty') : undefined);
152+
try {
153+
const sanitizedName = sanitizeSessionUsername(name);
154+
const trimName = sanitizedName.trim();
155+
setDisplayName(sanitizedName);
156+
setDisplayNameError(!trimName ? window.i18n('displayNameEmpty') : undefined);
157+
} catch (e) {
158+
setDisplayName(name);
159+
setDisplayNameError(window.i18n('displayNameTooLong'));
160+
ToastUtils.pushToastError('toolong', window.i18n('displayNameTooLong'));
161+
}
155162
}}
156163
onSeedChanged={(seed: string) => {
157164
setRecoveryPhrase(seed);

ts/components/registration/SignUpTab.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useContext, useEffect, useState } from 'react';
2+
import { ToastUtils } from '../../session/utils';
23
import { sanitizeSessionUsername } from '../../session/utils/String';
34
import { Flex } from '../basic/Flex';
45
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
@@ -144,10 +145,16 @@ export const SignUpTab = () => {
144145
displayName={displayName}
145146
handlePressEnter={signUpWithDetails}
146147
onDisplayNameChanged={(name: string) => {
147-
const sanitizedName = sanitizeSessionUsername(name);
148-
const trimName = sanitizedName.trim();
149-
setDisplayName(sanitizedName);
150-
setDisplayNameError(!trimName ? window.i18n('displayNameEmpty') : undefined);
148+
try {
149+
const sanitizedName = sanitizeSessionUsername(name);
150+
const trimName = sanitizedName.trim();
151+
setDisplayName(sanitizedName);
152+
setDisplayNameError(!trimName ? window.i18n('displayNameEmpty') : undefined);
153+
} catch (e) {
154+
setDisplayName(name);
155+
setDisplayNameError(window.i18n('displayNameTooLong'));
156+
ToastUtils.pushToastError('toolong', window.i18n('displayNameTooLong'));
157+
}
151158
}}
152159
stealAutoFocus={true}
153160
/>

ts/session/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,5 @@ export const UI = {
6060
export const QUOTED_TEXT_MAX_LENGTH = 150;
6161

6262
export const DEFAULT_RECENT_REACTS = ['😂', '🥰', '😢', '😡', '😮', '😈'];
63+
64+
export const MAX_USERNAME_BYTES = 64;

ts/session/utils/String.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ByteBuffer from 'bytebuffer';
2+
import { MAX_USERNAME_BYTES } from '../constants';
23

34
export type Encoding = 'base64' | 'hex' | 'binary' | 'utf8';
45
export type BufferType = ByteBuffer | Buffer | ArrayBuffer | Uint8Array;
@@ -54,10 +55,19 @@ const forbiddenDisplayCharRegex = /\uFFD2*/g;
5455
*
5556
* This function removes any forbidden char from a given display name.
5657
* This does not trim it as otherwise, a user cannot type User A as when he hits the space, it gets trimmed right away.
57-
* The trimming should hence happen after calling this and on saving the display name
58+
* The trimming should hence happen after calling this and on saving the display name.
59+
*
60+
* This functions makes sure that the MAX_USERNAME_BYTES is verified for utf8 byte length
5861
* @param inputName the input to sanitize
5962
* @returns a sanitized string, untrimmed
6063
*/
6164
export const sanitizeSessionUsername = (inputName: string) => {
62-
return inputName.replace(forbiddenDisplayCharRegex, '');
65+
const validChars = inputName.replace(forbiddenDisplayCharRegex, '');
66+
67+
const lengthBytes = encode(validChars, 'utf8').byteLength;
68+
if (lengthBytes > MAX_USERNAME_BYTES) {
69+
throw new Error('Display name is too long');
70+
}
71+
72+
return validChars;
6373
};

ts/types/LocalizerKeys.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ export type LocalizerKeys =
491491
| 'trustThisContactDialogDescription'
492492
| 'unknownCountry'
493493
| 'searchFor...'
494+
| 'displayNameTooLong'
494495
| 'joinedTheGroup'
495496
| 'editGroupName'
496497
| 'reportIssue';

0 commit comments

Comments
 (0)