Compare commits

...

2 Commits

Author SHA1 Message Date
Julien Calixte
6159ec00e0 ci(gitea): cache DENO_DIR keyed on deno.lock
Some checks failed
CI / check (push) Failing after 16s
setup-deno's built-in cache stores fetched JSR/npm modules and
compiled dependencies across runs. Keying on the lockfile hash
invalidates the cache only when dependencies change.
2026-06-07 22:22:10 +02:00
Julien Calixte
3611add21a test(auth): cover JWT decoding and authenticateRequest flow 2026-06-07 22:21:48 +02:00
2 changed files with 120 additions and 0 deletions

View File

@@ -14,6 +14,8 @@ jobs:
- uses: denoland/setup-deno@v2 - uses: denoland/setup-deno@v2
with: with:
deno-version: v2.x deno-version: v2.x
cache: true
cache-hash: ${{ hashFiles('deno.lock') }}
- name: Format check - name: Format check
run: deno fmt --check run: deno fmt --check

118
tests/auth_verify_test.ts Normal file
View File

@@ -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, unknown>): 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();
}
},
);