From 3611add21a602201387715a824d05558f8d74260 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Sun, 7 Jun 2026 22:21:48 +0200 Subject: [PATCH] test(auth): cover JWT decoding and authenticateRequest flow --- tests/auth_verify_test.ts | 118 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 tests/auth_verify_test.ts diff --git a/tests/auth_verify_test.ts b/tests/auth_verify_test.ts new file mode 100644 index 0000000..67b83eb --- /dev/null +++ b/tests/auth_verify_test.ts @@ -0,0 +1,118 @@ +import { assertEquals, assertRejects, assertThrows } from "@std/assert"; +import { authenticateRequest, decodeJwtPayload } from "../src/auth/verify.ts"; +import { installFetchStub, jsonResponse } from "./_helpers/fetch_stub.ts"; + +const makeJwt = (payload: Record): string => { + const header = btoa(JSON.stringify({ alg: "HS256", typ: "JWT" })); + const body = btoa(JSON.stringify(payload)); + return `${header}.${body}.sig`; +}; + +Deno.test("decodeJwtPayload parses sub from a 3-segment JWT", () => { + const token = makeJwt({ sub: "did:plc:abc", iat: 1 }); + const payload = decodeJwtPayload(token); + assertEquals(payload.sub, "did:plc:abc"); +}); + +Deno.test("decodeJwtPayload throws on malformed token", () => { + assertThrows(() => decodeJwtPayload("not.a.valid.jwt")); + assertThrows(() => decodeJwtPayload("onlytwo.parts")); +}); + +Deno.test("authenticateRequest rejects missing or non-Bearer Authorization", async () => { + await assertRejects(() => authenticateRequest(null)); + await assertRejects(() => authenticateRequest("")); + await assertRejects(() => authenticateRequest("Basic abc")); +}); + +Deno.test( + "authenticateRequest resolves PDS then verifies session and returns did", + async () => { + const did = "did:plc:user"; + const stub = installFetchStub((req) => { + if (req.url === `https://plc.directory/${did}`) { + return jsonResponse({ + service: [{ + id: "#atproto_pds", + serviceEndpoint: "https://pds.example.com", + }], + }); + } + if ( + req.url === + "https://pds.example.com/xrpc/com.atproto.server.getSession" + ) { + return jsonResponse({ did }); + } + return jsonResponse({ error: "unexpected" }, { status: 500 }); + }); + + try { + const result = await authenticateRequest( + `Bearer ${makeJwt({ sub: did })}`, + ); + assertEquals(result, did); + const sessionCall = stub.calls.find((c) => c.url.includes("/getSession")); + assertEquals( + sessionCall?.headers.get("Authorization")?.startsWith("Bearer "), + true, + ); + } finally { + stub.restore(); + } + }, +); + +Deno.test( + "authenticateRequest throws when plc.directory or getSession is non-2xx", + async () => { + const did = "did:plc:user"; + + const plcFailStub = installFetchStub(() => + new Response("not found", { status: 404 }) + ); + try { + await assertRejects(() => + authenticateRequest(`Bearer ${makeJwt({ sub: did })}`) + ); + } finally { + plcFailStub.restore(); + } + + const sessionFailStub = installFetchStub((req) => { + if (req.url.startsWith("https://plc.directory/")) { + return jsonResponse({ + service: [{ + id: "#atproto_pds", + serviceEndpoint: "https://pds.example.com", + }], + }); + } + return new Response("nope", { status: 401 }); + }); + try { + await assertRejects(() => + authenticateRequest(`Bearer ${makeJwt({ sub: did })}`) + ); + } finally { + sessionFailStub.restore(); + } + }, +); + +Deno.test( + "authenticateRequest throws when DID document has no atproto_pds service", + async () => { + const did = "did:plc:user"; + const stub = installFetchStub(() => + jsonResponse({ service: [{ id: "#other", serviceEndpoint: "x" }] }) + ); + try { + await assertRejects(() => + authenticateRequest(`Bearer ${makeJwt({ sub: did })}`) + ); + } finally { + stub.restore(); + } + }, +);