From 1b9f914b8bd6ecd673c63510749b14e2d2c1cdf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 4 Jun 2026 04:08:40 +0200 Subject: [PATCH] fix(crypto): cover the WHOOP encrypted columns in key rotation The rotation registry missed the per-user WHOOP credential columns and the WhoopConnection token columns, so a key rotation would skip those rows and leave them undecryptable once the operator drops the old key. Add whoopClientIdEncrypted / whoopClientSecretEncrypted to the User field list and a WhoopConnection accessToken / refreshToken pass. --- scripts/rotate-encryption-key.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/scripts/rotate-encryption-key.ts b/scripts/rotate-encryption-key.ts index 0976002c2..5ffe0c5de 100644 --- a/scripts/rotate-encryption-key.ts +++ b/scripts/rotate-encryption-key.ts @@ -100,6 +100,8 @@ async function main() { moodLogApiKeyEncrypted: true, withingsClientIdEncrypted: true, withingsClientSecretEncrypted: true, + whoopClientIdEncrypted: true, + whoopClientSecretEncrypted: true, }, }); @@ -115,6 +117,8 @@ async function main() { "moodLogApiKeyEncrypted", "withingsClientIdEncrypted", "withingsClientSecretEncrypted", + "whoopClientIdEncrypted", + "whoopClientSecretEncrypted", ]; for (const field of userFields) { const r = await rotateField( @@ -152,6 +156,26 @@ async function main() { results.push(r); } + // ───── WhoopConnection table (accessToken / refreshToken) ───── + const whoop = await prisma.whoopConnection.findMany({ + select: { id: true, accessToken: true, refreshToken: true }, + }); + for (const field of ["accessToken", "refreshToken"] as const) { + const r = await rotateField( + "WhoopConnection", + field, + whoop, + (w) => w[field], + async (id, ciphertext) => { + await prisma.whoopConnection.update({ + where: { id }, + data: { [field]: ciphertext } as Record, + }); + }, + ); + results.push(r); + } + // ───── AppSettings table (singleton typically) ───── const settings = await prisma.appSettings.findMany({ select: {