Skip to content

Commit 476cb3a

Browse files
csfalcaoclaudeascorbic
authored
fix(middleware): allow public access to search API at middleware layer (#424)
PR #107 removed the handler-level `requirePerm()` check from the search endpoints, but the auth middleware still returned 401 before the handlers ran because `/_emdash/api/search` was not in `PUBLIC_API_EXACT`. The handler-level changes therefore never executed for anonymous callers, and the shipped `LiveSearch` component (which fetches without credentials) silently showed "No results found" on every query. This change adds `/_emdash/api/search` to `PUBLIC_API_EXACT` so the middleware lets anonymous GET requests reach the handler. The query layer already hardcodes `status='published'`, so anonymous callers still only see published content. Admin endpoints (`/enable`, `/rebuild`, `/stats`, and `/suggest`) remain authenticated because they are not in the set. The existing E2E test `"search endpoint requires authentication"` asserted the buggy behavior and is replaced with two new tests: one verifying public access to `/_emdash/api/search`, and one verifying that `/stats` and `/enable` remain gated. Closes #104. Follows up on #107. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Matt Kane <mkane@cloudflare.com>
1 parent 9e06219 commit 476cb3a

3 files changed

Lines changed: 28 additions & 4 deletions

File tree

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+
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.

e2e/tests/search.spec.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,11 +186,26 @@ test.describe("Search", () => {
186186
});
187187

188188
test.describe("Search API", () => {
189-
test("search endpoint requires authentication", async ({ serverInfo }) => {
190-
// Request without auth token
189+
test("search endpoint is publicly accessible", async ({ serverInfo }) => {
190+
// The LiveSearch component is shipped for public-site use and calls this
191+
// endpoint without credentials. The query layer hardcodes status='published',
192+
// so anonymous callers can only see published content.
191193
const res = await fetch(`${serverInfo.baseUrl}/_emdash/api/search?q=test`);
192-
// Should be 401 or 403
193-
expect([401, 403]).toContain(res.status);
194+
expect(res.status).toBe(200);
195+
});
196+
197+
test("search admin endpoints still require authentication", async ({ serverInfo }) => {
198+
// Admin-only: enable, rebuild, stats must stay gated even though the
199+
// read endpoint is public.
200+
const stats = await fetch(`${serverInfo.baseUrl}/_emdash/api/search/stats`);
201+
expect([401, 403]).toContain(stats.status);
202+
203+
const enable = await fetch(`${serverInfo.baseUrl}/_emdash/api/search/enable`, {
204+
method: "POST",
205+
headers: { "Content-Type": "application/json" },
206+
body: JSON.stringify({ collection: "posts" }),
207+
});
208+
expect([401, 403]).toContain(enable.status);
194209
});
195210

196211
test("search endpoint requires a query parameter", async ({ serverInfo }) => {

packages/core/src/astro/middleware/auth.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ const PUBLIC_API_EXACT = new Set([
109109
"/_emdash/api/auth/passkey/verify",
110110
"/_emdash/api/oauth/token",
111111
"/_emdash/api/snapshot",
112+
// Public site search — read-only. The query layer hardcodes status='published'
113+
// so unauthenticated callers only see published content. Admin endpoints
114+
// (/enable, /rebuild, /stats) remain private because they're not in this set.
115+
"/_emdash/api/search",
112116
]);
113117

114118
function isPublicEmDashRoute(pathname: string): boolean {

0 commit comments

Comments
 (0)