Skip to content

fix(server-beta): accept X-Api-Key as fallback to Bearer in auth middleware#2719

Merged
thedotmack merged 2 commits into
thedotmack:mainfrom
alessandropcostabr:fix/server-beta-accept-x-api-key
Jun 6, 2026
Merged

fix(server-beta): accept X-Api-Key as fallback to Bearer in auth middleware#2719
thedotmack merged 2 commits into
thedotmack:mainfrom
alessandropcostabr:fix/server-beta-accept-x-api-key

Conversation

@alessandropcostabr

Copy link
Copy Markdown
Contributor

Summary

requireServerAuth / requirePostgresServerAuth currently only read Authorization: Bearer <key>. Clients using @better-auth/api-key default config send raw keys via X-Api-Key — including the worker bundle shipped from the Windows-canary line (#2699). The mismatch produces:

POST /v1/events  X-Api-Key: cmem_...
→ 401 {"error":"Unauthorized","message":"Missing bearer API key"}

Direct-LAN setups don't hit this because no proxy strips/normalizes headers and the worker's own retry path round-trips through other endpoints. Remote setups behind a proxy (e.g. Cloudflare → Express) fail closed and never capture against the public hostname.

This PR makes the middleware accept either header:

  • Bearer remains canonical — existing clients unaffected.
  • X-Api-Key is the fallback when Bearer is absent.
  • When both are present, Bearer wins (defense-in-depth — avoids surprises if an unrelated X-Api-Key leaks in via a proxy or stale env var).

Same change applied to:

  • src/server/middleware/auth.ts (bun:sqlite-backed)
  • src/server/middleware/postgres-auth.ts (PG-backed — what the server-beta runtime actually uses)

End-to-end verification

Against a server-beta deployment behind Cloudflare (mem.late.app.br → Express):

Before After
X-Api-Key: cmem_... on POST /v1/events 401 "Missing bearer API key" 400 ValidationError (auth ok, body invalid — expected)
Authorization: Bearer cmem_... (control) 400 ValidationError 400 (no regression)

Tests

New tests in tests/server/auth-api-key.test.ts:

  • middleware accepts X-Api-Key header as fallback when Bearer is absent
  • middleware prefers Bearer over X-Api-Key when both are present
  • middleware rejects requests with neither Bearer nor X-Api-Key
bun test tests/server/auth-api-key.test.ts
 15 pass / 0 fail / 43 expect() calls

No new test for postgres-auth.ts — the existing repo has no suite that wires a real PG pool for the middleware. The parser logic is byte-identical to auth.ts, so the bun:sqlite suite covers the behavioral contract.

Test plan

  • Existing 12 tests in auth-api-key.test.ts still pass (Bearer canonical path unchanged)
  • 3 new tests pass (X-Api-Key fallback, Bearer-wins precedence, both-absent rejection)
  • Live probe against server-beta deployment via Cloudflare confirms regression fixed
  • Reviewer to verify postgres-auth.ts change is equivalent (same 3-line edit, comments inline)

Related: #2699 (Windows Canary — bundle includes the client using @better-auth/api-key default).

🤖 Generated with Claude Code

…leware

The server-beta auth middleware (`requireServerAuth` and `requirePostgresServerAuth`)
only reads `Authorization: Bearer <key>`. Clients using `@better-auth/api-key`
default config send raw keys via the `X-Api-Key` header — including the worker
bundle shipped from the Windows-canary line (PR thedotmack#2699). The mismatch produces:

    POST /v1/events  X-Api-Key: cmem_...  → 401 {"error":"Unauthorized","message":"Missing bearer API key"}

Direct-LAN setups don't hit this because no proxy strips/normalizes headers and
the worker's own retry path eventually round-trips; remote setups behind a proxy
(Cloudflare → Express in our case) fail closed and never capture.

Fix: middleware accepts either header. Bearer remains canonical so existing
clients are unaffected; `X-Api-Key` is the fallback when Bearer is absent.
Same change applied to the bun:sqlite-backed `auth.ts` and the Postgres-backed
`postgres-auth.ts` (the latter is what server-beta actually uses).

Tests added in `tests/server/auth-api-key.test.ts`:
  - accepts X-Api-Key when Bearer absent (positive)
  - prefers Bearer when both present (defense-in-depth)
  - rejects requests with neither header (negative regression)

No new test for postgres-auth.ts (no existing suite that wires the PG pool);
the parser logic is byte-identical to auth.ts so the bun:sqlite suite covers
the behavioral contract.

Verified end-to-end against a deployed server-beta:
  curl -X POST https://mem.late.app.br/v1/events \
       -H 'X-Api-Key: cmem_...' -d '{"events":[]}'
  # before:  401 "Missing bearer API key"
  # after:   400 ValidationError (auth ok, body invalid — expected)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@greptile-apps

greptile-apps Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes both auth middleware implementations (auth.ts and postgres-auth.ts) accept X-Api-Key as a fallback to Authorization: Bearer for API key authentication. The change addresses a 401 failure for clients using @better-auth/api-key defaults (e.g. the Windows-canary worker bundle) that send keys via X-Api-Key instead of Bearer.

  • src/server/middleware/auth.ts & src/server/middleware/postgres-auth.ts: Three-line key-extraction change with Bearer remaining canonical; X-Api-Key accepted when Bearer is absent. The 401 error message is updated in both files to advertise both accepted header forms.
  • tests/server/auth-api-key.test.ts: Three new test cases added covering the X-Api-Key fallback path, Bearer-wins precedence, and rejection when neither header is present.

Confidence Score: 5/5

Safe to merge — the change is a minimal, additive fallback that does not alter the existing Bearer path.

Both middleware files receive the same three-line change. Bearer remains canonical and wins when both headers are present; X-Api-Key is only consulted when Bearer is absent. The 401 error message is updated consistently in both files. Three targeted tests cover the new fallback path, the precedence rule, and the all-absent rejection case. No existing auth behaviour is modified.

No files require special attention.

Important Files Changed

Filename Overview
src/server/middleware/auth.ts Adds X-Api-Key fallback to Bearer auth; Bearer remains canonical; error message updated to reflect both accepted headers.
src/server/middleware/postgres-auth.ts Mirrors the identical three-line change from auth.ts; logic and error message are byte-for-byte equivalent.
tests/server/auth-api-key.test.ts Three new tests cover the X-Api-Key fallback, Bearer-wins precedence, and dual-absent rejection; all assertions match the implemented behaviour.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Incoming Request] --> B{local-dev bypass met?}
    B -->|Yes| C[Attach local-dev authContext and call next]
    B -->|No| D{Parse Authorization Bearer header}
    D -->|non-null| E[rawKey = Bearer value]
    D -->|null| F{X-Api-Key header present?}
    F -->|Yes| G[rawKey = X-Api-Key value]
    F -->|No| H[rawKey = null]
    H --> I[401 - Missing API key]
    E --> J{verifyApiKey}
    G --> J
    J -->|invalid or expired| K[403 - Forbidden]
    J -->|valid| L[Attach api-key authContext and call next]
Loading

Reviews (2): Last reviewed commit: "fix(server-beta): update 401 message to ..." | Re-trigger Greptile

…aders

Addresses Greptile P2 feedback on thedotmack#2719: the 401 body still said "Missing
bearer API key" after the middleware started accepting X-Api-Key, which made
debugging harder for clients sending only X-Api-Key with a typo'd header name.

Message now lists both accepted forms in both auth.ts and postgres-auth.ts,
keeping the two backends consistent. Test that asserts the body in the
both-absent rejection path updated to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@thedotmack thedotmack merged commit 00d2356 into thedotmack:main Jun 6, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants