Send alternate numbers to CDSI

Co-authored-by: yash-signal <yash@signal.org>
This commit is contained in:
automated-signal 2024-12-03 10:25:43 -06:00 committed by GitHub
parent cbeb51a68b
commit 8edb054874
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 116 additions and 28 deletions

8
package-lock.json generated
View file

@ -49,7 +49,7 @@
"form-data": "4.0.1", "form-data": "4.0.1",
"fs-extra": "11.2.0", "fs-extra": "11.2.0",
"fuse.js": "6.5.3", "fuse.js": "6.5.3",
"google-libphonenumber": "3.2.38", "google-libphonenumber": "^3.2.39",
"got": "11.8.5", "got": "11.8.5",
"heic-convert": "2.1.0", "heic-convert": "2.1.0",
"humanize-duration": "3.27.1", "humanize-duration": "3.27.1",
@ -17417,9 +17417,9 @@
"dev": true "dev": true
}, },
"node_modules/google-libphonenumber": { "node_modules/google-libphonenumber": {
"version": "3.2.38", "version": "3.2.39",
"resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.38.tgz", "resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.39.tgz",
"integrity": "sha512-t/K0dsVmA0gMMVLJgcMeB9g1Ar4ANVWfkY+AJGSdfyJ2Ay7Bu8ceLYpUlC6FZSilZgaF1qbkM9tZydGBEBHqAg==", "integrity": "sha512-dpCbkY6ZxHXIHEFDwSir/gPBWkn22e2EixBv47guVs/NE8+qd35f1yu+fxQ8awRnHEXC60uhcPM9mbqmrD6nmw==",
"license": "(MIT AND Apache-2.0)", "license": "(MIT AND Apache-2.0)",
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"

View file

@ -133,7 +133,7 @@
"form-data": "4.0.1", "form-data": "4.0.1",
"fs-extra": "11.2.0", "fs-extra": "11.2.0",
"fuse.js": "6.5.3", "fuse.js": "6.5.3",
"google-libphonenumber": "3.2.38", "google-libphonenumber": "3.2.39",
"got": "11.8.5", "got": "11.8.5",
"heic-convert": "2.1.0", "heic-convert": "2.1.0",
"humanize-duration": "3.27.1", "humanize-duration": "3.27.1",

View file

@ -1402,15 +1402,15 @@ export class ConversationController {
async _forgetE164(e164: string): Promise<void> { async _forgetE164(e164: string): Promise<void> {
const { server } = window.textsecure; const { server } = window.textsecure;
strictAssert(server, 'Server must be initialized'); strictAssert(server, 'Server must be initialized');
const { entries: serviceIdMap } = await getServiceIdsForE164s(server, [ const { entries: serviceIdMap, transformedE164s } =
e164, await getServiceIdsForE164s(server, [e164]);
]);
const pni = serviceIdMap.get(e164)?.pni; const e164ToUse = transformedE164s.get(e164) ?? e164;
const pni = serviceIdMap.get(e164ToUse)?.pni;
log.info(`ConversationController: forgetting e164=${e164} pni=${pni}`); log.info(`ConversationController: forgetting e164=${e164ToUse} pni=${pni}`);
const convos = [this.get(e164), this.get(pni)]; const convos = [this.get(e164ToUse), this.get(pni)];
for (const convo of convos) { for (const convo of convos) {
if (!convo) { if (!convo) {

View file

@ -93,17 +93,17 @@ function checkForAccount(
log.info(`checkForAccount: looking ${phoneNumber} up on server`); log.info(`checkForAccount: looking ${phoneNumber} up on server`);
try { try {
const { entries: serviceIdLookup } = await getServiceIdsForE164s(server, [ const { entries: serviceIdLookup, transformedE164s } =
phoneNumber, await getServiceIdsForE164s(server, [phoneNumber]);
]); const phoneNumberToUse = transformedE164s.get(phoneNumber) ?? phoneNumber;
const maybePair = serviceIdLookup.get(phoneNumber); const maybePair = serviceIdLookup.get(phoneNumberToUse);
if (maybePair) { if (maybePair) {
const { conversation: maybeMerged } = const { conversation: maybeMerged } =
window.ConversationController.maybeMergeContacts({ window.ConversationController.maybeMergeContacts({
aci: maybePair.aci, aci: maybePair.aci,
pni: maybePair.pni, pni: maybePair.pni,
e164: phoneNumber, e164: phoneNumberToUse,
reason: 'checkForAccount', reason: 'checkForAccount',
}); });
serviceId = maybeMerged.getServiceId(); serviceId = maybeMerged.getServiceId();

View file

@ -27,7 +27,8 @@ export async function updateConversationsWithUuidLookup({
return; return;
} }
const { entries: serverLookup } = await getServiceIdsForE164s(server, e164s); const { entries: serverLookup, transformedE164s } =
await getServiceIdsForE164s(server, e164s);
await Promise.all( await Promise.all(
conversations.map(async conversation => { conversations.map(async conversation => {
@ -38,13 +39,14 @@ export async function updateConversationsWithUuidLookup({
let finalConversation: ConversationModel; let finalConversation: ConversationModel;
const pairFromServer = serverLookup.get(e164); const e164ToUse = transformedE164s.get(e164) ?? e164;
const pairFromServer = serverLookup.get(e164ToUse);
if (pairFromServer) { if (pairFromServer) {
const { conversation: maybeFinalConversation } = const { conversation: maybeFinalConversation } =
conversationController.maybeMergeContacts({ conversationController.maybeMergeContacts({
aci: pairFromServer.aci, aci: pairFromServer.aci,
pni: pairFromServer.pni, pni: pairFromServer.pni,
e164, e164: e164ToUse,
reason: 'updateConversationsWithUuidLookup', reason: 'updateConversationsWithUuidLookup',
}); });
assertDev( assertDev(

View file

@ -6,11 +6,73 @@ import type { WebAPIType } from '../textsecure/WebAPI';
import type { AciString } from '../types/ServiceId'; import type { AciString } from '../types/ServiceId';
import * as log from '../logging/log'; import * as log from '../logging/log';
import { isDirectConversation, isMe } from './whatTypeOfConversation'; import { isDirectConversation, isMe } from './whatTypeOfConversation';
import { parseNumber } from './libphonenumberUtil';
type PhoneNumberTransformation = {
oldPattern: RegExp;
newPattern: RegExp;
oldToNew: (e164: string) => string;
newToOld: (e164: string) => string;
};
const PHONE_TRANSFORMATIONS: Record<string, PhoneNumberTransformation> = {
'229': {
// Benin
oldPattern: /^\+229\d{8}$/,
newPattern: /^\+22901\d{8}$/,
oldToNew: (e164: string) => e164.replace(/^\+229(\d{8})$/, '+22901$1'),
newToOld: (e164: string) => e164.replace(/^\+22901(\d{8})$/, '+229$1'),
},
'52': {
// Mexico
oldPattern: /^\+521\d{10}$/,
newPattern: /^\+52\d{10}$/,
oldToNew: (e164: string) => e164.replace(/^\+521(\d{10})$/, '+52$1'),
newToOld: (e164: string) => e164.replace(/^\+52(\d{10})$/, '+521$1'),
},
'54': {
// Argentina
oldPattern: /^\+54\d{10}$/,
newPattern: /^\+549\d{10}$/,
oldToNew: (e164: string) => e164.replace(/^\+54(\d{10})$/, '+549$1'),
newToOld: (e164: string) => e164.replace(/^\+549(\d{10})$/, '+54$1'),
},
};
type ReturnType = CDSResponseType & {
// Maps from provided E164 phone numbers to their alternate representations
// found in CDSI. If a E164 appears as a key in this map, you should use the
// corresponding E164 value for any subsequent operations, as that's
// the format stored in CDSI's database.
transformedE164s: Map<string, string>;
};
export async function getServiceIdsForE164s( export async function getServiceIdsForE164s(
server: Pick<WebAPIType, 'cdsLookup'>, server: Pick<WebAPIType, 'cdsLookup'>,
e164s: ReadonlyArray<string> e164s: ReadonlyArray<string>
): Promise<CDSResponseType> { ): Promise<ReturnType> {
const expandedE164s = new Set(e164s);
const transformationMap = new Map<string, string>();
for (const e164 of e164s) {
const parsedNumber = parseNumber(e164);
if (parsedNumber.isValidNumber && parsedNumber.countryCode) {
const transform = PHONE_TRANSFORMATIONS[parsedNumber.countryCode];
if (transform) {
if (transform.oldPattern.test(e164)) {
const newFormat = transform.oldToNew(e164);
expandedE164s.add(newFormat);
transformationMap.set(e164, newFormat);
} else if (transform.newPattern.test(e164)) {
const oldFormat = transform.newToOld(e164);
expandedE164s.add(oldFormat);
transformationMap.set(e164, oldFormat);
}
}
}
}
// Note: these have no relationship to supplied e164s. We just provide // Note: these have no relationship to supplied e164s. We just provide
// all available information to the server so that it could return as many // all available information to the server so that it could return as many
// ACI+PNI+E164 matches as possible. // ACI+PNI+E164 matches as possible.
@ -35,16 +97,40 @@ export async function getServiceIdsForE164s(
acisAndAccessKeys.push({ aci, accessKey }); acisAndAccessKeys.push({ aci, accessKey });
} }
const expandedE164sArray = Array.from(expandedE164s);
log.info( log.info(
`getServiceIdsForE164s(${e164s}): acis=${acisAndAccessKeys.length} ` + `getServiceIdsForE164s(${expandedE164sArray}): acis=${acisAndAccessKeys.length} ` +
`accessKeys=${acisAndAccessKeys.length}` `accessKeys=${acisAndAccessKeys.length}`
); );
return server.cdsLookup({ const response = await server.cdsLookup({
e164s, e164s: expandedE164sArray,
acisAndAccessKeys, acisAndAccessKeys,
returnAcisWithoutUaks: false, returnAcisWithoutUaks: false,
useLibsignal: window.Signal.RemoteConfig.isEnabled( useLibsignal: window.Signal.RemoteConfig.isEnabled(
'desktop.cdsiViaLibsignal' 'desktop.cdsiViaLibsignal'
), ),
}); });
const e164sWithVariantsInCdsi = new Map(
Array.from(transformationMap).filter(([providedE164, alternateE164]) => {
if (
response.entries.has(providedE164) &&
response.entries.has(alternateE164)
) {
log.warn(`both ${providedE164} and ${alternateE164} are in CDSI`);
return false;
}
if (response.entries.has(alternateE164)) {
return true;
}
return false;
})
);
return {
...response,
transformedE164s: e164sWithVariantsInCdsi,
};
} }

View file

@ -71,18 +71,18 @@ export async function lookupConversationWithoutServiceId(
try { try {
let conversationId: string | undefined; let conversationId: string | undefined;
if (options.type === 'e164') { if (options.type === 'e164') {
const { entries: serverLookup } = await getServiceIdsForE164s(server, [ const { entries: serverLookup, transformedE164s } =
options.e164, await getServiceIdsForE164s(server, [options.e164]);
]); const e164ToUse = transformedE164s.get(options.e164) ?? options.e164;
const maybePair = serverLookup.get(options.e164); const maybePair = serverLookup.get(e164ToUse);
if (maybePair) { if (maybePair) {
const { conversation } = const { conversation } =
window.ConversationController.maybeMergeContacts({ window.ConversationController.maybeMergeContacts({
aci: maybePair.aci, aci: maybePair.aci,
pni: maybePair.pni, pni: maybePair.pni,
e164: options.e164, e164: e164ToUse,
reason: 'startNewConversationWithoutUuid(e164)', reason: 'startNewConversationWithoutUuid(e164)',
}); });
conversationId = conversation?.id; conversationId = conversation?.id;