From 0b6edd44c059c36613da396da2b230e5b8478456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emanuel=20Tesa=C5=99?= Date: Tue, 24 Oct 2023 12:13:10 +0200 Subject: [PATCH] Prevent future time drift timestamp (#92) --- packages/api/src/handlers.test.ts | 16 ++++++++++++++++ packages/api/src/handlers.ts | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/packages/api/src/handlers.test.ts b/packages/api/src/handlers.test.ts index 04f17d6e..f31e4499 100644 --- a/packages/api/src/handlers.test.ts +++ b/packages/api/src/handlers.test.ts @@ -81,6 +81,22 @@ describe(batchInsertData.name, () => { expect(cacheModule.getCache()[storedSignedData.airnode]![storedSignedData.templateId]!).toHaveLength(1); }); + it('rejects a batch if there is a beacon with timestamp too far in the future', async () => { + const batchData = [await createSignedData({ timestamp: (Math.floor(Date.now() / 1000) + 60 * 60 * 2).toString() })]; + + const result = await batchInsertData(batchData); + + expect(result).toStrictEqual({ + body: JSON.stringify({ message: 'Request timestamp is too far in the future', extra: batchData[0] }), + headers: { + 'access-control-allow-methods': '*', + 'access-control-allow-origin': '*', + 'content-type': 'application/json', + }, + statusCode: 400, + }); + }); + it('inserts the batch if data is valid', async () => { const batchData = [await createSignedData(), await createSignedData()]; diff --git a/packages/api/src/handlers.ts b/packages/api/src/handlers.ts index 28a696fe..56f24ae0 100644 --- a/packages/api/src/handlers.ts +++ b/packages/api/src/handlers.ts @@ -40,6 +40,12 @@ export const batchInsertData = async (requestBody: unknown): Promise { + // The on-chain contract prevents time drift by making sure the timestamp is at most 1 hour in the future. System + // time drift is less common, but we mirror the contract implementation. + if (Number.parseInt(signedData.timestamp, 10) > Math.floor(Date.now() / 1000) + 60 * 60) { + return generateErrorResponse(400, 'Request timestamp is too far in the future', undefined, signedData); + } + const goRecoverSigner = goSync(() => recoverSignerAddress(signedData)); if (!goRecoverSigner.success) { return generateErrorResponse(400, 'Unable to recover signer address', goRecoverSigner.error.message, signedData);