Limit linked device interaction with backup service

This commit is contained in:
trevor-signal 2025-06-30 16:18:52 -04:00 committed by GitHub
parent 4aecd47727
commit 3e24e510e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 69 additions and 38 deletions

View file

@ -40,7 +40,7 @@ import { isWindowDragElement } from './util/isWindowDragElement';
import { assertDev, strictAssert } from './util/assert';
import { filter } from './util/iterables';
import { isNotNil } from './util/isNotNil';
import { isBackupFeatureEnabled } from './util/isBackupEnabled';
import { areRemoteBackupsTurnedOn } from './util/isBackupEnabled';
import { setAppLoadingScreenMessage } from './setAppLoadingScreenMessage';
import { IdleDetector } from './IdleDetector';
import {
@ -2012,7 +2012,7 @@ export async function startApp(): Promise<void> {
drop(window.Signal.Services.initializeGroupCredentialFetcher());
drop(AttachmentDownloadManager.start());
if (isBackupFeatureEnabled()) {
if (areRemoteBackupsTurnedOn()) {
backupsService.start();
drop(AttachmentBackupManager.start());
}

View file

@ -38,8 +38,13 @@ import {
getBackupSignatureKey,
getBackupMediaSignatureKey,
} from './crypto';
import { isTestOrMockEnvironment } from '../../environment';
import {
areRemoteBackupsTurnedOn,
canAttemptRemoteBackupDownload,
} from '../../util/isBackupEnabled';
const log = createLogger('credentials');
const log = createLogger('Backup.Credentials');
const FETCH_INTERVAL = 3 * DAY;
@ -82,7 +87,7 @@ export class BackupCredentials {
});
if (result === undefined) {
log.info(`BackupCredentials: cache miss for ${now}`);
log.info(`cache miss for ${now}`);
credentials = await this.#fetch();
result = credentials.find(({ type, redemptionTimeMs }) => {
return type === credentialType && redemptionTimeMs === now;
@ -112,7 +117,7 @@ export class BackupCredentials {
return info;
}
log.warn(`BackupCredentials: uploading signature key (${storageKey})`);
log.warn(`uploading signature key (${storageKey})`);
const { server } = window.textsecure;
strictAssert(server, 'server not available');
@ -180,13 +185,13 @@ export class BackupCredentials {
const nextFetchAt = lastFetchAt + FETCH_INTERVAL;
const delay = Math.max(0, nextFetchAt - Date.now());
log.info(`BackupCredentials: scheduling fetch in ${delay}ms`);
log.info(`scheduling fetch in ${delay}ms`);
setTimeout(() => drop(this.#runPeriodicFetch()), delay);
}
async #runPeriodicFetch(): Promise<void> {
try {
log.info('BackupCredentials: run periodic fetch');
log.info('running periodic fetch');
await this.#fetch();
const now = Date.now();
@ -197,7 +202,7 @@ export class BackupCredentials {
} catch (error) {
const delay = this.#fetchBackoff.getAndIncrement();
log.error(
'BackupCredentials: periodic fetch failed with ' +
'periodic fetch failed with ' +
`error: ${toLogFormat(error)}, retrying in ${delay}ms`
);
setTimeout(() => this.#scheduleFetch(), delay);
@ -220,7 +225,16 @@ export class BackupCredentials {
}
async #doFetch(): Promise<ReadonlyArray<BackupCredentialWrapperType>> {
log.info('BackupCredentials: fetching');
const canInteractWithBackupService =
areRemoteBackupsTurnedOn() || canAttemptRemoteBackupDownload();
if (!canInteractWithBackupService) {
throw new Error(
'Cannot fetch credentials; remote backups are not active'
);
}
log.info('fetching');
const now = Date.now();
const startDayInMs = toDayMillis(now);
@ -239,37 +253,39 @@ export class BackupCredentials {
endDayInMs,
});
} catch (error) {
if (!(error instanceof HTTPError)) {
// A 404 indicates the backupId has not been set; only primary devices can set the
// backupId
if (
(isTestOrMockEnvironment() ||
window.ConversationController.areWePrimaryDevice()) &&
error instanceof HTTPError &&
error.code === 404
) {
// Backup id is missing
const messagesRequest = messagesCtx.getRequest();
const mediaRequest = mediaCtx.getRequest();
// Set it
await server.setBackupId({
messagesBackupAuthCredentialRequest: messagesRequest.serialize(),
mediaBackupAuthCredentialRequest: mediaRequest.serialize(),
});
// And try again!
response = await server.getBackupCredentials({
startDayInMs,
endDayInMs,
});
} else {
throw error;
}
if (error.code !== 404) {
throw error;
}
// Backup id is missing
const messagesRequest = messagesCtx.getRequest();
const mediaRequest = mediaCtx.getRequest();
// Set it
await server.setBackupId({
messagesBackupAuthCredentialRequest: messagesRequest.serialize(),
mediaBackupAuthCredentialRequest: mediaRequest.serialize(),
});
// And try again!
response = await server.getBackupCredentials({
startDayInMs,
endDayInMs,
});
}
const { messages: messageCredentials, media: mediaCredentials } =
response.credentials;
log.info(
'BackupCredentials: got ' +
`${messageCredentials.length}/${mediaCredentials.length}`
`got ${messageCredentials.length}/${mediaCredentials.length} message/media credentials`
);
const serverPublicParams = new GenericServerPublicParams(
@ -352,7 +368,7 @@ export class BackupCredentials {
const startMs = result[0].redemptionTimeMs;
const endMs = result[result.length - 1].redemptionTimeMs;
log.info(`BackupCredentials: saved [${startMs}, ${endMs}]`);
log.info(`saved [${startMs}, ${endMs}]`);
strictAssert(result.length === 14, 'Expected one week of credentials');

View file

@ -84,8 +84,9 @@ import {
} from './util/localBackup';
import { AttachmentLocalBackupManager } from '../../jobs/AttachmentLocalBackupManager';
import { decipherWithAesKey } from '../../util/decipherWithAesKey';
import { areRemoteBackupsTurnedOn } from '../../util/isBackupEnabled';
const log = createLogger('index');
const log = createLogger('backupsService');
export { BackupType };
@ -162,13 +163,18 @@ export class BackupsService {
);
public start(): void {
if (!areRemoteBackupsTurnedOn()) {
log.warn('remote backups are not turned on; not starting');
return;
}
if (this.#isStarted) {
log.warn('BackupsService: already started');
log.warn('already started');
return;
}
this.#isStarted = true;
log.info('BackupsService: starting...');
log.info('starting...');
setInterval(() => {
drop(this.#runPeriodicRefresh());

View file

@ -64,9 +64,9 @@ import { SignalService as Proto } from '../protobuf';
import { createLogger } from '../logging/log';
import type { StorageAccessType } from '../types/Storage';
import { getRelativePath, createName } from '../util/attachmentPath';
import { isBackupFeatureEnabled } from '../util/isBackupEnabled';
import { isLinkAndSyncEnabled } from '../util/isLinkAndSyncEnabled';
import { getMessageQueueTime } from '../util/getMessageQueueTime';
import { canAttemptRemoteBackupDownload } from '../util/isBackupEnabled';
const log = createLogger('AccountManager');
@ -1120,7 +1120,7 @@ export default class AccountManager extends EventTarget {
}
const shouldDownloadBackup =
isBackupFeatureEnabled() ||
canAttemptRemoteBackupDownload() ||
(isLinkAndSyncEnabled() && options.ephemeralBackupKey);
// Set backup download path before storing credentials to ensure that

View file

@ -5,6 +5,15 @@ import * as RemoteConfig from '../RemoteConfig';
import { isTestOrMockEnvironment } from '../environment';
import { isStagingServer } from './isStagingServer';
export function areRemoteBackupsTurnedOn(): boolean {
return isBackupFeatureEnabled() && window.storage.get('backupTier') != null;
}
// Downloading from a remote backup is currently a test-only feature
export function canAttemptRemoteBackupDownload(): boolean {
return isBackupFeatureEnabled() && isTestOrMockEnvironment();
}
export function isBackupFeatureEnabled(): boolean {
if (isStagingServer() || isTestOrMockEnvironment()) {
return true;