Commit 9fa6bab
* fix(plugin-auth): choose a JWT signing algorithm the host supports (#3585)
better-auth's `jwt` plugin defaults to EdDSA/Ed25519. On a host whose WebCrypto
lacks Ed25519 (StackBlitz/WebContainer) jose's `generateKeyPair` throws, and
because the plugin's `after` hook signs a `set-auth-jwt` header for EVERY
session, the first `/get-session` after sign-in returned 500 — on a plain dev
server, since the OIDC provider defaults on whenever the MCP server is.
Probe the capability once per manager (using the exact algorithm descriptor
jose uses) and pin `jwks.keyPairConfig` to EdDSA/Ed25519 or ES256 accordingly.
Pinning the algorithm is not sufficient on its own: `resolveSigningKey` falls
back to `getLatestKey()` — ANY algorithm — when no key matches the configured
one, so a deployment that had already minted an EdDSA key would still select it
and die in `importJWK`. On a host without Ed25519 we therefore also install
better-auth's `adapter.getJwks` keyring seam and hide keys the host cannot
import, so a fresh ES256 key is minted and the deployment converges. Rows are
hidden, never deleted. The seam is installed ONLY on such a host, so every
normal deployment runs better-auth's stock read path unchanged.
Finally, a signing failure now degrades the header rather than the session:
`/get-session` returns the session and omits `set-auth-jwt`, reporting once with
an error that names the algorithm and is queryable via `getDegradedAuthFeatures()`
under a new `jwtSigning` key. Note the guard must return the `{headers,response}`
shape `runAfterHooks` reads — returning bare `undefined` just moves the 500 one
frame up.
Tests run the real better-auth pipeline against a WebCrypto with Ed25519
removed, including the upgrade path where a real better-auth-minted EdDSA key
already exists. better-auth's EdDSA default and the `/get-session` hook shape
are pinned in better-auth-schema-parity.test.ts so an upgrade that moves either
fails a unit test rather than a production login.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t
* test(plugin-auth): pin the #3585 memory engine's delete to ObjectQL's dispatch predicate
`check:engine-double-contract` (#4550, landed after this branch was written)
flagged the fake engine in auth-manager.jwt-eddsa-fallback.test.ts: its
`delete` accepted any predicate, so it was structurally looser than
`ObjectQL.delete`, which is how #4434 shipped a dead REST route with a green
suite.
Route the fake's `delete` through `assertEngineDeleteDispatch` — the producer's
own decision — rather than hand-mirroring the guard. That required
`@objectstack/objectql` as a devDependency of `@objectstack/plugin-auth`
(workspace protocol, the way plugin-approvals declares it); no cycle, since
nothing reachable from objectql depends on plugin-auth. The suite stays green
because better-auth's ObjectQL adapter only ever deletes by scalar id —
`delete`/`deleteMany`/`consumeOne` each resolve the row first and then call
`delete(object, { where: { id } })` — so the assertion now pins that property
instead of assuming it.
The devDependency also invalidates the stated blocker on the sibling baseline
entry for auth-manager.optional-plugin-isolation.test.ts ("plugin-auth does not
depend on @objectstack/objectql"), so that entry's `why`/`closes` are corrected
to the measured state: the dependency exists, what remains is a one-line pin for
its own PR. Counts are untouched — the ratchet does not move.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t
* test(plugin-auth): say what the delete pin does and does not currently prove
Measured with a probe (temporary console.info in the fake's delete, run and
reverted): the paths this file drives — sign-up → get-session → /jwks — never
reach a delete, so the pin cannot flip this suite red today. The previous
comment could be read as claiming it does.
State it plainly instead: the assertion is a forward guard on better-auth's
adapter continuing to delete only by scalar id, so an upgrade that routes a
session/verification purge through as a bare predicate fails here rather than
500ing on a server. A gate claim nobody can reproduce is how a green run stops
meaning anything.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: os-zhuang <support@objectstack.ai>
1 parent a1a855a commit 9fa6bab
9 files changed
Lines changed: 1258 additions & 7 deletions
File tree
- .changeset
- packages/plugins/plugin-auth
- src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
36 | 37 | | |
37 | 38 | | |
38 | 39 | | |
| |||
0 commit comments