From caebc75456f8e3ec9f3bfda1c83c81b45901bad9 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Sat, 14 Mar 2026 02:58:53 +0000 Subject: [PATCH] Matrix: keep SAS notices in the verification DM --- .../matrix/src/matrix/monitor/events.test.ts | 68 +++++++++++++++++++ .../src/matrix/monitor/verification-events.ts | 29 ++++++++ 2 files changed, 97 insertions(+) diff --git a/extensions/matrix/src/matrix/monitor/events.test.ts b/extensions/matrix/src/matrix/monitor/events.test.ts index c3a73ba9ba8..54db8ad4f7f 100644 --- a/extensions/matrix/src/matrix/monitor/events.test.ts +++ b/extensions/matrix/src/matrix/monitor/events.test.ts @@ -391,6 +391,74 @@ describe("registerMatrixMonitorEvents verification routing", () => { expect(body).toContain("SAS decimal: 4321 8765 2109"); }); + it("prefers the most recent verification DM over the canonical active DM for unmapped SAS summaries", async () => { + const { sendMessage, roomEventListener, verificationSummaryListener } = createHarness({ + joinedMembersByRoom: { + "!dm-active:example.org": ["@alice:example.org", "@bot:example.org"], + "!dm-current:example.org": ["@alice:example.org", "@bot:example.org"], + }, + }); + if (!verificationSummaryListener) { + throw new Error("verification.summary listener was not registered"); + } + + roomEventListener("!dm-current:example.org", { + event_id: "$start-current", + sender: "@alice:example.org", + type: "m.key.verification.start", + origin_server_ts: Date.now(), + content: { + "m.relates_to": { event_id: "$req-current" }, + }, + }); + + await vi.waitFor(() => { + const bodies = (sendMessage.mock.calls as unknown[][]).map((call) => + String((call[1] as { body?: string } | undefined)?.body ?? ""), + ); + expect(bodies.some((body) => body.includes("Matrix verification started with"))).toBe(true); + }); + + verificationSummaryListener({ + id: "verification-current-room", + otherUserId: "@alice:example.org", + isSelfVerification: false, + initiatedByMe: false, + phase: 3, + phaseName: "started", + pending: true, + methods: ["m.sas.v1"], + canAccept: false, + hasSas: true, + sas: { + decimal: [2468, 1357, 9753], + emoji: [ + ["🔔", "Bell"], + ["📁", "Folder"], + ["🐴", "Horse"], + ], + }, + hasReciprocateQr: false, + completed: false, + createdAt: new Date("2026-02-25T21:42:54.000Z").toISOString(), + updatedAt: new Date("2026-02-25T21:42:55.000Z").toISOString(), + }); + + await vi.waitFor(() => { + const bodies = (sendMessage.mock.calls as unknown[][]).map((call) => + String((call[1] as { body?: string } | undefined)?.body ?? ""), + ); + expect(bodies.some((body) => body.includes("SAS decimal: 2468 1357 9753"))).toBe(true); + }); + const calls = sendMessage.mock.calls as unknown[][]; + const sasCall = calls.find((call) => + String((call[1] as { body?: string } | undefined)?.body ?? "").includes( + "SAS decimal: 2468 1357 9753", + ), + ); + expect((sasCall?.[0] ?? "") as string).toBe("!dm-current:example.org"); + }); + it("retries SAS notice lookup when start arrives before SAS payload is available", async () => { vi.useFakeTimers(); const verifications: Array<{ diff --git a/extensions/matrix/src/matrix/monitor/verification-events.ts b/extensions/matrix/src/matrix/monitor/verification-events.ts index 0e5d3573652..cc6a38d120c 100644 --- a/extensions/matrix/src/matrix/monitor/verification-events.ts +++ b/extensions/matrix/src/matrix/monitor/verification-events.ts @@ -310,6 +310,7 @@ export function createMatrixVerificationEventRouter(params: { const routedVerificationSasFingerprints = new Set(); const routedVerificationStageNotices = new Set(); const verificationFlowRooms = new Map(); + const verificationUserRooms = new Map(); function rememberVerificationRoom(roomId: string, event: MatrixRawEvent, flowId: string | null) { for (const candidate of resolveVerificationFlowCandidates({ event, flowId })) { @@ -323,6 +324,22 @@ export function createMatrixVerificationEventRouter(params: { } } + function rememberVerificationUserRoom(remoteUserId: string, roomId: string): void { + const normalizedUserId = trimMaybeString(remoteUserId); + const normalizedRoomId = trimMaybeString(roomId); + if (!normalizedUserId || !normalizedRoomId) { + return; + } + verificationUserRooms.delete(normalizedUserId); + verificationUserRooms.set(normalizedUserId, normalizedRoomId); + if (verificationUserRooms.size > MAX_TRACKED_VERIFICATION_EVENTS) { + const oldest = verificationUserRooms.keys().next().value; + if (typeof oldest === "string") { + verificationUserRooms.delete(oldest); + } + } + } + async function resolveSummaryRoomId( summary: MatrixVerificationSummaryLike, ): Promise { @@ -340,6 +357,17 @@ export function createMatrixVerificationEventRouter(params: { if (!remoteUserId) { return null; } + const recentRoomId = trimMaybeString(verificationUserRooms.get(remoteUserId)); + if ( + recentRoomId && + (await isStrictDirectRoom({ + client: params.client, + roomId: recentRoomId, + remoteUserId, + })) + ) { + return recentRoomId; + } const inspection = await inspectMatrixDirectRooms({ client: params.client, remoteUserId, @@ -406,6 +434,7 @@ export function createMatrixVerificationEventRouter(params: { ); return; } + rememberVerificationUserRoom(senderId, roomId); if (!trackBounded(routedVerificationEvents, sourceFingerprint)) { return; }