mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
feat(02-12.1): vary the record echo on the headers that produced it
A shared cache is one reverse proxy away, and without Vary the first one to appear would key a delegated record response on URL alone and hand it to the next request under a different context. Appends rather than replaces, so a route that already varies keeps what it had.
This commit is contained in:
1 parent
dc343f434c
commit
bcf2322f65
2 files changed
+85
No files matched your search
@@ -330,6 +330,64 @@ describe("the contract module is safe in the browser bundle", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("attachRecordContextEcho", () => {
|
||||
async function attach(
|
||||
context: { epoch: number; scope: string | null } | undefined,
|
||||
initial?: Record<string, string>,
|
||||
): Promise<Headers> {
|
||||
const { attachRecordContextEcho } =
|
||||
await import("@/lib/sharing/record-session-fence");
|
||||
const headers = new Headers(initial);
|
||||
attachRecordContextEcho(headers, context);
|
||||
return headers;
|
||||
}
|
||||
|
||||
it("declares that the answer varies on the two fence headers", async () => {
|
||||
// Without this a shared cache — one reverse proxy away — would key a
|
||||
// delegated record response on URL alone and serve it to the next request
|
||||
// for the same URL under a different context. One person's record handed
|
||||
// to another by an infrastructure change nobody thought was a code change.
|
||||
const headers = await attach({ epoch: 4, scope: "owner-1" });
|
||||
const vary = (headers.get("Vary") ?? "")
|
||||
.split(",")
|
||||
.map((part) => part.trim().toLowerCase());
|
||||
expect(vary).toContain(RECORD_EPOCH_HEADER);
|
||||
expect(vary).toContain(RECORD_SCOPE_HEADER);
|
||||
});
|
||||
|
||||
it("keeps a Vary the route had already set", async () => {
|
||||
const headers = await attach(
|
||||
{ epoch: 1, scope: null },
|
||||
{ Vary: "Accept-Encoding" },
|
||||
);
|
||||
const vary = (headers.get("Vary") ?? "")
|
||||
.split(",")
|
||||
.map((part) => part.trim().toLowerCase());
|
||||
expect(vary).toContain("accept-encoding");
|
||||
expect(vary).toContain(RECORD_EPOCH_HEADER);
|
||||
});
|
||||
|
||||
it("does not repeat a header the route already varied on", async () => {
|
||||
const headers = await attach(
|
||||
{ epoch: 1, scope: null },
|
||||
{ Vary: `Accept-Encoding, ${RECORD_EPOCH_HEADER.toUpperCase()}` },
|
||||
);
|
||||
const vary = (headers.get("Vary") ?? "")
|
||||
.split(",")
|
||||
.map((part) => part.trim().toLowerCase());
|
||||
expect(vary.filter((v) => v === RECORD_EPOCH_HEADER)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("adds nothing at all when no record scope was resolved", async () => {
|
||||
// The response carries no echo, so there is nothing for a cache to vary
|
||||
// on — and claiming otherwise would fragment the cache key of every public
|
||||
// route for no reason.
|
||||
const headers = await attach(undefined);
|
||||
expect(headers.get("Vary")).toBeNull();
|
||||
expect(headers.get(RECORD_EPOCH_HEADER)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("assertRecordSessionFence", () => {
|
||||
beforeEach(() => {
|
||||
headerJar.clear();
|
||||
|
||||
@@ -103,6 +103,33 @@ export function attachRecordContextEcho(
|
||||
if (!context) return;
|
||||
headers.set(RECORD_EPOCH_HEADER, String(context.epoch));
|
||||
headers.set(RECORD_SCOPE_HEADER, recordScopeHeaderValue(context.scope));
|
||||
// And declare that the answer DEPENDS on those two request headers.
|
||||
//
|
||||
// Nothing between the browser and this app caches today — the responses that
|
||||
// reach here are private and same-origin. But a shared cache is one reverse
|
||||
// proxy away, and without `Vary` the first one to appear would key a
|
||||
// delegated record response on URL alone and hand it to the next request for
|
||||
// the same URL under a different context. That is one person's health record
|
||||
// served to another, produced by an infrastructure change nobody thought was
|
||||
// a code change.
|
||||
//
|
||||
// Appended rather than assigned: a route that already varies on something
|
||||
// (`Accept-Encoding`, a locale header) must keep it. Case-insensitive
|
||||
// de-duplication, because a value repeated in two spellings is the sort of
|
||||
// header that makes a proxy give up and cache nothing.
|
||||
const existing = headers
|
||||
.get("Vary")
|
||||
?.split(",")
|
||||
.map((part) => part.trim())
|
||||
.filter((part) => part.length > 0);
|
||||
const names = new Set((existing ?? []).map((part) => part.toLowerCase()));
|
||||
const merged = [...(existing ?? [])];
|
||||
for (const header of [RECORD_EPOCH_HEADER, RECORD_SCOPE_HEADER]) {
|
||||
if (names.has(header.toLowerCase())) continue;
|
||||
names.add(header.toLowerCase());
|
||||
merged.push(header);
|
||||
}
|
||||
headers.set("Vary", merged.join(", "));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user