Add PNI endpoints to WebAPI
This commit is contained in:
parent
7c1ce3366d
commit
348012ef4c
17 changed files with 321 additions and 210 deletions
|
@ -222,10 +222,6 @@
|
||||||
></script>
|
></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="js/wall_clock_listener.js"></script>
|
<script type="text/javascript" src="js/wall_clock_listener.js"></script>
|
||||||
<script
|
|
||||||
type="text/javascript"
|
|
||||||
src="js/rotate_signed_prekey_listener.js"
|
|
||||||
></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="overflow-hidden">
|
<body class="overflow-hidden">
|
||||||
<div id="app-container">
|
<div id="app-container">
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
})();
|
|
|
@ -14,6 +14,7 @@ try {
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { strictAssert } = require('./ts/util/assert');
|
const { strictAssert } = require('./ts/util/assert');
|
||||||
const { parseIntWithFallback } = require('./ts/util/parseIntWithFallback');
|
const { parseIntWithFallback } = require('./ts/util/parseIntWithFallback');
|
||||||
|
const { UUIDKind } = require('./ts/types/UUID');
|
||||||
|
|
||||||
// It is important to call this as early as possible
|
// It is important to call this as early as possible
|
||||||
const { SignalContext } = require('./ts/windows/context');
|
const { SignalContext } = require('./ts/windows/context');
|
||||||
|
@ -185,7 +186,7 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ourUuid = window.textsecure.storage.user.getUuid();
|
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', {
|
event.sender.send('additional-log-data-response', {
|
||||||
capabilities: ourCapabilities || {},
|
capabilities: ourCapabilities || {},
|
||||||
|
|
|
@ -375,7 +375,7 @@ export class SignalProtocolStore extends EventsMixin {
|
||||||
const id: PreKeyIdType = `${ourUuid.toString()}:${keyId}`;
|
const id: PreKeyIdType = `${ourUuid.toString()}:${keyId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.trigger('removePreKey');
|
this.trigger('removePreKey', ourUuid);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(
|
log.error(
|
||||||
'removePreKey error triggering removePreKey:',
|
'removePreKey error triggering removePreKey:',
|
||||||
|
|
|
@ -83,6 +83,7 @@ import type {
|
||||||
import { VerifiedEvent } from './textsecure/messageReceiverEvents';
|
import { VerifiedEvent } from './textsecure/messageReceiverEvents';
|
||||||
import type { WebAPIType } from './textsecure/WebAPI';
|
import type { WebAPIType } from './textsecure/WebAPI';
|
||||||
import * as KeyChangeListener from './textsecure/KeyChangeListener';
|
import * as KeyChangeListener from './textsecure/KeyChangeListener';
|
||||||
|
import { RotateSignedPreKeyListener } from './textsecure/RotateSignedPreKeyListener';
|
||||||
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
|
import { isDirectConversation, isGroupV2 } from './util/whatTypeOfConversation';
|
||||||
import { getSendOptions } from './util/getSendOptions';
|
import { getSendOptions } from './util/getSendOptions';
|
||||||
import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff';
|
import { BackOff, FIBONACCI_TIMEOUTS } from './util/BackOff';
|
||||||
|
@ -116,6 +117,8 @@ import { onRetryRequest, onDecryptionError } from './util/handleRetry';
|
||||||
import { themeChanged } from './shims/themeChanged';
|
import { themeChanged } from './shims/themeChanged';
|
||||||
import { createIPCEvents } from './util/createIPCEvents';
|
import { createIPCEvents } from './util/createIPCEvents';
|
||||||
import { RemoveAllConfiguration } from './types/RemoveAllConfiguration';
|
import { RemoveAllConfiguration } from './types/RemoveAllConfiguration';
|
||||||
|
import type { UUID } from './types/UUID';
|
||||||
|
import { UUIDKind } from './types/UUID';
|
||||||
import * as log from './logging/log';
|
import * as log from './logging/log';
|
||||||
import {
|
import {
|
||||||
loadRecentEmojis,
|
loadRecentEmojis,
|
||||||
|
@ -498,8 +501,9 @@ export async function startApp(): Promise<void> {
|
||||||
window.document.title = window.getTitle();
|
window.document.title = window.getTitle();
|
||||||
|
|
||||||
KeyChangeListener.init(window.textsecure.storage.protocol);
|
KeyChangeListener.init(window.textsecure.storage.protocol);
|
||||||
window.textsecure.storage.protocol.on('removePreKey', () => {
|
window.textsecure.storage.protocol.on('removePreKey', (ourUuid: UUID) => {
|
||||||
window.getAccountManager().refreshPreKeys();
|
const uuidKind = window.textsecure.storage.user.getOurUuidKind(ourUuid);
|
||||||
|
window.getAccountManager().refreshPreKeys(uuidKind);
|
||||||
});
|
});
|
||||||
|
|
||||||
window.getSocketStatus = () => {
|
window.getSocketStatus = () => {
|
||||||
|
@ -2153,6 +2157,18 @@ export async function startApp(): Promise<void> {
|
||||||
return unlinkAndDisconnect(RemoveAllConfiguration.Full);
|
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) {
|
if (connectCount === 1) {
|
||||||
try {
|
try {
|
||||||
// Note: we always have to register our capabilities all at once, so we do this
|
// Note: we always have to register our capabilities all at once, so we do this
|
||||||
|
@ -2321,10 +2337,7 @@ export async function startApp(): Promise<void> {
|
||||||
window.readyForUpdates();
|
window.readyForUpdates();
|
||||||
|
|
||||||
// Start listeners here, after we get through our queue.
|
// Start listeners here, after we get through our queue.
|
||||||
window.Whisper.RotateSignedPreKeyListener.init(
|
RotateSignedPreKeyListener.init(window.Whisper.events, newVersion);
|
||||||
window.Whisper.events,
|
|
||||||
newVersion
|
|
||||||
);
|
|
||||||
|
|
||||||
// Go back to main process before processing delayed actions
|
// Go back to main process before processing delayed actions
|
||||||
await window.Signal.Data.goBackToMainProcess();
|
await window.Signal.Data.goBackToMainProcess();
|
||||||
|
|
|
@ -40,7 +40,7 @@ import { SendMessageProtoError } from '../textsecure/Errors';
|
||||||
import * as expirationTimer from '../util/expirationTimer';
|
import * as expirationTimer from '../util/expirationTimer';
|
||||||
|
|
||||||
import type { ReactionType } from '../types/Reactions';
|
import type { ReactionType } from '../types/Reactions';
|
||||||
import { UUID } from '../types/UUID';
|
import { UUID, UUIDKind } from '../types/UUID';
|
||||||
import type { UUIDStringType } from '../types/UUID';
|
import type { UUIDStringType } from '../types/UUID';
|
||||||
import * as reactionUtil from '../reactions/util';
|
import * as reactionUtil from '../reactions/util';
|
||||||
import {
|
import {
|
||||||
|
@ -1470,7 +1470,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hadSignedPreKeyRotationError) {
|
if (hadSignedPreKeyRotationError) {
|
||||||
promises.push(window.getAccountManager().rotateSignedPreKey());
|
promises.push(
|
||||||
|
window.getAccountManager().rotateSignedPreKey(UUIDKind.ACI)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
attributesToUpdate.sendStateByConversationId = sendStateByConversationId;
|
attributesToUpdate.sendStateByConversationId = sendStateByConversationId;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import type { GroupCredentialType } from '../textsecure/WebAPI';
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
import { BackOff } from '../util/BackOff';
|
import { BackOff } from '../util/BackOff';
|
||||||
import { sleep } from '../util/sleep';
|
import { sleep } from '../util/sleep';
|
||||||
|
import { UUIDKind } from '../types/UUID';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
|
|
||||||
export const GROUP_CREDENTIALS_KEY = 'groupCredentials';
|
export const GROUP_CREDENTIALS_KEY = 'groupCredentials';
|
||||||
|
@ -142,7 +143,7 @@ export async function maybeFetchNewCredentials(): Promise<void> {
|
||||||
serverPublicParamsBase64
|
serverPublicParamsBase64
|
||||||
);
|
);
|
||||||
const newCredentials = sortCredentials(
|
const newCredentials = sortCredentials(
|
||||||
await accountManager.getGroupCredentials(startDay, endDay)
|
await accountManager.getGroupCredentials(startDay, endDay, UUIDKind.ACI)
|
||||||
).map((item: GroupCredentialType) => {
|
).map((item: GroupCredentialType) => {
|
||||||
const authCredential = clientZKAuthOperations.receiveAuthCredential(
|
const authCredential = clientZKAuthOperations.receiveAuthCredential(
|
||||||
uuid,
|
uuid,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
import type { ThunkAction } from 'redux-thunk';
|
import type { ThunkAction } from 'redux-thunk';
|
||||||
import type { StateType as RootStateType } from '../reducer';
|
import type { StateType as RootStateType } from '../reducer';
|
||||||
import { getUserLanguages } from '../../util/userLanguages';
|
import { UUID } from '../../types/UUID';
|
||||||
|
|
||||||
import type { NoopActionType } from './noop';
|
import type { NoopActionType } from './noop';
|
||||||
|
|
||||||
|
@ -51,13 +51,9 @@ function checkForAccount(
|
||||||
let hasAccount = false;
|
let hasAccount = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.textsecure.messaging.getProfile(identifier, {
|
hasAccount = await window.textsecure.messaging.checkAccountExistence(
|
||||||
userLanguages: getUserLanguages(
|
new UUID(identifier)
|
||||||
navigator.languages,
|
);
|
||||||
window.getLocale()
|
|
||||||
),
|
|
||||||
});
|
|
||||||
hasAccount = true;
|
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
// Doing nothing with this failed fetch
|
// Doing nothing with this failed fetch
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ describe('Conversations', () => {
|
||||||
it('updates lastMessage even in race conditions with db', async () => {
|
it('updates lastMessage even in race conditions with db', async () => {
|
||||||
const ourNumber = '+15550000000';
|
const ourNumber = '+15550000000';
|
||||||
const ourUuid = UUID.generate().toString();
|
const ourUuid = UUID.generate().toString();
|
||||||
|
const ourPni = UUID.generate().toString();
|
||||||
|
|
||||||
// Creating a fake conversation
|
// Creating a fake conversation
|
||||||
const conversation = new window.Whisper.Conversation({
|
const conversation = new window.Whisper.Conversation({
|
||||||
|
@ -39,6 +40,7 @@ describe('Conversations', () => {
|
||||||
await window.textsecure.storage.user.setCredentials({
|
await window.textsecure.storage.user.setCredentials({
|
||||||
number: ourNumber,
|
number: ourNumber,
|
||||||
uuid: ourUuid,
|
uuid: ourUuid,
|
||||||
|
pni: ourPni,
|
||||||
deviceId: 2,
|
deviceId: 2,
|
||||||
deviceName: 'my device',
|
deviceName: 'my device',
|
||||||
password: 'password',
|
password: 'password',
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { generateKeyPair } from '../../Curve';
|
||||||
import type { GeneratedKeysType } from '../../textsecure/AccountManager';
|
import type { GeneratedKeysType } from '../../textsecure/AccountManager';
|
||||||
import AccountManager from '../../textsecure/AccountManager';
|
import AccountManager from '../../textsecure/AccountManager';
|
||||||
import type { PreKeyType, SignedPreKeyType } from '../../textsecure/Types.d';
|
import type { PreKeyType, SignedPreKeyType } from '../../textsecure/Types.d';
|
||||||
import { UUID } from '../../types/UUID';
|
import { UUID, UUIDKind } from '../../types/UUID';
|
||||||
|
|
||||||
const { textsecure } = window;
|
const { textsecure } = window;
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ describe('Key generation', function thisNeeded() {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const accountManager = new AccountManager({} as 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) {
|
for (let i = 1; i <= count; i += 1) {
|
||||||
|
@ -125,7 +125,7 @@ describe('Key generation', function thisNeeded() {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const accountManager = new AccountManager({} as 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) {
|
for (let i = 1; i <= 2 * count; i += 1) {
|
||||||
|
@ -159,7 +159,7 @@ describe('Key generation', function thisNeeded() {
|
||||||
before(async () => {
|
before(async () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const accountManager = new AccountManager({} as 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) {
|
for (let i = 1; i <= 3 * count; i += 1) {
|
||||||
|
|
|
@ -30,7 +30,8 @@ import {
|
||||||
generateSignedPreKey,
|
generateSignedPreKey,
|
||||||
generatePreKey,
|
generatePreKey,
|
||||||
} from '../Curve';
|
} 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 { isMoreRecentThan, isOlderThan } from '../util/timestamp';
|
||||||
import { ourProfileKeyService } from '../services/ourProfileKey';
|
import { ourProfileKeyService } from '../services/ourProfileKey';
|
||||||
import { assert, strictAssert } from '../util/assert';
|
import { assert, strictAssert } from '../util/assert';
|
||||||
|
@ -170,8 +171,12 @@ export default class AccountManager extends EventTarget {
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.clearSessionsAndPreKeys();
|
await this.clearSessionsAndPreKeys();
|
||||||
const keys = await this.generateKeys(SIGNED_KEY_GEN_BATCH_SIZE);
|
// TODO: DESKTOP-2788
|
||||||
await this.server.registerKeys(keys);
|
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.confirmKeys(keys);
|
||||||
await this.registrationDone();
|
await this.registrationDone();
|
||||||
});
|
});
|
||||||
|
@ -184,11 +189,6 @@ export default class AccountManager extends EventTarget {
|
||||||
) {
|
) {
|
||||||
const createAccount = this.createAccount.bind(this);
|
const createAccount = this.createAccount.bind(this);
|
||||||
const clearSessionsAndPreKeys = this.clearSessionsAndPreKeys.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 provisioningCipher = new ProvisioningCipher();
|
||||||
const pubKey = await provisioningCipher.getPublicKey();
|
const pubKey = await provisioningCipher.getPublicKey();
|
||||||
|
|
||||||
|
@ -274,37 +274,40 @@ export default class AccountManager extends EventTarget {
|
||||||
deviceName,
|
deviceName,
|
||||||
provisionMessage.userAgent,
|
provisionMessage.userAgent,
|
||||||
provisionMessage.readReceipts,
|
provisionMessage.readReceipts,
|
||||||
{ uuid: provisionMessage.uuid }
|
{
|
||||||
|
uuid: provisionMessage.uuid
|
||||||
|
? UUID.cast(provisionMessage.uuid)
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
await clearSessionsAndPreKeys();
|
await clearSessionsAndPreKeys();
|
||||||
const keys = await generateKeys();
|
// TODO: DESKTOP-2794
|
||||||
await this.server.registerKeys(keys);
|
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.confirmKeys(keys);
|
||||||
await this.registrationDone();
|
await this.registrationDone();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async refreshPreKeys() {
|
async refreshPreKeys(uuidKind: UUIDKind) {
|
||||||
const generateKeys = this.generateKeys.bind(
|
return this.queueTask(async () => {
|
||||||
this,
|
const preKeyCount = await this.server.getMyKeys(uuidKind);
|
||||||
SIGNED_KEY_GEN_BATCH_SIZE
|
log.info(`prekey count ${preKeyCount}`);
|
||||||
);
|
if (preKeyCount >= 10) {
|
||||||
const registerKeys = this.server.registerKeys.bind(this.server);
|
return;
|
||||||
|
}
|
||||||
return this.queueTask(async () =>
|
const keys = await this.generateKeys(SIGNED_KEY_GEN_BATCH_SIZE, uuidKind);
|
||||||
this.server.getMyKeys().then(async preKeyCount => {
|
await this.server.registerKeys(keys, uuidKind);
|
||||||
log.info(`prekey count ${preKeyCount}`);
|
});
|
||||||
if (preKeyCount < 10) {
|
|
||||||
return generateKeys().then(registerKeys);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async rotateSignedPreKey() {
|
async rotateSignedPreKey(uuidKind: UUIDKind) {
|
||||||
return this.queueTask(async () => {
|
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);
|
const signedKeyId = window.textsecure.storage.get('signedKeyId', 1);
|
||||||
if (typeof signedKeyId !== 'number') {
|
if (typeof signedKeyId !== 'number') {
|
||||||
throw new Error('Invalid signedKeyId');
|
throw new Error('Invalid signedKeyId');
|
||||||
|
@ -350,11 +353,14 @@ export default class AccountManager extends EventTarget {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
window.textsecure.storage.put('signedKeyId', signedKeyId + 1),
|
window.textsecure.storage.put('signedKeyId', signedKeyId + 1),
|
||||||
store.storeSignedPreKey(ourUuid, res.keyId, res.keyPair),
|
store.storeSignedPreKey(ourUuid, res.keyId, res.keyPair),
|
||||||
server.setSignedPreKey({
|
server.setSignedPreKey(
|
||||||
keyId: res.keyId,
|
{
|
||||||
publicKey: res.keyPair.pubKey,
|
keyId: res.keyId,
|
||||||
signature: res.signature,
|
publicKey: res.keyPair.pubKey,
|
||||||
}),
|
signature: res.signature,
|
||||||
|
},
|
||||||
|
uuidKind
|
||||||
|
),
|
||||||
])
|
])
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const confirmed = true;
|
const confirmed = true;
|
||||||
|
@ -456,7 +462,7 @@ export default class AccountManager extends EventTarget {
|
||||||
deviceName: string | null,
|
deviceName: string | null,
|
||||||
userAgent?: string | null,
|
userAgent?: string | null,
|
||||||
readReceipts?: boolean | null,
|
readReceipts?: boolean | null,
|
||||||
options: { accessKey?: Uint8Array; uuid?: string } = {}
|
options: { accessKey?: Uint8Array; uuid?: UUIDStringType } = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { storage } = window.textsecure;
|
const { storage } = window.textsecure;
|
||||||
const { accessKey, uuid } = options;
|
const { accessKey, uuid } = options;
|
||||||
|
@ -548,6 +554,7 @@ export default class AccountManager extends EventTarget {
|
||||||
// information.
|
// information.
|
||||||
await storage.user.setCredentials({
|
await storage.user.setCredentials({
|
||||||
uuid: ourUuid,
|
uuid: ourUuid,
|
||||||
|
pni: response.pni,
|
||||||
number,
|
number,
|
||||||
deviceId: response.deviceId ?? 1,
|
deviceId: response.deviceId ?? 1,
|
||||||
deviceName: deviceName ?? undefined,
|
deviceName: deviceName ?? undefined,
|
||||||
|
@ -617,8 +624,12 @@ export default class AccountManager extends EventTarget {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getGroupCredentials(startDay: number, endDay: number) {
|
async getGroupCredentials(
|
||||||
return this.server.getGroupCredentials(startDay, endDay);
|
startDay: number,
|
||||||
|
endDay: number,
|
||||||
|
uuidKind: UUIDKind
|
||||||
|
) {
|
||||||
|
return this.server.getGroupCredentials(startDay, endDay, uuidKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Takes the same object returned by generateKeys
|
// 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);
|
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 =
|
const progressCallback =
|
||||||
typeof providedProgressCallback === 'function'
|
typeof providedProgressCallback === 'function'
|
||||||
? providedProgressCallback
|
? providedProgressCallback
|
||||||
: null;
|
: null;
|
||||||
const startId = window.textsecure.storage.get('maxPreKeyId', 1);
|
const startId = window.textsecure.storage.get('maxPreKeyId', 1);
|
||||||
const signedKeyId = window.textsecure.storage.get('signedKeyId', 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') {
|
if (typeof startId !== 'number') {
|
||||||
throw new Error('Invalid maxPreKeyId');
|
throw new Error('Invalid maxPreKeyId');
|
||||||
|
|
104
ts/textsecure/RotateSignedPreKeyListener.ts
Normal file
104
ts/textsecure/RotateSignedPreKeyListener.ts
Normal file
|
@ -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<void> {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,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 { UUIDStringType } from '../types/UUID';
|
import type { UUID, UUIDStringType } from '../types/UUID';
|
||||||
import type {
|
import type {
|
||||||
ChallengeType,
|
ChallengeType,
|
||||||
GroupCredentialsType,
|
GroupCredentialsType,
|
||||||
|
@ -2039,7 +2039,7 @@ export default class MessageSender {
|
||||||
number: string,
|
number: string,
|
||||||
options: Readonly<{
|
options: Readonly<{
|
||||||
accessKey?: string;
|
accessKey?: string;
|
||||||
profileKeyVersion?: string;
|
profileKeyVersion: string;
|
||||||
profileKeyCredentialRequest?: string;
|
profileKeyCredentialRequest?: string;
|
||||||
userLanguages: ReadonlyArray<string>;
|
userLanguages: ReadonlyArray<string>;
|
||||||
}>
|
}>
|
||||||
|
@ -2057,6 +2057,10 @@ export default class MessageSender {
|
||||||
return this.server.getProfile(number, options);
|
return this.server.getProfile(number, 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']> {
|
||||||
|
|
|
@ -35,7 +35,8 @@ import { toWebSafeBase64 } from '../util/webSafeBase64';
|
||||||
import type { SocketStatus } from '../types/SocketStatus';
|
import type { SocketStatus } from '../types/SocketStatus';
|
||||||
import { toLogFormat } from '../types/errors';
|
import { toLogFormat } from '../types/errors';
|
||||||
import { isPackIdValid, redactPackId } from '../types/Stickers';
|
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 * as Bytes from '../Bytes';
|
||||||
import {
|
import {
|
||||||
constantTimeEqual,
|
constantTimeEqual,
|
||||||
|
@ -164,7 +165,7 @@ function getContentType(response: Response) {
|
||||||
|
|
||||||
type FetchHeaderListType = { [name: string]: string };
|
type FetchHeaderListType = { [name: string]: string };
|
||||||
export type HeaderListType = { [name: string]: string | ReadonlyArray<string> };
|
export type HeaderListType = { [name: string]: string | ReadonlyArray<string> };
|
||||||
type HTTPCodeType = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
type HTTPCodeType = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD';
|
||||||
|
|
||||||
type RedactUrl = (url: string) => string;
|
type RedactUrl = (url: string) => string;
|
||||||
|
|
||||||
|
@ -519,6 +520,7 @@ function makeHTTPError(
|
||||||
|
|
||||||
const URL_CALLS = {
|
const URL_CALLS = {
|
||||||
accounts: 'v1/accounts',
|
accounts: 'v1/accounts',
|
||||||
|
accountExistence: 'v1/accounts/account',
|
||||||
attachmentId: 'v2/attachments/form/upload',
|
attachmentId: 'v2/attachments/form/upload',
|
||||||
attestation: 'v1/attestation',
|
attestation: 'v1/attestation',
|
||||||
challenge: 'v1/challenge',
|
challenge: 'v1/challenge',
|
||||||
|
@ -745,10 +747,17 @@ export type MakeProxiedRequestResultType =
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WhoamiResultType = Readonly<{
|
export type WhoamiResultType = Readonly<{
|
||||||
uuid?: string;
|
uuid?: UUIDStringType;
|
||||||
|
pni?: UUIDStringType;
|
||||||
number?: string;
|
number?: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
export type ConfirmCodeResultType = Readonly<{
|
||||||
|
uuid: UUIDStringType;
|
||||||
|
pni: UUIDStringType;
|
||||||
|
deviceId?: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
export type WebAPIType = {
|
export type WebAPIType = {
|
||||||
confirmCode: (
|
confirmCode: (
|
||||||
number: string,
|
number: string,
|
||||||
|
@ -756,8 +765,8 @@ export type WebAPIType = {
|
||||||
newPassword: string,
|
newPassword: string,
|
||||||
registrationId: number,
|
registrationId: number,
|
||||||
deviceName?: string | null,
|
deviceName?: string | null,
|
||||||
options?: { accessKey?: Uint8Array; uuid?: string }
|
options?: { accessKey?: Uint8Array; uuid?: UUIDStringType }
|
||||||
) => Promise<{ uuid?: string; deviceId?: number }>;
|
) => Promise<ConfirmCodeResultType>;
|
||||||
createGroup: (
|
createGroup: (
|
||||||
group: Proto.IGroup,
|
group: Proto.IGroup,
|
||||||
options: GroupCredentialsType
|
options: GroupCredentialsType
|
||||||
|
@ -775,7 +784,8 @@ export type WebAPIType = {
|
||||||
getGroupAvatar: (key: string) => Promise<Uint8Array>;
|
getGroupAvatar: (key: string) => Promise<Uint8Array>;
|
||||||
getGroupCredentials: (
|
getGroupCredentials: (
|
||||||
startDay: number,
|
startDay: number,
|
||||||
endDay: number
|
endDay: number,
|
||||||
|
uuidKind: UUIDKind
|
||||||
) => Promise<Array<GroupCredentialType>>;
|
) => Promise<Array<GroupCredentialType>>;
|
||||||
getGroupExternalCredential: (
|
getGroupExternalCredential: (
|
||||||
options: GroupCredentialsType
|
options: GroupCredentialsType
|
||||||
|
@ -794,13 +804,14 @@ export type WebAPIType = {
|
||||||
deviceId?: number,
|
deviceId?: number,
|
||||||
options?: { accessKey?: string }
|
options?: { accessKey?: string }
|
||||||
) => Promise<ServerKeysType>;
|
) => Promise<ServerKeysType>;
|
||||||
getMyKeys: () => Promise<number>;
|
getMyKeys: (uuidKind: UUIDKind) => Promise<number>;
|
||||||
getProfile: (
|
getProfile: (
|
||||||
identifier: string,
|
identifier: string,
|
||||||
options: {
|
options: {
|
||||||
profileKeyVersion?: string;
|
profileKeyVersion: string;
|
||||||
profileKeyCredentialRequest?: string;
|
profileKeyCredentialRequest?: string;
|
||||||
userLanguages: ReadonlyArray<string>;
|
userLanguages: ReadonlyArray<string>;
|
||||||
|
credentialType?: 'pni' | 'profileKey';
|
||||||
}
|
}
|
||||||
) => Promise<ProfileType>;
|
) => Promise<ProfileType>;
|
||||||
getProfileForUsername: (username: string) => Promise<ProfileType>;
|
getProfileForUsername: (username: string) => Promise<ProfileType>;
|
||||||
|
@ -808,7 +819,7 @@ export type WebAPIType = {
|
||||||
identifier: string,
|
identifier: string,
|
||||||
options: {
|
options: {
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
profileKeyVersion?: string;
|
profileKeyVersion: string;
|
||||||
profileKeyCredentialRequest?: string;
|
profileKeyCredentialRequest?: string;
|
||||||
userLanguages: ReadonlyArray<string>;
|
userLanguages: ReadonlyArray<string>;
|
||||||
}
|
}
|
||||||
|
@ -866,11 +877,12 @@ export type WebAPIType = {
|
||||||
) => Promise<string>;
|
) => Promise<string>;
|
||||||
putUsername: (newUsername: string) => Promise<void>;
|
putUsername: (newUsername: string) => Promise<void>;
|
||||||
registerCapabilities: (capabilities: CapabilitiesUploadType) => Promise<void>;
|
registerCapabilities: (capabilities: CapabilitiesUploadType) => Promise<void>;
|
||||||
registerKeys: (genKeys: KeysType) => Promise<void>;
|
registerKeys: (genKeys: KeysType, uuidKind: UUIDKind) => Promise<void>;
|
||||||
registerSupportForUnauthenticatedDelivery: () => Promise<void>;
|
registerSupportForUnauthenticatedDelivery: () => Promise<void>;
|
||||||
reportMessage: (senderE164: string, serverGuid: string) => Promise<void>;
|
reportMessage: (senderE164: string, serverGuid: string) => Promise<void>;
|
||||||
requestVerificationSMS: (number: string, token: string) => Promise<void>;
|
requestVerificationSMS: (number: string, token: string) => Promise<void>;
|
||||||
requestVerificationVoice: (number: string, token: string) => Promise<void>;
|
requestVerificationVoice: (number: string, token: string) => Promise<void>;
|
||||||
|
checkAccountExistence: (uuid: UUID) => Promise<boolean>;
|
||||||
sendMessages: (
|
sendMessages: (
|
||||||
destination: string,
|
destination: string,
|
||||||
messageArray: ReadonlyArray<MessageType>,
|
messageArray: ReadonlyArray<MessageType>,
|
||||||
|
@ -890,7 +902,10 @@ export type WebAPIType = {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
online?: boolean
|
online?: boolean
|
||||||
) => Promise<MultiRecipient200ResponseType>;
|
) => Promise<MultiRecipient200ResponseType>;
|
||||||
setSignedPreKey: (signedPreKey: SignedPreKeyType) => Promise<void>;
|
setSignedPreKey: (
|
||||||
|
signedPreKey: SignedPreKeyType,
|
||||||
|
uuidKind: UUIDKind
|
||||||
|
) => Promise<void>;
|
||||||
updateDeviceName: (deviceName: string) => Promise<void>;
|
updateDeviceName: (deviceName: string) => Promise<void>;
|
||||||
uploadAvatar: (
|
uploadAvatar: (
|
||||||
uploadAvatarRequestHeaders: UploadAvatarHeadersType,
|
uploadAvatarRequestHeaders: UploadAvatarHeadersType,
|
||||||
|
@ -1091,6 +1106,7 @@ export function initialize({
|
||||||
unregisterRequestHandler,
|
unregisterRequestHandler,
|
||||||
authenticate,
|
authenticate,
|
||||||
logout,
|
logout,
|
||||||
|
checkAccountExistence,
|
||||||
confirmCode,
|
confirmCode,
|
||||||
createGroup,
|
createGroup,
|
||||||
deleteUsername,
|
deleteUsername,
|
||||||
|
@ -1180,13 +1196,13 @@ export function initialize({
|
||||||
(param.jsonData ? JSON.stringify(param.jsonData) : undefined),
|
(param.jsonData ? JSON.stringify(param.jsonData) : undefined),
|
||||||
headers: param.headers,
|
headers: param.headers,
|
||||||
host: param.host || url,
|
host: param.host || url,
|
||||||
password: param.password || password,
|
password: param.password ?? password,
|
||||||
path: URL_CALLS[param.call] + param.urlParameters,
|
path: URL_CALLS[param.call] + param.urlParameters,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
responseType: param.responseType,
|
responseType: param.responseType,
|
||||||
timeout: param.timeout,
|
timeout: param.timeout,
|
||||||
type: param.httpType,
|
type: param.httpType,
|
||||||
user: param.username || username,
|
user: param.username ?? username,
|
||||||
redactUrl: param.redactUrl,
|
redactUrl: param.redactUrl,
|
||||||
serverUrl: url,
|
serverUrl: url,
|
||||||
validateResponse: param.validateResponse,
|
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() {
|
async function whoami() {
|
||||||
return (await _ajax({
|
return (await _ajax({
|
||||||
call: 'whoami',
|
call: 'whoami',
|
||||||
|
@ -1378,16 +1406,14 @@ export function initialize({
|
||||||
|
|
||||||
function getProfileUrl(
|
function getProfileUrl(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
profileKeyVersion?: string,
|
profileKeyVersion: string,
|
||||||
profileKeyCredentialRequest?: string
|
profileKeyCredentialRequest?: string,
|
||||||
|
credentialType: 'pni' | 'profileKey' = 'profileKey'
|
||||||
) {
|
) {
|
||||||
let profileUrl = `/${identifier}`;
|
let profileUrl = `/${identifier}/${profileKeyVersion}`;
|
||||||
|
|
||||||
if (profileKeyVersion) {
|
if (profileKeyCredentialRequest) {
|
||||||
profileUrl += `/${profileKeyVersion}`;
|
profileUrl += `/${profileKeyCredentialRequest}?credentialType=${credentialType}`;
|
||||||
}
|
|
||||||
if (profileKeyVersion && profileKeyCredentialRequest) {
|
|
||||||
profileUrl += `/${profileKeyCredentialRequest}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return profileUrl;
|
return profileUrl;
|
||||||
|
@ -1396,13 +1422,18 @@ export function initialize({
|
||||||
async function getProfile(
|
async function getProfile(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
options: {
|
options: {
|
||||||
profileKeyVersion?: string;
|
profileKeyVersion: string;
|
||||||
profileKeyCredentialRequest?: string;
|
profileKeyCredentialRequest?: string;
|
||||||
userLanguages: ReadonlyArray<string>;
|
userLanguages: ReadonlyArray<string>;
|
||||||
|
credentialType?: 'pni' | 'profileKey';
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
const { profileKeyVersion, profileKeyCredentialRequest, userLanguages } =
|
const {
|
||||||
options;
|
profileKeyVersion,
|
||||||
|
profileKeyCredentialRequest,
|
||||||
|
userLanguages,
|
||||||
|
credentialType = 'profileKey',
|
||||||
|
} = options;
|
||||||
|
|
||||||
return (await _ajax({
|
return (await _ajax({
|
||||||
call: 'profile',
|
call: 'profile',
|
||||||
|
@ -1410,7 +1441,8 @@ export function initialize({
|
||||||
urlParameters: getProfileUrl(
|
urlParameters: getProfileUrl(
|
||||||
identifier,
|
identifier,
|
||||||
profileKeyVersion,
|
profileKeyVersion,
|
||||||
profileKeyCredentialRequest
|
profileKeyCredentialRequest,
|
||||||
|
credentialType
|
||||||
),
|
),
|
||||||
headers: {
|
headers: {
|
||||||
'Accept-Language': formatAcceptLanguageHeader(userLanguages),
|
'Accept-Language': formatAcceptLanguageHeader(userLanguages),
|
||||||
|
@ -1425,9 +1457,13 @@ export function initialize({
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getProfileForUsername(usernameToFetch: string) {
|
async function getProfileForUsername(usernameToFetch: string) {
|
||||||
return getProfile(`username/${usernameToFetch}`, {
|
return (await _ajax({
|
||||||
userLanguages: [],
|
call: 'profile',
|
||||||
});
|
httpType: 'GET',
|
||||||
|
urlParameters: `username/${usernameToFetch}`,
|
||||||
|
responseType: 'json',
|
||||||
|
redactUrl: _createRedactor(usernameToFetch),
|
||||||
|
})) as ProfileType;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function putProfile(
|
async function putProfile(
|
||||||
|
@ -1451,7 +1487,7 @@ export function initialize({
|
||||||
identifier: string,
|
identifier: string,
|
||||||
options: {
|
options: {
|
||||||
accessKey: string;
|
accessKey: string;
|
||||||
profileKeyVersion?: string;
|
profileKeyVersion: string;
|
||||||
profileKeyCredentialRequest?: string;
|
profileKeyCredentialRequest?: string;
|
||||||
userLanguages: ReadonlyArray<string>;
|
userLanguages: ReadonlyArray<string>;
|
||||||
}
|
}
|
||||||
|
@ -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(
|
async function confirmCode(
|
||||||
number: string,
|
number: string,
|
||||||
code: string,
|
code: string,
|
||||||
newPassword: string,
|
newPassword: string,
|
||||||
registrationId: number,
|
registrationId: number,
|
||||||
deviceName?: string | null,
|
deviceName?: string | null,
|
||||||
options: { accessKey?: Uint8Array; uuid?: string } = {}
|
options: { accessKey?: Uint8Array; uuid?: UUIDStringType } = {}
|
||||||
) {
|
) {
|
||||||
const capabilities: CapabilitiesUploadType = {
|
const capabilities: CapabilitiesUploadType = {
|
||||||
announcementGroup: true,
|
announcementGroup: true,
|
||||||
|
@ -1620,7 +1675,7 @@ export function initialize({
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
urlParameters: urlPrefix + code,
|
urlParameters: urlPrefix + code,
|
||||||
jsonData,
|
jsonData,
|
||||||
})) as { uuid?: string; deviceId?: number };
|
})) as ConfirmCodeResultType;
|
||||||
|
|
||||||
// Set final REST credentials to let `registerKeys` succeed.
|
// Set final REST credentials to let `registerKeys` succeed.
|
||||||
username = `${uuid || response.uuid || number}.${response.deviceId || 1}`;
|
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 => ({
|
const preKeys = genKeys.preKeys.map(key => ({
|
||||||
keyId: key.keyId,
|
keyId: key.keyId,
|
||||||
publicKey: Bytes.toBase64(key.publicKey),
|
publicKey: Bytes.toBase64(key.publicKey),
|
||||||
|
@ -1688,14 +1743,19 @@ export function initialize({
|
||||||
|
|
||||||
await _ajax({
|
await _ajax({
|
||||||
call: 'keys',
|
call: 'keys',
|
||||||
|
urlParameters: `?${uuidKindToQuery(uuidKind)}`,
|
||||||
httpType: 'PUT',
|
httpType: 'PUT',
|
||||||
jsonData: keys,
|
jsonData: keys,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setSignedPreKey(signedPreKey: SignedPreKeyType) {
|
async function setSignedPreKey(
|
||||||
|
signedPreKey: SignedPreKeyType,
|
||||||
|
uuidKind: UUIDKind
|
||||||
|
) {
|
||||||
await _ajax({
|
await _ajax({
|
||||||
call: 'signed',
|
call: 'signed',
|
||||||
|
urlParameters: `?${uuidKindToQuery(uuidKind)}`,
|
||||||
httpType: 'PUT',
|
httpType: 'PUT',
|
||||||
jsonData: {
|
jsonData: {
|
||||||
keyId: signedPreKey.keyId,
|
keyId: signedPreKey.keyId,
|
||||||
|
@ -1709,9 +1769,10 @@ export function initialize({
|
||||||
count: number;
|
count: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getMyKeys(): Promise<number> {
|
async function getMyKeys(uuidKind: UUIDKind): Promise<number> {
|
||||||
const result = (await _ajax({
|
const result = (await _ajax({
|
||||||
call: 'keys',
|
call: 'keys',
|
||||||
|
urlParameters: `?${uuidKindToQuery(uuidKind)}`,
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
validateResponse: { count: 'number' },
|
validateResponse: { count: 'number' },
|
||||||
|
@ -2233,11 +2294,12 @@ export function initialize({
|
||||||
|
|
||||||
async function getGroupCredentials(
|
async function getGroupCredentials(
|
||||||
startDay: number,
|
startDay: number,
|
||||||
endDay: number
|
endDay: number,
|
||||||
|
uuidKind: UUIDKind
|
||||||
): Promise<Array<GroupCredentialType>> {
|
): Promise<Array<GroupCredentialType>> {
|
||||||
const response = (await _ajax({
|
const response = (await _ajax({
|
||||||
call: 'getGroupCredentials',
|
call: 'getGroupCredentials',
|
||||||
urlParameters: `/${startDay}/${endDay}`,
|
urlParameters: `/${startDay}/${endDay}?${uuidKindToQuery(uuidKind)}`,
|
||||||
httpType: 'GET',
|
httpType: 'GET',
|
||||||
responseType: 'json',
|
responseType: 'json',
|
||||||
})) as CredentialResponseType;
|
})) as CredentialResponseType;
|
||||||
|
|
|
@ -11,7 +11,8 @@ import * as log from '../../logging/log';
|
||||||
import Helpers from '../Helpers';
|
import Helpers from '../Helpers';
|
||||||
|
|
||||||
export type SetCredentialsOptions = {
|
export type SetCredentialsOptions = {
|
||||||
uuid?: string;
|
uuid: string;
|
||||||
|
pni: string;
|
||||||
number: string;
|
number: string;
|
||||||
deviceId: number;
|
deviceId: number;
|
||||||
deviceName?: string;
|
deviceName?: string;
|
||||||
|
@ -58,22 +59,30 @@ export class User {
|
||||||
return Helpers.unencodeNumber(numberId)[0];
|
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');
|
const uuid = this.storage.get('uuid_id');
|
||||||
if (uuid === undefined) return undefined;
|
if (uuid === undefined) return undefined;
|
||||||
return new UUID(Helpers.unencodeNumber(uuid.toLowerCase())[0]);
|
return new UUID(Helpers.unencodeNumber(uuid.toLowerCase())[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCheckedUuid(): UUID {
|
public getCheckedUuid(uuidKind?: UUIDKind): UUID {
|
||||||
const uuid = this.getUuid();
|
const uuid = this.getUuid(uuidKind);
|
||||||
strictAssert(uuid !== undefined, 'Must have our own uuid');
|
strictAssert(uuid !== undefined, 'Must have our own uuid');
|
||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getPni(): UUID | undefined {
|
public async setPni(pni: string): Promise<void> {
|
||||||
const pni = this.storage.get('pni');
|
await this.storage.put('pni', UUID.cast(pni));
|
||||||
if (pni === undefined) return undefined;
|
|
||||||
return new UUID(pni);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOurUuidKind(uuid: UUID): UUIDKind {
|
public getOurUuidKind(uuid: UUID): UUIDKind {
|
||||||
|
@ -83,7 +92,7 @@ export class User {
|
||||||
return UUIDKind.ACI;
|
return UUIDKind.ACI;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pni = this.getPni();
|
const pni = this.getUuid(UUIDKind.PNI);
|
||||||
if (pni?.toString() === uuid.toString()) {
|
if (pni?.toString() === uuid.toString()) {
|
||||||
return UUIDKind.PNI;
|
return UUIDKind.PNI;
|
||||||
}
|
}
|
||||||
|
@ -118,12 +127,13 @@ export class User {
|
||||||
public async setCredentials(
|
public async setCredentials(
|
||||||
credentials: SetCredentialsOptions
|
credentials: SetCredentialsOptions
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { uuid, number, deviceId, deviceName, password } = credentials;
|
const { uuid, pni, number, deviceId, deviceName, password } = credentials;
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.storage.put('number_id', `${number}.${deviceId}`),
|
this.storage.put('number_id', `${number}.${deviceId}`),
|
||||||
this.storage.put('uuid_id', `${uuid}.${deviceId}`),
|
this.storage.put('uuid_id', `${uuid}.${deviceId}`),
|
||||||
this.storage.put('password', password),
|
this.storage.put('password', password),
|
||||||
|
this.setPni(pni),
|
||||||
deviceName
|
deviceName
|
||||||
? this.storage.put('device_name', deviceName)
|
? this.storage.put('device_name', deviceName)
|
||||||
: Promise.resolve(),
|
: Promise.resolve(),
|
||||||
|
|
1
ts/types/Storage.d.ts
vendored
1
ts/types/Storage.d.ts
vendored
|
@ -134,6 +134,7 @@ export type StorageAccessType = {
|
||||||
paymentAddress: string;
|
paymentAddress: string;
|
||||||
zoomFactor: ZoomFactorType;
|
zoomFactor: ZoomFactorType;
|
||||||
preferredLeftPaneWidth: number;
|
preferredLeftPaneWidth: number;
|
||||||
|
nextSignedKeyRotationTime: number;
|
||||||
areWeASubscriber: boolean;
|
areWeASubscriber: boolean;
|
||||||
subscriberId: Uint8Array;
|
subscriberId: Uint8Array;
|
||||||
subscriberCurrencyCode: string;
|
subscriberCurrencyCode: string;
|
||||||
|
|
1
ts/window.d.ts
vendored
1
ts/window.d.ts
vendored
|
@ -548,7 +548,6 @@ export type WhisperType = {
|
||||||
MessageCollection: typeof MessageModelCollectionType;
|
MessageCollection: typeof MessageModelCollectionType;
|
||||||
|
|
||||||
GroupMemberConversation: WhatIsThis;
|
GroupMemberConversation: WhatIsThis;
|
||||||
RotateSignedPreKeyListener: WhatIsThis;
|
|
||||||
WallClockListener: WhatIsThis;
|
WallClockListener: WhatIsThis;
|
||||||
|
|
||||||
deliveryReceiptQueue: PQueue;
|
deliveryReceiptQueue: PQueue;
|
||||||
|
|
Loading…
Reference in a new issue