Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/light-donuts-reply.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"emdash": patch
---

Avoid accessing sessions on prerendered public routes.
Comment thread
hayatosc marked this conversation as resolved.
6 changes: 4 additions & 2 deletions packages/core/src/astro/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export const onRequest = defineMiddleware(async (context, next) => {
const playgroundDb = locals.__playgroundDb;

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

if (d1Binding && typeof d1Binding === "object" && "withSession" in d1Binding) {
const isAuthenticated = !!(await context.session?.get("user"));
const isAuthenticated = context.isPrerendered
? false
: !!(await context.session?.get("user"));
const isWrite = request.method !== "GET" && request.method !== "HEAD";
Comment thread
hayatosc marked this conversation as resolved.

// Determine session constraint:
Expand Down
130 changes: 130 additions & 0 deletions packages/core/tests/unit/astro/middleware-prerender.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { describe, it, expect, vi } from "vitest";

vi.mock("astro:middleware", () => ({
defineMiddleware: (handler: unknown) => handler,
}));

vi.mock(
"virtual:emdash/config",
() => ({
default: {
database: { config: {} },
auth: { mode: "none" },
},
}),
{ virtual: true },
);

vi.mock(
"virtual:emdash/dialect",
() => ({
createDialect: vi.fn(),
isSessionEnabled: vi.fn().mockReturnValue(false),
getD1Binding: vi.fn(),
getDefaultConstraint: vi.fn().mockReturnValue("first-unconstrained"),
getBookmarkCookieName: vi.fn().mockReturnValue("emdash-bookmark"),
createSessionDialect: vi.fn(),
}),
{ virtual: true },
);

vi.mock("virtual:emdash/media-providers", () => ({ mediaProviders: [] }), { virtual: true });
vi.mock("virtual:emdash/plugins", () => ({ plugins: [] }), { virtual: true });
vi.mock(
"virtual:emdash/sandbox-runner",
() => ({
createSandboxRunner: null,
sandboxEnabled: false,
}),
{ virtual: true },
);
vi.mock("virtual:emdash/sandboxed-plugins", () => ({ sandboxedPlugins: [] }), { virtual: true });
vi.mock("virtual:emdash/storage", () => ({ createStorage: null }), { virtual: true });

vi.mock("../../../src/loader.js", () => ({
getDb: vi.fn(async () => ({
selectFrom: () => ({
selectAll: () => ({
limit: () => ({
execute: async () => [],
}),
}),
}),
})),
}));

import { createSessionDialect, getD1Binding, isSessionEnabled } from "virtual:emdash/dialect";

import onRequest from "../../../src/astro/middleware.js";

describe("astro middleware prerendered routes", () => {
it("does not access session on prerendered public runtime routes", async () => {
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined);
try {
vi.mocked(isSessionEnabled).mockReturnValue(true);
vi.mocked(getD1Binding).mockReturnValue({
withSession: () => {
throw new Error("withSession reached");
},
});
vi.mocked(createSessionDialect).mockReturnValue(undefined as never);

const cookies = {
get: vi.fn(() => undefined),
};

const context: Record<string, unknown> = {
request: new Request("https://example.com/robots.txt"),
url: new URL("https://example.com/robots.txt"),
cookies,
locals: {},
redirect: vi.fn(),
isPrerendered: true,
};

Object.defineProperty(context, "session", {
get() {
throw new Error("session should not be accessed during prerender");
},
});

await expect(
onRequest(context as Parameters<typeof onRequest>[0], async () => new Response("ok")),
).rejects.toThrow("withSession reached");
} finally {
consoleErrorSpy.mockRestore();
}
});

it("does not access session when prerendering public pages", async () => {
const cookies = {
get: vi.fn(() => undefined),
};
const redirect = vi.fn(
(location: string) => new Response(null, { status: 302, headers: { Location: location } }),
);

const context: Record<string, unknown> = {
request: new Request("https://example.com/"),
url: new URL("https://example.com/"),
cookies,
locals: {},
redirect,
isPrerendered: true,
};

Object.defineProperty(context, "session", {
get() {
throw new Error("session should not be accessed during prerender");
},
});

const response = await onRequest(
context as Parameters<typeof onRequest>[0],
async () => new Response("ok"),
);

expect(response.status).toBe(200);
expect(redirect).not.toHaveBeenCalled();
});
});
Loading