Block WebAPI during active registration

This commit is contained in:
Fedor Indutny 2021-12-17 22:26:50 +01:00 committed by GitHub
parent 9e9e5274cf
commit 8070b8b14f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 35 deletions

View File

@ -172,24 +172,32 @@ export default class AccountManager extends EventTarget {
const profileKey = getRandomBytes(PROFILE_KEY_LENGTH); const profileKey = getRandomBytes(PROFILE_KEY_LENGTH);
const accessKey = deriveAccessKey(profileKey); const accessKey = deriveAccessKey(profileKey);
await this.createAccount({ const registrationBaton = this.server.startRegistration();
number, try {
verificationCode, await this.createAccount({
identityKeyPair, number,
pniKeyPair, verificationCode,
profileKey, identityKeyPair,
accessKey, pniKeyPair,
}); profileKey,
accessKey,
});
await this.clearSessionsAndPreKeys(); await this.clearSessionsAndPreKeys();
await Promise.all( await Promise.all(
[UUIDKind.ACI, UUIDKind.PNI].map(async kind => { [UUIDKind.ACI, UUIDKind.PNI].map(async kind => {
const keys = await this.generateKeys(SIGNED_KEY_GEN_BATCH_SIZE, kind); const keys = await this.generateKeys(
await this.server.registerKeys(keys, kind); SIGNED_KEY_GEN_BATCH_SIZE,
await this.confirmKeys(keys, kind); kind
}) );
); await this.server.registerKeys(keys, kind);
await this.confirmKeys(keys, kind);
})
);
} finally {
this.server.finishRegistration(registrationBaton);
}
await this.registrationDone(); await this.registrationDone();
}); });
} }
@ -276,23 +284,30 @@ export default class AccountManager extends EventTarget {
); );
} }
await this.createAccount({ const registrationBaton = this.server.startRegistration();
number: provisionMessage.number,
verificationCode: provisionMessage.provisioningCode, try {
identityKeyPair: provisionMessage.identityKeyPair, await this.createAccount({
profileKey: provisionMessage.profileKey, number: provisionMessage.number,
deviceName, verificationCode: provisionMessage.provisioningCode,
userAgent: provisionMessage.userAgent, identityKeyPair: provisionMessage.identityKeyPair,
readReceipts: provisionMessage.readReceipts, profileKey: provisionMessage.profileKey,
}); deviceName,
await clearSessionsAndPreKeys(); userAgent: provisionMessage.userAgent,
// TODO: DESKTOP-2794 readReceipts: provisionMessage.readReceipts,
const keys = await this.generateKeys( });
SIGNED_KEY_GEN_BATCH_SIZE, await clearSessionsAndPreKeys();
UUIDKind.ACI // TODO: DESKTOP-2794
); const keys = await this.generateKeys(
await this.server.registerKeys(keys, UUIDKind.ACI); SIGNED_KEY_GEN_BATCH_SIZE,
await this.confirmKeys(keys, UUIDKind.ACI); UUIDKind.ACI
);
await this.server.registerKeys(keys, UUIDKind.ACI);
await this.confirmKeys(keys, UUIDKind.ACI);
} finally {
this.server.finishRegistration(registrationBaton);
}
await this.registrationDone(); await this.registrationDone();
}); });
} }

View File

