@better-auth/cimd 1.7.0-rc.1 → 1.7.0-rc.3
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.
- package/README.md +1 -1
- package/dist/index.d.mts +149 -78
- package/dist/index.mjs +513 -324
- package/dist/node.d.mts +14 -0
- package/dist/node.mjs +66 -0
- package/package.json +19 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Better Auth CIMD Plugin
|
|
2
2
|
|
|
3
|
-
Client ID Metadata Document plugin for [Better Auth](https://www.better-auth.com): unauthenticated
|
|
3
|
+
Client ID Metadata Document draft-02 plugin for [Better Auth](https://www.better-auth.com): unauthenticated client discovery over HTTPS. An explicit metadata profile applies the draft-00 requirements pinned by [MCP 2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents).
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,24 +1,114 @@
|
|
|
1
|
-
import { ClientDiscovery, SchemaClient, Scope } from "@better-auth/oauth-provider";
|
|
2
|
-
import * as better_auth0 from "better-auth";
|
|
1
|
+
import { ClientDiscovery, ClientMetadataResourceFetch, OAuthClientMetadata, SchemaClient, Scope } from "@better-auth/oauth-provider";
|
|
3
2
|
import { GenericEndpointContext } from "@better-auth/core";
|
|
4
|
-
|
|
5
3
|
//#region src/types.d.ts
|
|
4
|
+
type CimdMetadataProfile = "mcp-2026-07-28";
|
|
5
|
+
interface CimdClientCreatedEvent {
|
|
6
|
+
/** Newly persisted discovery-owned OAuth client. */
|
|
7
|
+
client: SchemaClient<Scope[]>;
|
|
8
|
+
/** Validated Client ID Metadata Document that produced the client. */
|
|
9
|
+
clientMetadataDocument: OAuthClientMetadata;
|
|
10
|
+
/** Better Auth endpoint context for the discovery request. */
|
|
11
|
+
context: GenericEndpointContext;
|
|
12
|
+
}
|
|
13
|
+
interface CimdClientRefreshedEvent {
|
|
14
|
+
/** Discovery-owned OAuth client after metadata reconciliation. */
|
|
15
|
+
client: SchemaClient<Scope[]>;
|
|
16
|
+
/** Client state captured before metadata reconciliation. */
|
|
17
|
+
previousClient: SchemaClient<Scope[]>;
|
|
18
|
+
/** Validated Client ID Metadata Document used for reconciliation. */
|
|
19
|
+
clientMetadataDocument: OAuthClientMetadata;
|
|
20
|
+
/** Better Auth endpoint context for the discovery request. */
|
|
21
|
+
context: GenericEndpointContext;
|
|
22
|
+
}
|
|
23
|
+
interface CimdMetadataFetchPolicy {
|
|
24
|
+
/**
|
|
25
|
+
* Minimum time between metadata fetch starts for one exact client ID.
|
|
26
|
+
* Fresh-cache hits and callers joining an in-flight fetch do not consume
|
|
27
|
+
* this interval. Numeric values are seconds. Set to `0` to disable
|
|
28
|
+
* per-client pacing.
|
|
29
|
+
*
|
|
30
|
+
* @default 1
|
|
31
|
+
*/
|
|
32
|
+
minimumFetchInterval?: number | string;
|
|
33
|
+
/**
|
|
34
|
+
* Maximum metadata fetches in flight across the plugin instance.
|
|
35
|
+
*
|
|
36
|
+
* @default 16
|
|
37
|
+
*/
|
|
38
|
+
maximumConcurrentFetches?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Maximum metadata fetches in flight for one URL origin.
|
|
41
|
+
*
|
|
42
|
+
* @default 4
|
|
43
|
+
*/
|
|
44
|
+
maximumConcurrentFetchesPerOrigin?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Maximum fetch starts in a rolling 60-second window across the
|
|
47
|
+
* plugin instance.
|
|
48
|
+
*
|
|
49
|
+
* @default 120
|
|
50
|
+
*/
|
|
51
|
+
maximumFetchesPerMinute?: number;
|
|
52
|
+
/**
|
|
53
|
+
* Maximum fetch starts in a rolling 60-second window for one URL
|
|
54
|
+
* origin.
|
|
55
|
+
*
|
|
56
|
+
* @default 30
|
|
57
|
+
*/
|
|
58
|
+
maximumFetchesPerOriginPerMinute?: number;
|
|
59
|
+
}
|
|
6
60
|
/**
|
|
7
61
|
* Options for the Client ID Metadata Document plugin.
|
|
8
62
|
*
|
|
9
|
-
* @see https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document
|
|
63
|
+
* @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-02
|
|
10
64
|
*/
|
|
11
65
|
interface CimdOptions {
|
|
12
66
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
67
|
+
* Fetch transport for metadata documents and discovery-owned metadata
|
|
68
|
+
* resources such as `jwks_uri`.
|
|
69
|
+
*
|
|
70
|
+
* The transport MUST resolve the hostname exactly once, reject RFC 6890
|
|
71
|
+
* special-use addresses, pin the approved address for the connection, and
|
|
72
|
+
* refuse redirects. Those guarantees cannot be implemented by wrapping the
|
|
73
|
+
* standard Fetch API after DNS resolution, so the application must provide
|
|
74
|
+
* them at its runtime-specific network boundary.
|
|
75
|
+
*/
|
|
76
|
+
fetchClientMetadataResource: ClientMetadataResourceFetch;
|
|
77
|
+
/**
|
|
78
|
+
* Apply an additional protocol profile to otherwise generic draft-02
|
|
79
|
+
* metadata validation.
|
|
80
|
+
*
|
|
81
|
+
* The MCP 2026-07-28 profile requires `client_name` and `redirect_uris`
|
|
82
|
+
* because that MCP revision normatively pins CIMD draft-00.
|
|
83
|
+
*/
|
|
84
|
+
metadataProfile?: CimdMetadataProfile;
|
|
85
|
+
/**
|
|
86
|
+
* Maximum and fallback cache freshness lifetime for a client's metadata
|
|
87
|
+
* document. An expired entry is revalidated on the next client resolution;
|
|
88
|
+
* the plugin does not perform periodic or background fetches.
|
|
15
89
|
*
|
|
16
90
|
* Accepts a number of seconds or a duration string (e.g. `"60m"`,
|
|
17
91
|
* `"1d"`).
|
|
18
92
|
*
|
|
19
93
|
* @default "60m"
|
|
20
94
|
*/
|
|
21
|
-
|
|
95
|
+
metadataRevalidationInterval?: number | string;
|
|
96
|
+
/**
|
|
97
|
+
* Bounded request-amplification policy for metadata document fetches.
|
|
98
|
+
*
|
|
99
|
+
* A permitted fetch consumes its concurrency and rolling-window budget when
|
|
100
|
+
* it starts. Same-client concurrent resolutions coalesce, while fresh-cache
|
|
101
|
+
* hits consume no budget. Limits reject immediately rather than queueing.
|
|
102
|
+
*/
|
|
103
|
+
metadataFetchPolicy?: CimdMetadataFetchPolicy;
|
|
104
|
+
/**
|
|
105
|
+
* Maximum number of validated metadata documents retained by this plugin
|
|
106
|
+
* instance. The least-recently-used entry is evicted when the bound is
|
|
107
|
+
* reached.
|
|
108
|
+
*
|
|
109
|
+
* @default 1000
|
|
110
|
+
*/
|
|
111
|
+
maxCacheEntries?: number;
|
|
22
112
|
/**
|
|
23
113
|
* Metadata fields whose URL values must share the same origin as the
|
|
24
114
|
* `client_id` URL. Prevents a client from claiming URIs on a different
|
|
@@ -27,107 +117,87 @@ interface CimdOptions {
|
|
|
27
117
|
* Pass an empty array to disable origin binding (not recommended for
|
|
28
118
|
* production).
|
|
29
119
|
*
|
|
30
|
-
*
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* Permit loopback `client_id` URLs (`localhost`, `127.0.0.0/8`, `::1`,
|
|
35
|
-
* `*.localhost`), including plain HTTP, so an auth server can fetch a
|
|
36
|
-
* metadata document hosted on the same machine. Off by default; enable
|
|
37
|
-
* only for local development.
|
|
120
|
+
* Redirect URIs are deliberately excluded by default: exact redirect URI
|
|
121
|
+
* matching remains mandatory at authorization time, while native and
|
|
122
|
+
* distributed clients commonly use a redirect origin different from their
|
|
123
|
+
* metadata-document origin.
|
|
38
124
|
*
|
|
39
|
-
* @default
|
|
125
|
+
* @default ["post_logout_redirect_uris", "client_uri"]
|
|
40
126
|
*/
|
|
41
|
-
|
|
127
|
+
originBoundFields?: readonly string[];
|
|
42
128
|
/**
|
|
43
129
|
* Pre-fetch gate called before a metadata document is requested. Return
|
|
44
130
|
* `false` to reject the `client_id` URL.
|
|
45
131
|
*
|
|
46
132
|
* Use this for origin allowlists, per-host rate limiting, or integrating
|
|
47
|
-
* with an external trust service.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
133
|
+
* with an external trust service. It is application policy, not a
|
|
134
|
+
* substitute for the required transport's resolve-once and connection-
|
|
135
|
+
* pinning guarantees; resolving here would introduce a TOCTOU boundary.
|
|
50
136
|
*
|
|
51
137
|
* @default always allow
|
|
52
138
|
*/
|
|
53
|
-
|
|
139
|
+
isMetadataDocumentUrlAllowed?: (clientIdUrl: string, context: GenericEndpointContext) => boolean | Promise<boolean>;
|
|
54
140
|
/**
|
|
55
141
|
* Called after a client is created from a metadata document for the
|
|
56
|
-
* first time. Use this to assign trust
|
|
57
|
-
* perform other post-creation processing.
|
|
142
|
+
* first time. Use this to assign local trust, emit an audit event, or
|
|
143
|
+
* perform other post-creation processing. Better Auth does not fetch or
|
|
144
|
+
* render metadata-owned remote assets such as `logo_uri`.
|
|
145
|
+
*
|
|
146
|
+
* This is a best-effort notification. A rejected callback is logged and
|
|
147
|
+
* does not roll back an otherwise valid registration.
|
|
58
148
|
*/
|
|
59
|
-
onClientCreated?: (
|
|
60
|
-
client: SchemaClient<Scope[]>;
|
|
61
|
-
metadata: Record<string, unknown>;
|
|
62
|
-
ctx: GenericEndpointContext;
|
|
63
|
-
}) => void | Promise<void>;
|
|
149
|
+
onClientCreated?: (event: CimdClientCreatedEvent) => void | Promise<void>;
|
|
64
150
|
/**
|
|
65
151
|
* Called after a client is refreshed from a re-fetched metadata
|
|
66
152
|
* document. Use this for change-detection logging or updating derived
|
|
67
153
|
* fields.
|
|
154
|
+
*
|
|
155
|
+
* This is a best-effort notification. A rejected callback is logged and
|
|
156
|
+
* does not roll back an otherwise valid refresh.
|
|
68
157
|
*/
|
|
69
|
-
onClientRefreshed?: (
|
|
70
|
-
client: SchemaClient<Scope[]>;
|
|
71
|
-
metadata: Record<string, unknown>;
|
|
72
|
-
ctx: GenericEndpointContext;
|
|
73
|
-
}) => void | Promise<void>;
|
|
158
|
+
onClientRefreshed?: (event: CimdClientRefreshedEvent) => void | Promise<void>;
|
|
74
159
|
}
|
|
75
160
|
//#endregion
|
|
76
|
-
//#region src/resolver.d.ts
|
|
77
|
-
/**
|
|
78
|
-
* Signature of the `resolve` function on a {@link ClientDiscovery}. Kept
|
|
79
|
-
* here to avoid a circular import back into `@better-auth/oauth-provider`.
|
|
80
|
-
*/
|
|
81
|
-
type CimdResolver = (ctx: GenericEndpointContext, clientId: string, existing: SchemaClient<Scope[]> | null) => Promise<SchemaClient<Scope[]> | null>;
|
|
82
|
-
/**
|
|
83
|
-
* Build the `resolve` function for a CIMD {@link ClientDiscovery}.
|
|
84
|
-
*
|
|
85
|
-
* Exposed for advanced composition. Most users should call
|
|
86
|
-
* {@link cimdClientDiscovery} (to contribute a complete discovery through
|
|
87
|
-
* `oauthProvider({ extensions: [{ clientDiscovery }] })`) or install the
|
|
88
|
-
* `cimd()` plugin.
|
|
89
|
-
*/
|
|
90
|
-
declare function createCimdResolver(cimdOptions?: CimdOptions): CimdResolver;
|
|
91
|
-
//#endregion
|
|
92
161
|
//#region src/validate-metadata-document.d.ts
|
|
93
|
-
|
|
94
|
-
valid:
|
|
95
|
-
|
|
162
|
+
type CimdMetadataValidationResult = {
|
|
163
|
+
valid: true;
|
|
164
|
+
metadata: OAuthClientMetadata;
|
|
165
|
+
error?: never;
|
|
96
166
|
warnings?: string[];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
167
|
+
} | {
|
|
168
|
+
valid: false;
|
|
169
|
+
error: string;
|
|
170
|
+
metadata?: never;
|
|
171
|
+
warnings?: string[];
|
|
172
|
+
};
|
|
173
|
+
interface CimdMetadataValidationOptions {
|
|
174
|
+
originBoundFields?: readonly string[];
|
|
175
|
+
metadataProfile?: CimdMetadataProfile;
|
|
104
176
|
}
|
|
105
177
|
/**
|
|
106
178
|
* Detect a URL-formatted client_id (Client ID Metadata Document pattern).
|
|
107
179
|
*
|
|
108
|
-
* HTTPS URLs
|
|
109
|
-
*
|
|
110
|
-
* gate: it performs no DNS resolution, so callers MUST also run
|
|
180
|
+
* HTTPS URLs match. This is a routing predicate, not a security gate: it
|
|
181
|
+
* performs no DNS resolution, so callers MUST also run
|
|
111
182
|
* {@link validateClientIdUrl} (and a fetch-time policy) before fetching.
|
|
112
183
|
*/
|
|
113
|
-
declare function
|
|
184
|
+
declare function isCimdClientIdUrlCandidate(clientId: string): boolean;
|
|
114
185
|
/**
|
|
115
|
-
* Validate a client_id URL per
|
|
186
|
+
* Validate a client_id URL per Client ID Metadata Document draft-02 §3.
|
|
116
187
|
* Returns null on success, an error string on failure.
|
|
117
188
|
*
|
|
118
|
-
* Loopback
|
|
119
|
-
*
|
|
120
|
-
* rejected.
|
|
189
|
+
* Loopback and every other non-public host (private, link-local,
|
|
190
|
+
* cloud-metadata, IPv6 tunnels) are rejected.
|
|
121
191
|
*/
|
|
122
|
-
declare function validateClientIdUrl(url: string
|
|
192
|
+
declare function validateClientIdUrl(url: string): string | null;
|
|
123
193
|
/**
|
|
124
194
|
* Validate a fetched Client ID Metadata Document per §4.1.
|
|
125
195
|
*
|
|
126
|
-
* @param
|
|
196
|
+
* @param clientIdUrl - The URL the document was fetched from.
|
|
127
197
|
* @param raw - The parsed JSON body of the response.
|
|
128
|
-
* @param
|
|
198
|
+
* @param options - Generic draft-02 validation options and an optional protocol profile.
|
|
129
199
|
*/
|
|
130
|
-
declare function validateCimdMetadata(
|
|
200
|
+
declare function validateCimdMetadata(clientIdUrl: string, raw: unknown, options?: CimdMetadataValidationOptions): CimdMetadataValidationResult;
|
|
131
201
|
//#endregion
|
|
132
202
|
//#region src/index.d.ts
|
|
133
203
|
declare module "@better-auth/core" {
|
|
@@ -145,22 +215,23 @@ declare module "@better-auth/core" {
|
|
|
145
215
|
* install the {@link cimd} plugin instead, which contributes this discovery
|
|
146
216
|
* alongside whatever else is configured.
|
|
147
217
|
*/
|
|
148
|
-
declare function
|
|
218
|
+
declare function createCimdClientDiscovery(options: CimdOptions): ClientDiscovery;
|
|
149
219
|
/**
|
|
150
220
|
* Client ID Metadata Document plugin.
|
|
151
221
|
*
|
|
152
222
|
* Adds unauthenticated dynamic client discovery over HTTPS to an
|
|
153
223
|
* `oauth-provider` instance. Clients identify themselves by providing
|
|
154
224
|
* an HTTPS URL as their `client_id`; the plugin fetches and validates
|
|
155
|
-
* the document at that URL, then creates a
|
|
225
|
+
* the document at that URL, then creates a client record whose authentication
|
|
226
|
+
* behavior is determined by `token_endpoint_auth_method`.
|
|
156
227
|
*
|
|
157
|
-
* See {@link https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document
|
|
158
|
-
* and {@link https://modelcontextprotocol.io/specification/
|
|
228
|
+
* See {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-02 | Client ID Metadata Document draft-02}
|
|
229
|
+
* and {@link https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents | the MCP authorization spec}.
|
|
159
230
|
*/
|
|
160
|
-
declare const cimd: (options
|
|
231
|
+
declare const cimd: (options: CimdOptions) => {
|
|
161
232
|
id: "cimd";
|
|
162
233
|
version: string;
|
|
163
|
-
init(ctx:
|
|
234
|
+
init(ctx: import("better-auth").AuthContext): void;
|
|
164
235
|
};
|
|
165
236
|
//#endregion
|
|
166
|
-
export { type
|
|
237
|
+
export { type CimdClientCreatedEvent, type CimdClientRefreshedEvent, type CimdMetadataFetchPolicy, type CimdMetadataProfile, type CimdMetadataValidationOptions, type CimdMetadataValidationResult, type CimdOptions, cimd, createCimdClientDiscovery, isCimdClientIdUrlCandidate, validateCimdMetadata, validateClientIdUrl };
|