Skip to content
Open
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/public-search-middleware.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"emdash": patch
---

Fixes public access to the search API (#104). The auth middleware blocked `/_emdash/api/search` before the handler ran, so #107's handler-level change never took effect for anonymous callers. Adds the endpoint to `PUBLIC_API_EXACT` so the shipped `LiveSearch` component works on public sites without credentials. Admin endpoints (`/search/enable`, `/search/rebuild`, `/search/stats`, `/search/suggest`) remain authenticated.
23 changes: 19 additions & 4 deletions e2e/tests/search.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,26 @@ test.describe("Search", () => {
});

test.describe("Search API", () => {
test("search endpoint requires authentication", async ({ serverInfo }) => {
// Request without auth token
test("search endpoint is publicly accessible", async ({ serverInfo }) => {
// The LiveSearch component is shipped for public-site use and calls this
// endpoint without credentials. The query layer hardcodes status='published',
// so anonymous callers can only see published content.
const res = await fetch(`${serverInfo.baseUrl}/_emdash/api/search?q=test`);
// Should be 401 or 403
expect([401, 403]).toContain(res.status);
expect(res.status).toBe(200);
});

test("search admin endpoints still require authentication", async ({ serverInfo }) => {
// Admin-only: enable, rebuild, stats must stay gated even though the
// read endpoint is public.
const stats = await fetch(`${serverInfo.baseUrl}/_emdash/api/search/stats`);
expect([401, 403]).toContain(stats.status);

const enable = await fetch(`${serverInfo.baseUrl}/_emdash/api/search/enable`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ collection: "posts" }),
});
expect([401, 403]).toContain(enable.status);
});

test("search endpoint requires a query parameter", async ({ serverInfo }) => {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/astro/middleware/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ const PUBLIC_API_EXACT = new Set([
"/_emdash/api/auth/passkey/verify",
"/_emdash/api/oauth/token",
"/_emdash/api/snapshot",
// Public site search — read-only. The query layer hardcodes status='published'
// so unauthenticated callers only see published content. Admin endpoints
// (/enable, /rebuild, /stats) remain private because they're not in this set.
"/_emdash/api/search",
]);

function isPublicEmDashRoute(pathname: string): boolean {
Expand Down
Loading