Skip to content
File

Blob: tests/worker/headers-cache.test.ts

typescript76 lines
1import { env, exports } from "cloudflare:workers";
2import { beforeAll, describe, expect, it } from "vitest";
3 
4import { makeAuth } from "@/worker/auth";
5 
6import { ISSUER, signInForCookie, testHeaders } from "./helpers";
7 
8const SELF = exports.default;
9 
10// Cache headers protect authenticated payloads (admin lists, account
11// data, secret-bearing responses) from intermediary or shared-cache
12// retention. Public OIDC documents are cacheable; other dynamic worker
13// responses get a baseline `Cache-Control: no-store, private` + `Vary:
14// Cookie`. Secret-bearing responses (invite create, OAuth client
15// create/rotate) carry the stricter `no-store, private, max-age=0`
16// directly.
17describe("Cache and Vary headers", () => {
18 const adminCred = {
19 email: "headers-cache-admin@example.com",
20 password: "correct-horse-battery-staple",
21 name: "Cache Headers Admin",
22 };
23 let adminCookie: string;
24 
25 beforeAll(async () => {
26 const auth = makeAuth(env, { baseURL: ISSUER, issuer: ISSUER });
27 await auth.api.signUpEmail({ body: adminCred, asResponse: false });
28 await env.DB.prepare("UPDATE users SET role = ? WHERE email = ?").bind("admin", adminCred.email).run();
29 adminCookie = await signInForCookie(adminCred.email, adminCred.password, "10.74.0.1");
30 });
31 
32 it("emits Cache-Control: no-store, private and Vary: Cookie on authenticated GET", async () => {
33 const res = await SELF.fetch(`${ISSUER}/api/admin/clients`, { headers: { cookie: adminCookie } });
34 expect(res.status).toBe(200);
35 expect(res.headers.get("cache-control")).toBe("no-store, private");
36 expect(res.headers.get("vary")).toBe("Cookie");
37 });
38 
39 it("emits Cache-Control: no-store, private on unauthenticated public route", async () => {
40 // /api/config is public but still goes through the worker — the
41 // middleware-set defaults must apply there too so a misconfigured
42 // proxy does not cache config drift across users.
43 const res = await SELF.fetch(`${ISSUER}/api/config`);
44 expect(res.status).toBe(200);
45 expect(res.headers.get("cache-control")).toBe("no-store, private");
46 expect(res.headers.get("vary")).toBe("Cookie");
47 });
48 
49 it("emits public cache headers without Vary: Cookie on JWKS", async () => {
50 const res = await SELF.fetch(`${ISSUER}/api/auth/jwks`);
51 expect(res.status).toBe(200);
52 expect(res.headers.get("cache-control")).toBe("public, max-age=300, stale-while-revalidate=600");
53 expect(res.headers.get("vary")).toBeNull();
54 });
55 
56 it("does not add Vary: Cookie to OIDC discovery metadata", async () => {
57 const res = await SELF.fetch(`${ISSUER}/.well-known/openid-configuration`);
58 expect(res.status).toBe(200);
59 expect(res.headers.get("cache-control")).toMatch(/^public, /);
60 expect(res.headers.get("vary")).toBeNull();
61 });
62 
63 it("emits Cache-Control: no-store, private, max-age=0 on secret-bearing OAuth client create", async () => {
64 const res = await SELF.fetch(`${ISSUER}/api/admin/clients`, {
65 method: "POST",
66 headers: testHeaders({ cookie: adminCookie }),
67 body: JSON.stringify({
68 name: "headers-cache-fixture",
69 redirectUris: ["http://127.0.0.1:0/cb"],
70 }),
71 });
72 expect(res.status).toBe(201);
73 expect(res.headers.get("cache-control")).toBe("no-store, private, max-age=0");
74 });
75});