From 3aebfa3dccbf36338d88d0e74f6f41e44db203f3 Mon Sep 17 00:00:00 2001 From: LinzMatt Date: Wed, 22 Jul 2026 21:07:13 +0200 Subject: [PATCH] Dedupe E2EE one-time prekeys before unique index --- ...0000-add-chat-security-and-e2ee-devices.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/database/migrations/20260518000000-add-chat-security-and-e2ee-devices.js b/src/database/migrations/20260518000000-add-chat-security-and-e2ee-devices.js index 57dd66a..edaa673 100644 --- a/src/database/migrations/20260518000000-add-chat-security-and-e2ee-devices.js +++ b/src/database/migrations/20260518000000-add-chat-security-and-e2ee-devices.js @@ -48,6 +48,30 @@ const removeIndexIfExists = async (queryInterface, tableName, indexName) => { } }; +const removeDuplicateOneTimePreKeys = async (queryInterface) => { + if (!(await tableExists(queryInterface, "DeviceOneTimePreKeys"))) { + return; + } + + await queryInterface.sequelize.query(` + DELETE FROM "DeviceOneTimePreKeys" duplicate_keys + USING ( + SELECT + id, + ROW_NUMBER() OVER ( + PARTITION BY "userDeviceId", "preKeyId" + ORDER BY + CASE WHEN "usedAt" IS NOT NULL THEN 0 ELSE 1 END, + "createdAt" ASC NULLS LAST, + id ASC + ) AS duplicate_rank + FROM "DeviceOneTimePreKeys" + ) ranked_keys + WHERE duplicate_keys.id = ranked_keys.id + AND ranked_keys.duplicate_rank > 1; + `); +}; + module.exports = { up: async (queryInterface, Sequelize) => { await addColumnIfMissing(queryInterface, "Chats", "securityMode", { @@ -240,6 +264,8 @@ module.exports = { await addIndexIfMissing(queryInterface, "DeviceOneTimePreKeys", ["userDeviceId", "usedAt"], { name: "idx_device_one_time_pre_keys_device_used_at", }); + await removeDuplicateOneTimePreKeys(queryInterface); + await addIndexIfMissing(queryInterface, "DeviceOneTimePreKeys", ["userDeviceId", "preKeyId"], { name: "idx_device_one_time_pre_keys_device_prekey", unique: true,