From 0b60be84319abae6ca4677d1e92fd3069b7e3201 Mon Sep 17 00:00:00 2001 From: automated-signal <37887102+automated-signal@users.noreply.github.com> Date: Fri, 17 Jan 2025 12:02:35 -0600 Subject: [PATCH] Use X-Signal-Timestamp header Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> --- package-lock.json | 8 ++++---- package.json | 2 +- ts/RemoteConfig.ts | 8 ++++---- ts/test-both/helpers/RemoteConfigStub.ts | 3 +-- ts/textsecure/WebAPI.ts | 24 +++++++++++++++++------- 5 files changed, 27 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9688bf3cd51..7a7b6837a02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -126,7 +126,7 @@ "@indutny/rezip-electron": "2.0.1", "@indutny/symbolicate-mac": "2.3.0", "@napi-rs/canvas": "0.1.61", - "@signalapp/mock-server": "10.2.0", + "@signalapp/mock-server": "10.4.0", "@storybook/addon-a11y": "8.4.4", "@storybook/addon-actions": "8.4.4", "@storybook/addon-controls": "8.4.4", @@ -6491,9 +6491,9 @@ } }, "node_modules/@signalapp/mock-server": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@signalapp/mock-server/-/mock-server-10.2.0.tgz", - "integrity": "sha512-5OL/ajXrEpBmwEpZlZiZbcJSJb9RRy19rMyeYiwIxkPSH3xnyobw510y4p4WkcSfeiV/uif4Tmr99Fk/3oAfxg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@signalapp/mock-server/-/mock-server-10.4.0.tgz", + "integrity": "sha512-Y2Fj2rP8sI/Z8JBjXgJoHO+6VqfORopeFixka11CrxxDBXQWr4u3+P3hsS5wIwwtgcsaXIultRW+kYhbkttRLw==", "dev": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/package.json b/package.json index 9f7e2d35086..769f469a88e 100644 --- a/package.json +++ b/package.json @@ -217,7 +217,7 @@ "@indutny/rezip-electron": "2.0.1", "@indutny/symbolicate-mac": "2.3.0", "@napi-rs/canvas": "0.1.61", - "@signalapp/mock-server": "10.2.0", + "@signalapp/mock-server": "10.4.0", "@storybook/addon-a11y": "8.4.4", "@storybook/addon-actions": "8.4.4", "@storybook/addon-controls": "8.4.4", diff --git a/ts/RemoteConfig.ts b/ts/RemoteConfig.ts index 2aa97d64824..7c31a0108b8 100644 --- a/ts/RemoteConfig.ts +++ b/ts/RemoteConfig.ts @@ -7,7 +7,7 @@ import type { WebAPIType } from './textsecure/WebAPI'; import * as log from './logging/log'; import type { AciString } from './types/ServiceId'; import { parseIntOrThrow } from './util/parseIntOrThrow'; -import { SECOND, HOUR } from './util/durations'; +import { HOUR } from './util/durations'; import * as Bytes from './Bytes'; import { uuidToBytes } from './util/uuidToBytes'; import { dropNull } from './util/dropNull'; @@ -87,14 +87,14 @@ export const refreshRemoteConfig = async ( server: WebAPIType ): Promise => { const now = Date.now(); - const { config: newConfig, serverEpochTime } = await server.getConfig(); + const { config: newConfig, serverTimestamp } = await server.getConfig(); - const serverTimeSkew = serverEpochTime * SECOND - now; + const serverTimeSkew = serverTimestamp - now; if (Math.abs(serverTimeSkew) > HOUR) { log.warn( 'Remote Config: sever clock skew detected. ' + - `Server time ${serverEpochTime * SECOND}, local time ${now}` + `Server time ${serverTimestamp}, local time ${now}` ); } diff --git a/ts/test-both/helpers/RemoteConfigStub.ts b/ts/test-both/helpers/RemoteConfigStub.ts index 48c863da6a1..e0da5d557ac 100644 --- a/ts/test-both/helpers/RemoteConfigStub.ts +++ b/ts/test-both/helpers/RemoteConfigStub.ts @@ -6,14 +6,13 @@ import type { WebAPIType, RemoteConfigResponseType, } from '../../textsecure/WebAPI'; -import { SECOND } from '../../util/durations'; export async function updateRemoteConfig( newConfig: RemoteConfigResponseType['config'] ): Promise { const fakeServer = { async getConfig() { - return { config: newConfig, serverEpochTime: Date.now() / SECOND }; + return { config: newConfig, serverTimestamp: Date.now() }; }, } as Partial as unknown as WebAPIType; diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index 2dd8612fbe2..e0fda86318b 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -829,9 +829,11 @@ const remoteConfigResponseZod = z.object({ value: z.string().or(z.null()).optional(), }) .array(), - serverEpochTime: z.number(), }); -export type RemoteConfigResponseType = z.infer; +export type RemoteConfigResponseType = z.infer & + Readonly<{ + serverTimestamp: number; + }>; export type ProfileType = Readonly<{ identityKey?: string; @@ -2130,16 +2132,24 @@ export function initialize({ } async function getConfig() { - const rawRes = await _ajax({ + const { data, response } = await _ajax({ call: 'config', httpType: 'GET', - responseType: 'json', + responseType: 'jsonwithdetails', }); - const res = parseUnknown(remoteConfigResponseZod, rawRes); + const json = parseUnknown(remoteConfigResponseZod, data); + + const serverTimestamp = safeParseNumber( + response.headers.get('x-signal-timestamp') || '' + ); + if (serverTimestamp == null) { + throw new Error('Missing required x-signal-timestamp header'); + } return { - ...res, - config: res.config.filter( + ...json, + serverTimestamp, + config: json.config.filter( ({ name }: { name: string }) => name.startsWith('desktop.') || name.startsWith('global.') ||