@better-auth/cimd 1.7.0-rc.2 → 1.7.0-rc.4
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 +148 -75
- package/dist/index.mjs +512 -323
- package/dist/node.d.mts +14 -0
- package/dist/node.mjs +66 -0
- package/package.json +18 -7
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,22 +1,114 @@
|
|
|
1
|
-
import { ClientDiscovery, SchemaClient, Scope } from "@better-auth/oauth-provider";
|
|
1
|
+
import { ClientDiscovery, ClientMetadataResourceFetch, OAuthClientMetadata, SchemaClient, Scope } from "@better-auth/oauth-provider";
|
|
2
2
|
import { GenericEndpointContext } from "@better-auth/core";
|
|
3
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
|
+
}
|
|
4
60
|
/**
|
|
5
61
|
* Options for the Client ID Metadata Document plugin.
|
|
6
62
|
*
|
|
7
|
-
* @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
|
|
8
64
|
*/
|
|
9
65
|
interface CimdOptions {
|
|
10
66
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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.
|
|
13
89
|
*
|
|
14
90
|
* Accepts a number of seconds or a duration string (e.g. `"60m"`,
|
|
15
91
|
* `"1d"`).
|
|
16
92
|
*
|
|
17
93
|
* @default "60m"
|
|
18
94
|
*/
|
|
19
|
-
|
|
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;
|
|
20
112
|
/**
|
|
21
113
|
* Metadata fields whose URL values must share the same origin as the
|
|
22
114
|
* `client_id` URL. Prevents a client from claiming URIs on a different
|
|
@@ -25,107 +117,87 @@ interface CimdOptions {
|
|
|
25
117
|
* Pass an empty array to disable origin binding (not recommended for
|
|
26
118
|
* production).
|
|
27
119
|
*
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* Permit loopback `client_id` URLs (`localhost`, `127.0.0.0/8`, `::1`,
|
|
33
|
-
* `*.localhost`), including plain HTTP, so an auth server can fetch a
|
|
34
|
-
* metadata document hosted on the same machine. Off by default; enable
|
|
35
|
-
* 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.
|
|
36
124
|
*
|
|
37
|
-
* @default
|
|
125
|
+
* @default ["post_logout_redirect_uris", "client_uri"]
|
|
38
126
|
*/
|
|
39
|
-
|
|
127
|
+
originBoundFields?: readonly string[];
|
|
40
128
|
/**
|
|
41
129
|
* Pre-fetch gate called before a metadata document is requested. Return
|
|
42
130
|
* `false` to reject the `client_id` URL.
|
|
43
131
|
*
|
|
44
132
|
* Use this for origin allowlists, per-host rate limiting, or integrating
|
|
45
|
-
* with an external trust service.
|
|
46
|
-
*
|
|
47
|
-
*
|
|
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.
|
|
48
136
|
*
|
|
49
137
|
* @default always allow
|
|
50
138
|
*/
|
|
51
|
-
|
|
139
|
+
isMetadataDocumentUrlAllowed?: (clientIdUrl: string, context: GenericEndpointContext) => boolean | Promise<boolean>;
|
|
52
140
|
/**
|
|
53
141
|
* Called after a client is created from a metadata document for the
|
|
54
|
-
* first time. Use this to assign trust
|
|
55
|
-
* 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.
|
|
56
148
|
*/
|
|
57
|
-
onClientCreated?: (
|
|
58
|
-
client: SchemaClient<Scope[]>;
|
|
59
|
-
metadata: Record<string, unknown>;
|
|
60
|
-
ctx: GenericEndpointContext;
|
|
61
|
-
}) => void | Promise<void>;
|
|
149
|
+
onClientCreated?: (event: CimdClientCreatedEvent) => void | Promise<void>;
|
|
62
150
|
/**
|
|
63
151
|
* Called after a client is refreshed from a re-fetched metadata
|
|
64
152
|
* document. Use this for change-detection logging or updating derived
|
|
65
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.
|
|
66
157
|
*/
|
|
67
|
-
onClientRefreshed?: (
|
|
68
|
-
client: SchemaClient<Scope[]>;
|
|
69
|
-
metadata: Record<string, unknown>;
|
|
70
|
-
ctx: GenericEndpointContext;
|
|
71
|
-
}) => void | Promise<void>;
|
|
158
|
+
onClientRefreshed?: (event: CimdClientRefreshedEvent) => void | Promise<void>;
|
|
72
159
|
}
|
|
73
160
|
//#endregion
|
|
74
|
-
//#region src/resolver.d.ts
|
|
75
|
-
/**
|
|
76
|
-
* Signature of the `resolve` function on a {@link ClientDiscovery}. Kept
|
|
77
|
-
* here to avoid a circular import back into `@better-auth/oauth-provider`.
|
|
78
|
-
*/
|
|
79
|
-
type CimdResolver = (ctx: GenericEndpointContext, clientId: string, existing: SchemaClient<Scope[]> | null) => Promise<SchemaClient<Scope[]> | null>;
|
|
80
|
-
/**
|
|
81
|
-
* Build the `resolve` function for a CIMD {@link ClientDiscovery}.
|
|
82
|
-
*
|
|
83
|
-
* Exposed for advanced composition. Most users should call
|
|
84
|
-
* {@link cimdClientDiscovery} (to contribute a complete discovery through
|
|
85
|
-
* `oauthProvider({ extensions: [{ clientDiscovery }] })`) or install the
|
|
86
|
-
* `cimd()` plugin.
|
|
87
|
-
*/
|
|
88
|
-
declare function createCimdResolver(cimdOptions?: CimdOptions): CimdResolver;
|
|
89
|
-
//#endregion
|
|
90
161
|
//#region src/validate-metadata-document.d.ts
|
|
91
|
-
|
|
92
|
-
valid:
|
|
93
|
-
|
|
162
|
+
type CimdMetadataValidationResult = {
|
|
163
|
+
valid: true;
|
|
164
|
+
metadata: OAuthClientMetadata;
|
|
165
|
+
error?: never;
|
|
94
166
|
warnings?: string[];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
167
|
+
} | {
|
|
168
|
+
valid: false;
|
|
169
|
+
error: string;
|
|
170
|
+
metadata?: never;
|
|
171
|
+
warnings?: string[];
|
|
172
|
+
};
|
|
173
|
+
interface CimdMetadataValidationOptions {
|
|
174
|
+
originBoundFields?: readonly string[];
|
|
175
|
+
metadataProfile?: CimdMetadataProfile;
|
|
102
176
|
}
|
|
103
177
|
/**
|
|
104
178
|
* Detect a URL-formatted client_id (Client ID Metadata Document pattern).
|
|
105
179
|
*
|
|
106
|
-
* HTTPS URLs
|
|
107
|
-
*
|
|
108
|
-
* 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
|
|
109
182
|
* {@link validateClientIdUrl} (and a fetch-time policy) before fetching.
|
|
110
183
|
*/
|
|
111
|
-
declare function
|
|
184
|
+
declare function isCimdClientIdUrlCandidate(clientId: string): boolean;
|
|
112
185
|
/**
|
|
113
|
-
* Validate a client_id URL per
|
|
186
|
+
* Validate a client_id URL per Client ID Metadata Document draft-02 §3.
|
|
114
187
|
* Returns null on success, an error string on failure.
|
|
115
188
|
*
|
|
116
|
-
* Loopback
|
|
117
|
-
*
|
|
118
|
-
* rejected.
|
|
189
|
+
* Loopback and every other non-public host (private, link-local,
|
|
190
|
+
* cloud-metadata, IPv6 tunnels) are rejected.
|
|
119
191
|
*/
|
|
120
|
-
declare function validateClientIdUrl(url: string
|
|
192
|
+
declare function validateClientIdUrl(url: string): string | null;
|
|
121
193
|
/**
|
|
122
194
|
* Validate a fetched Client ID Metadata Document per §4.1.
|
|
123
195
|
*
|
|
124
|
-
* @param
|
|
196
|
+
* @param clientIdUrl - The URL the document was fetched from.
|
|
125
197
|
* @param raw - The parsed JSON body of the response.
|
|
126
|
-
* @param
|
|
198
|
+
* @param options - Generic draft-02 validation options and an optional protocol profile.
|
|
127
199
|
*/
|
|
128
|
-
declare function validateCimdMetadata(
|
|
200
|
+
declare function validateCimdMetadata(clientIdUrl: string, raw: unknown, options?: CimdMetadataValidationOptions): CimdMetadataValidationResult;
|
|
129
201
|
//#endregion
|
|
130
202
|
//#region src/index.d.ts
|
|
131
203
|
declare module "@better-auth/core" {
|
|
@@ -143,22 +215,23 @@ declare module "@better-auth/core" {
|
|
|
143
215
|
* install the {@link cimd} plugin instead, which contributes this discovery
|
|
144
216
|
* alongside whatever else is configured.
|
|
145
217
|
*/
|
|
146
|
-
declare function
|
|
218
|
+
declare function createCimdClientDiscovery(options: CimdOptions): ClientDiscovery;
|
|
147
219
|
/**
|
|
148
220
|
* Client ID Metadata Document plugin.
|
|
149
221
|
*
|
|
150
222
|
* Adds unauthenticated dynamic client discovery over HTTPS to an
|
|
151
223
|
* `oauth-provider` instance. Clients identify themselves by providing
|
|
152
224
|
* an HTTPS URL as their `client_id`; the plugin fetches and validates
|
|
153
|
-
* 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`.
|
|
154
227
|
*
|
|
155
|
-
* See {@link https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document
|
|
156
|
-
* 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}.
|
|
157
230
|
*/
|
|
158
|
-
declare const cimd: (options
|
|
231
|
+
declare const cimd: (options: CimdOptions) => {
|
|
159
232
|
id: "cimd";
|
|
160
233
|
version: string;
|
|
161
234
|
init(ctx: import("better-auth").AuthContext): void;
|
|
162
235
|
};
|
|
163
236
|
//#endregion
|
|
164
|
-
export { type
|
|
237
|
+
export { type CimdClientCreatedEvent, type CimdClientRefreshedEvent, type CimdMetadataFetchPolicy, type CimdMetadataProfile, type CimdMetadataValidationOptions, type CimdMetadataValidationResult, type CimdOptions, cimd, createCimdClientDiscovery, isCimdClientIdUrlCandidate, validateCimdMetadata, validateClientIdUrl };
|