From 0f2e294fcb093a0576bedda6d850a7b719bce359 Mon Sep 17 00:00:00 2001 From: Heimdall Date: Tue, 3 Nov 2026 09:50:34 +0000 Subject: [PATCH] Pin completion expiry at redeem: 410 at and after expiresAt, for any caller and target Co-Authored-By: Claude Sonnet 5.5 --- tests/link-binding.test.ts | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/link-binding.test.ts b/tests/link-binding.test.ts index 1d37e72..49f6662 100644 --- a/tests/link-binding.test.ts +++ b/tests/link-binding.test.ts @@ -1,7 +1,7 @@ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { buildApp } from "../src/app.js"; import { type HeimdallConfig } from "../src/config.js"; import { type OAuthProviderRuntime } from "../src/oauth.js"; @@ -78,6 +78,7 @@ function discordRuntime(providerUserId: string): OAuthProviderRuntime { const apps: Awaited>[] = []; afterEach(async () => { + vi.useRealTimers(); await Promise.all(apps.splice(0).map((app) => app.close())); }); @@ -202,6 +203,38 @@ describe("redeem binds caller, target and replay", () => { expect((await redeem(r, { completionCode, targetId: "target-a" }, appAuth)).statusCode).toBe(410); }); + it("refuses a completion at and after its expiry with 410, whatever the caller and target, and honours it just before", async () => { + // completionTtlSeconds is 300. Only Date is faked: the app and store run on real timers. + vi.useFakeTimers({ toFake: ["Date"] }); + const t0 = Date.parse("2026-11-03T00:00:00.000Z"); + const ttlMs = createTestConfig().completionTtlSeconds * 1000; + const expired = { statusCode: 410, error: "invalid_or_expired_completion_code" }; + const outcome = async (r: Rig, payload: Record) => { + const res = await redeem(r, payload, appAuth); + return { statusCode: res.statusCode, error: res.json().error }; + }; + + vi.setSystemTime(t0); + const r = await rig("d-exp"); + const code = await linked(r, "target-a"); + const row = await r.store.findAuthCompletion("repixelizer", code); + expect(row?.expiresAt).toBe(new Date(t0 + ttlMs).toISOString()); + + // Just before expiry the right caller and target redeem it. + const early = await rig("d-exp-early"); + const earlyCode = await linked(early, "target-a"); + vi.setSystemTime(t0 + ttlMs - 1); + expect((await redeem(early, { completionCode: earlyCode, targetId: "target-a" }, appAuth)).statusCode).toBe(201); + + // At the expiry instant and after it: 410, for the right target and for a wrong one (not 403), unconsumed. + for (const at of [t0 + ttlMs, t0 + ttlMs + 1]) { + vi.setSystemTime(at); + expect(await outcome(r, { completionCode: code, targetId: "target-a" })).toEqual(expired); + expect(await outcome(r, { completionCode: code, targetId: "target-b" })).toEqual(expired); + } + expect((await r.store.findAuthCompletion("repixelizer", code))?.consumedAt).toBeUndefined(); + }); + it("stores the target on the completion row", async () => { const r = await rig("d-r4"); const completionCode = await linked(r, "target-a");