@nocobase/app-plugin-api-keys 0.0.0-stage → 1.0.0-beta.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +468 -0
  2. package/README.md +162 -2
  3. package/dist/client/api-keys-client.d.ts +19 -0
  4. package/dist/client/api-keys-client.d.ts.map +1 -0
  5. package/dist/client/api-keys-client.js +7 -0
  6. package/dist/client/api-keys-client.js.map +1 -0
  7. package/dist/client/expiry.d.ts +23 -0
  8. package/dist/client/expiry.d.ts.map +1 -0
  9. package/dist/client/expiry.js +40 -0
  10. package/dist/client/expiry.js.map +1 -0
  11. package/dist/client/index.d.ts +4 -0
  12. package/dist/client/index.d.ts.map +1 -0
  13. package/dist/client/index.js +6 -0
  14. package/dist/client/index.js.map +1 -0
  15. package/dist/client/plugin.d.ts +8 -0
  16. package/dist/client/plugin.d.ts.map +1 -0
  17. package/dist/client/plugin.js +10 -0
  18. package/dist/client/plugin.js.map +1 -0
  19. package/dist/database/migrations/.manifest.json +9 -0
  20. package/dist/database/migrations/202609150001_create_api_key_table.js +58 -0
  21. package/dist/database/migrations/202609150001_create_api_key_table.js.map +1 -0
  22. package/dist/server/api-docs.d.ts +24 -0
  23. package/dist/server/api-docs.d.ts.map +1 -0
  24. package/dist/server/api-docs.js +73 -0
  25. package/dist/server/api-docs.js.map +1 -0
  26. package/dist/server/api-keys.d.ts +52 -0
  27. package/dist/server/api-keys.d.ts.map +1 -0
  28. package/dist/server/api-keys.js +199 -0
  29. package/dist/server/api-keys.js.map +1 -0
  30. package/dist/server/index.d.ts +23 -0
  31. package/dist/server/index.d.ts.map +1 -0
  32. package/dist/server/index.js +22 -0
  33. package/dist/server/index.js.map +1 -0
  34. package/dist/server/key-sessions.d.ts +35 -0
  35. package/dist/server/key-sessions.d.ts.map +1 -0
  36. package/dist/server/key-sessions.js +50 -0
  37. package/dist/server/key-sessions.js.map +1 -0
  38. package/dist/server/plugin.d.ts +4 -0
  39. package/dist/server/plugin.d.ts.map +1 -0
  40. package/dist/server/plugin.js +15 -0
  41. package/dist/server/plugin.js.map +1 -0
  42. package/dist/server/providers/index.d.ts +19 -0
  43. package/dist/server/providers/index.d.ts.map +1 -0
  44. package/dist/server/providers/index.js +72 -0
  45. package/dist/server/providers/index.js.map +1 -0
  46. package/dist/server/routes/errors.d.ts +8 -0
  47. package/dist/server/routes/errors.d.ts.map +1 -0
  48. package/dist/server/routes/errors.js +48 -0
  49. package/dist/server/routes/errors.js.map +1 -0
  50. package/dist/server/routes/index.d.ts +10 -0
  51. package/dist/server/routes/index.d.ts.map +1 -0
  52. package/dist/server/routes/index.js +177 -0
  53. package/dist/server/routes/index.js.map +1 -0
  54. package/dist/server/routes/schemas.d.ts +25 -0
  55. package/dist/server/routes/schemas.d.ts.map +1 -0
  56. package/dist/server/routes/schemas.js +127 -0
  57. package/dist/server/routes/schemas.js.map +1 -0
  58. package/dist/server/scoped-keys.d.ts +116 -0
  59. package/dist/server/scoped-keys.d.ts.map +1 -0
  60. package/dist/server/scoped-keys.js +451 -0
  61. package/dist/server/scoped-keys.js.map +1 -0
  62. package/dist/server/scopes.d.ts +70 -0
  63. package/dist/server/scopes.d.ts.map +1 -0
  64. package/dist/server/scopes.js +165 -0
  65. package/dist/server/scopes.js.map +1 -0
  66. package/dist/server/service.d.ts +28 -0
  67. package/dist/server/service.d.ts.map +1 -0
  68. package/dist/server/service.js +69 -0
  69. package/dist/server/service.js.map +1 -0
  70. package/dist/server/tokens.d.ts +6 -0
  71. package/dist/server/tokens.d.ts.map +1 -0
  72. package/dist/server/tokens.js +5 -0
  73. package/dist/server/tokens.js.map +1 -0
  74. package/dist/shared/scopes.d.ts +160 -0
  75. package/dist/shared/scopes.d.ts.map +1 -0
  76. package/dist/shared/scopes.js +83 -0
  77. package/dist/shared/scopes.js.map +1 -0
  78. package/package.json +76 -3
  79. package/skills/nocobase-app-plugin-api-keys/SKILL.md +161 -0
