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 {
|
}: PropsType): JSX.Element {
|
||||||
const [focusRef] = useRestoreFocus();
|
const [focusRef] = useRestoreFocus();
|
||||||
|
|
||||||
|
const parsedUsername = getUsernameFromSearch(searchTerm);
|
||||||
let username: string | undefined;
|
let username: string | undefined;
|
||||||
let isUsernameChecked = false;
|
let isUsernameChecked = false;
|
||||||
let isUsernameVisible = false;
|
let isUsernameVisible = false;
|
||||||
if (isUsernamesEnabled) {
|
if (isUsernamesEnabled) {
|
||||||
username = getUsernameFromSearch(searchTerm);
|
username = parsedUsername;
|
||||||
|
|
||||||
isUsernameChecked = selectedContacts.some(
|
isUsernameChecked = selectedContacts.some(
|
||||||
contact => contact.username === username
|
contact => contact.username === username
|
||||||
|
@ -114,7 +115,7 @@ export function ChooseGroupMembersModal({
|
||||||
}
|
}
|
||||||
|
|
||||||
let phoneNumber: ParsedE164Type | undefined;
|
let phoneNumber: ParsedE164Type | undefined;
|
||||||
if (!username) {
|
if (!parsedUsername) {
|
||||||
phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
|
phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,13 +91,13 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
||||||
isShowingRecommendedGroupSizeModal;
|
isShowingRecommendedGroupSizeModal;
|
||||||
this.searchTerm = searchTerm;
|
this.searchTerm = searchTerm;
|
||||||
|
|
||||||
if (isUsernamesEnabled) {
|
const username = getUsernameFromSearch(searchTerm);
|
||||||
const username = getUsernameFromSearch(searchTerm);
|
const isUsernameVisible =
|
||||||
const isVisible = this.candidateContacts.every(
|
username !== undefined &&
|
||||||
contact => contact.username !== username
|
this.candidateContacts.every(contact => contact.username !== username);
|
||||||
);
|
|
||||||
|
|
||||||
if (isVisible) {
|
if (isUsernamesEnabled) {
|
||||||
|
if (isUsernameVisible) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ export class LeftPaneChooseGroupMembersHelper extends LeftPaneHelper<LeftPaneCho
|
||||||
}
|
}
|
||||||
|
|
||||||
const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
|
const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
|
||||||
if (!this.username && phoneNumber) {
|
if (!isUsernameVisible && phoneNumber) {
|
||||||
this.isPhoneNumberChecked =
|
this.isPhoneNumberChecked =
|
||||||
phoneNumber.isValid &&
|
phoneNumber.isValid &&
|
||||||
selectedContacts.some(contact => contact.e164 === phoneNumber.e164);
|
selectedContacts.some(contact => contact.e164 === phoneNumber.e164);
|
||||||
|
|
|
@ -68,20 +68,20 @@ export class LeftPaneComposeHelper extends LeftPaneHelper<LeftPaneComposePropsTy
|
||||||
this.searchTerm = searchTerm;
|
this.searchTerm = searchTerm;
|
||||||
this.uuidFetchState = uuidFetchState;
|
this.uuidFetchState = uuidFetchState;
|
||||||
|
|
||||||
|
const username = getUsernameFromSearch(this.searchTerm);
|
||||||
|
|
||||||
if (isUsernamesEnabled) {
|
if (isUsernamesEnabled) {
|
||||||
this.username = getUsernameFromSearch(this.searchTerm);
|
this.username = username;
|
||||||
this.isUsernameVisible =
|
this.isUsernameVisible =
|
||||||
isUsernamesEnabled &&
|
isUsernamesEnabled &&
|
||||||
Boolean(this.username) &&
|
Boolean(username) &&
|
||||||
this.composeContacts.every(
|
this.composeContacts.every(contact => contact.username !== username);
|
||||||
contact => contact.username !== this.username
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.isUsernameVisible = false;
|
this.isUsernameVisible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
|
const phoneNumber = parseAndFormatPhoneNumber(searchTerm, regionCode);
|
||||||
if (!this.username && phoneNumber) {
|
if (!username && phoneNumber) {
|
||||||
this.phoneNumber = phoneNumber;
|
this.phoneNumber = phoneNumber;
|
||||||
this.isPhoneNumberVisible = this.composeContacts.every(
|
this.isPhoneNumberVisible = this.composeContacts.every(
|
||||||
contact => contact.e164 !== phoneNumber.e164
|
contact => contact.e164 !== phoneNumber.e164
|
||||||
|
|
|
@ -258,7 +258,7 @@ describe('pnp/username', function needsName() {
|
||||||
await window.locator('button[aria-label="New chat"]').click();
|
await window.locator('button[aria-label="New chat"]').click();
|
||||||
|
|
||||||
const searchInput = window.locator('.module-SearchInput__container input');
|
const searchInput = window.locator('.module-SearchInput__container input');
|
||||||
await searchInput.type(`@${CARL_USERNAME}`);
|
await searchInput.type(CARL_USERNAME);
|
||||||
|
|
||||||
debug('starting lookup');
|
debug('starting lookup');
|
||||||
await window.locator(`div.ListTile >> "${CARL_USERNAME}"`).click();
|
await window.locator(`div.ListTile >> "${CARL_USERNAME}"`).click();
|
||||||
|
|
|
@ -185,7 +185,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
|
||||||
const helper = new LeftPaneChooseGroupMembersHelper({
|
const helper = new LeftPaneChooseGroupMembersHelper({
|
||||||
...defaults,
|
...defaults,
|
||||||
candidateContacts: [],
|
candidateContacts: [],
|
||||||
searchTerm: 'signal',
|
searchTerm: 'signal.01',
|
||||||
selectedContacts: [],
|
selectedContacts: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ describe('LeftPaneChooseGroupMembersHelper', () => {
|
||||||
);
|
);
|
||||||
assert.deepEqual(helper.getRow(1), {
|
assert.deepEqual(helper.getRow(1), {
|
||||||
type: RowType.UsernameCheckbox,
|
type: RowType.UsernameCheckbox,
|
||||||
username: 'signal',
|
username: 'signal.01',
|
||||||
isChecked: false,
|
isChecked: false,
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -88,7 +88,7 @@ describe('LeftPaneComposeHelper', () => {
|
||||||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||||
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
||||||
regionCode: 'US',
|
regionCode: 'US',
|
||||||
searchTerm: 'someone',
|
searchTerm: 'someone.01',
|
||||||
isUsernamesEnabled: true,
|
isUsernamesEnabled: true,
|
||||||
uuidFetchState: {},
|
uuidFetchState: {},
|
||||||
}).getRowCount(),
|
}).getRowCount(),
|
||||||
|
@ -102,7 +102,7 @@ describe('LeftPaneComposeHelper', () => {
|
||||||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||||
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
composeGroups: [getDefaultGroupListItem(), getDefaultGroupListItem()],
|
||||||
regionCode: 'US',
|
regionCode: 'US',
|
||||||
searchTerm: 'someone',
|
searchTerm: 'someone.54321',
|
||||||
isUsernamesEnabled: false,
|
isUsernamesEnabled: false,
|
||||||
uuidFetchState: {},
|
uuidFetchState: {},
|
||||||
}).getRowCount(),
|
}).getRowCount(),
|
||||||
|
@ -116,7 +116,7 @@ describe('LeftPaneComposeHelper', () => {
|
||||||
composeContacts: [],
|
composeContacts: [],
|
||||||
composeGroups: [],
|
composeGroups: [],
|
||||||
regionCode: 'US',
|
regionCode: 'US',
|
||||||
searchTerm: 'foobar',
|
searchTerm: 'foobar.01',
|
||||||
isUsernamesEnabled: true,
|
isUsernamesEnabled: true,
|
||||||
uuidFetchState: {},
|
uuidFetchState: {},
|
||||||
}).getRowCount(),
|
}).getRowCount(),
|
||||||
|
@ -127,7 +127,7 @@ describe('LeftPaneComposeHelper', () => {
|
||||||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||||
composeGroups: [],
|
composeGroups: [],
|
||||||
regionCode: 'US',
|
regionCode: 'US',
|
||||||
searchTerm: 'foobar',
|
searchTerm: 'foobar.01',
|
||||||
isUsernamesEnabled: true,
|
isUsernamesEnabled: true,
|
||||||
uuidFetchState: {},
|
uuidFetchState: {},
|
||||||
}).getRowCount(),
|
}).getRowCount(),
|
||||||
|
@ -138,7 +138,7 @@ describe('LeftPaneComposeHelper', () => {
|
||||||
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
composeContacts: [getDefaultConversation(), getDefaultConversation()],
|
||||||
composeGroups: [getDefaultGroupListItem()],
|
composeGroups: [getDefaultGroupListItem()],
|
||||||
regionCode: 'US',
|
regionCode: 'US',
|
||||||
searchTerm: 'foobar',
|
searchTerm: 'foobar.01',
|
||||||
isUsernamesEnabled: true,
|
isUsernamesEnabled: true,
|
||||||
uuidFetchState: {},
|
uuidFetchState: {},
|
||||||
}).getRowCount(),
|
}).getRowCount(),
|
||||||
|
@ -166,7 +166,7 @@ describe('LeftPaneComposeHelper', () => {
|
||||||
composeContacts: [],
|
composeContacts: [],
|
||||||
composeGroups: [],
|
composeGroups: [],
|
||||||
regionCode: 'US',
|
regionCode: 'US',
|
||||||
searchTerm: 'someone',
|
searchTerm: 'someone.02',
|
||||||
isUsernamesEnabled: true,
|
isUsernamesEnabled: true,
|
||||||
uuidFetchState: {},
|
uuidFetchState: {},
|
||||||
}).getRowCount(),
|
}).getRowCount(),
|
||||||
|
@ -346,7 +346,7 @@ describe('LeftPaneComposeHelper', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns just a "find by username" header if no results', () => {
|
it('returns just a "find by username" header if no results', () => {
|
||||||
const username = 'someone';
|
const username = 'someone.02';
|
||||||
|
|
||||||
const helper = new LeftPaneComposeHelper({
|
const helper = new LeftPaneComposeHelper({
|
||||||
composeContacts: [],
|
composeContacts: [],
|
||||||
|
|
|
@ -10,38 +10,13 @@ describe('Username', () => {
|
||||||
const { getUsernameFromSearch } = Username;
|
const { getUsernameFromSearch } = Username;
|
||||||
|
|
||||||
it('matches invalid username searches', () => {
|
it('matches invalid username searches', () => {
|
||||||
assert.strictEqual(getUsernameFromSearch('use'), 'use');
|
assert.isUndefined(getUsernameFromSearch('use'));
|
||||||
assert.strictEqual(
|
assert.isUndefined(getUsernameFromSearch('username9012345678901234567'));
|
||||||
getUsernameFromSearch('username9012345678901234567'),
|
|
||||||
'username9012345678901234567'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('matches valid username searches', () => {
|
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('username.12'), 'username.12');
|
||||||
assert.strictEqual(getUsernameFromSearch('user'), 'user');
|
assert.strictEqual(getUsernameFromSearch('xyz.568'), 'xyz.568');
|
||||||
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');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not match something that looks like a phone number', () => {
|
it('does not match something that looks like a phone number', () => {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { usernames } from '@signalapp/libsignal-client';
|
||||||
|
|
||||||
export type UsernameReservationType = Readonly<{
|
export type UsernameReservationType = Readonly<{
|
||||||
username: string;
|
username: string;
|
||||||
previousUsername: string | undefined;
|
previousUsername: string | undefined;
|
||||||
|
@ -24,23 +26,12 @@ export enum ConfirmUsernameResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUsernameFromSearch(searchTerm: string): string | undefined {
|
export function getUsernameFromSearch(searchTerm: string): string | undefined {
|
||||||
// Search term contains username if it:
|
try {
|
||||||
// - Is a valid username with or without a discriminator
|
usernames.hash(searchTerm);
|
||||||
// - Starts with @
|
return searchTerm;
|
||||||
// - Ends with @
|
} catch {
|
||||||
const match = searchTerm.match(
|
|
||||||
/^(?:(?<valid>[a-z_][0-9a-z_]*(?:\.\d*)?)|@(?<start>.*?)@?|@?(?<end>.*?)?@)$/
|
|
||||||
);
|
|
||||||
if (!match) {
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { groups } = match;
|
|
||||||
if (!groups) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (groups.valid || groups.start || groups.end) ?? undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNickname(username: string): string | undefined {
|
export function getNickname(username: string): string | undefined {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue