Skip to content

fix(middleware): allow public access to search API (#104)#424

Open
csfalcao wants to merge 1 commit intoemdash-cms:mainfrom
csfalcao:fix/104-public-search-middleware
Open

fix(middleware): allow public access to search API (#104)#424
csfalcao wants to merge 1 commit intoemdash-cms:mainfrom
csfalcao:fix/104-public-search-middleware

Conversation

@csfalcao
Copy link
Copy Markdown

What does this PR do?

Fixes the public search bug from #104 at the middleware layer.

#107 removed the handler-level requirePerm("search:read") check from packages/core/src/astro/routes/api/search/index.ts and .../suggest.ts, but the auth middleware at packages/core/src/astro/middleware/auth.ts (handlePasskeyAuth, ~line 587) still returned 401 NOT_AUTHENTICATED before the handler ran, because /_emdash/api/search was not in PUBLIC_API_EXACT or PUBLIC_API_PREFIXES. The handler-level change from #107 therefore never executed for anonymous callers, and the shipped <LiveSearch> component (which fetches without credentials per its JSDoc example) silently showed "No results found" on every query.

Reproducible on main at commit 59bdca1:

$ curl "http://localhost:4321/_emdash/api/search?q=design"
{"error":{"code":"NOT_AUTHENTICATED","message":"Not authenticated"}}
HTTP 401

This PR adds /_emdash/api/search to PUBLIC_API_EXACT. The searchWithDb query layer already hardcodes status = 'published' and the SQL in query.ts repeats the filter, so anonymous callers only see published content — matching the safety justification given in #104's closing comment. Admin endpoints (/search/enable, /search/rebuild, /search/stats) remain gated because they are not added to the set — the exact-match check in isPublicEmDashRoute means the sub-paths do not inherit access.

/search/suggest is deliberately not included: while investigating I hit a separate FTS5 syntax bug in getSuggestions (double * prefix, query.ts:279) that makes it return 500 regardless of auth. I'll file that as a separate issue with a failing test so it stays scoped.

The existing E2E test "search endpoint requires authentication" asserted the buggy behavior. It is replaced with:

  • "search endpoint is publicly accessible" — asserts 200 for unauthenticated GET
  • "search admin endpoints still require authentication" — asserts /stats and /enable stay gated

Verified locally against a fresh demos/simple with the seeded blog content:

$ curl "http://localhost:4321/_emdash/api/search?q=design"
HTTP 200
# 4 results with <mark>Designing</mark> FTS5 highlighting

$ curl "http://localhost:4321/_emdash/api/search/stats"
HTTP 401   # admin-only, as intended

$ curl -X POST "http://localhost:4321/_emdash/api/search/enable" \
    -H "Content-Type: application/json" -d '{"collection":"posts"}'
HTTP 403   # CSRF-rejected (correct — no X-EmDash-Request header)

Closes #104. Follows up on #107.

Type of change

  • Bug fix
  • Feature (requires approved Discussion)
  • Refactor (no behavior change)
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm --silent lint:json | jq '.diagnostics | length' returns 0
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion — n/a, this is a bug fix

On the unchecked boxes — full transparency:

  • lint:json returns 2 diagnostics on both main (commit 59bdca1) and this branch. Both are pre-existing typescript-eslint warnings in files this PR does not touch (packages/admin/vitest.config.ts, packages/core/src/astro/integration/vite-config.ts). This PR introduces zero new lint findings. I followed AGENTS.md's "do not touch code outside the scope of your change" guidance and left them alone.
  • pnpm --filter emdash test shows 2 pre-existing failures in tests/integration/plugins/capabilities.test.ts (HTTP capability timeouts at 5000ms). Reproduced on clean main at 59bdca1. Unrelated to auth or search. Targeted test run for the openapi test (tests/unit/api/openapi.test.ts, which references /search) passes 22/22.
  • E2E tests for the modified search.spec.ts were not run locally because Playwright browsers aren't installed on this machine. The behavior was verified directly via curl against a running demos/simple instance — the same assertions the tests make. CI will execute the full Playwright suite.

AI-generated code disclosure

  • This PR includes AI-generated code

Investigation, diff, test rewrites, and PR body were produced with Claude Opus 4.6 acting as an agent in this repo. Every change was reviewed line by line and all claims in this PR (repro commands, file paths, line numbers, pre-existing warnings, test names) were independently verified against the running demo and git log on main.

Screenshots / test output

End-to-end verification via curl against pnpm --filter emdash-demo dev from a clean clone at commit 59bdca1 (confirming bug), and after applying this patch (confirming fix):

Before (on main):

$ curl -o /dev/null -w "%{http_code}\n" "http://localhost:4321/_emdash/api/search?q=design"
401

After (this branch):

$ curl -o /dev/null -w "%{http_code}\n" "http://localhost:4321/_emdash/api/search?q=design"
200

$ curl "http://localhost:4321/_emdash/api/search?q=design" | jq '.data.items | length'
4

The <LiveSearch> header widget in demos/simple / templates/blog now returns live results in the dropdown instead of "No results found" for any query.

PR emdash-cms#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 emdash-cms#104. Follows up on emdash-cms#107.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Apr 10, 2026

🦋 Changeset detected

Latest commit: 424ef63

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 9 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/plugin-ai-moderation Patch
@emdash-cms/plugin-atproto Patch
@emdash-cms/plugin-audit-log Patch
@emdash-cms/plugin-color Patch
@emdash-cms/plugin-embeds Patch
@emdash-cms/plugin-forms Patch
@emdash-cms/plugin-webhook-notifier Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 10, 2026

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@csfalcao
Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions bot added a commit that referenced this pull request Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Search API requires authentication, preventing frontend search

1 participant