package/README.md CHANGED
@@ -1,3 +1,163 @@
1
- # Temporary Holding Version
1
+ # @nocobase/app-plugin-api-keys
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ API key authentication for NocoBase applications, built on Better Auth.
4
+
5
+ A key authenticates as the user who created it. `Auth.getSession()` resolves the `x-api-key` header the same way it resolves a session cookie, so `auth.required()`, the route guards, and `@nocobase/app-plugin-authorization` all see the owning user and exactly the roles that user holds. Nothing in an application has to know a request arrived by key rather than by cookie.
6
+
7
+ Keys are self-service: each signed-in user creates and revokes their own. The plugin ships no page for that; an application builds one on `authClient.apiKey.*` or the scoped key API below and declares it among its own routes.
8
+
9
+ ## What this package is
10
+
11
+ Better Auth's [API Key plugin](https://www.better-auth.com/docs/plugins/api-key), plus the parts an application needs around it:
12
+
13
+ - the `apikey` table migration;
14
+ - `apiKey` and `apiKeyClient`, carrying Better Auth's own names and options.
15
+
16
+ `apiKey` is wrapped only to supply three defaults, all of them overridable; everything else is Better Auth's behavior and its documentation applies unchanged. They come from this package rather than from a dependency each application installs because the migration here has to match the schema that version of `@better-auth/api-key` declares; a test asserts the table carries a column for every field the plugin declares.
17
+
18
+ ## Installing it
19
+
20
+ ```ts
21
+ // server/plugins.ts — migrations for the apikey table
22
+ import apiKeys from '@nocobase/app-plugin-api-keys/server';
23
+
24
+ // server/config/auth.ts — the Better Auth plugin itself
25
+ import { apiKey } from '@nocobase/app-plugin-api-keys/server';
26
+
27
+ export default defineAppConfig((_runtime) => ({
28
+ plugins: [username({ displayUsername: false }), apiKey()],
29
+ }));
30
+ ```
31
+
32
+ ```ts
33
+ // client/plugins.ts — registers the plugin; it contributes no pages
34
+ import apiKeys from '@nocobase/app-plugin-api-keys/client';
35
+ apiKeys();
36
+
37
+ // client/config/auth.ts — so authClient.apiKey.* reaches the endpoints
38
+ import { apiKeyClient } from '@nocobase/app-plugin-api-keys/client';
39
+
40
+ export default defineAppConfig((_runtime) => ({
41
+ plugins: [usernameClient({ displayUsername: false }), apiKeyClient()],
42
+ }));
43
+ ```
44
+
45
+ Then run `pnpm nocobase db apply`.
46
+
47
+ Registering the server plugin without `apiKey()` creates the table and mounts no endpoints; registering `apiKey()` without the server plugin mounts endpoints against a table that does not exist.
48
+
49
+ ## The three defaults
50
+
51
+ | Option | Better Auth | Here | Why |
52
+ | ------------------------- | ----------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |
53
+ | `enableSessionForAPIKeys` | `false` | `true` | Without it a key authenticates nothing: keys are still issued, and every request carrying one answers 401 |
54
+ | `rateLimit.enabled` | `true`, 10 requests per key per day | `false` | That is a quota for issuing keys rather than for using them, and it silently breaks the first integration anyone writes |
55
+ | `requireName` | `false` | `true` | A key is identified by its name, and a key listed as "unnamed" cannot be revoked with any confidence |
56
+
57
+ All three are overridable. `apiKey({ rateLimit: { enabled: true, maxRequests: 1000, timeWindow: 60_000 } })` sets a quota, and passing all three back reproduces Better Auth's own behaviour exactly.
58
+
59
+ ## Using a key
60
+
61
+ ```bash
62
+ curl -H 'x-api-key: <key>' https://example.com/api/orders
63
+ ```
64
+
65
+ ## Reading the API document with a key
66
+
67
+ A valid key may read the application's OpenAPI document and its Swagger UI, exactly as a signed-in session may: `curl -H 'x-api-key: <key>' https://example.com/api/swagger` returns the JSON document, and `/api/swagger/docs` the page. The key is checked the way every other request checks it, so an unknown, expired or disabled key, or one whose owner is disabled, gets `401` with `error.reason` `API_DOCS_UNAUTHENTICATED`, and reading the document extends no session. The document lists this plugin's endpoints under `/api/auth/api-key/...` with the rest of Better Auth's, tagged `Authentication`. A configuration with a `customAPIKeyGetter` is not consulted by this check, because the getter needs a Better Auth endpoint context.
68
+
69
+ ## What a key can do
70
+
71
+ A key is its owner. Beyond the application's own routes, it reaches the Better Auth endpoints Better Auth lets a session reach — verified against `@better-auth/api-key` 1.7.1 with only an `x-api-key` header:
72
+
73
+ | Endpoint | |
74
+ | ------------------------------------------------------------------ | ----------------------------------------------------------------------- |
75
+ | `/api-key/*` (create, list, update, delete) | 403 `API_KEY_SESSION_FORBIDDEN` — managing keys takes a sign-in |
76
+ | `POST /update-user` | reachable — profile fields |
77
+ | `GET /list-sessions` | reachable — returns session rows including the unsigned session `token` |
78
+ | `POST /sign-out` | reachable |
79
+ | `POST /delete-user`, `POST /change-email`, `POST /revoke-sessions` | 401 |
80
+ | `POST /change-password` | requires `currentPassword` |
81
+
82
+ **No key manages keys.** Every `/api-key/*` endpoint refuses a request made with a key, scoped or not, so a leaked key cannot mint its own successors or revoke the evidence; only a signed-in session creates, updates or deletes keys. Routes of the application that manage keys apply the same rule with `requireSignInSession()` after `auth.required()`.
83
+
84
+ One consequence of an unscoped key reaching the rest is worth planning for. **`/list-sessions` discloses the session token.** It is the unsigned half of the session cookie, which is `token.signature`; replaying it alone does not authenticate, because the HMAC is computed with the auth secret. It becomes a working session takeover only if that secret also leaks or a deployment turns cookie signing off.
85
+
86
+ An application that wants it closed gives the key a scope, or adds a Better Auth `before` hook of its own.
87
+
88
+ A rejected key — expired, revoked, or wrong — is answered by a guarded `/api` route with Better Auth's own status in the standard error body, its code as `error.reason` and `authentication` as `error.domain`, so the caller can tell why: `401 KEY_EXPIRED`, `401 KEY_NOT_FOUND`, `429 USAGE_EXCEEDED`. Branch on `error.reason`, never on `message`. `Auth.getSession()` throws Better Auth's `APIError` for a refused key, exactly as Better Auth itself does, and a caller that only asks who is signed in catches it. A disabled user's keys stop working immediately, because `Auth.getSession()` re-reads `user.disabledAt` on every request.
89
+
90
+ ## Scoped keys
91
+
92
+ A key may carry a scope, as a GitHub fine-grained token does. Its effective permission is its owner's current permission intersected with the scope: the scope never grants anything, and an owner who loses a permission takes it from every key at once. A key without a scope behaves exactly as described above.
93
+
94
+ The scope is a set of permission groups, each at a level — read, write, or admin, each including the ones before it — and, where a group offers it, limited to some records of a business ("only selected Apps"). Plugins declare their groups as plain data (`KeyScopeGroupDeclaration`, `KeyScopePresetDeclaration` from `@nocobase/app-plugin-api-keys/shared/scopes`, written as literals so the plugin does not depend on this package); the application assembles them, because only it knows every plugin:
95
+
96
+ ```ts
97
+ const scopes = container.resolve(apiKeyScopesToken);
98
+ scopes.mapAccess((ref) => /* page, settings or business access → { resource, action } */);
99
+ scopes.groups.add(RELEASES_KEY_SCOPE_GROUPS[0], releasesKeyScopeObjects(services));
100
+ scopes.presets.add(CI_DEPLOY_PRESET);
101
+ ```
102
+
103
+ A scope is stored in Better Auth's `permissions` column, which Better Auth refuses to take from a client on create or update (`SERVER_ONLY_PROPERTY`), so a key's holder cannot widen it: `{ "$v": ["1"], "releases.apps": ["read", "write"], "releases.apps@": ["app-1"] }`. `/api/apiKeys` offers no way to change a person's key's scope; create another. An application that manages keys of its own (an organization's keys, say) may change one with `ScopedApiKeys.setScope`, deciding itself who may and what they may give. A group the application no longer registers grants nothing, so removing one only narrows the keys that named it.
104
+
105
+ What enforces a scope on each request:
106
+
107
+ - Better Auth turns the key into its owner's session, as for any key. This plugin's provider then recognizes the key behind the session (its id and token), reads its scope once per request, and adds it to the authorization identity as `keyScope` (`authz.use`). `authz.can`, `require`, `authorize` and the permissions snapshot deny anything outside it with `KEY_SCOPE`.
108
+ - `auth.required()` refuses a scoped key with 403 `SCOPED_KEY_FORBIDDEN` unless the route opts in with `auth.required({ scopedKeys: true })`. Opt in only where every operation is authorized through `authz`, or where the code narrows by `identity.keyScope` itself; anything that derives permissions on its own (from `permissionSets.getEffective`, say) must intersect with `keyScope` or it widens the key back to its owner.
109
+ - A scoped key reaches no Better Auth account endpoint but `/get-session`: it cannot mint or list keys, change the profile, list sessions or sign out.
110
+ - Record selections are enforced only by the plugin that owns the records, through `keyScope.objects(business)`. An empty selection reaches none of them: the key holds the group's actions, so the commands and routes that need them are offered, while every record the plugin checks is refused.
111
+
112
+ A service account's key is treated as scoped even without a scope: `required()` refuses it unless the route opts in, it reaches no account endpoint, and its identity carries a `keyScope` that narrows nothing (`permissions: null`), so plugins can tell a key from a person. Disabling the account stops all its keys immediately.
113
+
114
+ `apiKeys.maxScopedKeyDays` in the application's configuration caps how long a scoped key lives; unset, "never expires" stays available. The editor proposes 90 days.
115
+
116
+ ### Server API
117
+
118
+ | Export | What it is |
119
+ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
120
+ | `apiKeyScopesToken` | The registry: `groups.add`, `presets.add`, `mapAccess`, `validate`, `compile` |
121
+ | `scopedApiKeysToken` | `ScopedApiKeys`: `scopeOptions`, `scopeObjects`, `list`, `get`, `check`, `create`, `issueChecked`, `setScope`, `update`, `rotate`, `revoke`, `revokeAll`, `resolve`, `setOwnKeyPolicy` |
122
+ | `ApiKeyScopeError` | A refused scope, 400 with a stable code |
123
+ | `encodeKeyScope`, `decodeKeyScope` | The storage format |
124
+
125
+ `ScopedApiKeys` acts on whichever user it is told; the caller decides who may manage whose keys. An application builds its own key pages and a service account's keys on it.
126
+
127
+ - `check(input, identity)` validates what `create` would issue — name, description, scope, the records it picks (each must be one `identity` may see) and expiry — without issuing anything, so a caller can refuse before it creates whatever the key belongs to; `issueChecked(userId, checked)` then issues it.
128
+ - `rotate(userId, keyId)` gives a key a new secret in place: the same id, name, description, scope and owner, with the expiry renewed for the lifetime the key had. The old secret stops at once. Better Auth generates and hashes the new value, so its key options apply.
129
+ - `setScope(userId, keyId, scope, identity)` replaces a scoped key's scope; `update(userId, keyId, { name?, description? })` renames it.
130
+ - `setOwnKeyPolicy(policy)` says who may create (and rotate) keys of their own: `(userId) => Promise<boolean>`. Without one everyone may. With one, `POST /api/apiKeys`, its rotation and Better Auth's own `/api-key/create` from a sign-in answer 403 `API_KEY_CREATION_FORBIDDEN` to anyone the policy refuses, while listing and revoking stay open. The plugin's provider connects the Better Auth hook at boot (`connectOwnKeyPolicy`).
131
+
132
+ ### HTTP API
133
+
134
+ `/api/apiKeys`, for the signed-in person's own keys (a scoped key or a service account is refused with 403 `SCOPED_KEY_FORBIDDEN`, any other key with 403 `API_KEY_SESSION_FORBIDDEN`). Failures answer in the standard error body with domain `apiKeys` and the codes above as `reason`; an invalid body is 400 `INVALID_INPUT` naming the field, and an unknown field is rejected.
135
+
136
+ | Request | Answer |
137
+ | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
138
+ | `GET /scopeOptions` | `{ data }`, `KeyScopeOptions`: the groups with what the caller holds today, presets, expiry, `mayCreate` |
139
+ | `GET /scopeObjects/:group?q=&id=` | `{ data, meta: { total } }`: the records the caller may choose for a group; `id` may repeat |
140
+ | `GET /` | `{ data, meta: { total } }`: the caller's keys, with scope and last use, never a secret |
141
+ | `POST /` `{ name, description?, expiresInDays, scope }` | 201 `{ data: { key, secret } }`, the secret shown once |
142
+ | `POST /:keyId/rotate` | `{ data: { key, secret } }`: same id, name, scope and lifetime; the old secret stops at once |
143
+ | `DELETE /:keyId` | 204 |
144
+
145
+ `toApiKeysApiError(error)` turns `ApiKeyScopeError` and `ApiKeyRequestError` into that body's `ApiError`, for an application's own key routes: `router.onError((error, context) => apiErrorHandler(toApiKeysApiError(error), context))`.
146
+
147
+ ## Verification
148
+
149
+ ```bash
150
+ pnpm --filter @nocobase/app-plugin-api-keys check
151
+ ```
152
+
153
+ ## Server extensions
154
+
155
+ Pass an array to `apiKey()` to configure multiple key classes with unique `configId` values. Each entry receives the usual overridable NocoBase defaults. Set `enableSessionForAPIKeys: false` for keys that an application verifies explicitly instead of resolving into the owner’s Session.
156
+
157
+ `ApiKeyService` from the server entry binds trusted `create`, `get`, `verify`, `disable`, and `remove` calls to one registered configuration and the Authentication plugin API. It never returns a hash, and creation returns the plaintext once. The service supports database storage only; callers own authorization, the resources they bind keys to, and restrictions on public self-service endpoints. No application-specific scope or App identity is built into this plugin.
158
+
159
+ Server calls run through `Auth.pluginApi()` and the normal Better Auth hooks; `withConnection(connection)` binds all credential writes to a caller-owned transaction. The added get/delete operations are declared with `createAuthEndpoint.serverOnly` and cannot be reached over HTTP.
160
+
161
+ `findRequestApiKey(plugin, headers)` returns the key a request carries, read from the headers of every configuration that resolves keys into sessions, and `createApiKeyApiDocsAccess(resolveAuth)` is the API document access check the plugin's server registers; an application assembling its own providers can register it on `apiDocsToken` itself. `Auth.plugin<ApiKeysPlugin>('api-key')?.options.configurations` holds the configurations `apiKey()` was created with, after its defaults.
162
+
163
+ Trusted lifecycle integrations can call `removeUserApiKeys(connection, userId, configIds)` within their user-deletion transaction to remove database-backed credentials for explicit user-referencing configurations. Do not pass organization-referencing or custom-storage configurations.
@@ -0,0 +1,19 @@
1
+ import type { ApiKey } from '@better-auth/api-key';
2
+ import type { apiKeyClient } from '@better-auth/api-key/client';
3
+ /**
4
+ * Better Auth's API Key client plugin, which an application adds to `plugins`
5
+ * in `client/config/auth.ts` so `authClient.apiKey.*` reaches the endpoints
6
+ * the server plugin mounts.
7
+ */
8
+ export { apiKeyClient } from '@better-auth/api-key/client';
9
+ declare module '@nocobase/app-plugin-authentication/client' {
10
+ interface AuthClientPluginRegistry {
11
+ 'api-key': ReturnType<typeof apiKeyClient>;
12
+ }
13
+ }
14
+ /**
15
+ * A key as the list endpoint returns it: Better Auth's own model, minus the
16
+ * hash the endpoint strips before responding.
17
+ */
18
+ export type ApiKeySummary = Omit<ApiKey, 'key'>;
19
+ //# sourceMappingURL=api-keys-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-keys-client.d.ts","sourceRoot":"","sources":["../../client/api-keys-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAIhE;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAG3D,OAAO,QAAQ,4CAA4C,CAAC;IAC1D,UAAU,wBAAwB;QAChC,SAAS,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;KAC5C;CACF;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Better Auth's API Key client plugin, which an application adds to `plugins`
3
+ * in `client/config/auth.ts` so `authClient.apiKey.*` reaches the endpoints
4
+ * the server plugin mounts.
5
+ */
6
+ export { apiKeyClient } from '@better-auth/api-key/client';
7
+ //# sourceMappingURL=api-keys-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-keys-client.js","sourceRoot":"","sources":["../../client/api-keys-client.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { ApiKeySummary } from './api-keys-client.js';
2
+ export type ApiKeyExpiryChoice = 'never' | '7' | '30' | '90' | '365';
3
+ /**
4
+ * The lifetimes the create dialog offers.
5
+ *
6
+ * Better Auth measures `expiresIn` in seconds but validates it in whole days
7
+ * against `keyExpiration.minExpiresIn` and `maxExpiresIn`, which default to 1
8
+ * and 365. Every choice here stays inside that window, so the dialog cannot
9
+ * offer a lifetime the server will refuse.
10
+ */
11
+ export declare const API_KEY_EXPIRY_CHOICES: readonly ApiKeyExpiryChoice[];
12
+ /** Seconds to send as `expiresIn`, or undefined for a key that never expires. */
13
+ export declare function expiryChoiceToSeconds(choice: ApiKeyExpiryChoice): number | undefined;
14
+ /**
15
+ * The part of a key that is safe to show.
16
+ *
17
+ * `start` is the leading characters Better Auth stored at creation, prefix
18
+ * included. The stored `key` is a hash and is never returned by the list
19
+ * endpoint, so this is all a listed key can be recognized by.
20
+ */
21
+ export declare function formatKeyHint(key: Pick<ApiKeySummary, 'prefix' | 'start'>): string;
22
+ export declare function isExpired(expiresAt: string | Date | null | undefined, now?: Date): boolean;
23
+ //# sourceMappingURL=expiry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expiry.d.ts","sourceRoot":"","sources":["../../client/expiry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAC;AAErE;;;;;;;GAOG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,kBAAkB,EAM/D,CAAC;AAIF,iFAAiF;AACjF,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,kBAAkB,GACzB,MAAM,GAAG,SAAS,CAEpB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,OAAO,CAAC,GAC3C,MAAM,CAIR;AAED,wBAAgB,SAAS,CACvB,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,EAC3C,GAAG,GAAE,IAAiB,GACrB,OAAO,CAGT"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The lifetimes the create dialog offers.
3
+ *
4
+ * Better Auth measures `expiresIn` in seconds but validates it in whole days
5
+ * against `keyExpiration.minExpiresIn` and `maxExpiresIn`, which default to 1
6
+ * and 365. Every choice here stays inside that window, so the dialog cannot
7
+ * offer a lifetime the server will refuse.
8
+ */
9
+ export const API_KEY_EXPIRY_CHOICES = [
10
+ 'never',
11
+ '7',
12
+ '30',
13
+ '90',
14
+ '365',
15
+ ];
16
+ const SECONDS_PER_DAY = 24 * 60 * 60;
17
+ /** Seconds to send as `expiresIn`, or undefined for a key that never expires. */
18
+ export function expiryChoiceToSeconds(choice) {
19
+ return choice === 'never' ? undefined : Number(choice) * SECONDS_PER_DAY;
20
+ }
21
+ /**
22
+ * The part of a key that is safe to show.
23
+ *
24
+ * `start` is the leading characters Better Auth stored at creation, prefix
25
+ * included. The stored `key` is a hash and is never returned by the list
26
+ * endpoint, so this is all a listed key can be recognized by.
27
+ */
28
+ export function formatKeyHint(key) {
29
+ if (key.start)
30
+ return `${key.start}…`;
31
+ if (key.prefix)
32
+ return `${key.prefix}…`;
33
+ return '—';
34
+ }
35
+ export function isExpired(expiresAt, now = new Date()) {
36
+ if (!expiresAt)
37
+ return false;
38
+ return new Date(expiresAt).getTime() <= now.getTime();
39
+ }
40
+ //# sourceMappingURL=expiry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expiry.js","sourceRoot":"","sources":["../../client/expiry.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAkC;IACnE,OAAO;IACP,GAAG;IACH,IAAI;IACJ,IAAI;IACJ,KAAK;CACN,CAAC;AAEF,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAErC,iFAAiF;AACjF,MAAM,UAAU,qBAAqB,CACnC,MAA0B;IAE1B,OAAO,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,eAAe,CAAC;AAC3E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAC3B,GAA4C;IAE5C,IAAI,GAAG,CAAC,KAAK;QAAE,OAAO,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC;IACtC,IAAI,GAAG,CAAC,MAAM;QAAE,OAAO,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC;IACxC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,SAA2C,EAC3C,MAAY,IAAI,IAAI,EAAE;IAEtB,IAAI,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7B,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;AACxD,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { default } from './plugin.js';
2
+ export { apiKeyClient, type ApiKeySummary } from './api-keys-client.js';
3
+ export { API_KEY_EXPIRY_CHOICES, expiryChoiceToSeconds, formatKeyHint, isExpired, type ApiKeyExpiryChoice, } from './expiry.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../client/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,aAAa,EACb,SAAS,EACT,KAAK,kBAAkB,GACxB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,6 @@
1
+ // The plugin's public client surface. The default export is the registration
2
+ // factory an application lists in its client/plugins.ts.
3
+ export { default } from './plugin.js';
4
+ export { apiKeyClient } from './api-keys-client.js';
5
+ export { API_KEY_EXPIRY_CHOICES, expiryChoiceToSeconds, formatKeyHint, isExpired, } from './expiry.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../client/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,yDAAyD;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,YAAY,EAAsB,MAAM,sBAAsB,CAAC;AACxE,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,aAAa,EACb,SAAS,GAEV,MAAM,aAAa,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { type AppClientPluginFactory } from '@nocobase/app-client/plugins';
2
+ /**
3
+ * Registers the plugin with the client application. It contributes no pages: an application that wants a page for
4
+ * managing keys builds it on `apiKeyClient` and declares it among its own routes.
5
+ */
6
+ declare const apiKeys: AppClientPluginFactory;
7
+ export default apiKeys;
8
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../client/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,8BAA8B,CAAC;AAEtC;;;GAGG;AACH,QAAA,MAAM,OAAO,EAAE,sBAEb,CAAC;AAEH,eAAe,OAAO,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { defineClientPlugin, } from '@nocobase/app-client/plugins';
2
+ /**
3
+ * Registers the plugin with the client application. It contributes no pages: an application that wants a page for
4
+ * managing keys builds it on `apiKeyClient` and declares it among its own routes.
5
+ */
6
+ const apiKeys = defineClientPlugin({
7
+ packageName: '@nocobase/app-plugin-api-keys',
8
+ });
9
+ export default apiKeys;
10
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../client/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,GAEnB,MAAM,8BAA8B,CAAC;AAEtC;;;GAGG;AACH,MAAM,OAAO,GAA2B,kBAAkB,CAAC;IACzD,WAAW,EAAE,+BAA+B;CAC7C,CAAC,CAAC;AAEH,eAAe,OAAO,CAAC"}
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": 1,
3
+ "files": {
4
+ "202609150001_create_api_key_table.js": {
5
+ "sourceChecksum": "370b04daffda210c32e8a2931944bcd9e4b19938483d75e02f9fbb525fea72d5",
6
+ "artifactChecksum": "270d6f8ec497876bff3aa8d304aa6ad51889b5e5ef632a475e8ca797bd80eb86"
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,58 @@
1
+ import { defineMigration } from '@nocobase/db';
2
+ /**
3
+ * Creates the table the Better Auth API Key plugin reads and writes.
4
+ *
5
+ * The shape is spelled out here rather than derived from the plugin's own
6
+ * schema, so a later release of `@better-auth/api-key` cannot change what this
7
+ * migration did. When that schema gains a field, add it in a new migration.
8
+ *
9
+ * The duration fields are milliseconds held in a 32-bit integer, which caps a
10
+ * refill interval or rate-limit window at roughly 24 days. That is well past
11
+ * the one-day default and keeps every dialect returning a number; a longer
12
+ * window needs a widening migration rather than a value that silently wraps.
13
+ */
14
+ const migration = defineMigration({
15
+ name: '202609150001_create_api_key_table',
16
+ async up({ builder }) {
17
+ await builder.createCollection('apikey', (collection) => {
18
+ collection.string('id', { length: 64 }).notNull();
19
+ collection
20
+ .string('configId', { length: 128 })
21
+ .notNull()
22
+ .defaultTo('default');
23
+ collection.string('name', { length: 255 }).nullable();
24
+ collection.string('prefix', { length: 64 }).nullable();
25
+ // The first few characters, shown in the UI so a listed key is
26
+ // recognizable. `key` itself is a hash and is never displayed.
27
+ collection.string('start', { length: 64 }).nullable();
28
+ collection.string('key', { length: 255 }).notNull();
29
+ collection.string('referenceId', { length: 64 }).notNull();
30
+ collection.integer('refillInterval').nullable();
31
+ collection.integer('refillAmount').nullable();
32
+ collection.datetime('lastRefillAt').nullable();
33
+ collection.boolean('enabled').notNull().defaultTo(true);
34
+ collection.boolean('rateLimitEnabled').notNull().defaultTo(true);
35
+ collection.integer('rateLimitTimeWindow').nullable();
36
+ collection.integer('rateLimitMax').nullable();
37
+ collection.integer('requestCount').notNull().defaultTo(0);
38
+ collection.integer('remaining').nullable();
39
+ collection.datetime('lastRequest').nullable();
40
+ collection.datetime('expiresAt').nullable();
41
+ collection.datetime('createdAt').notNull();
42
+ collection.datetime('updatedAt').notNull();
43
+ collection.text('permissions').nullable();
44
+ collection.text('metadata').nullable();
45
+ collection.primary('id', { name: 'pk_apikey' });
46
+ collection.index('key', { name: 'idx_apikey_key' });
47
+ collection.index('referenceId', { name: 'idx_apikey_reference' });
48
+ collection.index('configId', { name: 'idx_apikey_config' });
49
+ collection.index('expiresAt', { name: 'idx_apikey_expires_at' });
50
+ });
51
+ },
52
+ async down({ builder }) {
53
+ await builder.dropCollection('apikey');
54
+ },
55
+ });
56
+ export default migration;
57
+ //# sourceMappingURL=202609150001_create_api_key_table.js.map
58
+ // @nocobase/task-manifest v1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"202609150001_create_api_key_table.js","sourceRoot":"","sources":["../../../database/migrations/202609150001_create_api_key_table.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { Auth } from '@nocobase/app-plugin-authentication/server';
2
+ import type { ApiDocsAccess, ApiDocumentFragment } from '@nocobase/app-server/router';
3
+ /** The security scheme an API key satisfies, in `components.securitySchemes`. */
4
+ export declare const API_KEY_SECURITY_SCHEME = "apiKeyAuth";
5
+ /**
6
+ * Lets a request carrying a valid API key read the API documentation. The key is checked exactly as for any other
7
+ * request — Better Auth resolves it to its owner's session, which refuses a disabled, expired or unknown key and a key
8
+ * whose owner is disabled — but without extending a session, so reading the documentation changes no state.
9
+ *
10
+ * Allows nothing when the application has not configured this package's `apiKey()` Better Auth plugin, or when the
11
+ * request carries no key in the headers that plugin reads.
12
+ */
13
+ export declare function createApiKeyApiDocsAccess(resolveAuth: () => Pick<Auth, 'getSession' | 'plugin'>): ApiDocsAccess;
14
+ /**
15
+ * The API key header as the document's security scheme, `apiKeyAuth`, and a top-level requirement naming it, offered
16
+ * beside the session cookie's: a request carrying either is authenticated. The header is the first one this package's
17
+ * `apiKey()` Better Auth plugin reads a key from, `x-api-key` unless configured. Swagger UI's "Authorize" takes the key
18
+ * and sends it in that header.
19
+ *
20
+ * Contributes nothing when the application has not configured the `apiKey()` plugin, or when no configuration turns
21
+ * keys into sessions through a header, because then no request authenticates with a key.
22
+ */
23
+ export declare function createApiKeySecurityFragment(resolveAuth: () => Pick<Auth, 'plugin'>): ApiDocumentFragment;
24
+ //# sourceMappingURL=api-docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-docs.d.ts","sourceRoot":"","sources":["../../server/api-docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,4CAA4C,CAAC;AACvE,OAAO,KAAK,EACV,aAAa,EACb,mBAAmB,EACpB,MAAM,6BAA6B,CAAC;AAWrC,iFAAiF;AACjF,eAAO,MAAM,uBAAuB,eAAe,CAAC;AAEpD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,WAAW,EAAE,MAAM,IAAI,CAAC,IAAI,EAAE,YAAY,GAAG,QAAQ,CAAC,GACrD,aAAa,CAsBf;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,MAAM,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,GACtC,mBAAmB,CAqBrB"}
@@ -0,0 +1,73 @@
1
+ import { APIError } from 'better-auth/api';
2
+ import { findRequestApiKey, requestApiKeyHeaders, } from './api-keys.js';
3
+ const PACKAGE_NAME = '@nocobase/app-plugin-api-keys';
4
+ /** The security scheme an API key satisfies, in `components.securitySchemes`. */
5
+ export const API_KEY_SECURITY_SCHEME = 'apiKeyAuth';
6
+ /**
7
+ * Lets a request carrying a valid API key read the API documentation. The key is checked exactly as for any other
8
+ * request — Better Auth resolves it to its owner's session, which refuses a disabled, expired or unknown key and a key
9
+ * whose owner is disabled — but without extending a session, so reading the documentation changes no state.
10
+ *
11
+ * Allows nothing when the application has not configured this package's `apiKey()` Better Auth plugin, or when the
12
+ * request carries no key in the headers that plugin reads.
13
+ */
14
+ export function createApiKeyApiDocsAccess(resolveAuth) {
15
+ return {
16
+ name: PACKAGE_NAME,
17
+ check: async (context) => {
18
+ const auth = resolveAuth();
19
+ const plugin = auth.plugin('api-key');
20
+ if (!plugin?.options?.configurations)
21
+ return false;
22
+ const headers = context.req.raw.headers;
23
+ const key = findRequestApiKey(plugin, headers);
24
+ if (!key)
25
+ return false;
26
+ try {
27
+ const session = await auth.getSession(headers, {
28
+ disableRefresh: true,
29
+ });
30
+ // Better Auth answers a request carrying a key with the key's session, whose token is the key itself.
31
+ return session?.session.token === key;
32
+ }
33
+ catch (error) {
34
+ if (error instanceof APIError)
35
+ return false;
36
+ throw error;
37
+ }
38
+ },
39
+ };
40
+ }
41
+ /**
42
+ * The API key header as the document's security scheme, `apiKeyAuth`, and a top-level requirement naming it, offered
43
+ * beside the session cookie's: a request carrying either is authenticated. The header is the first one this package's
44
+ * `apiKey()` Better Auth plugin reads a key from, `x-api-key` unless configured. Swagger UI's "Authorize" takes the key
45
+ * and sends it in that header.
46
+ *
47
+ * Contributes nothing when the application has not configured the `apiKey()` plugin, or when no configuration turns
48
+ * keys into sessions through a header, because then no request authenticates with a key.
49
+ */
50
+ export function createApiKeySecurityFragment(resolveAuth) {
51
+ const plugin = resolveAuth().plugin('api-key');
52
+ const [header, ...alternatives] = plugin?.options?.configurations
53
+ ? requestApiKeyHeaders(plugin)
54
+ : [];
55
+ if (!header)
56
+ return { owner: PACKAGE_NAME };
57
+ return {
58
+ owner: PACKAGE_NAME,
59
+ namespace: 'apiKeys',
60
+ components: {
61
+ securitySchemes: {
62
+ [API_KEY_SECURITY_SCHEME]: {
63
+ type: 'apiKey',
64
+ in: 'header',
65
+ name: header,
66
+ description: `An API key issued on the API keys settings page or through \`/api/auth/api-key/create\`. It acts as the user who owns it.${alternatives.length > 0 ? ` The ${alternatives.map((name) => `\`${name}\``).join(', ')} header${alternatives.length > 1 ? 's carry' : ' carries'} it as well.` : ''}`,
67
+ },
68
+ },
69
+ },
70
+ security: [{ [API_KEY_SECURITY_SCHEME]: [] }],
71
+ };
72
+ }
73
+ //# sourceMappingURL=api-docs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-docs.js","sourceRoot":"","sources":["../../server/api-docs.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,OAAO,EACL,iBAAiB,EACjB,oBAAoB,GAErB,MAAM,eAAe,CAAC;AAEvB,MAAM,YAAY,GAAG,+BAA+B,CAAC;AAErD,iFAAiF;AACjF,MAAM,CAAC,MAAM,uBAAuB,GAAG,YAAY,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,UAAU,yBAAyB,CACvC,WAAsD;IAEtD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YACvB,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAgB,SAAS,CAAC,CAAC;YACrD,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,cAAc;gBAAE,OAAO,KAAK,CAAC;YACnD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC;YACxC,MAAM,GAAG,GAAG,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC/C,IAAI,CAAC,GAAG;gBAAE,OAAO,KAAK,CAAC;YACvB,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE;oBAC7C,cAAc,EAAE,IAAI;iBACrB,CAAC,CAAC;gBACH,sGAAsG;gBACtG,OAAO,OAAO,EAAE,OAAO,CAAC,KAAK,KAAK,GAAG,CAAC;YACxC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,KAAK,YAAY,QAAQ;oBAAE,OAAO,KAAK,CAAC;gBAC5C,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,4BAA4B,CAC1C,WAAuC;IAEvC,MAAM,MAAM,GAAG,WAAW,EAAE,CAAC,MAAM,CAAgB,SAAS,CAAC,CAAC;IAC9D,MAAM,CAAC,MAAM,EAAE,GAAG,YAAY,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,cAAc;QAC/D,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC;QAC9B,CAAC,CAAC,EAAE,CAAC;IACP,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAC5C,OAAO;QACL,KAAK,EAAE,YAAY;QACnB,SAAS,EAAE,SAAS;QACpB,UAAU,EAAE;YACV,eAAe,EAAE;gBACf,CAAC,uBAAuB,CAAC,EAAE;oBACzB,IAAI,EAAE,QAAQ;oBACd,EAAE,EAAE,QAAQ;oBACZ,IAAI,EAAE,MAAM;oBACZ,WAAW,EAAE,4HAA4H,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE;iBAC/S;aACF;SACF;QACD,QAAQ,EAAE,CAAC,EAAE,CAAC,uBAAuB,CAAC,EAAE,EAAE,EAAE,CAAC;KAC9C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { apiKey as betterAuthApiKey, type ApiKeyConfigurationOptions } from '@better-auth/api-key';
2
+ import { createAuthEndpoint } from 'better-auth/api';
3
+ import type { ApiKey } from '@better-auth/api-key';
4
+ type ServerEndpoint<Options extends Parameters<typeof createAuthEndpoint.serverOnly>[0], Result> = ReturnType<typeof createAuthEndpoint.serverOnly<string, Options, Result>>;
5
+ type UpstreamPlugin = ReturnType<typeof betterAuthApiKey>;
6
+ type ManagedQuery = ReturnType<UpstreamPlugin['endpoints']['getApiKey']['options']['query']['partial']>;
7
+ type ManagedBody = UpstreamPlugin['endpoints']['deleteApiKey']['options']['body'];
8
+ /** The configurations the plugin was created with, after this package's defaults are applied. */
9
+ export interface ApiKeysPluginOptions {
10
+ readonly configurations: readonly ApiKeyConfigurationOptions[];
11
+ }
12
+ export type ApiKeysPlugin = Omit<UpstreamPlugin, 'endpoints'> & {
13
+ /** Read by this package's server, for instance to find the key a request carries; Better Auth ignores it. */
14
+ options: ApiKeysPluginOptions;
15
+ endpoints: UpstreamPlugin['endpoints'] & {
16
+ getServerApiKey: ServerEndpoint<{
17
+ method: 'GET';
18
+ query: ManagedQuery;
19
+ }, Omit<ApiKey, 'key'> | null>;
20
+ deleteServerApiKey: ServerEndpoint<{
21
+ method: 'POST';
22
+ body: ManagedBody;
23
+ }, {
24
+ success: boolean;
25
+ }>;
26
+ };
27
+ };
28
+ /**
29
+ * Where the plugin's provider puts the application's own-key policy (`ScopedApiKeys.setOwnKeyPolicy`), so the hook on
30
+ * Better Auth's own `/api-key/create` asks the same question `/api/apiKeys` does.
31
+ */
32
+ export declare const OWN_KEY_POLICY_SLOT: unique symbol;
33
+ export interface OwnKeyPolicySlot {
34
+ current: ((userId: string) => Promise<boolean>) | null;
35
+ }
36
+ /** The slot of a plugin `apiKey()` made, or undefined for any other plugin. */
37
+ export declare function ownKeyPolicySlotOf(plugin: unknown): OwnKeyPolicySlot | undefined;
38
+ /**
39
+ * The API key a request carries, read the way the plugin reads it to authenticate a request: from the header each
40
+ * configuration that turns keys into sessions names, `x-api-key` unless configured. A configuration with a
41
+ * `customAPIKeyGetter` is skipped, because that getter needs a Better Auth endpoint context this caller does not have.
42
+ */
43
+ export declare function findRequestApiKey(plugin: Pick<ApiKeysPlugin, 'options'>, headers: Headers): string | undefined;
44
+ /**
45
+ * The headers a request may carry its API key in, in the order `findRequestApiKey` reads them: those of each
46
+ * configuration that turns keys into sessions, `x-api-key` unless configured, without one that has a
47
+ * `customAPIKeyGetter`.
48
+ */
49
+ export declare function requestApiKeyHeaders(plugin: Pick<ApiKeysPlugin, 'options'>): string[];
50
+ export declare function apiKey(options?: ApiKeyConfigurationOptions | ApiKeyConfigurationOptions[]): ApiKeysPlugin;
51
+ export {};
52
+ //# sourceMappingURL=api-keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-keys.d.ts","sourceRoot":"","sources":["../../server/api-keys.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,IAAI,gBAAgB,EAC1B,KAAK,0BAA0B,EAChC,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAEL,kBAAkB,EAGnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAGnD,KAAK,cAAc,CACjB,OAAO,SAAS,UAAU,CAAC,OAAO,kBAAkB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EACnE,MAAM,IACJ,UAAU,CAAC,OAAO,kBAAkB,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAE9E,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC1D,KAAK,YAAY,GAAG,UAAU,CAC5B,cAAc,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CACxE,CAAC;AACF,KAAK,WAAW,GACd,cAAc,CAAC,WAAW,CAAC,CAAC,cAAc,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AACjE,iGAAiG;AACjG,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,cAAc,EAAE,SAAS,0BAA0B,EAAE,CAAC;CAChE;AAED,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,GAAG;IAC9D,6GAA6G;IAC7G,OAAO,EAAE,oBAAoB,CAAC;IAC9B,SAAS,EAAE,cAAc,CAAC,WAAW,CAAC,GAAG;QACvC,eAAe,EAAE,cAAc,CAC7B;YAAE,MAAM,EAAE,KAAK,CAAC;YAAC,KAAK,EAAE,YAAY,CAAA;SAAE,EACtC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,IAAI,CAC3B,CAAC;QACF,kBAAkB,EAAE,cAAc,CAChC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,WAAW,CAAA;SAAE,EACrC;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,CACrB,CAAC;KACH,CAAC;CACH,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,OAAO,MAExC,CAAC;AAEF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;CACxD;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,OAAO,GACd,gBAAgB,GAAG,SAAS,CAI9B;AA+KD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,EACtC,OAAO,EAAE,OAAO,GACf,MAAM,GAAG,SAAS,CAMpB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,GACrC,MAAM,EAAE,CAUV;AAED,wBAAgB,MAAM,CACpB,OAAO,GAAE,0BAA0B,GAAG,0BAA0B,EAAO,GACtE,aAAa,CAEf"}