diff --git a/background.html b/background.html
index 885c94ea1..ec11b1664 100644
--- a/background.html
+++ b/background.html
@@ -222,10 +222,6 @@
>
-
diff --git a/js/rotate_signed_prekey_listener.js b/js/rotate_signed_prekey_listener.js
deleted file mode 100644
index 4304a7946..000000000
--- a/js/rotate_signed_prekey_listener.js
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright 2017-2020 Signal Messenger, LLC
-// SPDX-License-Identifier: AGPL-3.0-only
-
-/* global Whisper, storage, getAccountManager */
-
-// eslint-disable-next-line func-names
-(function () {
- window.Whisper = window.Whisper || {};
- const ROTATION_INTERVAL = 48 * 60 * 60 * 1000;
- let timeout;
-
- function scheduleNextRotation() {
- const now = Date.now();
- const nextTime = now + ROTATION_INTERVAL;
- storage.put('nextSignedKeyRotationTime', nextTime);
- }
-
- function scheduleRotationForNow() {
- const now = Date.now();
- storage.put('nextSignedKeyRotationTime', now);
- }
-
- async function run() {
- window.SignalContext.log.info('Rotating signed prekey...');
- try {
- await getAccountManager().rotateSignedPreKey();
- scheduleNextRotation();
- setTimeoutForNextRun();
- } catch (error) {
- window.SignalContext.log.error(
- 'rotateSignedPrekey() failed. Trying again in five minutes'
- );
- setTimeout(setTimeoutForNextRun, 5 * 60 * 1000);
- }
- }
-
- function runWhenOnline() {
- if (navigator.onLine) {
- run();
- } else {
- window.SignalContext.log.info(
- 'We are offline; keys will be rotated when we are next online'
- );
- const listener = () => {
- window.removeEventListener('online', listener);
- setTimeoutForNextRun();
- };
- window.addEventListener('online', listener);
- }
- }
-
- function setTimeoutForNextRun() {
- const now = Date.now();
- const time = storage.get('nextSignedKeyRotationTime', now);
-
- window.SignalContext.log.info(
- 'Next signed key rotation scheduled for',
- new Date(time).toISOString()
- );
-
- let waitTime = time - now;
- if (waitTime < 0) {
- waitTime = 0;
- }
-
- clearTimeout(timeout);
- timeout = setTimeout(runWhenOnline, waitTime);
- }
-
- let initComplete;
- Whisper.RotateSignedPreKeyListener = {
- init(events, newVersion) {
- if (initComplete) {
- window.SignalContext.log.info(
- 'Rotate signed prekey listener: Already initialized'
- );
- return;
- }
- initComplete = true;
-
- if (newVersion) {
- scheduleRotationForNow();
- setTimeoutForNextRun();
- } else {
- setTimeoutForNextRun();
- }
-
- events.on('timetravel', () => {
- if (window.Signal.Util.Registration.isDone()) {
- setTimeoutForNextRun();
- }
- });
- },
- };
-})();
diff --git a/preload.js b/preload.js
index e07cbdf5b..c82c5c919 100644
--- a/preload.js
+++ b/preload.js
@@ -14,6 +14,7 @@ try {
const _ = require('lodash');
const { strictAssert } = require('./ts/util/assert');
const { parseIntWithFallback } = require('./ts/util/parseIntWithFallback');
+ const { UUIDKind } = require('./ts/types/UUID');
// It is important to call this as early as possible
const { SignalContext } = require('./ts/windows/context');
@@ -185,7 +186,7 @@ try {
}
const ourUuid = window.textsecure.storage.user.getUuid();
- const ourPni = window.textsecure.storage.user.getPni();
+ const ourPni = window.textsecure.storage.user.getUuid(UUIDKind.PNI);
event.sender.send('additional-log-data-response', {
capabilities: ourCapabilities || {},
diff --git a/ts/SignalProtocolStore.ts b/ts/SignalProtocolStore.ts
index 4feb05fda..02e7922e4 100644
--- a/ts/SignalProtocolStore.ts
+++ b/ts/SignalProtocolStore.ts
@@ -375,7 +375,7 @@ export class SignalProtocolStore extends EventsMixin {
const id: PreKeyIdType = `${ourUuid.toString()}:${keyId}`;
try {
- this.trigger('removePreKey');
+ this.trigger('removePreKey', ourUuid);
} catch (error) {
log.error(
'removePreKey error triggering removePreKey:',
diff --git a/ts/background.ts b/ts/background.ts
index 9649df36f..85a888ddb 100644
--- a/ts/background.ts
+++ b/ts/background.ts
@@ -83,6 +83,7 @@ import type {
import { VerifiedEvent } from './textsecure/messageReceiverEvents';
import type { WebAPIType } from './textsecure/WebAPI';
import * as KeyChangeListener from './textsecure/KeyChangeListener';
+import { RotateSignedPreKeyListener } from './textsecure/RotateSignedPreKeyListener';
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
import { getSendOptions } from './util/getSendOptions';
import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff';
@@ -116,6 +117,8 @@ import { onRetryRequest, onDecryptionError } from './util/handleRetry';
import { themeChanged } from './shims/themeChanged';
import { createIPCEvents } from './util/createIPCEvents';
import { RemoveAllConfiguration } from './types/RemoveAllConfiguration';
+import type { UUID } from './types/UUID';
+import { UUIDKind } from './types/UUID';
import * as log from './logging/log';
import {
loadRecentEmojis,
@@ -498,8 +501,9 @@ export async function startApp(): Promise
{
window.document.title = window.getTitle();
KeyChangeListener.init(window.textsecure.storage.protocol);
- window.textsecure.storage.protocol.on('removePreKey', () => {
- window.getAccountManager().refreshPreKeys();
+ window.textsecure.storage.protocol.on('removePreKey', (ourUuid: UUID) => {
+ const uuidKind = window.textsecure.storage.user.getOurUuidKind(ourUuid);
+ window.getAccountManager().refreshPreKeys(uuidKind);
});
window.getSocketStatus = () => {
@@ -2153,6 +2157,18 @@ export async function startApp(): Promise {
return unlinkAndDisconnect(RemoveAllConfiguration.Full);
}
+ if (!window.textsecure.storage.user.getUuid(UUIDKind.PNI)) {
+ log.info('PNI not captured during registration, fetching');
+ const { pni } = await server.whoami();
+ if (!pni) {
+ log.error('No PNI found, unlinking');
+ return unlinkAndDisconnect(RemoveAllConfiguration.Soft);
+ }
+
+ log.info('Setting PNI to', pni);
+ await window.textsecure.storage.user.setPni(pni);
+ }
+
if (connectCount === 1) {
try {
// Note: we always have to register our capabilities all at once, so we do this
@@ -2321,10 +2337,7 @@ export async function startApp(): Promise {
window.readyForUpdates();
// Start listeners here, after we get through our queue.
- window.Whisper.RotateSignedPreKeyListener.init(
- window.Whisper.events,
- newVersion
- );
+ RotateSignedPreKeyListener.init(window.Whisper.events, newVersion);
// Go back to main process before processing delayed actions
await window.Signal.Data.goBackToMainProcess();
diff --git a/ts/models/messages.ts b/ts/models/messages.ts
index 96977e4bf..253c271d8 100644
--- a/ts/models/messages.ts
+++ b/ts/models/messages.ts
@@ -40,7 +40,7 @@ import { SendMessageProtoError } from '../textsecure/Errors';
import * as expirationTimer from '../util/expirationTimer';
import type { ReactionType } from '../types/Reactions';
-import { UUID } from '../types/UUID';
+import { UUID, UUIDKind } from '../types/UUID';
import type { UUIDStringType } from '../types/UUID';
import * as reactionUtil from '../reactions/util';
import {
@@ -1470,7 +1470,9 @@ export class MessageModel extends window.Backbone.Model {
});
if (hadSignedPreKeyRotationError) {
- promises.push(window.getAccountManager().rotateSignedPreKey());
+ promises.push(
+ window.getAccountManager().rotateSignedPreKey(UUIDKind.ACI)
+ );
}
attributesToUpdate.sendStateByConversationId = sendStateByConversationId;
diff --git a/ts/services/groupCredentialFetcher.ts b/ts/services/groupCredentialFetcher.ts
index cb9e92fbd..1eb6deaa7 100644
--- a/ts/services/groupCredentialFetcher.ts
+++ b/ts/services/groupCredentialFetcher.ts
@@ -10,6 +10,7 @@ import type { GroupCredentialType } from '../textsecure/WebAPI';
import * as durations from '../util/durations';
import { BackOff } from '../util/BackOff';
import { sleep } from '../util/sleep';
+import { UUIDKind } from '../types/UUID';
import * as log from '../logging/log';
export const GROUP_CREDENTIALS_KEY = 'groupCredentials';
@@ -142,7 +143,7 @@ export async function maybeFetchNewCredentials(): Promise {
serverPublicParamsBase64
);
const newCredentials = sortCredentials(
- await accountManager.getGroupCredentials(startDay, endDay)
+ await accountManager.getGroupCredentials(startDay, endDay, UUIDKind.ACI)
).map((item: GroupCredentialType) => {
const authCredential = clientZKAuthOperations.receiveAuthCredential(
uuid,
diff --git a/ts/state/ducks/accounts.ts b/ts/state/ducks/accounts.ts
index c2550766a..d4a4171e9 100644
--- a/ts/state/ducks/accounts.ts
+++ b/ts/state/ducks/accounts.ts
@@ -3,7 +3,7 @@
import type { ThunkAction } from 'redux-thunk';
import type { StateType as RootStateType } from '../reducer';
-import { getUserLanguages } from '../../util/userLanguages';
+import { UUID } from '../../types/UUID';
import type { NoopActionType } from './noop';
@@ -51,13 +51,9 @@ function checkForAccount(
let hasAccount = false;
try {
- await window.textsecure.messaging.getProfile(identifier, {
- userLanguages: getUserLanguages(
- navigator.languages,
- window.getLocale()
- ),
- });
- hasAccount = true;
+ hasAccount = await window.textsecure.messaging.checkAccountExistence(
+ new UUID(identifier)
+ );
} catch (_error) {
// Doing nothing with this failed fetch
}
diff --git a/ts/test-electron/models/conversations_test.ts b/ts/test-electron/models/conversations_test.ts
index 58b9f5d6e..2eac06977 100644
--- a/ts/test-electron/models/conversations_test.ts
+++ b/ts/test-electron/models/conversations_test.ts
@@ -18,6 +18,7 @@ describe('Conversations', () => {
it('updates lastMessage even in race conditions with db', async () => {
const ourNumber = '+15550000000';
const ourUuid = UUID.generate().toString();
+ const ourPni = UUID.generate().toString();
// Creating a fake conversation
const conversation = new window.Whisper.Conversation({
@@ -39,6 +40,7 @@ describe('Conversations', () => {
await window.textsecure.storage.user.setCredentials({
number: ourNumber,
uuid: ourUuid,
+ pni: ourPni,
deviceId: 2,
deviceName: 'my device',
password: 'password',
diff --git a/ts/test-electron/textsecure/generate_keys_test.ts b/ts/test-electron/textsecure/generate_keys_test.ts
index 3823bc7e7..214d5e9fc 100644
--- a/ts/test-electron/textsecure/generate_keys_test.ts
+++ b/ts/test-electron/textsecure/generate_keys_test.ts
@@ -9,7 +9,7 @@ import { generateKeyPair } from '../../Curve';
import type { GeneratedKeysType } from '../../textsecure/AccountManager';
import AccountManager from '../../textsecure/AccountManager';
import type { PreKeyType, SignedPreKeyType } from '../../textsecure/Types.d';
-import { UUID } from '../../types/UUID';
+import { UUID, UUIDKind } from '../../types/UUID';
const { textsecure } = window;
@@ -91,7 +91,7 @@ describe('Key generation', function thisNeeded() {
before(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const accountManager = new AccountManager({} as any);
- result = await accountManager.generateKeys(count);
+ result = await accountManager.generateKeys(count, UUIDKind.ACI);
});
for (let i = 1; i <= count; i += 1) {
@@ -125,7 +125,7 @@ describe('Key generation', function thisNeeded() {
before(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const accountManager = new AccountManager({} as any);
- result = await accountManager.generateKeys(count);
+ result = await accountManager.generateKeys(count, UUIDKind.ACI);
});
for (let i = 1; i <= 2 * count; i += 1) {
@@ -159,7 +159,7 @@ describe('Key generation', function thisNeeded() {
before(async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const accountManager = new AccountManager({} as any);
- result = await accountManager.generateKeys(count);
+ result = await accountManager.generateKeys(count, UUIDKind.ACI);
});
for (let i = 1; i <= 3 * count; i += 1) {
diff --git a/ts/textsecure/AccountManager.ts b/ts/textsecure/AccountManager.ts
index c87360f51..6b3fecc95 100644
--- a/ts/textsecure/AccountManager.ts
+++ b/ts/textsecure/AccountManager.ts
@@ -30,7 +30,8 @@ import {
generateSignedPreKey,
generatePreKey,
} from '../Curve';
-import { UUID } from '../types/UUID';
+import type { UUIDStringType } from '../types/UUID';
+import { UUID, UUIDKind } from '../types/UUID';
import { isMoreRecentThan, isOlderThan } from '../util/timestamp';
import { ourProfileKeyService } from '../services/ourProfileKey';
import { assert, strictAssert } from '../util/assert';
@@ -170,8 +171,12 @@ export default class AccountManager extends EventTarget {
);
await this.clearSessionsAndPreKeys();
- const keys = await this.generateKeys(SIGNED_KEY_GEN_BATCH_SIZE);
- await this.server.registerKeys(keys);
+ // TODO: DESKTOP-2788
+ const keys = await this.generateKeys(
+ SIGNED_KEY_GEN_BATCH_SIZE,
+ UUIDKind.ACI
+ );
+ await this.server.registerKeys(keys, UUIDKind.ACI);
await this.confirmKeys(keys);
await this.registrationDone();
});
@@ -184,11 +189,6 @@ export default class AccountManager extends EventTarget {
) {
const createAccount = this.createAccount.bind(this);
const clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.bind(this);
- const generateKeys = this.generateKeys.bind(
- this,
- SIGNED_KEY_GEN_BATCH_SIZE,
- progressCallback
- );
const provisioningCipher = new ProvisioningCipher();
const pubKey = await provisioningCipher.getPublicKey();
@@ -274,37 +274,40 @@ export default class AccountManager extends EventTarget {
deviceName,
provisionMessage.userAgent,
provisionMessage.readReceipts,
- { uuid: provisionMessage.uuid }
+ {
+ uuid: provisionMessage.uuid
+ ? UUID.cast(provisionMessage.uuid)
+ : undefined,
+ }
);
await clearSessionsAndPreKeys();
- const keys = await generateKeys();
- await this.server.registerKeys(keys);
+ // TODO: DESKTOP-2794
+ const keys = await this.generateKeys(
+ SIGNED_KEY_GEN_BATCH_SIZE,
+ UUIDKind.ACI,
+ progressCallback
+ );
+ await this.server.registerKeys(keys, UUIDKind.ACI);
await this.confirmKeys(keys);
await this.registrationDone();
});
}
- async refreshPreKeys() {
- const generateKeys = this.generateKeys.bind(
- this,
- SIGNED_KEY_GEN_BATCH_SIZE
- );
- const registerKeys = this.server.registerKeys.bind(this.server);
-
- return this.queueTask(async () =>
- this.server.getMyKeys().then(async preKeyCount => {
- log.info(`prekey count ${preKeyCount}`);
- if (preKeyCount < 10) {
- return generateKeys().then(registerKeys);
- }
- return null;
- })
- );
+ async refreshPreKeys(uuidKind: UUIDKind) {
+ return this.queueTask(async () => {
+ const preKeyCount = await this.server.getMyKeys(uuidKind);
+ log.info(`prekey count ${preKeyCount}`);
+ if (preKeyCount >= 10) {
+ return;
+ }
+ const keys = await this.generateKeys(SIGNED_KEY_GEN_BATCH_SIZE, uuidKind);
+ await this.server.registerKeys(keys, uuidKind);
+ });
}
- async rotateSignedPreKey() {
+ async rotateSignedPreKey(uuidKind: UUIDKind) {
return this.queueTask(async () => {
- const ourUuid = window.textsecure.storage.user.getCheckedUuid();
+ const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
if (typeof signedKeyId !== 'number') {
throw new Error('Invalid signedKeyId');
@@ -350,11 +353,14 @@ export default class AccountManager extends EventTarget {
return Promise.all([
window.textsecure.storage.put('signedKeyId', signedKeyId + 1),
store.storeSignedPreKey(ourUuid, res.keyId, res.keyPair),
- server.setSignedPreKey({
- keyId: res.keyId,
- publicKey: res.keyPair.pubKey,
- signature: res.signature,
- }),
+ server.setSignedPreKey(
+ {
+ keyId: res.keyId,
+ publicKey: res.keyPair.pubKey,
+ signature: res.signature,
+ },
+ uuidKind
+ ),
])
.then(async () => {
const confirmed = true;
@@ -456,7 +462,7 @@ export default class AccountManager extends EventTarget {
deviceName: string | null,
userAgent?: string | null,
readReceipts?: boolean | null,
- options: { accessKey?: Uint8Array; uuid?: string } = {}
+ options: { accessKey?: Uint8Array; uuid?: UUIDStringType } = {}
): Promise {
const { storage } = window.textsecure;
const { accessKey, uuid } = options;
@@ -548,6 +554,7 @@ export default class AccountManager extends EventTarget {
// information.
await storage.user.setCredentials({
uuid: ourUuid,
+ pni: response.pni,
number,
deviceId: response.deviceId ?? 1,
deviceName: deviceName ?? undefined,
@@ -617,8 +624,12 @@ export default class AccountManager extends EventTarget {
]);
}
- async getGroupCredentials(startDay: number, endDay: number) {
- return this.server.getGroupCredentials(startDay, endDay);
+ async getGroupCredentials(
+ startDay: number,
+ endDay: number,
+ uuidKind: UUIDKind
+ ) {
+ return this.server.getGroupCredentials(startDay, endDay, uuidKind);
}
// Takes the same object returned by generateKeys
@@ -636,14 +647,18 @@ export default class AccountManager extends EventTarget {
await store.storeSignedPreKey(ourUuid, key.keyId, key.keyPair, confirmed);
}
- async generateKeys(count: number, providedProgressCallback?: Function) {
+ async generateKeys(
+ count: number,
+ uuidKind: UUIDKind,
+ providedProgressCallback?: Function
+ ) {
const progressCallback =
typeof providedProgressCallback === 'function'
? providedProgressCallback
: null;
const startId = window.textsecure.storage.get('maxPreKeyId', 1);
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
- const ourUuid = window.textsecure.storage.user.getCheckedUuid();
+ const ourUuid = window.textsecure.storage.user.getCheckedUuid(uuidKind);
if (typeof startId !== 'number') {
throw new Error('Invalid maxPreKeyId');
diff --git a/ts/textsecure/RotateSignedPreKeyListener.ts b/ts/textsecure/RotateSignedPreKeyListener.ts
new file mode 100644
index 000000000..55a2f065a
--- /dev/null
+++ b/ts/textsecure/RotateSignedPreKeyListener.ts
@@ -0,0 +1,104 @@
+// Copyright 2017-2020 Signal Messenger, LLC
+// SPDX-License-Identifier: AGPL-3.0-only
+
+import * as durations from '../util/durations';
+import { UUIDKind } from '../types/UUID';
+import * as log from '../logging/log';
+
+const ROTATION_INTERVAL = 2 * durations.DAY;
+
+export type MinimalEventsType = {
+ on(event: 'timetravel', callback: () => void): void;
+};
+
+let initComplete = false;
+
+export class RotateSignedPreKeyListener {
+ public timeout: NodeJS.Timeout | undefined;
+
+ protected scheduleRotationForNow(): void {
+ const now = Date.now();
+ window.textsecure.storage.put('nextSignedKeyRotationTime', now);
+ }
+
+ protected setTimeoutForNextRun(): void {
+ const now = Date.now();
+ const time = window.textsecure.storage.get(
+ 'nextSignedKeyRotationTime',
+ now
+ );
+
+ log.info(
+ 'Next signed key rotation scheduled for',
+ new Date(time).toISOString()
+ );
+
+ let waitTime = time - now;
+ if (waitTime < 0) {
+ waitTime = 0;
+ }
+
+ if (this.timeout !== undefined) {
+ clearTimeout(this.timeout);
+ }
+ this.timeout = setTimeout(() => this.runWhenOnline(), waitTime);
+ }
+
+ private scheduleNextRotation(): void {
+ const now = Date.now();
+ const nextTime = now + ROTATION_INTERVAL;
+ window.textsecure.storage.put('nextSignedKeyRotationTime', nextTime);
+ }
+
+ private async run(): Promise {
+ log.info('Rotating signed prekey...');
+ try {
+ const accountManager = window.getAccountManager();
+ await Promise.all([
+ accountManager.rotateSignedPreKey(UUIDKind.ACI),
+ accountManager.rotateSignedPreKey(UUIDKind.PNI),
+ ]);
+ this.scheduleNextRotation();
+ this.setTimeoutForNextRun();
+ } catch (error) {
+ log.error('rotateSignedPrekey() failed. Trying again in five minutes');
+ setTimeout(() => this.setTimeoutForNextRun(), 5 * durations.MINUTE);
+ }
+ }
+
+ private runWhenOnline() {
+ if (window.navigator.onLine) {
+ this.run();
+ } else {
+ log.info('We are offline; keys will be rotated when we are next online');
+ const listener = () => {
+ window.removeEventListener('online', listener);
+ this.setTimeoutForNextRun();
+ };
+ window.addEventListener('online', listener);
+ }
+ }
+
+ public static init(events: MinimalEventsType, newVersion: boolean): void {
+ if (initComplete) {
+ window.SignalContext.log.info(
+ 'Rotate signed prekey listener: Already initialized'
+ );
+ return;
+ }
+ initComplete = true;
+
+ const listener = new RotateSignedPreKeyListener();
+
+ if (newVersion) {
+ listener.scheduleRotationForNow();
+ }
+ listener.setTimeoutForNextRun();
+
+ events.on('timetravel', () => {
+ if (window.Signal.Util.Registration.isDone()) {
+ listener.setTimeoutForNextRun();
+ }
+ });
+ }
+}
diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts
index a0ae3180d..efc78b5b3 100644
--- a/ts/textsecure/SendMessage.ts
+++ b/ts/textsecure/SendMessage.ts
@@ -23,7 +23,7 @@ import { SenderKeys } from '../LibSignalStores';
import type { LinkPreviewType } from '../types/message/LinkPreviews';
import { MIMETypeToString } from '../types/MIME';
import type * as Attachment from '../types/Attachment';
-import type { UUIDStringType } from '../types/UUID';
+import type { UUID, UUIDStringType } from '../types/UUID';
import type {
ChallengeType,
GroupCredentialsType,
@@ -2039,7 +2039,7 @@ export default class MessageSender {
number: string,
options: Readonly<{
accessKey?: string;
- profileKeyVersion?: string;
+ profileKeyVersion: string;
profileKeyCredentialRequest?: string;
userLanguages: ReadonlyArray;
}>
@@ -2057,6 +2057,10 @@ export default class MessageSender {
return this.server.getProfile(number, options);
}
+ async checkAccountExistence(uuid: UUID): Promise {
+ return this.server.checkAccountExistence(uuid);
+ }
+
async getProfileForUsername(
username: string
): ReturnType {
diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts
index a50944121..d5fb0f4e3 100644
--- a/ts/textsecure/WebAPI.ts
+++ b/ts/textsecure/WebAPI.ts
@@ -35,7 +35,8 @@ import { toWebSafeBase64 } from '../util/webSafeBase64';
import type { SocketStatus } from '../types/SocketStatus';
import { toLogFormat } from '../types/errors';
import { isPackIdValid, redactPackId } from '../types/Stickers';
-import type { UUIDStringType } from '../types/UUID';
+import type { UUID, UUIDStringType } from '../types/UUID';
+import { UUIDKind } from '../types/UUID';
import * as Bytes from '../Bytes';
import {
constantTimeEqual,
@@ -164,7 +165,7 @@ function getContentType(response: Response) {
type FetchHeaderListType = { [name: string]: string };
export type HeaderListType = { [name: string]: string | ReadonlyArray };
-type HTTPCodeType = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
+type HTTPCodeType = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
type RedactUrl = (url: string) => string;
@@ -519,6 +520,7 @@ function makeHTTPError(
const URL_CALLS = {
accounts: 'v1/accounts',
+ accountExistence: 'v1/accounts/account',
attachmentId: 'v2/attachments/form/upload',
attestation: 'v1/attestation',
challenge: 'v1/challenge',
@@ -745,10 +747,17 @@ export type MakeProxiedRequestResultType =
};
export type WhoamiResultType = Readonly<{
- uuid?: string;
+ uuid?: UUIDStringType;
+ pni?: UUIDStringType;
number?: string;
}>;
+export type ConfirmCodeResultType = Readonly<{
+ uuid: UUIDStringType;
+ pni: UUIDStringType;
+ deviceId?: number;
+}>;
+
export type WebAPIType = {
confirmCode: (
number: string,
@@ -756,8 +765,8 @@ export type WebAPIType = {
newPassword: string,
registrationId: number,
deviceName?: string | null,
- options?: { accessKey?: Uint8Array; uuid?: string }
- ) => Promise<{ uuid?: string; deviceId?: number }>;
+ options?: { accessKey?: Uint8Array; uuid?: UUIDStringType }
+ ) => Promise;
createGroup: (
group: Proto.IGroup,
options: GroupCredentialsType
@@ -775,7 +784,8 @@ export type WebAPIType = {
getGroupAvatar: (key: string) => Promise;
getGroupCredentials: (
startDay: number,
- endDay: number
+ endDay: number,
+ uuidKind: UUIDKind
) => Promise>;
getGroupExternalCredential: (
options: GroupCredentialsType
@@ -794,13 +804,14 @@ export type WebAPIType = {
deviceId?: number,
options?: { accessKey?: string }
) => Promise;
- getMyKeys: () => Promise;
+ getMyKeys: (uuidKind: UUIDKind) => Promise;
getProfile: (
identifier: string,
options: {
- profileKeyVersion?: string;
+ profileKeyVersion: string;
profileKeyCredentialRequest?: string;
userLanguages: ReadonlyArray;
+ credentialType?: 'pni' | 'profileKey';
}
) => Promise;
getProfileForUsername: (username: string) => Promise;
@@ -808,7 +819,7 @@ export type WebAPIType = {
identifier: string,
options: {
accessKey: string;
- profileKeyVersion?: string;
+ profileKeyVersion: string;
profileKeyCredentialRequest?: string;
userLanguages: ReadonlyArray;
}
@@ -866,11 +877,12 @@ export type WebAPIType = {
) => Promise;
putUsername: (newUsername: string) => Promise;
registerCapabilities: (capabilities: CapabilitiesUploadType) => Promise;
- registerKeys: (genKeys: KeysType) => Promise;
+ registerKeys: (genKeys: KeysType, uuidKind: UUIDKind) => Promise;
registerSupportForUnauthenticatedDelivery: () => Promise;
reportMessage: (senderE164: string, serverGuid: string) => Promise;
requestVerificationSMS: (number: string, token: string) => Promise;
requestVerificationVoice: (number: string, token: string) => Promise;
+ checkAccountExistence: (uuid: UUID) => Promise;
sendMessages: (
destination: string,
messageArray: ReadonlyArray,
@@ -890,7 +902,10 @@ export type WebAPIType = {
timestamp: number,
online?: boolean
) => Promise;
- setSignedPreKey: (signedPreKey: SignedPreKeyType) => Promise;
+ setSignedPreKey: (
+ signedPreKey: SignedPreKeyType,
+ uuidKind: UUIDKind
+ ) => Promise;
updateDeviceName: (deviceName: string) => Promise;
uploadAvatar: (
uploadAvatarRequestHeaders: UploadAvatarHeadersType,
@@ -1091,6 +1106,7 @@ export function initialize({
unregisterRequestHandler,
authenticate,
logout,
+ checkAccountExistence,
confirmCode,
createGroup,
deleteUsername,
@@ -1180,13 +1196,13 @@ export function initialize({
(param.jsonData ? JSON.stringify(param.jsonData) : undefined),
headers: param.headers,
host: param.host || url,
- password: param.password || password,
+ password: param.password ?? password,
path: URL_CALLS[param.call] + param.urlParameters,
proxyUrl,
responseType: param.responseType,
timeout: param.timeout,
type: param.httpType,
- user: param.username || username,
+ user: param.username ?? username,
redactUrl: param.redactUrl,
serverUrl: url,
validateResponse: param.validateResponse,
@@ -1209,6 +1225,18 @@ export function initialize({
}
}
+ function uuidKindToQuery(kind: UUIDKind): string {
+ let value: string;
+ if (kind === UUIDKind.ACI) {
+ value = 'aci';
+ } else if (kind === UUIDKind.PNI) {
+ value = 'pni';
+ } else {
+ throw new Error(`Unsupported UUIDKind: ${kind}`);
+ }
+ return `identity=${value}`;
+ }
+
async function whoami() {
return (await _ajax({
call: 'whoami',
@@ -1378,16 +1406,14 @@ export function initialize({
function getProfileUrl(
identifier: string,
- profileKeyVersion?: string,
- profileKeyCredentialRequest?: string
+ profileKeyVersion: string,
+ profileKeyCredentialRequest?: string,
+ credentialType: 'pni' | 'profileKey' = 'profileKey'
) {
- let profileUrl = `/${identifier}`;
+ let profileUrl = `/${identifier}/${profileKeyVersion}`;
- if (profileKeyVersion) {
- profileUrl += `/${profileKeyVersion}`;
- }
- if (profileKeyVersion && profileKeyCredentialRequest) {
- profileUrl += `/${profileKeyCredentialRequest}`;
+ if (profileKeyCredentialRequest) {
+ profileUrl += `/${profileKeyCredentialRequest}?credentialType=${credentialType}`;
}
return profileUrl;
@@ -1396,13 +1422,18 @@ export function initialize({
async function getProfile(
identifier: string,
options: {
- profileKeyVersion?: string;
+ profileKeyVersion: string;
profileKeyCredentialRequest?: string;
userLanguages: ReadonlyArray;
+ credentialType?: 'pni' | 'profileKey';
}
) {
- const { profileKeyVersion, profileKeyCredentialRequest, userLanguages } =
- options;
+ const {
+ profileKeyVersion,
+ profileKeyCredentialRequest,
+ userLanguages,
+ credentialType = 'profileKey',
+ } = options;
return (await _ajax({
call: 'profile',
@@ -1410,7 +1441,8 @@ export function initialize({
urlParameters: getProfileUrl(
identifier,
profileKeyVersion,
- profileKeyCredentialRequest
+ profileKeyCredentialRequest,
+ credentialType
),
headers: {
'Accept-Language': formatAcceptLanguageHeader(userLanguages),
@@ -1425,9 +1457,13 @@ export function initialize({
}
async function getProfileForUsername(usernameToFetch: string) {
- return getProfile(`username/${usernameToFetch}`, {
- userLanguages: [],
- });
+ return (await _ajax({
+ call: 'profile',
+ httpType: 'GET',
+ urlParameters: `username/${usernameToFetch}`,
+ responseType: 'json',
+ redactUrl: _createRedactor(usernameToFetch),
+ })) as ProfileType;
}
async function putProfile(
@@ -1451,7 +1487,7 @@ export function initialize({
identifier: string,
options: {
accessKey: string;
- profileKeyVersion?: string;
+ profileKeyVersion: string;
profileKeyCredentialRequest?: string;
userLanguages: ReadonlyArray;
}
@@ -1573,13 +1609,32 @@ export function initialize({
});
}
+ async function checkAccountExistence(uuid: UUID) {
+ try {
+ await _ajax({
+ httpType: 'HEAD',
+ call: 'accountExistence',
+ urlParameters: `/${uuid.toString()}`,
+ unauthenticated: true,
+ accessKey: undefined,
+ });
+ return true;
+ } catch (error) {
+ if (error instanceof HTTPError && error.code === 404) {
+ return false;
+ }
+
+ throw error;
+ }
+ }
+
async function confirmCode(
number: string,
code: string,
newPassword: string,
registrationId: number,
deviceName?: string | null,
- options: { accessKey?: Uint8Array; uuid?: string } = {}
+ options: { accessKey?: Uint8Array; uuid?: UUIDStringType } = {}
) {
const capabilities: CapabilitiesUploadType = {
announcementGroup: true,
@@ -1620,7 +1675,7 @@ export function initialize({
responseType: 'json',
urlParameters: urlPrefix + code,
jsonData,
- })) as { uuid?: string; deviceId?: number };
+ })) as ConfirmCodeResultType;
// Set final REST credentials to let `registerKeys` succeed.
username = `${uuid || response.uuid || number}.${response.deviceId || 1}`;
@@ -1670,7 +1725,7 @@ export function initialize({
}>;
};
- async function registerKeys(genKeys: KeysType) {
+ async function registerKeys(genKeys: KeysType, uuidKind: UUIDKind) {
const preKeys = genKeys.preKeys.map(key => ({
keyId: key.keyId,
publicKey: Bytes.toBase64(key.publicKey),
@@ -1688,14 +1743,19 @@ export function initialize({
await _ajax({
call: 'keys',
+ urlParameters: `?${uuidKindToQuery(uuidKind)}`,
httpType: 'PUT',
jsonData: keys,
});
}
- async function setSignedPreKey(signedPreKey: SignedPreKeyType) {
+ async function setSignedPreKey(
+ signedPreKey: SignedPreKeyType,
+ uuidKind: UUIDKind
+ ) {
await _ajax({
call: 'signed',
+ urlParameters: `?${uuidKindToQuery(uuidKind)}`,
httpType: 'PUT',
jsonData: {
keyId: signedPreKey.keyId,
@@ -1709,9 +1769,10 @@ export function initialize({
count: number;
};
- async function getMyKeys(): Promise {
+ async function getMyKeys(uuidKind: UUIDKind): Promise {
const result = (await _ajax({
call: 'keys',
+ urlParameters: `?${uuidKindToQuery(uuidKind)}`,
httpType: 'GET',
responseType: 'json',
validateResponse: { count: 'number' },
@@ -2233,11 +2294,12 @@ export function initialize({
async function getGroupCredentials(
startDay: number,
- endDay: number
+ endDay: number,
+ uuidKind: UUIDKind
): Promise> {
const response = (await _ajax({
call: 'getGroupCredentials',
- urlParameters: `/${startDay}/${endDay}`,
+ urlParameters: `/${startDay}/${endDay}?${uuidKindToQuery(uuidKind)}`,
httpType: 'GET',
responseType: 'json',
})) as CredentialResponseType;
diff --git a/ts/textsecure/storage/User.ts b/ts/textsecure/storage/User.ts
index b24dc1ccf..a1050b7ed 100644
--- a/ts/textsecure/storage/User.ts
+++ b/ts/textsecure/storage/User.ts
@@ -11,7 +11,8 @@ import * as log from '../../logging/log';
import Helpers from '../Helpers';
export type SetCredentialsOptions = {
- uuid?: string;
+ uuid: string;
+ pni: string;
number: string;
deviceId: number;
deviceName?: string;
@@ -58,22 +59,30 @@ export class User {
return Helpers.unencodeNumber(numberId)[0];
}
- public getUuid(): UUID | undefined {
+ public getUuid(uuidKind = UUIDKind.ACI): UUID | undefined {
+ if (uuidKind === UUIDKind.PNI) {
+ const pni = this.storage.get('pni');
+ if (pni === undefined) return undefined;
+ return new UUID(pni);
+ }
+
+ strictAssert(
+ uuidKind === UUIDKind.ACI,
+ `Unsupported uuid kind: ${uuidKind}`
+ );
const uuid = this.storage.get('uuid_id');
if (uuid === undefined) return undefined;
return new UUID(Helpers.unencodeNumber(uuid.toLowerCase())[0]);
}
- public getCheckedUuid(): UUID {
- const uuid = this.getUuid();
+ public getCheckedUuid(uuidKind?: UUIDKind): UUID {
+ const uuid = this.getUuid(uuidKind);
strictAssert(uuid !== undefined, 'Must have our own uuid');
return uuid;
}
- public getPni(): UUID | undefined {
- const pni = this.storage.get('pni');
- if (pni === undefined) return undefined;
- return new UUID(pni);
+ public async setPni(pni: string): Promise {
+ await this.storage.put('pni', UUID.cast(pni));
}
public getOurUuidKind(uuid: UUID): UUIDKind {
@@ -83,7 +92,7 @@ export class User {
return UUIDKind.ACI;
}
- const pni = this.getPni();
+ const pni = this.getUuid(UUIDKind.PNI);
if (pni?.toString() === uuid.toString()) {
return UUIDKind.PNI;
}
@@ -118,12 +127,13 @@ export class User {
public async setCredentials(
credentials: SetCredentialsOptions
): Promise {
- const { uuid, number, deviceId, deviceName, password } = credentials;
+ const { uuid, pni, number, deviceId, deviceName, password } = credentials;
await Promise.all([
this.storage.put('number_id', `${number}.${deviceId}`),
this.storage.put('uuid_id', `${uuid}.${deviceId}`),
this.storage.put('password', password),
+ this.setPni(pni),
deviceName
? this.storage.put('device_name', deviceName)
: Promise.resolve(),
diff --git a/ts/types/Storage.d.ts b/ts/types/Storage.d.ts
index fb0e13140..d05b50923 100644
--- a/ts/types/Storage.d.ts
+++ b/ts/types/Storage.d.ts
@@ -134,6 +134,7 @@ export type StorageAccessType = {
paymentAddress: string;
zoomFactor: ZoomFactorType;
preferredLeftPaneWidth: number;
+ nextSignedKeyRotationTime: number;
areWeASubscriber: boolean;
subscriberId: Uint8Array;
subscriberCurrencyCode: string;
diff --git a/ts/window.d.ts b/ts/window.d.ts
index c7bd4fe71..59e771bee 100644
--- a/ts/window.d.ts
+++ b/ts/window.d.ts
@@ -548,7 +548,6 @@ export type WhisperType = {
MessageCollection: typeof MessageModelCollectionType;
GroupMemberConversation: WhatIsThis;
- RotateSignedPreKeyListener: WhatIsThis;
WallClockListener: WhatIsThis;
deliveryReceiptQueue: PQueue;