Include ACI+Access Keys pairs with CDSI requests

This commit is contained in:
Fedor Indutny 2022-08-18 13:44:53 -07:00 committed by GitHub
parent 13046dc020
commit 757af2cbbe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 145 additions and 144 deletions

View file

@ -9,7 +9,7 @@
"directoryV2PublicKey": null, "directoryV2PublicKey": null,
"directoryV2CodeHashes": null, "directoryV2CodeHashes": null,
"directoryV3Url": "https://cdsi.staging.signal.org", "directoryV3Url": "https://cdsi.staging.signal.org",
"directoryV3MRENCLAVE": "7b75dd6e862decef9b37132d54be082441917a7790e82fe44f9cf653de03a75f", "directoryV3MRENCLAVE": "ddc7b9b1cbcc932e24b9905e26c4ecbea3f9b7effd033f9e96488c2e8449f64e",
"cdn": { "cdn": {
"0": "https://cdn-staging.signal.org", "0": "https://cdn-staging.signal.org",
"2": "https://cdn2-staging.signal.org" "2": "https://cdn2-staging.signal.org"

View file

@ -27,6 +27,7 @@ import { QualifiedAddress } from './types/QualifiedAddress';
import { sleep } from './util/sleep'; import { sleep } from './util/sleep';
import { isNotNil } from './util/isNotNil'; import { isNotNil } from './util/isNotNil';
import { MINUTE, SECOND } from './util/durations'; import { MINUTE, SECOND } from './util/durations';
import { getUuidsForE164s } from './util/getUuidsForE164s';
type ConvoMatchType = type ConvoMatchType =
| { | {
@ -1104,7 +1105,9 @@ 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 { [e164]: pni } = await server.getUuidsForE164s([e164]); const uuidMap = await getUuidsForE164s(server, [e164]);
const pni = uuidMap.get(e164)?.pni;
log.info(`ConversationController: forgetting e164=${e164} pni=${pni}`); log.info(`ConversationController: forgetting e164=${e164} pni=${pni}`);

View file

@ -2128,15 +2128,16 @@ export async function startApp(): Promise<void> {
!c.isEverUnregistered() !c.isEverUnregistered()
) )
); );
strictAssert(window.textsecure.server, 'server must be initialized');
await updateConversationsWithUuidLookup({ await updateConversationsWithUuidLookup({
conversationController: window.ConversationController, conversationController: window.ConversationController,
conversations: lonelyE164Conversations, conversations: lonelyE164Conversations,
messaging: window.textsecure.messaging, server: window.textsecure.server,
}); });
} catch (error) { } catch (error) {
log.error( log.error(
'connect: Error fetching UUIDs for lonely e164s:', 'connect: Error fetching UUIDs for lonely e164s:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }

View file

@ -1033,8 +1033,8 @@ export class ConversationModel extends window.Backbone
} }
async fetchSMSOnlyUUID(): Promise<void> { async fetchSMSOnlyUUID(): Promise<void> {
const { messaging } = window.textsecure; const { server } = window.textsecure;
if (!messaging) { if (!server) {
return; return;
} }
if (!this.isSMSOnly()) { if (!this.isSMSOnly()) {
@ -1053,7 +1053,7 @@ export class ConversationModel extends window.Backbone
await updateConversationsWithUuidLookup({ await updateConversationsWithUuidLookup({
conversationController: window.ConversationController, conversationController: window.ConversationController,
conversations: [this], conversations: [this],
messaging, server,
}); });
} finally { } finally {
// No redux update here // No redux update here

View file

@ -837,10 +837,6 @@ async function removeAllSignedPreKeys(): Promise<void> {
// Items // Items
const ITEM_SPECS: Partial<Record<ItemKeyType, ObjectMappingSpecType>> = { const ITEM_SPECS: Partial<Record<ItemKeyType, ObjectMappingSpecType>> = {
senderCertificate: ['value.serialized'],
senderCertificateNoE164: ['value.serialized'],
subscriberId: ['value'],
profileKey: ['value'],
identityKeyMap: { identityKeyMap: {
key: 'value', key: 'value',
valueSpec: { valueSpec: {
@ -848,6 +844,10 @@ const ITEM_SPECS: Partial<Record<ItemKeyType, ObjectMappingSpecType>> = {
valueSpec: ['privKey', 'pubKey'], valueSpec: ['privKey', 'pubKey'],
}, },
}, },
profileKey: ['value'],
senderCertificate: ['value.serialized'],
senderCertificateNoE164: ['value.serialized'],
subscriberId: ['value'],
}; };
async function createOrUpdateItem<K extends ItemKeyType>( async function createOrUpdateItem<K extends ItemKeyType>(
data: ItemType<K> data: ItemType<K>

View file

@ -8,6 +8,7 @@ import * as log from '../../logging/log';
import type { StateType as RootStateType } from '../reducer'; import type { StateType as RootStateType } from '../reducer';
import type { UUIDStringType } from '../../types/UUID'; import type { UUIDStringType } from '../../types/UUID';
import { getUuidsForE164s } from '../../util/getUuidsForE164s';
import type { NoopActionType } from './noop'; import type { NoopActionType } from './noop';
@ -44,7 +45,8 @@ function checkForAccount(
AccountUpdateActionType | NoopActionType AccountUpdateActionType | NoopActionType
> { > {
return async (dispatch, getState) => { return async (dispatch, getState) => {
if (!window.textsecure.messaging) { const { server } = window.textsecure;
if (!server) {
dispatch({ dispatch({
type: 'NOOP', type: 'NOOP',
payload: null, payload: null,
@ -77,16 +79,24 @@ function checkForAccount(
type: 'NOOP', type: 'NOOP',
payload: null, payload: null,
}); });
return;
} }
let uuid: UUIDStringType | undefined; let uuid: UUIDStringType | undefined;
log.info(`checkForAccount: looking ${phoneNumber} up on server`); log.info(`checkForAccount: looking ${phoneNumber} up on server`);
try { try {
const uuidLookup = await window.textsecure.messaging.getUuidsForE164s([ const uuidLookup = await getUuidsForE164s(server, [phoneNumber]);
phoneNumber, const maybePair = uuidLookup.get(phoneNumber);
]);
uuid = uuidLookup[phoneNumber] || undefined; if (maybePair) {
uuid = window.ConversationController.maybeMergeContacts({
aci: maybePair.aci,
pni: maybePair.pni,
e164: phoneNumber,
reason: 'checkForAccount',
})?.get('uuid');
}
} catch (error) { } catch (error) {
log.error('checkForAccount:', Errors.toLogFormat(error)); log.error('checkForAccount:', Errors.toLogFormat(error));
} }

View file

@ -7,7 +7,7 @@ import { assert } from 'chai';
import sinon from 'sinon'; import sinon from 'sinon';
import { ConversationModel } from '../models/conversations'; import { ConversationModel } from '../models/conversations';
import type { ConversationAttributesType } from '../model-types.d'; import type { ConversationAttributesType } from '../model-types.d';
import type SendMessage from '../textsecure/SendMessage'; import type { WebAPIType } from '../textsecure/WebAPI';
import { UUID } from '../types/UUID'; import { UUID } from '../types/UUID';
import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup'; import { updateConversationsWithUuidLookup } from '../updateConversationsWithUuidLookup';
@ -137,22 +137,19 @@ describe('updateConversationsWithUuidLookup', () => {
let sinonSandbox: sinon.SinonSandbox; let sinonSandbox: sinon.SinonSandbox;
let fakeGetUuidsForE164s: sinon.SinonStub; let fakeCdsLookup: sinon.SinonStub;
let fakeCheckAccountExistence: sinon.SinonStub; let fakeCheckAccountExistence: sinon.SinonStub;
let fakeMessaging: Pick< let fakeServer: Pick<WebAPIType, 'cdsLookup' | 'checkAccountExistence'>;
SendMessage,
'getUuidsForE164s' | 'checkAccountExistence'
>;
beforeEach(() => { beforeEach(() => {
sinonSandbox = sinon.createSandbox(); sinonSandbox = sinon.createSandbox();
sinonSandbox.stub(window.Signal.Data, 'updateConversation'); sinonSandbox.stub(window.Signal.Data, 'updateConversation');
fakeGetUuidsForE164s = sinonSandbox.stub().resolves({}); fakeCdsLookup = sinonSandbox.stub().resolves(new Map());
fakeCheckAccountExistence = sinonSandbox.stub().resolves(false); fakeCheckAccountExistence = sinonSandbox.stub().resolves(false);
fakeMessaging = { fakeServer = {
getUuidsForE164s: fakeGetUuidsForE164s, cdsLookup: fakeCdsLookup,
checkAccountExistence: fakeCheckAccountExistence, checkAccountExistence: fakeCheckAccountExistence,
}; };
}); });
@ -165,10 +162,10 @@ describe('updateConversationsWithUuidLookup', () => {
await updateConversationsWithUuidLookup({ await updateConversationsWithUuidLookup({
conversationController: new FakeConversationController(), conversationController: new FakeConversationController(),
conversations: [], conversations: [],
messaging: fakeMessaging, server: fakeServer,
}); });
sinon.assert.notCalled(fakeMessaging.getUuidsForE164s as sinon.SinonStub); sinon.assert.notCalled(fakeServer.cdsLookup as sinon.SinonStub);
}); });
it('does nothing when called with an array of conversations that lack E164s', async () => { it('does nothing when called with an array of conversations that lack E164s', async () => {
@ -178,10 +175,10 @@ describe('updateConversationsWithUuidLookup', () => {
createConversation(), createConversation(),
createConversation({ uuid: UUID.generate().toString() }), createConversation({ uuid: UUID.generate().toString() }),
], ],
messaging: fakeMessaging, server: fakeServer,
}); });
sinon.assert.notCalled(fakeMessaging.getUuidsForE164s as sinon.SinonStub); sinon.assert.notCalled(fakeServer.cdsLookup as sinon.SinonStub);
}); });
it('updates conversations with their UUID', async () => { it('updates conversations with their UUID', async () => {
@ -194,10 +191,12 @@ describe('updateConversationsWithUuidLookup', () => {
const uuid1 = UUID.generate().toString(); const uuid1 = UUID.generate().toString();
const uuid2 = UUID.generate().toString(); const uuid2 = UUID.generate().toString();
fakeGetUuidsForE164s.resolves({ fakeCdsLookup.resolves(
'+13215559876': uuid1, new Map([
'+16545559876': uuid2, ['+13215559876', { aci: uuid1, pni: undefined }],
}); ['+16545559876', { aci: uuid2, pni: undefined }],
])
);
await updateConversationsWithUuidLookup({ await updateConversationsWithUuidLookup({
conversationController: new FakeConversationController([ conversationController: new FakeConversationController([
@ -205,7 +204,7 @@ describe('updateConversationsWithUuidLookup', () => {
conversation2, conversation2,
]), ]),
conversations: [conversation1, conversation2], conversations: [conversation1, conversation2],
messaging: fakeMessaging, server: fakeServer,
}); });
assert.strictEqual(conversation1.get('uuid'), uuid1); assert.strictEqual(conversation1.get('uuid'), uuid1);
@ -219,12 +218,10 @@ describe('updateConversationsWithUuidLookup', () => {
'Test was not set up correctly' 'Test was not set up correctly'
); );
fakeGetUuidsForE164s.resolves({ '+13215559876': null });
await updateConversationsWithUuidLookup({ await updateConversationsWithUuidLookup({
conversationController: new FakeConversationController([conversation]), conversationController: new FakeConversationController([conversation]),
conversations: [conversation], conversations: [conversation],
messaging: fakeMessaging, server: fakeServer,
}); });
assert.approximately( assert.approximately(
@ -245,13 +242,12 @@ describe('updateConversationsWithUuidLookup', () => {
'Test was not set up correctly' 'Test was not set up correctly'
); );
fakeGetUuidsForE164s.resolves({ '+13215559876': null });
fakeCheckAccountExistence.resolves(true); fakeCheckAccountExistence.resolves(true);
await updateConversationsWithUuidLookup({ await updateConversationsWithUuidLookup({
conversationController: new FakeConversationController([conversation]), conversationController: new FakeConversationController([conversation]),
conversations: [conversation], conversations: [conversation],
messaging: fakeMessaging, server: fakeServer,
}); });
assert.strictEqual(conversation.get('uuid'), existingUuid); assert.strictEqual(conversation.get('uuid'), existingUuid);
@ -269,13 +265,12 @@ describe('updateConversationsWithUuidLookup', () => {
'Test was not set up correctly' 'Test was not set up correctly'
); );
fakeGetUuidsForE164s.resolves({ '+13215559876': null });
fakeCheckAccountExistence.resolves(false); fakeCheckAccountExistence.resolves(false);
await updateConversationsWithUuidLookup({ await updateConversationsWithUuidLookup({
conversationController: new FakeConversationController([conversation]), conversationController: new FakeConversationController([conversation]),
conversations: [conversation], conversations: [conversation],
messaging: fakeMessaging, server: fakeServer,
}); });
assert.isUndefined(conversation.get('uuid')); assert.isUndefined(conversation.get('uuid'));

View file

@ -671,9 +671,9 @@ export default class OutgoingMessage {
if (isValidUuid(identifier)) { if (isValidUuid(identifier)) {
// We're good! // We're good!
} else if (isValidNumber(identifier)) { } else if (isValidNumber(identifier)) {
if (!window.textsecure.messaging) { if (!window.textsecure.server) {
throw new Error( throw new Error(
'sendToIdentifier: window.textsecure.messaging is not available!' 'sendToIdentifier: window.textsecure.server is not available!'
); );
} }
@ -683,7 +683,7 @@ export default class OutgoingMessage {
conversations: [ conversations: [
window.ConversationController.getOrCreate(identifier, 'private'), window.ConversationController.getOrCreate(identifier, 'private'),
], ],
messaging: window.textsecure.messaging, server: window.textsecure.server,
}); });
const uuid = const uuid =

View file

@ -5,7 +5,6 @@
/* eslint-disable max-classes-per-file */ /* eslint-disable max-classes-per-file */
import { z } from 'zod'; import { z } from 'zod';
import type { Dictionary } from 'lodash';
import Long from 'long'; import Long from 'long';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
import type { PlaintextContent } from '@signalapp/libsignal-client'; import type { PlaintextContent } from '@signalapp/libsignal-client';
@ -25,7 +24,7 @@ import { SenderKeys } from '../LibSignalStores';
import type { LinkPreviewType } from '../types/message/LinkPreviews'; import type { LinkPreviewType } from '../types/message/LinkPreviews';
import { MIMETypeToString } from '../types/MIME'; import { MIMETypeToString } from '../types/MIME';
import type * as Attachment from '../types/Attachment'; import type * as Attachment from '../types/Attachment';
import type { UUID, UUIDStringType } from '../types/UUID'; import type { UUID } from '../types/UUID';
import type { import type {
ChallengeType, ChallengeType,
GetGroupLogOptionsType, GetGroupLogOptionsType,
@ -49,7 +48,6 @@ import type {
SendLogCallbackType, SendLogCallbackType,
} from './OutgoingMessage'; } from './OutgoingMessage';
import OutgoingMessage from './OutgoingMessage'; import OutgoingMessage from './OutgoingMessage';
import type { CDSResponseType } from './cds/Types.d';
import * as Bytes from '../Bytes'; import * as Bytes from '../Bytes';
import { getRandomBytes, getZeroes, encryptAttachment } from '../Crypto'; import { getRandomBytes, getZeroes, encryptAttachment } from '../Crypto';
import { import {
@ -2447,34 +2445,12 @@ export default class MessageSender {
return this.server.getProfile(uuid.toString(), options); return this.server.getProfile(uuid.toString(), options);
} }
async checkAccountExistence(uuid: UUID): Promise<boolean> {
return this.server.checkAccountExistence(uuid);
}
async getProfileForUsername( async getProfileForUsername(
username: string username: string
): ReturnType<WebAPIType['getProfileForUsername']> { ): ReturnType<WebAPIType['getProfileForUsername']> {
return this.server.getProfileForUsername(username); return this.server.getProfileForUsername(username);
} }
async getUuidsForE164s(
numbers: ReadonlyArray<string>
): Promise<Dictionary<UUIDStringType | null>> {
return this.server.getUuidsForE164s(numbers);
}
async getUuidsForE164sV2(
e164s: ReadonlyArray<string>,
acis: ReadonlyArray<UUIDStringType>,
accessKeys: ReadonlyArray<string>
): Promise<CDSResponseType> {
return this.server.getUuidsForE164sV2({
e164s,
acis,
accessKeys,
});
}
async getAvatar(path: string): Promise<ReturnType<WebAPIType['getAvatar']>> { async getAvatar(path: string): Promise<ReturnType<WebAPIType['getAvatar']>> {
return this.server.getAvatar(path); return this.server.getAvatar(path);
} }

View file

@ -11,7 +11,6 @@ import type { Response } from 'node-fetch';
import fetch from 'node-fetch'; import fetch from 'node-fetch';
import ProxyAgent from 'proxy-agent'; import ProxyAgent from 'proxy-agent';
import { Agent } from 'https'; import { Agent } from 'https';
import type { Dictionary } from 'lodash';
import { escapeRegExp, isNumber } from 'lodash'; import { escapeRegExp, isNumber } from 'lodash';
import is from '@sindresorhus/is'; import is from '@sindresorhus/is';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
@ -767,10 +766,10 @@ export type ConfirmCodeResultType = Readonly<{
deviceId?: number; deviceId?: number;
}>; }>;
export type GetUuidsForE164sV2OptionsType = Readonly<{ export type CdsLookupOptionsType = Readonly<{
e164s: ReadonlyArray<string>; e164s: ReadonlyArray<string>;
acis: ReadonlyArray<UUIDStringType>; acis?: ReadonlyArray<UUIDStringType>;
accessKeys: ReadonlyArray<string>; accessKeys?: ReadonlyArray<string>;
}>; }>;
type GetProfileCommonOptionsType = Readonly< type GetProfileCommonOptionsType = Readonly<
@ -812,6 +811,7 @@ export type GetGroupCredentialsResultType = Readonly<{
export type WebAPIType = { export type WebAPIType = {
startRegistration(): unknown; startRegistration(): unknown;
finishRegistration(baton: unknown): void; finishRegistration(baton: unknown): void;
cdsLookup: (options: CdsLookupOptionsType) => Promise<CDSResponseType>;
confirmCode: ( confirmCode: (
number: string, number: string,
code: string, code: string,
@ -880,12 +880,6 @@ export type WebAPIType = {
getStorageCredentials: MessageSender['getStorageCredentials']; getStorageCredentials: MessageSender['getStorageCredentials'];
getStorageManifest: MessageSender['getStorageManifest']; getStorageManifest: MessageSender['getStorageManifest'];
getStorageRecords: MessageSender['getStorageRecords']; getStorageRecords: MessageSender['getStorageRecords'];
getUuidsForE164s: (
e164s: ReadonlyArray<string>
) => Promise<Dictionary<UUIDStringType | null>>;
getUuidsForE164sV2: (
options: GetUuidsForE164sV2OptionsType
) => Promise<CDSResponseType>;
fetchLinkPreviewMetadata: ( fetchLinkPreviewMetadata: (
href: string, href: string,
abortSignal: AbortSignal abortSignal: AbortSignal
@ -1251,6 +1245,7 @@ export function initialize({
unregisterRequestHandler, unregisterRequestHandler,
authenticate, authenticate,
logout, logout,
cdsLookup,
checkAccountExistence, checkAccountExistence,
confirmCode, confirmCode,
createGroup, createGroup,
@ -1285,8 +1280,6 @@ export function initialize({
getStorageCredentials, getStorageCredentials,
getStorageManifest, getStorageManifest,
getStorageRecords, getStorageRecords,
getUuidsForE164s,
getUuidsForE164sV2,
makeProxiedRequest, makeProxiedRequest,
makeSfuRequest, makeSfuRequest,
modifyGroup, modifyGroup,
@ -2858,25 +2851,11 @@ export function initialize({
return socketManager.getProvisioningResource(handler); return socketManager.getProvisioningResource(handler);
} }
async function getUuidsForE164s( async function cdsLookup({
e164s: ReadonlyArray<string>
): Promise<Dictionary<UUIDStringType | null>> {
const map = await cds.request({
e164s, e164s,
}); acis = [],
accessKeys = [],
const result: Dictionary<UUIDStringType | null> = {}; }: CdsLookupOptionsType): Promise<CDSResponseType> {
for (const [key, value] of map) {
result[key] = value.pni ?? value.aci ?? null;
}
return result;
}
async function getUuidsForE164sV2({
e164s,
acis,
accessKeys,
}: GetUuidsForE164sV2OptionsType): Promise<CDSResponseType> {
return cds.request({ return cds.request({
e164s, e164s,
acis, acis,

View file

@ -85,18 +85,13 @@ export abstract class CDSSocketBase<
const aciUakPairs = new Array<Uint8Array>(); const aciUakPairs = new Array<Uint8Array>();
let version: 1 | 2; const version = 2;
if (acis) {
strictAssert(accessKeys, 'accessKeys are required when acis are present');
strictAssert( strictAssert(
acis.length === accessKeys.length, acis.length === accessKeys.length,
`Number of ACIs ${acis.length} is different ` + `Number of ACIs ${acis.length} is different ` +
`from number of access keys ${accessKeys.length}` `from number of access keys ${accessKeys.length}`
); );
version = 2;
for (let i = 0; i < acis.length; i += 1) { for (let i = 0; i < acis.length; i += 1) {
aciUakPairs.push( aciUakPairs.push(
Bytes.concatenate([ Bytes.concatenate([
@ -105,9 +100,6 @@ export abstract class CDSSocketBase<
]) ])
); );
} }
} else {
version = 1;
}
const request = Proto.CDSClientRequest.encode({ const request = Proto.CDSClientRequest.encode({
newE164s: Buffer.concat( newE164s: Buffer.concat(

View file

@ -17,7 +17,6 @@ import {
} from '../../Crypto'; } from '../../Crypto';
import { calculateAgreement, generateKeyPair } from '../../Curve'; import { calculateAgreement, generateKeyPair } from '../../Curve';
import * as Bytes from '../../Bytes'; import * as Bytes from '../../Bytes';
import { strictAssert } from '../../util/assert';
import { UUID } from '../../types/UUID'; import { UUID } from '../../types/UUID';
import type { CDSBaseOptionsType } from './CDSBase'; import type { CDSBaseOptionsType } from './CDSBase';
import { CDSBase } from './CDSBase'; import { CDSBase } from './CDSBase';
@ -125,11 +124,7 @@ function getSgxConstants() {
export class LegacyCDS extends CDSBase<LegacyCDSOptionsType> { export class LegacyCDS extends CDSBase<LegacyCDSOptionsType> {
public override async request({ public override async request({
e164s, e164s,
acis,
accessKeys,
}: CDSRequestOptionsType): Promise<CDSResponseType> { }: CDSRequestOptionsType): Promise<CDSResponseType> {
strictAssert(!acis && !accessKeys, 'LegacyCDS does not support PNP');
const directoryAuth = await this.getAuth(); const directoryAuth = await this.getAuth();
const attestationResult = await this.putAttestation(directoryAuth); const attestationResult = await this.putAttestation(directoryAuth);

View file

@ -17,7 +17,7 @@ export type CDSResponseType = ReadonlyMap<string, CDSResponseEntryType>;
export type CDSRequestOptionsType = Readonly<{ export type CDSRequestOptionsType = Readonly<{
e164s: ReadonlyArray<string>; e164s: ReadonlyArray<string>;
acis?: ReadonlyArray<UUIDStringType>; acis: ReadonlyArray<UUIDStringType>;
accessKeys?: ReadonlyArray<string>; accessKeys: ReadonlyArray<string>;
timeout?: number; timeout?: number;
}>; }>;

View file

@ -3,22 +3,22 @@
import type { ConversationController } from './ConversationController'; import type { ConversationController } from './ConversationController';
import type { ConversationModel } from './models/conversations'; import type { ConversationModel } from './models/conversations';
import type SendMessage from './textsecure/SendMessage'; import type { WebAPIType } from './textsecure/WebAPI';
import { assert } from './util/assert'; import { assert } from './util/assert';
import { getOwn } from './util/getOwn';
import { isNotNil } from './util/isNotNil'; import { isNotNil } from './util/isNotNil';
import { getUuidsForE164s } from './util/getUuidsForE164s';
export async function updateConversationsWithUuidLookup({ export async function updateConversationsWithUuidLookup({
conversationController, conversationController,
conversations, conversations,
messaging, server,
}: Readonly<{ }: Readonly<{
conversationController: Pick< conversationController: Pick<
ConversationController, ConversationController,
'maybeMergeContacts' | 'get' 'maybeMergeContacts' | 'get'
>; >;
conversations: ReadonlyArray<ConversationModel>; conversations: ReadonlyArray<ConversationModel>;
messaging: Pick<SendMessage, 'getUuidsForE164s' | 'checkAccountExistence'>; server: Pick<WebAPIType, 'cdsLookup' | 'checkAccountExistence'>;
}>): Promise<void> { }>): Promise<void> {
const e164s = conversations const e164s = conversations
.map(conversation => conversation.get('e164')) .map(conversation => conversation.get('e164'))
@ -27,7 +27,7 @@ export async function updateConversationsWithUuidLookup({
return; return;
} }
const serverLookup = await messaging.getUuidsForE164s(e164s); const serverLookup = await getUuidsForE164s(server, e164s);
await Promise.all( await Promise.all(
conversations.map(async conversation => { conversations.map(async conversation => {
@ -38,11 +38,12 @@ export async function updateConversationsWithUuidLookup({
let finalConversation: ConversationModel; let finalConversation: ConversationModel;
const uuidFromServer = getOwn(serverLookup, e164); const pairFromServer = serverLookup.get(e164);
if (uuidFromServer) { if (pairFromServer) {
const maybeFinalConversation = const maybeFinalConversation =
conversationController.maybeMergeContacts({ conversationController.maybeMergeContacts({
aci: uuidFromServer, aci: pairFromServer.aci,
pni: pairFromServer.pni,
e164, e164,
reason: 'updateConversationsWithUuidLookup', reason: 'updateConversationsWithUuidLookup',
}); });
@ -59,10 +60,8 @@ export async function updateConversationsWithUuidLookup({
// they can't be looked up by a phone number. Check that uuid still exists, // they can't be looked up by a phone number. Check that uuid still exists,
// and if not - drop it. // and if not - drop it.
let finalUuid = finalConversation.getUuid(); let finalUuid = finalConversation.getUuid();
if (!uuidFromServer && finalUuid) { if (!pairFromServer && finalUuid) {
const doesAccountExist = await messaging.checkAccountExistence( const doesAccountExist = await server.checkAccountExistence(finalUuid);
finalUuid
);
if (!doesAccountExist) { if (!doesAccountExist) {
finalConversation.updateUuid(undefined); finalConversation.updateUuid(undefined);
finalUuid = undefined; finalUuid = undefined;

View file

@ -0,0 +1,45 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { CDSResponseType } from '../textsecure/cds/Types.d';
import type { WebAPIType } from '../textsecure/WebAPI';
import type { UUIDStringType } from '../types/UUID';
import * as log from '../logging/log';
import { isDirectConversation, isMe } from './whatTypeOfConversation';
export async function getUuidsForE164s(
server: Pick<WebAPIType, 'cdsLookup'>,
e164s: ReadonlyArray<string>
): Promise<CDSResponseType> {
// Note: these have no relationship to supplied e164s. We just provide
// all available information to the server so that it could return as many
// ACI+PNI+E164 matches as possible.
const acis = new Array<UUIDStringType>();
const accessKeys = new Array<string>();
for (const convo of window.ConversationController.getAll()) {
if (!isDirectConversation(convo.attributes) || isMe(convo.attributes)) {
continue;
}
const aci = convo.getUuid();
if (!aci) {
continue;
}
convo.deriveAccessKeyIfNeeded();
const accessKey = convo.get('accessKey');
if (!accessKey) {
continue;
}
acis.push(aci.toString());
accessKeys.push(accessKey);
}
log.info(
`getUuidsForE164s(${e164s}): acis=${acis.length} ` +
`accessKeys=${accessKeys.length}`
);
return server.cdsLookup({ e164s, acis, accessKeys });
}

View file

@ -12,6 +12,7 @@ import { downloadAttachment } from './downloadAttachment';
import { generateSecurityNumber } from './safetyNumber'; import { generateSecurityNumber } from './safetyNumber';
import { getStringForProfileChange } from './getStringForProfileChange'; import { getStringForProfileChange } from './getStringForProfileChange';
import { getTextWithMentions } from './getTextWithMentions'; import { getTextWithMentions } from './getTextWithMentions';
import { getUuidsForE164s } from './getUuidsForE164s';
import { getUserAgent } from './getUserAgent'; import { getUserAgent } from './getUserAgent';
import { hasExpired } from './hasExpired'; import { hasExpired } from './hasExpired';
import { import {
@ -81,4 +82,5 @@ export {
toWebSafeBase64, toWebSafeBase64,
zkgroup, zkgroup,
expirationTimer, expirationTimer,
getUuidsForE164s,
}; };

View file

@ -13,6 +13,7 @@ import { HTTPError } from '../textsecure/Errors';
import { showToast } from './showToast'; import { showToast } from './showToast';
import { strictAssert } from './assert'; import { strictAssert } from './assert';
import type { UUIDFetchStateKeyType } from './uuidFetchState'; import type { UUIDFetchStateKeyType } from './uuidFetchState';
import { getUuidsForE164s } from './getUuidsForE164s';
export type LookupConversationWithoutUuidActionsType = Readonly<{ export type LookupConversationWithoutUuidActionsType = Readonly<{
lookupConversationWithoutUuid: typeof lookupConversationWithoutUuid; lookupConversationWithoutUuid: typeof lookupConversationWithoutUuid;
@ -62,19 +63,22 @@ export async function lookupConversationWithoutUuid(
const { showUserNotFoundModal, setIsFetchingUUID } = options; const { showUserNotFoundModal, setIsFetchingUUID } = options;
setIsFetchingUUID(identifier, true); setIsFetchingUUID(identifier, true);
const { messaging } = window.textsecure; const { server } = window.textsecure;
if (!messaging) { if (!server) {
throw new Error('messaging is not available!'); throw new Error('server is not available!');
} }
try { try {
let conversationId: string | undefined; let conversationId: string | undefined;
if (options.type === 'e164') { if (options.type === 'e164') {
const serverLookup = await messaging.getUuidsForE164s([options.e164]); const serverLookup = await getUuidsForE164s(server, [options.e164]);
if (serverLookup[options.e164]) { const maybePair = serverLookup.get(options.e164);
if (maybePair) {
const convo = window.ConversationController.maybeMergeContacts({ const convo = window.ConversationController.maybeMergeContacts({
aci: serverLookup[options.e164] || undefined, aci: maybePair.aci,
pni: maybePair.pni,
e164: options.e164, e164: options.e164,
reason: 'startNewConversationWithoutUuid(e164)', reason: 'startNewConversationWithoutUuid(e164)',
}); });