Skip to content

Commit

Permalink
Use libsignal-client for username validation
Browse files Browse the repository at this point in the history
  • Loading branch information
indutny-signal committed May 24, 2023
1 parent 3ff390e commit c0663ed
Show file tree
Hide file tree
Showing 8 changed files with 35 additions and 68 deletions.
Expand Up @@ -98,11 +98,12 @@ export function ChooseGroupMembersModal({
}: PropsType): JSX.Element {
const [focusRef] = useRestoreFocus();

const parsedUsername = getUsernameFromSearch(searchTerm);
let username: string | undefined;
let isUsernameChecked = false;
let isUsernameVisible = false;
if (isUsernamesEnabled) {
username = getUsernameFromSearch(searchTerm);
username = parsedUsername;

isUsernameChecked = selectedContacts.some(
contact => contact.username === username
Expand All @@ -114,7 +115,7 @@ export function ChooseGroupMembersModal({
}

let phoneNumber: ParsedE164Type | undefined;
if (!username) {
if (!parsedUsername) {
phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
}

Expand Down
14 changes: 7 additions & 7 deletions ts/components/leftPane/LeftPaneChooseGroupMembersHelper.tsx
Expand Up @@ -91,13 +91,13 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
isShowingRecommendedGroupSizeModal;
this.searchTerm = searchTerm;

if (isUsernamesEnabled) {
const username = getUsernameFromSearch(searchTerm);
const isVisible = this.candidateContacts.every(
contact => contact.username !== username
);
const username = getUsernameFromSearch(searchTerm);
const isUsernameVisible =
username !== undefined &&
this.candidateContacts.every(contact => contact.username !== username);

if (isVisible) {
if (isUsernamesEnabled) {
if (isUsernameVisible) {
this.username = username;
}

Expand All @@ -109,7 +109,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
}

const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
if (!this.username && phoneNumber) {
if (!isUsernameVisible && phoneNumber) {
this.isPhoneNumberChecked =
phoneNumber.isValid &&
selectedContacts.some(contact => contact.e164 === phoneNumber.e164);
Expand Down
12 changes: 6 additions & 6 deletions ts/components/leftPane/LeftPaneComposeHelper.tsx
Expand Up @@ -68,20 +68,20 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
this.searchTerm = searchTerm;
this.uuidFetchState = uuidFetchState;

const username = getUsernameFromSearch(this.searchTerm);

if (isUsernamesEnabled) {
this.username = getUsernameFromSearch(this.searchTerm);
this.username = username;
this.isUsernameVisible =
isUsernamesEnabled &&
Boolean(this.username) &&
this.composeContacts.every(
contact => contact.username !== this.username
);
Boolean(username) &&
this.composeContacts.every(contact => contact.username !== username);
} else {
this.isUsernameVisible = false;
}

const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
if (!this.username && phoneNumber) {
if (!username && phoneNumber) {
this.phoneNumber = phoneNumber;
this.isPhoneNumberVisible = this.composeContacts.every(
contact => contact.e164 !== phoneNumber.e164
Expand Down
2 changes: 1 addition & 1 deletion ts/test-mock/pnp/username_test.ts
Expand Up @@ -258,7 +258,7 @@ describe('pnp/username', function needsName() {
await window.locator('button[aria-label="New chat"]').click();

const searchInput = window.locator('.module-SearchInput__container input');
await searchInput.type(`@${CARL_USERNAME}`);
await searchInput.type(CARL_USERNAME);

debug('starting lookup');
await window.locator(`div.ListTile >> "${CARL_USERNAME}"`).click();
Expand Down
Expand Up @@ -185,7 +185,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
const helper = new LeftPaneChooseGroupMembersHelper({
...defaults,
candidateContacts: [],
searchTerm: 'signal',
searchTerm: 'signal.01',
selectedContacts: [],
});

Expand All @@ -195,7 +195,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
);
assert.deepEqual(helper.getRow(1), {
type: RowType.UsernameCheckbox,
username: 'signal',
username: 'signal.01',
isChecked: false,
isFetching: false,
});
Expand Down
14 changes: 7 additions & 7 deletions ts/test-node/components/leftPane/LeftPaneComposeHelper_test.ts
Expand Up @@ -88,7 +88,7 @@ describe('LeftPaneComposeHelper', () => {
composeContacts: [getDefaultConversation(), getDefaultConversation()],
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
regionCode: 'US',
searchTerm: 'someone',
searchTerm: 'someone.01',
isUsernamesEnabled: true,
uuidFetchState: {},
}).getRowCount(),
Expand All @@ -102,7 +102,7 @@ describe('LeftPaneComposeHelper', () => {
composeContacts: [getDefaultConversation(), getDefaultConversation()],
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
regionCode: 'US',
searchTerm: 'someone',
searchTerm: 'someone.54321',
isUsernamesEnabled: false,
uuidFetchState: {},
}).getRowCount(),
Expand All @@ -116,7 +116,7 @@ describe('LeftPaneComposeHelper', () => {
composeContacts: [],
composeGroups: [],
regionCode: 'US',
searchTerm: 'foobar',
searchTerm: 'foobar.01',
isUsernamesEnabled: true,
uuidFetchState: {},
}).getRowCount(),
Expand All @@ -127,7 +127,7 @@ describe('LeftPaneComposeHelper', () => {
composeContacts: [getDefaultConversation(), getDefaultConversation()],
composeGroups: [],
regionCode: 'US',
searchTerm: 'foobar',
searchTerm: 'foobar.01',
isUsernamesEnabled: true,
uuidFetchState: {},
}).getRowCount(),
Expand All @@ -138,7 +138,7 @@ describe('LeftPaneComposeHelper', () => {
composeContacts: [getDefaultConversation(), getDefaultConversation()],
composeGroups: [getDefaultGroupListItem()],
regionCode: 'US',
searchTerm: 'foobar',
searchTerm: 'foobar.01',
isUsernamesEnabled: true,
uuidFetchState: {},
}).getRowCount(),
Expand Down Expand Up @@ -166,7 +166,7 @@ describe('LeftPaneComposeHelper', () => {
composeContacts: [],
composeGroups: [],
regionCode: 'US',
searchTerm: 'someone',
searchTerm: 'someone.02',
isUsernamesEnabled: true,
uuidFetchState: {},
}).getRowCount(),
Expand Down Expand Up @@ -346,7 +346,7 @@ describe('LeftPaneComposeHelper', () => {
});

it('returns just a "find by username" header if no results', () => {
const username = 'someone';
const username = 'someone.02';

const helper = new LeftPaneComposeHelper({
composeContacts: [],
Expand Down
31 changes: 3 additions & 28 deletions ts/test-node/types/Username_test.ts
Expand Up @@ -10,38 +10,13 @@ describe('Username', () => {
const { getUsernameFromSearch } = Username;

it('matches invalid username searches', () => {
assert.strictEqual(getUsernameFromSearch('use'), 'use');
assert.strictEqual(
getUsernameFromSearch('username9012345678901234567'),
'username9012345678901234567'
);
assert.isUndefined(getUsernameFromSearch('use'));
assert.isUndefined(getUsernameFromSearch('username9012345678901234567'));
});

it('matches valid username searches', () => {
assert.strictEqual(getUsernameFromSearch('username_34'), 'username_34');
assert.strictEqual(getUsernameFromSearch('u5ername'), 'u5ername');
assert.strictEqual(getUsernameFromSearch('username.12'), 'username.12');
assert.strictEqual(getUsernameFromSearch('user'), 'user');
assert.strictEqual(
getUsernameFromSearch('username901234567890123456'),
'username901234567890123456'
);
});

it('matches valid and invalid usernames with @ prefix', () => {
assert.strictEqual(getUsernameFromSearch('@username!'), 'username!');
assert.strictEqual(getUsernameFromSearch('@1username'), '1username');
assert.strictEqual(getUsernameFromSearch('@username_34'), 'username_34');
assert.strictEqual(getUsernameFromSearch('@username.34'), 'username.34');
assert.strictEqual(getUsernameFromSearch('@u5ername'), 'u5ername');
});

it('matches valid and invalid usernames with @ suffix', () => {
assert.strictEqual(getUsernameFromSearch('username!@'), 'username!');
assert.strictEqual(getUsernameFromSearch('1username@'), '1username');
assert.strictEqual(getUsernameFromSearch('username_34@'), 'username_34');
assert.strictEqual(getUsernameFromSearch('username.34@'), 'username.34');
assert.strictEqual(getUsernameFromSearch('u5ername@'), 'u5ername');
assert.strictEqual(getUsernameFromSearch('xyz.568'), 'xyz.568');
});

it('does not match something that looks like a phone number', () => {
Expand Down
21 changes: 6 additions & 15 deletions ts/types/Username.ts
@@ -1,6 +1,8 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import { usernames } from '@signalapp/libsignal-client';

export type UsernameReservationType = Readonly<{
username: string;
previousUsername: string | undefined;
Expand All @@ -24,23 +26,12 @@ export enum ConfirmUsernameResult {
}

export function getUsernameFromSearch(searchTerm: string): string | undefined {
// Search term contains username if it:
// - Is a valid username with or without a discriminator
// - Starts with @
// - Ends with @
const match = searchTerm.match(
/^(?:(?<valid>[a-z_][0-9a-z_]*(?:\.\d*)?)|@(?<start>.*?)@?|@?(?<end>.*?)?@)$/
);
if (!match) {
return undefined;
}

const { groups } = match;
if (!groups) {
try {
usernames.hash(searchTerm);
return searchTerm;
} catch {
return undefined;
}

return (groups.valid || groups.start || groups.end) ?? undefined;
}

export function getNickname(username: string): string | undefined {
Expand Down

0 comments on commit c0663ed

Please sign in to comment.