Skip to content

Commit 9cb5a28

Browse files
hayatoscascorbic
andauthored
avoid to get context.session to use export const prerender = true (#434)
* fix: avoid session access during prerender * test: cover prerender runtime route and fix changeset frontmatter --------- Co-authored-by: Matt Kane <mkane@cloudflare.com>
1 parent 4d4ac53 commit 9cb5a28

3 files changed

Lines changed: 139 additions & 2 deletions

File tree

.changeset/light-donuts-reply.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"emdash": patch
3+
---
4+
5+
Avoid accessing sessions on prerendered public routes.

packages/core/src/astro/middleware.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
199199
const playgroundDb = locals.__playgroundDb;
200200

201201
if (!isEmDashRoute && !isPublicRuntimeRoute && !hasEditCookie && !hasPreviewToken) {
202-
const sessionUser = await context.session?.get("user");
202+
const sessionUser = context.isPrerendered ? null : await context.session?.get("user");
203203
if (!sessionUser && !playgroundDb) {
204204
// On a fresh deployment the database may be completely empty.
205205
// Public pages call getSiteSettings() / getMenu() via getDb(), which
@@ -374,7 +374,9 @@ export const onRequest = defineMiddleware(async (context, next) => {
374374
const d1Binding = (virtualGetD1Binding as (config: unknown) => unknown)(dbConfig);
375375

376376
if (d1Binding && typeof d1Binding === "object" && "withSession" in d1Binding) {
377-
const isAuthenticated = !!(await context.session?.get("user"));
377+
const isAuthenticated = context.isPrerendered
378+
? false
379+
: !!(await context.session?.get("user"));
378380
const isWrite = request.method !== "GET" && request.method !== "HEAD";
379381

380382
// Determine session constraint:
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { describe, it, expect, vi } from "vitest";
2+
3+
vi.mock("astro:middleware", () => ({
4+
defineMiddleware: (handler: unknown) => handler,
5+
}));
6+
7+
vi.mock(
8+
"virtual:emdash/config",
9+
() => ({
10+
default: {
11+
database: { config: {} },
12+
auth: { mode: "none" },
13+
},
14+
}),
15+
{ virtual: true },
16+
);
17+
18+
vi.mock(
19+
"virtual:emdash/dialect",
20+
() => ({
21+
createDialect: vi.fn(),
22+
isSessionEnabled: vi.fn().mockReturnValue(false),
23+
getD1Binding: vi.fn(),
24+
getDefaultConstraint: vi.fn().mockReturnValue("first-unconstrained"),
25+
getBookmarkCookieName: vi.fn().mockReturnValue("emdash-bookmark"),
26+
createSessionDialect: vi.fn(),
27+
}),
28+
{ virtual: true },
29+
);
30+
31+
vi.mock("virtual:emdash/media-providers", () => ({ mediaProviders: [] }), { virtual: true });
32+
vi.mock("virtual:emdash/plugins", () => ({ plugins: [] }), { virtual: true });
33+
vi.mock(
34+
"virtual:emdash/sandbox-runner",
35+
() => ({
36+
createSandboxRunner: null,
37+
sandboxEnabled: false,
38+
}),
39+
{ virtual: true },
40+
);
41+
vi.mock("virtual:emdash/sandboxed-plugins", () => ({ sandboxedPlugins: [] }), { virtual: true });
42+
vi.mock("virtual:emdash/storage", () => ({ createStorage: null }), { virtual: true });
43+
44+
vi.mock("../../../src/loader.js", () => ({
45+
getDb: vi.fn(async () => ({
46+
selectFrom: () => ({
47+
selectAll: () => ({
48+
limit: () => ({
49+
execute: async () => [],
50+
}),
51+
}),
52+
}),
53+
})),
54+
}));
55+
56+
import { createSessionDialect, getD1Binding, isSessionEnabled } from "virtual:emdash/dialect";
57+
58+
import onRequest from "../../../src/astro/middleware.js";
59+
60+
describe("astro middleware prerendered routes", () => {
61+
it("does not access session on prerendered public runtime routes", async () => {
62+
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
63+
try {
64+
vi.mocked(isSessionEnabled).mockReturnValue(true);
65+
vi.mocked(getD1Binding).mockReturnValue({
66+
withSession: () => {
67+
throw new Error("withSession reached");
68+
},
69+
});
70+
vi.mocked(createSessionDialect).mockReturnValue(undefined as never);
71+
72+
const cookies = {
73+
get: vi.fn(() => undefined),
74+
};
75+
76+
const context: Record<string, unknown> = {
77+
request: new Request("https://example.com/robots.txt"),
78+
url: new URL("https://example.com/robots.txt"),
79+
cookies,
80+
locals: {},
81+
redirect: vi.fn(),
82+
isPrerendered: true,
83+
};
84+
85+
Object.defineProperty(context, "session", {
86+
get() {
87+
throw new Error("session should not be accessed during prerender");
88+
},
89+
});
90+
91+
await expect(
92+
onRequest(context as Parameters<typeof onRequest>[0], async () => new Response("ok")),
93+
).rejects.toThrow("withSession reached");
94+
} finally {
95+
consoleErrorSpy.mockRestore();
96+
}
97+
});
98+
99+
it("does not access session when prerendering public pages", async () => {
100+
const cookies = {
101+
get: vi.fn(() => undefined),
102+
};
103+
const redirect = vi.fn(
104+
(location: string) => new Response(null, { status: 302, headers: { Location: location } }),
105+
);
106+
107+
const context: Record<string, unknown> = {
108+
request: new Request("https://example.com/"),
109+
url: new URL("https://example.com/"),
110+
cookies,
111+
locals: {},
112+
redirect,
113+
isPrerendered: true,
114+
};
115+
116+
Object.defineProperty(context, "session", {
117+
get() {
118+
throw new Error("session should not be accessed during prerender");
119+
},
120+
});
121+
122+
const response = await onRequest(
123+
context as Parameters<typeof onRequest>[0],
124+
async () => new Response("ok"),
125+
);
126+
127+
expect(response.status).toBe(200);
128+
expect(redirect).not.toHaveBeenCalled();
129+
});
130+
});

0 commit comments

Comments
 (0)