Include ACI+Access Keys pairs with CDSI requests
This commit is contained in:
parent
13046dc020
commit
757af2cbbe
17 changed files with 145 additions and 144 deletions
|
@ -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"
|
||||||
|
|
|
@ -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}`);
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
4
ts/textsecure/cds/Types.d.ts
vendored
4
ts/textsecure/cds/Types.d.ts
vendored
|
@ -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;
|
||||||
}>;
|
}>;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
45
ts/util/getUuidsForE164s.ts
Normal file
45
ts/util/getUuidsForE164s.ts
Normal 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 });
|
||||||
|
}
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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)',
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue