Matrix: ignore historical verification catch-up

This commit is contained in:
Gustavo Madeira Santana
2026-03-14 13:53:38 +00:00
parent d51920c6ac
commit e32ee22f9f
2 changed files with 69 additions and 0 deletions

View File

@@ -118,6 +118,56 @@ function createHarness(params?: {
}
describe("registerMatrixMonitorEvents verification routing", () => {
it("does not repost historical verification completions during startup catch-up", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-14T13:10:00.000Z"));
try {
const { sendMessage, roomEventListener } = createHarness();
roomEventListener("!room:example.org", {
event_id: "$done-old",
sender: "@alice:example.org",
type: "m.key.verification.done",
origin_server_ts: Date.now() - 10 * 60 * 1000,
content: {
"m.relates_to": { event_id: "$req-old" },
},
});
await vi.runAllTimersAsync();
expect(sendMessage).not.toHaveBeenCalled();
} finally {
vi.useRealTimers();
}
});
it("still posts fresh verification completions", async () => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-03-14T13:10:00.000Z"));
try {
const { sendMessage, roomEventListener } = createHarness();
roomEventListener("!room:example.org", {
event_id: "$done-fresh",
sender: "@alice:example.org",
type: "m.key.verification.done",
origin_server_ts: Date.now(),
content: {
"m.relates_to": { event_id: "$req-fresh" },
},
});
await vi.waitFor(() => {
expect(sendMessage).toHaveBeenCalledTimes(1);
});
expect(getSentNoticeBody(sendMessage)).toContain(
"Matrix verification completed with @alice:example.org.",
);
} finally {
vi.useRealTimers();
}
});
it("forwards reaction room events into the shared room handler", async () => {
const { onRoomMessage, sendMessage, roomEventListener } = createHarness();

View File

@@ -11,6 +11,7 @@ import {
const MAX_TRACKED_VERIFICATION_EVENTS = 1024;
const SAS_NOTICE_RETRY_DELAY_MS = 750;
const VERIFICATION_EVENT_STARTUP_GRACE_MS = 30_000;
type MatrixVerificationStage = "request" | "ready" | "start" | "cancel" | "done" | "other";
@@ -306,12 +307,24 @@ export function createMatrixVerificationEventRouter(params: {
client: MatrixClient;
logVerboseMessage: (message: string) => void;
}) {
const routerStartedAtMs = Date.now();
const routedVerificationEvents = new Set<string>();
const routedVerificationSasFingerprints = new Set<string>();
const routedVerificationStageNotices = new Set<string>();
const verificationFlowRooms = new Map<string, string>();
const verificationUserRooms = new Map<string, string>();
function shouldEmitVerificationEventNotice(event: MatrixRawEvent): boolean {
const eventTs =
typeof event.origin_server_ts === "number" && Number.isFinite(event.origin_server_ts)
? event.origin_server_ts
: null;
if (eventTs === null) {
return true;
}
return eventTs >= routerStartedAtMs - VERIFICATION_EVENT_STARTUP_GRACE_MS;
}
function rememberVerificationRoom(roomId: string, event: MatrixRawEvent, flowId: string | null) {
for (const candidate of resolveVerificationFlowCandidates({ event, flowId })) {
verificationFlowRooms.set(candidate, roomId);
@@ -420,6 +433,12 @@ export function createMatrixVerificationEventRouter(params: {
rememberVerificationRoom(roomId, event, signal.flowId);
void (async () => {
if (!shouldEmitVerificationEventNotice(event)) {
params.logVerboseMessage(
`matrix: ignoring historical verification event room=${roomId} id=${event.event_id ?? "unknown"} type=${event.type ?? "unknown"}`,
);
return;
}
const flowId = signal.flowId;
const sourceEventId = trimMaybeString(event?.event_id);
const sourceFingerprint = sourceEventId ?? `${senderId}:${event.type}:${flowId ?? "none"}`;