@ -28,6 +28,8 @@ import type { Readable } from 'stream';
import { assert, strictAssert } from '../util/assert'; import { assert, strictAssert } from '../util/assert';
import { isRecord } from '../util/isRecord'; import { isRecord } from '../util/isRecord';
import * as durations from '../util/durations'; import * as durations from '../util/durations';
import type { ExplodePromiseResultType } from '../util/explodePromise';
import { explodePromise } from '../util/explodePromise';
import { getUserAgent } from '../util/getUserAgent'; import { getUserAgent } from '../util/getUserAgent';
import { getStreamWithTimeout } from '../util/getStreamWithTimeout'; import { getStreamWithTimeout } from '../util/getStreamWithTimeout';
import { formatAcceptLanguageHeader } from '../util/userLanguages'; import { formatAcceptLanguageHeader } from '../util/userLanguages';
@ -633,6 +635,7 @@ type AjaxOptionsType = {
urlParameters?: string; urlParameters?: string;
username?: string; username?: string;
validateResponse?: any; validateResponse?: any;
isRegistration?: true;
} & ( } & (
| { | {
unauthenticated?: false; unauthenticated?: false;
@ -766,6 +769,8 @@ export type GetUuidsForE164sV2OptionsType = Readonly<{
}>; }>;
export type WebAPIType = { export type WebAPIType = {
startRegistration(): unknown;
finishRegistration(baton: unknown): void;
confirmCode: ( confirmCode: (
number: string, number: string,
code: string, code: string,
@ -1067,6 +1072,8 @@ export function initialize({
const PARSE_GROUP_LOG_RANGE_HEADER = const PARSE_GROUP_LOG_RANGE_HEADER =
/$versions (\d{1,10})-(\d{1,10})\/(d{1,10})/; /$versions (\d{1,10})-(\d{1,10})\/(d{1,10})/;
let activeRegistration: ExplodePromiseResultType<void> | undefined;
const socketManager = new SocketManager({ const socketManager = new SocketManager({
url, url,
certificateAuthority, certificateAuthority,
@ -1117,6 +1124,7 @@ export function initialize({
confirmCode, confirmCode,
createGroup, createGroup,
deleteUsername, deleteUsername,
finishRegistration,
fetchLinkPreviewImage, fetchLinkPreviewImage,
fetchLinkPreviewMetadata, fetchLinkPreviewMetadata,
getAttachment, getAttachment,
@ -1165,6 +1173,7 @@ export function initialize({
sendMessagesUnauth, sendMessagesUnauth,
sendWithSenderKey, sendWithSenderKey,
setSignedPreKey, setSignedPreKey,
startRegistration,
updateDeviceName, updateDeviceName,
uploadAvatar, uploadAvatar,
uploadGroupAvatar, uploadGroupAvatar,
@ -1186,6 +1195,18 @@ export function initialize({
): Promise<unknown>; ): Promise<unknown>;
async function _ajax(param: AjaxOptionsType): Promise<unknown> { async function _ajax(param: AjaxOptionsType): Promise<unknown> {
if (
!param.unauthenticated &&
activeRegistration &&
!param.isRegistration
) {
log.info('WebAPI: request blocked by active registration');
const start = Date.now();
await activeRegistration.promise;
const duration = Date.now() - start;
log.info(`WebAPI: request unblocked after ${duration}ms`);
}
if (!param.urlParameters) { if (!param.urlParameters) {
param.urlParameters = ''; param.urlParameters = '';
} }
@ -1635,6 +1656,31 @@ export function initialize({
} }
} }
function startRegistration() {
strictAssert(
activeRegistration === undefined,
'Registration already in progress'
);
activeRegistration = explodePromise<void>();
log.info('WebAPI: starting registration');
return activeRegistration;
}
function finishRegistration(registration: unknown) {
strictAssert(activeRegistration !== undefined, 'No active registration');
strictAssert(
activeRegistration === registration,
'Invalid registration baton'
);
log.info('WebAPI: finishing registration');
const current = activeRegistration;
activeRegistration = undefined;
current.resolve();
}
async function confirmCode( async function confirmCode(
number: string, number: string,
code: string, code: string,
@ -1677,6 +1723,7 @@ export function initialize({
password = newPassword; password = newPassword;
const response = (await _ajax({ const response = (await _ajax({
isRegistration: true,
call, call,
httpType: 'PUT', httpType: 'PUT',
responseType: 'json', responseType: 'json',
@ -1749,6 +1796,7 @@ export function initialize({
}; };
await _ajax({ await _ajax({
isRegistration: true,
call: 'keys', call: 'keys',
urlParameters: `?${uuidKindToQuery(uuidKind)}`, urlParameters: `?${uuidKindToQuery(uuidKind)}`,
httpType: 'PUT', httpType: 'PUT',

View File

@ -1,11 +1,13 @@
// Copyright 2021 Signal Messenger, LLC // Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
export function explodePromise<T>(): { export type ExplodePromiseResultType<T> = Readonly<{
promise: Promise<T>; promise: Promise<T>;
resolve: (value: T) => void; resolve: (value: T) => void;
reject: (error: Error) => void; reject: (error: Error) => void;
} { }>;
export function explodePromise<T>(): ExplodePromiseResultType<T> {
let resolve: (value: T) => void; let resolve: (value: T) => void;
let reject: (error: Error) => void; let reject: (error: Error) => void;