diff --git a/src/__tests__/predicate-gate.test.ts b/src/__tests__/predicate-gate.test.ts index a96b385..817e284 100644 --- a/src/__tests__/predicate-gate.test.ts +++ b/src/__tests__/predicate-gate.test.ts @@ -683,6 +683,54 @@ describe("predicateGate", () => { }) }) + it("X-Payment: rejects an authorization that omits validBefore", async () => { + const { predicateGate } = await import( + "../lib/middleware/predicate-gate.js" + ) + const operator = + "0x5ECA0441311643608a8c9Ab8B250f695Dd32E2a8" as `0x${string}` + const gate = predicateGate({ + toolId: TEST_TOOL_ID, + operatorAddress: operator, + }) + const ctx: Partial = { gates: {} } + + // Authorization with NO validBefore. The signer (recovery mock -> TEST_CALLER) + // matches `from`, so the expiry check is the only bound on this token. When + // validBefore is absent that check is skipped, so an unbounded (non-expiring) + // proof is accepted. The sibling EIP-3009 path (verifyEip3009Auth) requires + // validBefore, so the two auth paths must agree: reject when it is missing. + const header = Buffer.from( + JSON.stringify({ + x402Version: 1, + scheme: "exact", + network: "base", + payload: { + signature: "0xabcd", + authorization: { + from: TEST_CALLER, + to: operator, + value: "0", + validAfter: "0", + // validBefore intentionally omitted + nonce: + "0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", + }, + }, + }), + ).toString("base64") + const request = new Request("https://example.com/api", { + method: "POST", + headers: { "X-Payment": header }, + }) + + const response = await gate.check(request, ctx) + + expect(response?.status).toBe(401) + const body = await response?.json() + expect(body.error).toMatch(/missing required authorization fields/i) + }) + it("X-Payment: takes precedence over Authorization header", async () => { const { predicateGate } = await import( "../lib/middleware/predicate-gate.js" diff --git a/src/lib/middleware/predicate-gate.ts b/src/lib/middleware/predicate-gate.ts index 127e947..194b686 100644 --- a/src/lib/middleware/predicate-gate.ts +++ b/src/lib/middleware/predicate-gate.ts @@ -522,7 +522,7 @@ async function verifyXPaymentAuth( const auth = paymentPayload.payload?.authorization const signature = paymentPayload.payload?.signature - if (!auth?.from || !auth?.to || !signature || !auth?.nonce) { + if (!auth?.from || !auth?.to || !signature || !auth?.nonce || !auth?.validBefore) { return { error: "Predicate gate: X-Payment missing required authorization fields",