tiny-http-mcp-server 0.1.33 → 0.1.34
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/dist/composition.json +1 -1
- package/node_modules/mcp-oauth/README.md +10 -0
- package/node_modules/mcp-oauth/dist/client/client-registration.js +9 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +39 -5
- package/node_modules/mcp-oauth/dist/client/token-auth-method.d.ts +2 -0
- package/node_modules/mcp-oauth/dist/client/token-auth-method.js +7 -0
- package/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +3 -1
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +19 -9
- package/node_modules/mcp-oauth/dist/client/types.d.ts +4 -0
- package/node_modules/mcp-oauth/dist/index.d.ts +1 -1
- package/node_modules/tiny-mcp-client/dist/index.d.ts +4 -0
- package/node_modules/tiny-mcp-client/dist/index.js +69 -15
- package/package.json +1 -1
package/dist/composition.json
CHANGED
|
@@ -119,6 +119,16 @@ Explicit ID/secret values must agree with the imported response. Sessions and
|
|
|
119
119
|
native registration stores retain arrays, issuance/expiry timestamps and JSON
|
|
120
120
|
provider metadata. Registration input is copied, bounded to 64 KiB and 64
|
|
121
121
|
levels, and rejects invalid standard field types and non-JSON extensions.
|
|
122
|
+
Set `client.tokenEndpointAuthMethod` to `none`, `client_secret_post` or
|
|
123
|
+
`client_secret_basic`; a full registration can supply the same field as
|
|
124
|
+
`token_endpoint_auth_method`. Public clients never transmit a stored secret.
|
|
125
|
+
Basic credentials are individually form-encoded before Base64 encoding and
|
|
126
|
+
are omitted from the form body. Cached grants retain their registered method;
|
|
127
|
+
an explicitly different configured method requires separate persistence or a
|
|
128
|
+
reset. Native DCR chooses a supported method, preferring public PKCE when
|
|
129
|
+
advertised. Unsupported methods and missing confidential secrets fail before
|
|
130
|
+
token requests. Existing clients without a method keep the previous default:
|
|
131
|
+
body authentication when a secret is present, public authentication otherwise.
|
|
122
132
|
Static clients and dynamic initial-grant imports require cached grants to match
|
|
123
133
|
the original normalized client ID and secret. A different client configuration
|
|
124
134
|
fails before attaching or refreshing credentials and retains the stored record;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeOAuthScope } from "./scope.js";
|
|
2
|
+
import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
|
|
2
3
|
/** Validate and copy a bounded JSON DCR response without quoting credential input. */
|
|
3
4
|
export function parseOAuthClientRegistration(value) {
|
|
4
5
|
const invalid = () => new Error("Invalid OAuth client registration metadata");
|
|
@@ -83,12 +84,20 @@ export function normalizeStoredOAuthClient(value) {
|
|
|
83
84
|
(clientSecret !== undefined && (typeof clientSecret !== "string" || clientSecret.trim() === "")))
|
|
84
85
|
return null;
|
|
85
86
|
const client = { clientId: clientId.trim(), ...(clientSecret === undefined ? {} : { clientSecret: clientSecret.trim() }) };
|
|
87
|
+
const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record, "tokenEndpointAuthMethod") ? record.tokenEndpointAuthMethod : undefined);
|
|
86
88
|
if (Object.hasOwn(record, "registration") && record.registration !== undefined) {
|
|
87
89
|
const registration = parseOAuthClientRegistration(record.registration);
|
|
88
90
|
const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : undefined;
|
|
89
91
|
if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
|
|
90
92
|
throw new Error("OAuth client registration does not match the client identity");
|
|
91
93
|
client.registration = registration;
|
|
94
|
+
const registrationMethod = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(registration, "token_endpoint_auth_method") ? registration.token_endpoint_auth_method : undefined);
|
|
95
|
+
if (method !== undefined && registrationMethod !== undefined && method !== registrationMethod)
|
|
96
|
+
throw new Error("OAuth token endpoint authentication conflicts with the client registration");
|
|
97
|
+
if (registrationMethod !== undefined)
|
|
98
|
+
client.tokenEndpointAuthMethod = registrationMethod;
|
|
92
99
|
}
|
|
100
|
+
if (method !== undefined)
|
|
101
|
+
client.tokenEndpointAuthMethod = method;
|
|
93
102
|
return client;
|
|
94
103
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizeStoredOAuthClient, parseOAuthClientRegistration } from "./client-registration.js";
|
|
2
2
|
import { normalizeOAuthScope } from "./scope.js";
|
|
3
|
+
import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
|
|
3
4
|
import { isIP } from "node:net";
|
|
4
5
|
import { fetchMcpResponse } from "../http-fetch.js";
|
|
5
6
|
import { URL } from "node:url";
|
|
@@ -23,6 +24,8 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
23
24
|
const clientMetadata = getClientMetadata(options.client);
|
|
24
25
|
const requestedScope = clientMetadata?.scope;
|
|
25
26
|
const configuredClient = normalizeConfiguredClient(options.client);
|
|
27
|
+
const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
|
|
28
|
+
const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
|
|
26
29
|
const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
|
|
27
30
|
const clientStore = options.authStore === undefined ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
|
|
28
31
|
const now = options.now ?? Date.now;
|
|
@@ -156,6 +159,9 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
156
159
|
}
|
|
157
160
|
if (requestedScope !== undefined && session?.tokens !== undefined && normalizeOAuthScope(session.tokens.scope ?? session.requestedScope) !== requestedScope)
|
|
158
161
|
throw new Error("Stored session does not match the requested OAuth scope; authorize again or select separate persistence");
|
|
162
|
+
if (configuredTokenMethod !== undefined && session !== null && (session.tokens !== undefined || session.refreshState === "pending") &&
|
|
163
|
+
(session.client.tokenEndpointAuthMethod ?? (session.client.clientSecret === undefined ? "none" : "client_secret_post")) !== configuredTokenMethod)
|
|
164
|
+
throw new Error("Stored session does not match the requested OAuth token endpoint authentication; select separate persistence or reset it");
|
|
159
165
|
if (session?.refreshState === "pending") {
|
|
160
166
|
if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === undefined)
|
|
161
167
|
throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
|
|
@@ -190,6 +196,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
190
196
|
if (session.tokens?.refreshToken === undefined) {
|
|
191
197
|
return session;
|
|
192
198
|
}
|
|
199
|
+
assertTokenEndpointAuthentication(session.client, discovery.authorizationServerMetadata);
|
|
193
200
|
const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
|
|
194
201
|
await saveSession(resource, pendingSession);
|
|
195
202
|
signal?.throwIfAborted();
|
|
@@ -201,6 +208,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
201
208
|
tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
|
|
202
209
|
clientId: session.client.clientId,
|
|
203
210
|
clientSecret: session.client.clientSecret,
|
|
211
|
+
tokenEndpointAuthMethod: session.client.tokenEndpointAuthMethod,
|
|
204
212
|
refreshToken: session.tokens.refreshToken,
|
|
205
213
|
resource,
|
|
206
214
|
fetch, signal,
|
|
@@ -266,6 +274,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
266
274
|
let resolvedClient = null;
|
|
267
275
|
try {
|
|
268
276
|
resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch, signal);
|
|
277
|
+
assertTokenEndpointAuthentication(resolvedClient.client, discovery.authorizationServerMetadata);
|
|
269
278
|
const sessionWithoutTokens = {
|
|
270
279
|
resource,
|
|
271
280
|
authorizationServer: discovery.authorizationServer,
|
|
@@ -289,6 +298,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
289
298
|
tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
|
|
290
299
|
clientId: resolvedClient.client.clientId,
|
|
291
300
|
clientSecret: resolvedClient.client.clientSecret,
|
|
301
|
+
tokenEndpointAuthMethod: resolvedClient.client.tokenEndpointAuthMethod,
|
|
292
302
|
code,
|
|
293
303
|
codeVerifier: verifier,
|
|
294
304
|
redirectUri: loopback.redirectUri,
|
|
@@ -378,7 +388,12 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
378
388
|
};
|
|
379
389
|
}
|
|
380
390
|
}
|
|
381
|
-
const
|
|
391
|
+
const supported = getSupportedTokenAuthMethods(discovery.authorizationServerMetadata);
|
|
392
|
+
const registrationMethod = requestedTokenMethod ?? (supported === undefined ? "none" :
|
|
393
|
+
["none", "client_secret_basic", "client_secret_post"].find(method => supported.includes(method)));
|
|
394
|
+
if (registrationMethod === undefined || (supported !== undefined && !supported.includes(registrationMethod)))
|
|
395
|
+
throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
|
|
396
|
+
const registrationBody = buildClientRegistrationBody(clientMetadata, redirectUri, registrationMethod);
|
|
382
397
|
const deadline = AbortSignal.timeout(30_000);
|
|
383
398
|
const signal = parentSignal === undefined ? deadline : AbortSignal.any([parentSignal, deadline]);
|
|
384
399
|
const response = await fetchMcpResponse(fetch, registrationEndpoint, {
|
|
@@ -392,9 +407,12 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
392
407
|
const payload = await readOAuthJsonObjectResponse(response, signal);
|
|
393
408
|
const registration = parseOAuthClientRegistration(payload);
|
|
394
409
|
const registeredSecret = getOwnString(registration, "client_secret");
|
|
410
|
+
const responseMethod = normalizeOAuthTokenEndpointAuthMethod(getOwnEntry(registration, "token_endpoint_auth_method")) ??
|
|
411
|
+
requestedTokenMethod ?? (supported === undefined ? undefined : normalizeOAuthTokenEndpointAuthMethod(registrationMethod));
|
|
395
412
|
const registeredClient = {
|
|
396
413
|
clientId: registration.client_id.trim(),
|
|
397
414
|
...(registeredSecret === undefined ? {} : { clientSecret: registeredSecret.trim() }),
|
|
415
|
+
...(responseMethod === undefined ? {} : { tokenEndpointAuthMethod: responseMethod }),
|
|
398
416
|
registration
|
|
399
417
|
};
|
|
400
418
|
await saveRegisteredClient(discovery.authorizationServer, registeredClient);
|
|
@@ -408,7 +426,7 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
408
426
|
return normalizeLoadedSession(await sessionStore.load(resource));
|
|
409
427
|
}
|
|
410
428
|
async function saveSession(resource, session) {
|
|
411
|
-
await sessionStore.save(resource, session);
|
|
429
|
+
await sessionStore.save(resource, structuredClone(session));
|
|
412
430
|
}
|
|
413
431
|
async function clearSession(resource) {
|
|
414
432
|
await sessionStore.clear(resource);
|
|
@@ -564,7 +582,7 @@ function normalizeConfiguredClient(client) {
|
|
|
564
582
|
if (clientId === undefined)
|
|
565
583
|
return null;
|
|
566
584
|
const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === undefined ? undefined : getOwnString(registration, "client_secret")?.trim());
|
|
567
|
-
return normalizeStoredOAuthClient({ clientId, clientSecret, registration });
|
|
585
|
+
return normalizeStoredOAuthClient({ clientId, clientSecret, registration, tokenEndpointAuthMethod: client.tokenEndpointAuthMethod });
|
|
568
586
|
}
|
|
569
587
|
function normalizeOptionalOAuthString(value) {
|
|
570
588
|
if (value === undefined) {
|
|
@@ -671,12 +689,28 @@ function assertRequestMatchesResource(requestUrl, resource) {
|
|
|
671
689
|
throw new Error(`OAuth request URL ${requestUrl} does not match discovered resource ${resource}`);
|
|
672
690
|
}
|
|
673
691
|
}
|
|
674
|
-
function
|
|
692
|
+
function getSupportedTokenAuthMethods(metadata) {
|
|
693
|
+
const value = getOwnEntry(metadata, "token_endpoint_auth_methods_supported");
|
|
694
|
+
if (value === undefined)
|
|
695
|
+
return undefined;
|
|
696
|
+
if (!Array.isArray(value) || value.length > 128 || value.some(method => typeof method !== "string"))
|
|
697
|
+
throw new Error("Invalid OAuth token endpoint authentication metadata");
|
|
698
|
+
return value;
|
|
699
|
+
}
|
|
700
|
+
function assertTokenEndpointAuthentication(client, metadata) {
|
|
701
|
+
const method = client.tokenEndpointAuthMethod ?? (client.clientSecret === undefined ? "none" : "client_secret_post");
|
|
702
|
+
if (method !== "none" && client.clientSecret === undefined)
|
|
703
|
+
throw new Error("OAuth token endpoint authentication requires a client secret");
|
|
704
|
+
const supported = getSupportedTokenAuthMethods(metadata);
|
|
705
|
+
if (supported !== undefined && !supported.includes(method))
|
|
706
|
+
throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
|
|
707
|
+
}
|
|
708
|
+
function buildClientRegistrationBody(metadata, redirectUri, tokenEndpointAuthMethod) {
|
|
675
709
|
const body = {
|
|
676
710
|
redirect_uris: [redirectUri],
|
|
677
711
|
grant_types: ["authorization_code", "refresh_token"],
|
|
678
712
|
response_types: ["code"],
|
|
679
|
-
token_endpoint_auth_method:
|
|
713
|
+
token_endpoint_auth_method: tokenEndpointAuthMethod
|
|
680
714
|
};
|
|
681
715
|
const clientName = metadata === undefined ? undefined : getOwnString(metadata, "clientName");
|
|
682
716
|
const scope = metadata === undefined ? undefined : getOwnString(metadata, "scope");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function normalizeOAuthTokenEndpointAuthMethod(value) {
|
|
2
|
+
if (value === undefined || value === null)
|
|
3
|
+
return undefined;
|
|
4
|
+
if (value !== "none" && value !== "client_secret_post" && value !== "client_secret_basic")
|
|
5
|
+
throw new Error("Unsupported OAuth token endpoint authentication method");
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OAuthMetadataFetch, StoredOAuthTokens } from "./types.js";
|
|
1
|
+
import type { OAuthMetadataFetch, StoredOAuthTokens, OAuthTokenEndpointAuthMethod } from "./types.js";
|
|
2
2
|
interface OAuthErrorShape {
|
|
3
3
|
error: string;
|
|
4
4
|
error_description?: string;
|
|
@@ -22,6 +22,7 @@ export declare function exchangeAuthorizationCode(input: {
|
|
|
22
22
|
tokenEndpoint: string;
|
|
23
23
|
clientId: string;
|
|
24
24
|
clientSecret?: string;
|
|
25
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
25
26
|
code: string;
|
|
26
27
|
codeVerifier: string;
|
|
27
28
|
redirectUri: string;
|
|
@@ -34,6 +35,7 @@ export declare function refreshAccessToken(input: {
|
|
|
34
35
|
tokenEndpoint: string;
|
|
35
36
|
clientId: string;
|
|
36
37
|
clientSecret?: string;
|
|
38
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
37
39
|
refreshToken: string;
|
|
38
40
|
resource: string;
|
|
39
41
|
fetch: OAuthMetadataFetch;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeOAuthTokenEndpointAuthMethod } from "./token-auth-method.js";
|
|
1
2
|
import { canonicalizeResourceIndicator } from "../resource-indicator.js";
|
|
2
3
|
import { readBoundedResponseText } from "../http-response.js";
|
|
3
4
|
import { fetchMcpResponse } from "../http-fetch.js";
|
|
@@ -40,6 +41,7 @@ export async function exchangeAuthorizationCode(input) {
|
|
|
40
41
|
tokenEndpoint: input.tokenEndpoint,
|
|
41
42
|
clientId: input.clientId,
|
|
42
43
|
clientSecret: input.clientSecret,
|
|
44
|
+
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
43
45
|
params: {
|
|
44
46
|
grant_type: "authorization_code",
|
|
45
47
|
code: input.code,
|
|
@@ -58,6 +60,7 @@ export async function refreshAccessToken(input) {
|
|
|
58
60
|
tokenEndpoint: input.tokenEndpoint,
|
|
59
61
|
clientId: input.clientId,
|
|
60
62
|
clientSecret: input.clientSecret,
|
|
63
|
+
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
61
64
|
params: {
|
|
62
65
|
grant_type: "refresh_token",
|
|
63
66
|
refresh_token: input.refreshToken,
|
|
@@ -69,21 +72,28 @@ export async function refreshAccessToken(input) {
|
|
|
69
72
|
});
|
|
70
73
|
}
|
|
71
74
|
async function requestTokens(input) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const method = normalizeOAuthTokenEndpointAuthMethod(input.tokenEndpointAuthMethod) ??
|
|
76
|
+
(input.clientSecret === undefined ? "none" : "client_secret_post");
|
|
77
|
+
if (method !== "none" && (input.clientSecret === undefined || input.clientSecret.trim() === ""))
|
|
78
|
+
throw new Error("OAuth token endpoint authentication requires a client secret");
|
|
79
|
+
const body = new URLSearchParams(input.params);
|
|
80
|
+
const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded" });
|
|
81
|
+
if (method === "client_secret_basic") {
|
|
82
|
+
const encoded = new URLSearchParams({ credential: input.clientId }).toString().slice("credential=".length);
|
|
83
|
+
const encodedSecret = new URLSearchParams({ credential: input.clientSecret }).toString().slice("credential=".length);
|
|
84
|
+
headers.set("Authorization", `Basic ${Buffer.from(`${encoded}:${encodedSecret}`).toString("base64")}`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
body.set("client_id", input.clientId);
|
|
88
|
+
if (method === "client_secret_post")
|
|
89
|
+
body.set("client_secret", input.clientSecret);
|
|
78
90
|
}
|
|
79
91
|
input.signal?.throwIfAborted();
|
|
80
92
|
const deadline = AbortSignal.timeout(30_000);
|
|
81
93
|
const signal = input.signal === undefined ? deadline : AbortSignal.any([input.signal, deadline]);
|
|
82
94
|
const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
|
|
83
95
|
method: "POST",
|
|
84
|
-
headers
|
|
85
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
86
|
-
},
|
|
96
|
+
headers,
|
|
87
97
|
body: body.toString(),
|
|
88
98
|
signal
|
|
89
99
|
});
|
|
@@ -86,7 +86,9 @@ export interface StoredOAuthClient {
|
|
|
86
86
|
clientId: string;
|
|
87
87
|
clientSecret?: string;
|
|
88
88
|
registration?: OAuthClientRegistration;
|
|
89
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
89
90
|
}
|
|
91
|
+
export type OAuthTokenEndpointAuthMethod = "none" | "client_secret_post" | "client_secret_basic";
|
|
90
92
|
export interface StoredOAuthSession {
|
|
91
93
|
resource: string;
|
|
92
94
|
authorizationServer: string;
|
|
@@ -120,12 +122,14 @@ export interface DefaultOAuthClientProviderOptions {
|
|
|
120
122
|
metadata?: OAuthClientMetadata;
|
|
121
123
|
/** Import a complete registration owned by the caller. */
|
|
122
124
|
registration?: OAuthClientRegistration;
|
|
125
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
123
126
|
} | {
|
|
124
127
|
mode: "static";
|
|
125
128
|
clientId: string;
|
|
126
129
|
clientSecret?: string;
|
|
127
130
|
metadata?: OAuthClientMetadata;
|
|
128
131
|
registration?: OAuthClientRegistration;
|
|
132
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
129
133
|
};
|
|
130
134
|
/** Disable interactive authorization while allowing cached tokens and silent refresh. */
|
|
131
135
|
allowInteractive?: boolean;
|
|
@@ -6,7 +6,7 @@ export { generateCodeChallenge, generateCodeVerifier, } from "./client/pkce.js";
|
|
|
6
6
|
export { OAuthError, } from "./client/token-endpoint.js";
|
|
7
7
|
export { canonicalizeResourceIndicator, } from "./resource-indicator.js";
|
|
8
8
|
export { createJwksTokenVerifier, } from "./server/jwks-token-verifier.js";
|
|
9
|
-
export type { DefaultOAuthClientProviderOptions, OAuthAuthorizationServerMetadata, OAuthClientMetadata, OAuthClientRegistration, OAuthClientProvider, OAuthClientProviderOptions, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthSessionStore, OAuthUnauthorizedChallenge, StoredOAuthSession, StoredOAuthClient, StoredOAuthTokens, } from "./client/types.js";
|
|
9
|
+
export type { DefaultOAuthClientProviderOptions, OAuthAuthorizationServerMetadata, OAuthClientMetadata, OAuthClientRegistration, OAuthClientProvider, OAuthClientProviderOptions, OAuthDiscoveryResult, OAuthMetadataFetch, OAuthProtectedResourceMetadata, OAuthSessionStore, OAuthUnauthorizedChallenge, OAuthTokenEndpointAuthMethod, StoredOAuthSession, StoredOAuthClient, StoredOAuthTokens, } from "./client/types.js";
|
|
10
10
|
export type { JwksTokenVerifier, JwksTokenVerifierOptions, JwksVerifiedAccessToken, } from "./server/jwks-token-verifier.js";
|
|
11
11
|
export type { LoopbackAuthorizationOptions, LoopbackAuthorizationSession, OAuthLandingPage, } from "./client/loopback-authorization.js";
|
|
12
12
|
export { readBoundedResponseText } from "./http-response.js";
|
|
@@ -189,7 +189,9 @@ interface StoredOAuthClient {
|
|
|
189
189
|
clientId: string;
|
|
190
190
|
clientSecret?: string;
|
|
191
191
|
registration?: OAuthClientRegistration;
|
|
192
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
192
193
|
}
|
|
194
|
+
type OAuthTokenEndpointAuthMethod = "none" | "client_secret_post" | "client_secret_basic";
|
|
193
195
|
interface StoredOAuthSession {
|
|
194
196
|
resource: string;
|
|
195
197
|
authorizationServer: string;
|
|
@@ -223,12 +225,14 @@ interface DefaultOAuthClientProviderOptions {
|
|
|
223
225
|
metadata?: OAuthClientMetadata;
|
|
224
226
|
/** Import a complete registration owned by the caller. */
|
|
225
227
|
registration?: OAuthClientRegistration;
|
|
228
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
226
229
|
} | {
|
|
227
230
|
mode: "static";
|
|
228
231
|
clientId: string;
|
|
229
232
|
clientSecret?: string;
|
|
230
233
|
metadata?: OAuthClientMetadata;
|
|
231
234
|
registration?: OAuthClientRegistration;
|
|
235
|
+
tokenEndpointAuthMethod?: OAuthTokenEndpointAuthMethod;
|
|
232
236
|
};
|
|
233
237
|
/** Disable interactive authorization while allowing cached tokens and silent refresh. */
|
|
234
238
|
allowInteractive?: boolean;
|
|
@@ -3405,6 +3405,15 @@ function normalizeOAuthScope(scope) {
|
|
|
3405
3405
|
return normalized || void 0;
|
|
3406
3406
|
}
|
|
3407
3407
|
|
|
3408
|
+
// ../mcp-oauth/dist/client/token-auth-method.js
|
|
3409
|
+
function normalizeOAuthTokenEndpointAuthMethod(value) {
|
|
3410
|
+
if (value === void 0 || value === null)
|
|
3411
|
+
return void 0;
|
|
3412
|
+
if (value !== "none" && value !== "client_secret_post" && value !== "client_secret_basic")
|
|
3413
|
+
throw new Error("Unsupported OAuth token endpoint authentication method");
|
|
3414
|
+
return value;
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3408
3417
|
// ../mcp-oauth/dist/client/client-registration.js
|
|
3409
3418
|
function parseOAuthClientRegistration(value) {
|
|
3410
3419
|
const invalid = () => new Error("Invalid OAuth client registration metadata");
|
|
@@ -3503,13 +3512,21 @@ function normalizeStoredOAuthClient(value) {
|
|
|
3503
3512
|
if (typeof clientId !== "string" || clientId.trim() === "" || clientSecret !== void 0 && (typeof clientSecret !== "string" || clientSecret.trim() === ""))
|
|
3504
3513
|
return null;
|
|
3505
3514
|
const client = { clientId: clientId.trim(), ...clientSecret === void 0 ? {} : { clientSecret: clientSecret.trim() } };
|
|
3515
|
+
const method = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(record2, "tokenEndpointAuthMethod") ? record2.tokenEndpointAuthMethod : void 0);
|
|
3506
3516
|
if (Object.hasOwn(record2, "registration") && record2.registration !== void 0) {
|
|
3507
3517
|
const registration = parseOAuthClientRegistration(record2.registration);
|
|
3508
3518
|
const registeredSecret = Object.hasOwn(registration, "client_secret") ? registration.client_secret?.trim() : void 0;
|
|
3509
3519
|
if (registration.client_id.trim() !== client.clientId || registeredSecret !== client.clientSecret)
|
|
3510
3520
|
throw new Error("OAuth client registration does not match the client identity");
|
|
3511
3521
|
client.registration = registration;
|
|
3512
|
-
|
|
3522
|
+
const registrationMethod = normalizeOAuthTokenEndpointAuthMethod(Object.hasOwn(registration, "token_endpoint_auth_method") ? registration.token_endpoint_auth_method : void 0);
|
|
3523
|
+
if (method !== void 0 && registrationMethod !== void 0 && method !== registrationMethod)
|
|
3524
|
+
throw new Error("OAuth token endpoint authentication conflicts with the client registration");
|
|
3525
|
+
if (registrationMethod !== void 0)
|
|
3526
|
+
client.tokenEndpointAuthMethod = registrationMethod;
|
|
3527
|
+
}
|
|
3528
|
+
if (method !== void 0)
|
|
3529
|
+
client.tokenEndpointAuthMethod = method;
|
|
3513
3530
|
return client;
|
|
3514
3531
|
}
|
|
3515
3532
|
|
|
@@ -4760,6 +4777,7 @@ async function exchangeAuthorizationCode(input) {
|
|
|
4760
4777
|
tokenEndpoint: input.tokenEndpoint,
|
|
4761
4778
|
clientId: input.clientId,
|
|
4762
4779
|
clientSecret: input.clientSecret,
|
|
4780
|
+
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
4763
4781
|
params: {
|
|
4764
4782
|
grant_type: "authorization_code",
|
|
4765
4783
|
code: input.code,
|
|
@@ -4778,6 +4796,7 @@ async function refreshAccessToken(input) {
|
|
|
4778
4796
|
tokenEndpoint: input.tokenEndpoint,
|
|
4779
4797
|
clientId: input.clientId,
|
|
4780
4798
|
clientSecret: input.clientSecret,
|
|
4799
|
+
tokenEndpointAuthMethod: input.tokenEndpointAuthMethod,
|
|
4781
4800
|
params: {
|
|
4782
4801
|
grant_type: "refresh_token",
|
|
4783
4802
|
refresh_token: input.refreshToken,
|
|
@@ -4789,21 +4808,26 @@ async function refreshAccessToken(input) {
|
|
|
4789
4808
|
});
|
|
4790
4809
|
}
|
|
4791
4810
|
async function requestTokens(input) {
|
|
4792
|
-
const
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4811
|
+
const method = normalizeOAuthTokenEndpointAuthMethod(input.tokenEndpointAuthMethod) ?? (input.clientSecret === void 0 ? "none" : "client_secret_post");
|
|
4812
|
+
if (method !== "none" && (input.clientSecret === void 0 || input.clientSecret.trim() === ""))
|
|
4813
|
+
throw new Error("OAuth token endpoint authentication requires a client secret");
|
|
4814
|
+
const body = new URLSearchParams(input.params);
|
|
4815
|
+
const headers = new Headers({ "Content-Type": "application/x-www-form-urlencoded" });
|
|
4816
|
+
if (method === "client_secret_basic") {
|
|
4817
|
+
const encoded = new URLSearchParams({ credential: input.clientId }).toString().slice("credential=".length);
|
|
4818
|
+
const encodedSecret = new URLSearchParams({ credential: input.clientSecret }).toString().slice("credential=".length);
|
|
4819
|
+
headers.set("Authorization", `Basic ${Buffer.from(`${encoded}:${encodedSecret}`).toString("base64")}`);
|
|
4820
|
+
} else {
|
|
4821
|
+
body.set("client_id", input.clientId);
|
|
4822
|
+
if (method === "client_secret_post")
|
|
4823
|
+
body.set("client_secret", input.clientSecret);
|
|
4798
4824
|
}
|
|
4799
4825
|
input.signal?.throwIfAborted();
|
|
4800
4826
|
const deadline = AbortSignal.timeout(3e4);
|
|
4801
4827
|
const signal = input.signal === void 0 ? deadline : AbortSignal.any([input.signal, deadline]);
|
|
4802
4828
|
const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
|
|
4803
4829
|
method: "POST",
|
|
4804
|
-
headers
|
|
4805
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
4806
|
-
},
|
|
4830
|
+
headers,
|
|
4807
4831
|
body: body.toString(),
|
|
4808
4832
|
signal
|
|
4809
4833
|
});
|
|
@@ -4957,6 +4981,8 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
4957
4981
|
const clientMetadata = getClientMetadata(options.client);
|
|
4958
4982
|
const requestedScope = clientMetadata?.scope;
|
|
4959
4983
|
const configuredClient = normalizeConfiguredClient(options.client);
|
|
4984
|
+
const requestedTokenMethod = normalizeOAuthTokenEndpointAuthMethod(options.client.tokenEndpointAuthMethod);
|
|
4985
|
+
const configuredTokenMethod = requestedTokenMethod ?? configuredClient?.tokenEndpointAuthMethod;
|
|
4960
4986
|
const sessionStore = options.sessionStore ?? createAuthStoreSessionStore(options.authStore, options.persistenceNamespace);
|
|
4961
4987
|
const clientStore = options.authStore === void 0 ? null : createAuthStoreClientStore(options.authStore, options.persistenceNamespace);
|
|
4962
4988
|
const now = options.now ?? Date.now;
|
|
@@ -5085,6 +5111,8 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5085
5111
|
}
|
|
5086
5112
|
if (requestedScope !== void 0 && session?.tokens !== void 0 && normalizeOAuthScope(session.tokens.scope ?? session.requestedScope) !== requestedScope)
|
|
5087
5113
|
throw new Error("Stored session does not match the requested OAuth scope; authorize again or select separate persistence");
|
|
5114
|
+
if (configuredTokenMethod !== void 0 && session !== null && (session.tokens !== void 0 || session.refreshState === "pending") && (session.client.tokenEndpointAuthMethod ?? (session.client.clientSecret === void 0 ? "none" : "client_secret_post")) !== configuredTokenMethod)
|
|
5115
|
+
throw new Error("Stored session does not match the requested OAuth token endpoint authentication; select separate persistence or reset it");
|
|
5088
5116
|
if (session?.refreshState === "pending") {
|
|
5089
5117
|
if (!allowInteractive || options.allowInteractive === false || sessionDiscovery === void 0)
|
|
5090
5118
|
throw new Error("OAuth refresh outcome is unknown; authorize again before using this resource");
|
|
@@ -5117,6 +5145,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5117
5145
|
if (session.tokens?.refreshToken === void 0) {
|
|
5118
5146
|
return session;
|
|
5119
5147
|
}
|
|
5148
|
+
assertTokenEndpointAuthentication(session.client, discovery.authorizationServerMetadata);
|
|
5120
5149
|
const pendingSession = { ...clearSessionTokens(session), refreshState: "pending" };
|
|
5121
5150
|
await saveSession(resource, pendingSession);
|
|
5122
5151
|
signal?.throwIfAborted();
|
|
@@ -5128,6 +5157,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5128
5157
|
tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
|
|
5129
5158
|
clientId: session.client.clientId,
|
|
5130
5159
|
clientSecret: session.client.clientSecret,
|
|
5160
|
+
tokenEndpointAuthMethod: session.client.tokenEndpointAuthMethod,
|
|
5131
5161
|
refreshToken: session.tokens.refreshToken,
|
|
5132
5162
|
resource,
|
|
5133
5163
|
fetch: fetch2,
|
|
@@ -5191,6 +5221,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5191
5221
|
let resolvedClient = null;
|
|
5192
5222
|
try {
|
|
5193
5223
|
resolvedClient = await resolveClient(currentSession, discovery, loopback.redirectUri, fetch2, signal);
|
|
5224
|
+
assertTokenEndpointAuthentication(resolvedClient.client, discovery.authorizationServerMetadata);
|
|
5194
5225
|
const sessionWithoutTokens = {
|
|
5195
5226
|
resource,
|
|
5196
5227
|
authorizationServer: discovery.authorizationServer,
|
|
@@ -5214,6 +5245,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5214
5245
|
tokenEndpoint: requireOwnString(discovery.authorizationServerMetadata, "token_endpoint", "Authorization server metadata"),
|
|
5215
5246
|
clientId: resolvedClient.client.clientId,
|
|
5216
5247
|
clientSecret: resolvedClient.client.clientSecret,
|
|
5248
|
+
tokenEndpointAuthMethod: resolvedClient.client.tokenEndpointAuthMethod,
|
|
5217
5249
|
code,
|
|
5218
5250
|
codeVerifier: verifier,
|
|
5219
5251
|
redirectUri: loopback.redirectUri,
|
|
@@ -5300,7 +5332,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5300
5332
|
};
|
|
5301
5333
|
}
|
|
5302
5334
|
}
|
|
5303
|
-
const
|
|
5335
|
+
const supported = getSupportedTokenAuthMethods(discovery.authorizationServerMetadata);
|
|
5336
|
+
const registrationMethod = requestedTokenMethod ?? (supported === void 0 ? "none" : ["none", "client_secret_basic", "client_secret_post"].find((method) => supported.includes(method)));
|
|
5337
|
+
if (registrationMethod === void 0 || supported !== void 0 && !supported.includes(registrationMethod))
|
|
5338
|
+
throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
|
|
5339
|
+
const registrationBody = buildClientRegistrationBody(clientMetadata, redirectUri, registrationMethod);
|
|
5304
5340
|
const deadline = AbortSignal.timeout(3e4);
|
|
5305
5341
|
const signal = parentSignal === void 0 ? deadline : AbortSignal.any([parentSignal, deadline]);
|
|
5306
5342
|
const response = await fetchMcpResponse(fetch2, registrationEndpoint, {
|
|
@@ -5314,9 +5350,11 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5314
5350
|
const payload = await readOAuthJsonObjectResponse(response, signal);
|
|
5315
5351
|
const registration = parseOAuthClientRegistration(payload);
|
|
5316
5352
|
const registeredSecret = getOwnString2(registration, "client_secret");
|
|
5353
|
+
const responseMethod = normalizeOAuthTokenEndpointAuthMethod(getOwnEntry6(registration, "token_endpoint_auth_method")) ?? requestedTokenMethod ?? (supported === void 0 ? void 0 : normalizeOAuthTokenEndpointAuthMethod(registrationMethod));
|
|
5317
5354
|
const registeredClient = {
|
|
5318
5355
|
clientId: registration.client_id.trim(),
|
|
5319
5356
|
...registeredSecret === void 0 ? {} : { clientSecret: registeredSecret.trim() },
|
|
5357
|
+
...responseMethod === void 0 ? {} : { tokenEndpointAuthMethod: responseMethod },
|
|
5320
5358
|
registration
|
|
5321
5359
|
};
|
|
5322
5360
|
await saveRegisteredClient(discovery.authorizationServer, registeredClient);
|
|
@@ -5330,7 +5368,7 @@ function createDefaultOAuthClientProvider(options) {
|
|
|
5330
5368
|
return normalizeLoadedSession(await sessionStore.load(resource));
|
|
5331
5369
|
}
|
|
5332
5370
|
async function saveSession(resource, session) {
|
|
5333
|
-
await sessionStore.save(resource, session);
|
|
5371
|
+
await sessionStore.save(resource, structuredClone(session));
|
|
5334
5372
|
}
|
|
5335
5373
|
async function clearSession(resource) {
|
|
5336
5374
|
await sessionStore.clear(resource);
|
|
@@ -5466,7 +5504,7 @@ function normalizeConfiguredClient(client) {
|
|
|
5466
5504
|
if (clientId === void 0)
|
|
5467
5505
|
return null;
|
|
5468
5506
|
const clientSecret = normalizeOptionalOAuthString(client.clientSecret) ?? (registration === void 0 ? void 0 : getOwnString2(registration, "client_secret")?.trim());
|
|
5469
|
-
return normalizeStoredOAuthClient({ clientId, clientSecret, registration });
|
|
5507
|
+
return normalizeStoredOAuthClient({ clientId, clientSecret, registration, tokenEndpointAuthMethod: client.tokenEndpointAuthMethod });
|
|
5470
5508
|
}
|
|
5471
5509
|
function normalizeOptionalOAuthString(value) {
|
|
5472
5510
|
if (value === void 0) {
|
|
@@ -5566,12 +5604,28 @@ function assertRequestMatchesResource(requestUrl, resource) {
|
|
|
5566
5604
|
throw new Error(`OAuth request URL ${requestUrl} does not match discovered resource ${resource}`);
|
|
5567
5605
|
}
|
|
5568
5606
|
}
|
|
5569
|
-
function
|
|
5607
|
+
function getSupportedTokenAuthMethods(metadata) {
|
|
5608
|
+
const value = getOwnEntry6(metadata, "token_endpoint_auth_methods_supported");
|
|
5609
|
+
if (value === void 0)
|
|
5610
|
+
return void 0;
|
|
5611
|
+
if (!Array.isArray(value) || value.length > 128 || value.some((method) => typeof method !== "string"))
|
|
5612
|
+
throw new Error("Invalid OAuth token endpoint authentication metadata");
|
|
5613
|
+
return value;
|
|
5614
|
+
}
|
|
5615
|
+
function assertTokenEndpointAuthentication(client, metadata) {
|
|
5616
|
+
const method = client.tokenEndpointAuthMethod ?? (client.clientSecret === void 0 ? "none" : "client_secret_post");
|
|
5617
|
+
if (method !== "none" && client.clientSecret === void 0)
|
|
5618
|
+
throw new Error("OAuth token endpoint authentication requires a client secret");
|
|
5619
|
+
const supported = getSupportedTokenAuthMethods(metadata);
|
|
5620
|
+
if (supported !== void 0 && !supported.includes(method))
|
|
5621
|
+
throw new Error("Authorization server does not support the requested OAuth token endpoint authentication");
|
|
5622
|
+
}
|
|
5623
|
+
function buildClientRegistrationBody(metadata, redirectUri, tokenEndpointAuthMethod) {
|
|
5570
5624
|
const body = {
|
|
5571
5625
|
redirect_uris: [redirectUri],
|
|
5572
5626
|
grant_types: ["authorization_code", "refresh_token"],
|
|
5573
5627
|
response_types: ["code"],
|
|
5574
|
-
token_endpoint_auth_method:
|
|
5628
|
+
token_endpoint_auth_method: tokenEndpointAuthMethod
|
|
5575
5629
|
};
|
|
5576
5630
|
const clientName = metadata === void 0 ? void 0 : getOwnString2(metadata, "clientName");
|
|
5577
5631
|
const scope = metadata === void 0 ? void 0 : getOwnString2(metadata, "scope");
|