Files
YANG QIAandClaude Fable 5.1 154e09ef66 feat: add API keys plugin built on Better Auth (#227)
* feat: add API keys plugin built on Better Auth

Add @nocobase/app-plugin-api-keys: the apikey table migration, a self-service
Settings page, and Better Auth's apiKey/apiKeyClient re-exported with the
defaults a NocoBase application needs (enableSessionForAPIKeys on, rate
limiting off, requireName on — all overridable). A key resolves to a session
for its owner, so auth.required(), the route guards and Authorization see the
owning user and that user's roles unchanged.

In app-plugin-authentication:
- AuthClientPluginRegistry, an augmentable interface, so AuthClient is typed
  by the client plugins an application registers instead of being pinned to
  usernameClient.
- required()/optional() forward Better Auth's own status and body when a
  credential is present but refused (401 KEY_EXPIRED, 429 USAGE_EXCEEDED)
  instead of letting the APIError escape as a 500; getSession() folds it
  into null for callers that only ask who is signed in.

Register the plugin in app-template-default and app-template-hub.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(authentication): keep getSession as the single resolution seam

required() and optional() had been routed through a private authenticate()
so they could forward Better Auth's rejection, which bypassed getSession —
the method tests replace to fake a signed-in user. Every authenticated
request in app-plugin-repository-example's suite answered 401.

Fold the resolution back into getSession and let the middleware ask for
the reason with { throwOnRejected: true }. A test double that replaces
getSession is honoured again, programmatic callers still read a refused
credential as null, and the middleware still forwards 401 KEY_EXPIRED and
429 USAGE_EXCEEDED.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(authentication): follow Better Auth's getSession contract

getSession() returns the session, null when nobody is signed in, and
throws Better Auth's APIError when a credential is refused — Better Auth's
own semantics, with no option and no second method. required() and
optional() translate that APIError into a response with its status and
body, which is the one thing the wrapper was missing. Callers that only
ask who is signed in catch it themselves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 14:49:14 +08:00
..