Use libsignal-client for username validation
This commit is contained in:
parent
3ff390e1c4
commit
c0663ed57c
8 changed files with 35 additions and 68 deletions
|
@ -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
|
||||
|
@ -114,7 +115,7 @@ export function ChooseGroupMembersModal({
|
|||
}
|
||||
|
||||
let phoneNumber: ParsedE164Type | undefined;
|
||||
if (!username) {
|
||||
if (!parsedUsername) {
|
||||
phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -185,7 +185,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
|
|||
const helper = new LeftPaneChooseGroupMembersHelper({
|
||||
...defaults,
|
||||
candidateContacts: [],
|
||||
searchTerm: 'signal',
|
||||
searchTerm: 'signal.01',
|
||||
selectedContacts: [],
|
||||
});
|
||||
|
||||
|
@ -195,7 +195,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
|
|||
);
|
||||
assert.deepEqual(helper.getRow(1), {
|
||||
type: RowType.UsernameCheckbox,
|
||||
username: 'signal',
|
||||
username: 'signal.01',
|
||||
isChecked: false,
|
||||
isFetching: false,
|
||||
});
|
||||
|
|
|
@ -88,7 +88,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'someone',
|
||||
searchTerm: 'someone.01',
|
||||
isUsernamesEnabled: true,
|
||||
uuidFetchState: {},
|
||||
}).getRowCount(),
|
||||
|
@ -102,7 +102,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'someone',
|
||||
searchTerm: 'someone.54321',
|
||||
isUsernamesEnabled: false,
|
||||
uuidFetchState: {},
|
||||
}).getRowCount(),
|
||||
|
@ -116,7 +116,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
composeContacts: [],
|
||||
composeGroups: [],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'foobar',
|
||||
searchTerm: 'foobar.01',
|
||||
isUsernamesEnabled: true,
|
||||
uuidFetchState: {},
|
||||
}).getRowCount(),
|
||||
|
@ -127,7 +127,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'foobar',
|
||||
searchTerm: 'foobar.01',
|
||||
isUsernamesEnabled: true,
|
||||
uuidFetchState: {},
|
||||
}).getRowCount(),
|
||||
|
@ -138,7 +138,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||
composeGroups: [getDefaultGroupListItem()],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'foobar',
|
||||
searchTerm: 'foobar.01',
|
||||
isUsernamesEnabled: true,
|
||||
uuidFetchState: {},
|
||||
}).getRowCount(),
|
||||
|
@ -166,7 +166,7 @@ describe('LeftPaneComposeHelper', () => {
|
|||
composeContacts: [],
|
||||
composeGroups: [],
|
||||
regionCode: 'US',
|
||||
searchTerm: 'someone',
|
||||
searchTerm: 'someone.02',
|
||||
isUsernamesEnabled: true,
|
||||
uuidFetchState: {},
|
||||
}).getRowCount(),
|
||||
|
@ -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: [],
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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;
|
||||
|
@ -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) {
|
||||
try {
|
||||
usernames.hash(searchTerm);
|
||||
return searchTerm;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { groups } = match;
|
||||
if (!groups) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (groups.valid || groups.start || groups.end) ?? undefined;
|
||||
}
|
||||
|
||||
export function getNickname(username: string): string | undefined {
|
||||
|
|
Loading…
Reference in a new issue