@ni-c/mcp-hub 0.9.2 → 0.11.0
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/CHANGELOG.md +619 -0
- package/README.md +189 -64
- package/dist/admin.js +232 -8
- package/dist/admin.js.map +1 -1
- package/dist/auth/address.js +89 -0
- package/dist/auth/address.js.map +1 -0
- package/dist/auth/api-tokens.js +27 -0
- package/dist/auth/api-tokens.js.map +1 -0
- package/dist/auth/cimd.js +367 -0
- package/dist/auth/cimd.js.map +1 -0
- package/dist/auth/consent-page.js +4 -7
- package/dist/auth/consent-page.js.map +1 -1
- package/dist/auth/headers.js +22 -1
- package/dist/auth/headers.js.map +1 -1
- package/dist/auth/login-page.js +4 -7
- package/dist/auth/login-page.js.map +1 -1
- package/dist/auth/oidc/adapter.js +135 -0
- package/dist/auth/oidc/adapter.js.map +1 -0
- package/dist/auth/oidc/interactions.js +187 -0
- package/dist/auth/oidc/interactions.js.map +1 -0
- package/dist/auth/oidc/mount.js +144 -0
- package/dist/auth/oidc/mount.js.map +1 -0
- package/dist/auth/oidc/provider.js +440 -0
- package/dist/auth/oidc/provider.js.map +1 -0
- package/dist/auth/oidc/quirks.js +234 -0
- package/dist/auth/oidc/quirks.js.map +1 -0
- package/dist/auth/oidc/verifier.js +121 -0
- package/dist/auth/oidc/verifier.js.map +1 -0
- package/dist/auth/page.js +22 -0
- package/dist/auth/page.js.map +1 -1
- package/dist/auth/pinned-fetch.js +129 -0
- package/dist/auth/pinned-fetch.js.map +1 -0
- package/dist/auth/protected-resource.js +41 -0
- package/dist/auth/protected-resource.js.map +1 -0
- package/dist/auth/rate-limit.js +156 -0
- package/dist/auth/rate-limit.js.map +1 -0
- package/dist/auth/redirect-uri.js +90 -0
- package/dist/auth/redirect-uri.js.map +1 -0
- package/dist/auth/registration.js +146 -0
- package/dist/auth/registration.js.map +1 -0
- package/dist/auth/session.js +43 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/auth/signed-token.js +49 -0
- package/dist/auth/signed-token.js.map +1 -0
- package/dist/auth/store.js +516 -5
- package/dist/auth/store.js.map +1 -1
- package/dist/auth/text.js +51 -0
- package/dist/auth/text.js.map +1 -0
- package/dist/config.js +184 -5
- package/dist/config.js.map +1 -1
- package/dist/docker-proxy/policy.js +1 -0
- package/dist/docker-proxy/policy.js.map +1 -1
- package/dist/docker-proxy/server.js +1 -0
- package/dist/docker-proxy/server.js.map +1 -1
- package/dist/elicitation.js +0 -0
- package/dist/elicitation.js.map +1 -0
- package/dist/forward.js +0 -0
- package/dist/forward.js.map +1 -0
- package/dist/health.js +22 -3
- package/dist/health.js.map +1 -1
- package/dist/hub.js +337 -29
- package/dist/hub.js.map +1 -1
- package/dist/index.js +218 -23
- package/dist/index.js.map +1 -1
- package/dist/limits.js +13 -1
- package/dist/limits.js.map +1 -1
- package/dist/mcp-limits.js +14 -2
- package/dist/mcp-limits.js.map +1 -1
- package/dist/proxy.js +262 -34
- package/dist/proxy.js.map +1 -1
- package/dist/stdio.js +97 -7
- package/dist/stdio.js.map +1 -1
- package/dist/subscriptions.js +236 -0
- package/dist/subscriptions.js.map +1 -0
- package/dist/supervisor.js +472 -28
- package/dist/supervisor.js.map +1 -1
- package/dist/timings.js +61 -0
- package/dist/timings.js.map +1 -0
- package/dist/tool-filter.js +59 -0
- package/dist/tool-filter.js.map +1 -0
- package/dist/transports/docker.js.map +1 -1
- package/dist/transports/stream.js +33 -20
- package/dist/transports/stream.js.map +1 -1
- package/dist/upstream/auth.js +435 -0
- package/dist/upstream/auth.js.map +1 -0
- package/dist/upstream/login.js +94 -0
- package/dist/upstream/login.js.map +1 -0
- package/dist/upstream/provider.js +286 -0
- package/dist/upstream/provider.js.map +1 -0
- package/dist/upstream/routes.js +97 -0
- package/dist/upstream/routes.js.map +1 -0
- package/package.json +23 -7
- package/dist/auth/provider.js +0 -380
- package/dist/auth/provider.js.map +0 -1
- package/dist/auth/routes.js +0 -223
- package/dist/auth/routes.js.map +0 -1
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import Provider, { errors } from 'oidc-provider';
|
|
2
|
+
import { isSafeRedirectUri, redirectUriMatches } from '../redirect-uri.js';
|
|
3
|
+
import { clampDisplayName } from '../text.js';
|
|
4
|
+
import { createOidcAdapter } from './adapter.js';
|
|
5
|
+
import { HUB_SCOPE, installDiscoveryFixups, installThrowawaySecret } from './quirks.js';
|
|
6
|
+
/**
|
|
7
|
+
* The grant types this server has. `clientDefaults` below and the discovery
|
|
8
|
+
* document say the same thing; this is the list a client's own declaration is
|
|
9
|
+
* trimmed against, so it lives where both can be checked against it.
|
|
10
|
+
*/
|
|
11
|
+
const SUPPORTED_GRANT_TYPES = ['authorization_code', 'refresh_token'];
|
|
12
|
+
/** Pinned by test/e2e.test.ts — a client that caches `expires_in` must not be
|
|
13
|
+
* handed a different number by the new authorization server. */
|
|
14
|
+
const ACCESS_TOKEN_TTL_S = 15 * 60;
|
|
15
|
+
const REFRESH_TOKEN_TTL_S = 30 * 24 * 3600;
|
|
16
|
+
const CODE_TTL_S = 10 * 60;
|
|
17
|
+
const SESSION_TTL_S = 30 * 60;
|
|
18
|
+
/** RFC 7523 §3 recommends a short window; the hub has always required five
|
|
19
|
+
* minutes, and a captured assertion is only useful inside it. */
|
|
20
|
+
const MAX_ASSERTION_LIFETIME_S = 5 * 60;
|
|
21
|
+
/** The single pseudo-account. The hub authenticates one operator with one
|
|
22
|
+
* password; there are no user accounts to tell apart. */
|
|
23
|
+
export const HUB_ACCOUNT_ID = 'mcp-hub-user';
|
|
24
|
+
/**
|
|
25
|
+
* The hub's authorization server.
|
|
26
|
+
*
|
|
27
|
+
* Routes are reconfigured onto mcp-hub's existing paths rather than mounted
|
|
28
|
+
* under a prefix: `#mountPath` is derived from the issuer (provider.js), and the
|
|
29
|
+
* hub's issuer is the origin root, so a prefixed mount would publish URLs the
|
|
30
|
+
* router does not answer on. See `mount.ts`.
|
|
31
|
+
*/
|
|
32
|
+
export function buildOidcProvider(store, options) {
|
|
33
|
+
// NOT `.origin`. The hub publishes EXTERNAL_URL in `URL.href` form -- with a
|
|
34
|
+
// trailing slash -- in `iss`, `aud`, the AS metadata issuer and the PRM
|
|
35
|
+
// document, because claude.ai compares them byte for byte. oidc-provider
|
|
36
|
+
// accepts it: `mountPath` becomes '/' and pathFor() still yields '/token'.
|
|
37
|
+
const issuer = new URL(options.externalUrl).href;
|
|
38
|
+
/** Undefined for anything the hub would not serve; the caller turns that into
|
|
39
|
+
* `invalid_target` rather than minting an unusable token. */
|
|
40
|
+
const resolve = (indicator) => {
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
parsed = new URL(indicator);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return options.resolveResource ? options.resolveResource(parsed) : parsed;
|
|
49
|
+
};
|
|
50
|
+
const configuration = {
|
|
51
|
+
adapter: createOidcAdapter(store, options.cimd),
|
|
52
|
+
// mcp-hub's paths, not oidc-provider's defaults. Every one of these is
|
|
53
|
+
// already in RESERVED_NAMES, so no MCP server can collide with them.
|
|
54
|
+
routes: {
|
|
55
|
+
authorization: '/authorize',
|
|
56
|
+
token: '/token',
|
|
57
|
+
revocation: '/revoke',
|
|
58
|
+
registration: '/register',
|
|
59
|
+
jwks: '/jwks',
|
|
60
|
+
end_session: '/session/end',
|
|
61
|
+
userinfo: '/userinfo'
|
|
62
|
+
},
|
|
63
|
+
// --- OAuth 2.1, no OIDC ------------------------------------------------
|
|
64
|
+
// `openid` cannot be removed from scopes_supported and the id_token alg
|
|
65
|
+
// list cannot be removed from the discovery document — configuration.js
|
|
66
|
+
// adds the first unconditionally and discovery.js the second. Documented
|
|
67
|
+
// rather than fought: nothing is issued for either.
|
|
68
|
+
responseTypes: ['code'],
|
|
69
|
+
scopes: [],
|
|
70
|
+
claims: {},
|
|
71
|
+
features: {
|
|
72
|
+
registration: { enabled: options.allowDynamicRegistration !== false },
|
|
73
|
+
// Enabled so a registration_access_token and registration_client_uri are
|
|
74
|
+
// issued at all -- but the hub serves GET/PUT/DELETE itself, mounted
|
|
75
|
+
// ahead of this. oidc-provider's version does not withdraw an approval
|
|
76
|
+
// when the redirect URIs move, does not hold an update to the same
|
|
77
|
+
// redirect-URI policy as the registration, and does not clamp a client
|
|
78
|
+
// name that arrives with newlines in it.
|
|
79
|
+
registrationManagement: { enabled: options.allowDynamicRegistration !== false, rotateRegistrationAccessToken: false },
|
|
80
|
+
revocation: { enabled: true },
|
|
81
|
+
userinfo: { enabled: false },
|
|
82
|
+
// Ships hardcoded /interaction/:uid routes that accept ANY password.
|
|
83
|
+
devInteractions: { enabled: false },
|
|
84
|
+
// Both are on by default and both would be ADVERTISED in the discovery
|
|
85
|
+
// document while their endpoints are not mounted -- a client that
|
|
86
|
+
// believed the document would get a 404. Neither is wanted: the hub has
|
|
87
|
+
// no browser session to end, and PAR solves a problem it does not have.
|
|
88
|
+
pushedAuthorizationRequests: { enabled: false },
|
|
89
|
+
rpInitiatedLogout: { enabled: false },
|
|
90
|
+
/**
|
|
91
|
+
* Enabled for what it advertises and for the client_id URL check — NOT
|
|
92
|
+
* for its fetcher. The adapter resolves metadata documents through the
|
|
93
|
+
* hub's own CimdResolver, which models/client.js consults first, and
|
|
94
|
+
* `allowFetch` refuses so the built-in path can never run.
|
|
95
|
+
*
|
|
96
|
+
* That is a deliberate choice, not an accident of ordering. The library
|
|
97
|
+
* caps the body at 5 KiB, times out at 2.5 s and refuses redirects; it
|
|
98
|
+
* does NOT pin the resolved address against DNS rebinding, enforce an
|
|
99
|
+
* origin allowlist, or rate-limit per origin. The hub does all three.
|
|
100
|
+
*/
|
|
101
|
+
clientIdMetadataDocument: {
|
|
102
|
+
enabled: options.cimd !== undefined,
|
|
103
|
+
ack: 'draft-02',
|
|
104
|
+
allowFetch: () => false
|
|
105
|
+
},
|
|
106
|
+
resourceIndicators: {
|
|
107
|
+
enabled: true,
|
|
108
|
+
useGrantedResource: () => true,
|
|
109
|
+
/**
|
|
110
|
+
* Real clients omit the RFC 8707 parameter (older Codex logins, Google
|
|
111
|
+
* ADK, Gemini Enterprise), so "none requested" has to mean something.
|
|
112
|
+
*
|
|
113
|
+
* - DEFAULT_RESOURCE, when the operator set one: the token is still
|
|
114
|
+
* bound, just not by the client's choosing.
|
|
115
|
+
* - the issuer itself in unbound mode, which is the pre-0.5 migration
|
|
116
|
+
* behaviour the hand-written server had — one token reaches every
|
|
117
|
+
* route, and the per-route check is what narrows it.
|
|
118
|
+
* - a refusal when binding is required, matching the old
|
|
119
|
+
* `invalid_target` rather than letting the flow die later as
|
|
120
|
+
* `access_denied`, which says nothing the client can act on.
|
|
121
|
+
*/
|
|
122
|
+
defaultResource: () => {
|
|
123
|
+
if (options.defaultResource)
|
|
124
|
+
return options.defaultResource.href;
|
|
125
|
+
if (!options.requireResource)
|
|
126
|
+
return options.externalUrl;
|
|
127
|
+
throw new errors.InvalidTarget('A resource indicator is required');
|
|
128
|
+
},
|
|
129
|
+
getResourceServerInfo: (_ctx, resourceIndicator) => {
|
|
130
|
+
// The issuer itself is the unbound audience; it is not an MCP route,
|
|
131
|
+
// so the canonicaliser would refuse it.
|
|
132
|
+
if (resourceIndicator === options.externalUrl) {
|
|
133
|
+
return { audience: resourceIndicator, accessTokenTTL: ACCESS_TOKEN_TTL_S, scope: HUB_SCOPE };
|
|
134
|
+
}
|
|
135
|
+
// Same canonicalisation the resource server applies (/hub/mcp -> /hub,
|
|
136
|
+
// /<name> -> /<name>/mcp). Minting a token for an audience the hub
|
|
137
|
+
// would not accept produces a 401 the client cannot act on.
|
|
138
|
+
const canonical = resolve(resourceIndicator);
|
|
139
|
+
if (!canonical)
|
|
140
|
+
throw new errors.InvalidTarget('unknown resource');
|
|
141
|
+
return {
|
|
142
|
+
audience: canonical.href,
|
|
143
|
+
accessTokenTTL: ACCESS_TOKEN_TTL_S,
|
|
144
|
+
scope: HUB_SCOPE
|
|
145
|
+
// No `accessTokenFormat` -> opaque. Deliberate: oidc-provider never
|
|
146
|
+
// persists a JWT access token (formats/jwt.js returns no payload),
|
|
147
|
+
// so with JWTs `revokedBefore` could only be an expiry window.
|
|
148
|
+
// Opaque tokens go through the adapter, where revocation is immediate.
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
/**
|
|
154
|
+
* Exactly the three the hub advertised before, and no more. Dropping
|
|
155
|
+
* `client_secret_basic` and `client_secret_jwt` from oidc-provider's
|
|
156
|
+
* defaults is a deliberate narrowing: nothing the hub issues can use them,
|
|
157
|
+
* and an authentication mechanism nobody uses is only an attack surface.
|
|
158
|
+
*/
|
|
159
|
+
clientAuthMethods: ['client_secret_post', 'none', 'private_key_jwt'],
|
|
160
|
+
// The set the hub advertised before. oidc-provider's default is narrower
|
|
161
|
+
// (one algorithm per family), which would refuse a private_key_jwt client
|
|
162
|
+
// that signs with RS384 -- something the old document promised to accept.
|
|
163
|
+
enabledJWA: {
|
|
164
|
+
clientAuthSigningAlgValues: ['RS256', 'RS384', 'RS512', 'PS256', 'PS384', 'PS512', 'ES256', 'ES384', 'ES512', 'EdDSA']
|
|
165
|
+
},
|
|
166
|
+
// --- Quirk 3: refresh tokens without offline_access ---------------------
|
|
167
|
+
issueRefreshToken: async (_ctx, client) => client.grantTypeAllowed('refresh_token'),
|
|
168
|
+
// Not optional alongside the above: the default marks every code issued
|
|
169
|
+
// without `offline_access` as session-bound, and a session-bound token
|
|
170
|
+
// stops resolving once the browser session is gone. The hub's clients are
|
|
171
|
+
// headless.
|
|
172
|
+
expiresWithSession: async () => false,
|
|
173
|
+
// Forces refresh_token into grant_types for clients that sent their own
|
|
174
|
+
// list. Runs between #assign and #initialize, must be synchronous, and
|
|
175
|
+
// needs at least one declared property or it is never called.
|
|
176
|
+
extraClientMetadata: {
|
|
177
|
+
properties: ['x_mcp_hub'],
|
|
178
|
+
validator(_ctx, key, _value, metadata) {
|
|
179
|
+
if (key !== 'x_mcp_hub')
|
|
180
|
+
return;
|
|
181
|
+
/**
|
|
182
|
+
* Two adjustments, and the second one is why claude.ai could not
|
|
183
|
+
* connect to 0.11.0 at all.
|
|
184
|
+
*
|
|
185
|
+
* A client may declare grant types this server does not offer, and
|
|
186
|
+
* oidc-provider refuses the whole registration when it sees one —
|
|
187
|
+
* `invalid_client_metadata: grant_types can only contain
|
|
188
|
+
* 'authorization_code' or 'refresh_token'`. claude.ai's metadata
|
|
189
|
+
* document declares `urn:ietf:params:oauth:grant-type:jwt-bearer`
|
|
190
|
+
* alongside the two it actually uses here, so every attempt died on a
|
|
191
|
+
* grant it was never going to ask for. The hand-written server this
|
|
192
|
+
* replaced ignored the extras, which is why the failure arrived with
|
|
193
|
+
* the rewrite rather than with the client.
|
|
194
|
+
*
|
|
195
|
+
* Dropping them is the right answer rather than a lenient one: what a
|
|
196
|
+
* client may do is decided by what the authorization server supports,
|
|
197
|
+
* the registration response says which types were actually registered
|
|
198
|
+
* (RFC 7591 §3.2.1), and a client reading that response learns the
|
|
199
|
+
* truth. Rejecting instead makes an optimistic superset — the sensible
|
|
200
|
+
* thing for a client that talks to many servers — fatal.
|
|
201
|
+
*/
|
|
202
|
+
const declared = metadata.grant_types ?? ['authorization_code'];
|
|
203
|
+
const grants = declared.filter(grant => SUPPORTED_GRANT_TYPES.includes(grant));
|
|
204
|
+
// Everything it asked for is something this server has never heard of.
|
|
205
|
+
// That is not a superset to trim, it is a client aimed at the wrong
|
|
206
|
+
// kind of server, and saying so beats registering it with no way to
|
|
207
|
+
// get a token.
|
|
208
|
+
if (grants.length === 0) {
|
|
209
|
+
throw new errors.InvalidClientMetadata(`grant_types must include one of: ${SUPPORTED_GRANT_TYPES.join(', ')}`);
|
|
210
|
+
}
|
|
211
|
+
if (!grants.includes('refresh_token'))
|
|
212
|
+
grants.push('refresh_token');
|
|
213
|
+
metadata.grant_types = grants;
|
|
214
|
+
/**
|
|
215
|
+
* An empty `scope` is not a narrow scope, it is a malformed one: RFC
|
|
216
|
+
* 6749 §3.3 defines the value as at least one scope-token, so `''` is
|
|
217
|
+
* outside the grammar and client_schema.js refuses the entire
|
|
218
|
+
* registration with `scope must be a non-empty string if provided`.
|
|
219
|
+
*
|
|
220
|
+
* The MCP Inspector sends exactly that. Its scope input is optional,
|
|
221
|
+
* an untouched field serialises as `''` rather than being left out,
|
|
222
|
+
* and the SDK copies `clientMetadata` into the registration body
|
|
223
|
+
* verbatim — so an empty text box, which is the default state, is
|
|
224
|
+
* enough to make dynamic registration impossible. Any client built on
|
|
225
|
+
* `@modelcontextprotocol/client` that carries a scope field it does
|
|
226
|
+
* not fill has the same shape, and none of them can tell from the
|
|
227
|
+
* error that the field they never touched is the problem.
|
|
228
|
+
*
|
|
229
|
+
* Dropping it is the substitution RFC 7591 §3.2.1 provides for ("MAY
|
|
230
|
+
* reject or replace any of the client's requested metadata values"),
|
|
231
|
+
* and it lands the client exactly where omitting the optional field
|
|
232
|
+
* would have. Nothing is widened by that: `scope` is a CEILING on what
|
|
233
|
+
* a client may ask for, the hub authorizes a client and binds the
|
|
234
|
+
* token to a resource, and neither decision reads this field. A scope
|
|
235
|
+
* that says something is left untouched, and `scopes()` still refuses
|
|
236
|
+
* one this server does not know.
|
|
237
|
+
*/
|
|
238
|
+
if (metadata.scope === '')
|
|
239
|
+
delete metadata.scope;
|
|
240
|
+
/**
|
|
241
|
+
* The hub's redirect-URI policy, which oidc-provider does not have:
|
|
242
|
+
* it keeps out javascript:/data:/vbscript: but accepts plain http to
|
|
243
|
+
* any host. That last one matters — the authorization code travels in
|
|
244
|
+
* the clear on the final redirect, and a public client has nothing
|
|
245
|
+
* else to prove itself with.
|
|
246
|
+
*
|
|
247
|
+
* A metadata-document client is held to the stricter half: the MCP
|
|
248
|
+
* specification allows it only https or loopback, never a private-use
|
|
249
|
+
* scheme.
|
|
250
|
+
*/
|
|
251
|
+
/**
|
|
252
|
+
* A client name goes straight onto the consent page and into the log.
|
|
253
|
+
* The hub clamps it -- newlines out, length capped -- and oidc-provider
|
|
254
|
+
* does not, so an application could otherwise choose a "name" that took
|
|
255
|
+
* over the page it is being approved on.
|
|
256
|
+
*/
|
|
257
|
+
if (metadata.client_name !== undefined) {
|
|
258
|
+
metadata.client_name = clampDisplayName(metadata.client_name);
|
|
259
|
+
}
|
|
260
|
+
// A private-use scheme (com.example.app:/cb) is only valid for a native
|
|
261
|
+
// client as far as oidc-provider is concerned, and native is what such
|
|
262
|
+
// a client actually is. Without this the registration is refused and
|
|
263
|
+
// RFC 8252 clients have no way in.
|
|
264
|
+
const uris = metadata.redirect_uris ?? [];
|
|
265
|
+
if (uris.some(uri => !/^https?:/i.test(uri)))
|
|
266
|
+
metadata.application_type = 'native';
|
|
267
|
+
const allowPrivateUseSchemes = !String(metadata.client_id ?? '').startsWith('https://');
|
|
268
|
+
for (const uri of uris) {
|
|
269
|
+
if (!isSafeRedirectUri(uri, { allowPrivateUseSchemes })) {
|
|
270
|
+
throw new errors.InvalidClientMetadata('redirect_uris must be https, loopback http, or a private-use scheme');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
clientDefaults: {
|
|
276
|
+
grant_types: [...SUPPORTED_GRANT_TYPES],
|
|
277
|
+
token_endpoint_auth_method: 'none',
|
|
278
|
+
// Not cosmetic: the hub's key is Ed25519, so RS256 — which is what
|
|
279
|
+
// oidc-provider defaults a new client to — is not an algorithm this
|
|
280
|
+
// server has a key for, and every registration is refused with
|
|
281
|
+
// "id_token_signed_response_alg must be 'EdDSA' or 'Ed25519'". No
|
|
282
|
+
// id_token is ever issued; the client still has to declare an algorithm
|
|
283
|
+
// the server could sign one with.
|
|
284
|
+
id_token_signed_response_alg: 'EdDSA'
|
|
285
|
+
},
|
|
286
|
+
/**
|
|
287
|
+
* The hub approves a CLIENT, not a set of scopes, so the grant is minted
|
|
288
|
+
* here rather than assembled from a consent screen's checkboxes. This is
|
|
289
|
+
* also what makes an already-approved client skip consent, which is what
|
|
290
|
+
* AuthStore.getApproval() records.
|
|
291
|
+
*/
|
|
292
|
+
loadExistingGrant: async (ctx) => {
|
|
293
|
+
const clientId = ctx.oidc.client?.clientId;
|
|
294
|
+
if (!clientId)
|
|
295
|
+
return undefined;
|
|
296
|
+
/**
|
|
297
|
+
* The approval is checked FIRST, before any grant already attached to the
|
|
298
|
+
* session, because approval is per redirect TARGET and not per client.
|
|
299
|
+
* Returning the session's existing grant straight away would mean that
|
|
300
|
+
* approving a client for `http://localhost:1234/callback` silently
|
|
301
|
+
* approved it for `/other` as well — a code sent somewhere the user never
|
|
302
|
+
* agreed to.
|
|
303
|
+
*
|
|
304
|
+
* Minting only for an approved client is also what keeps the consent page
|
|
305
|
+
* reachable at all: approving unconditionally would satisfy the prompt
|
|
306
|
+
* every time, and the page that asks "did you start this?" would never be
|
|
307
|
+
* shown again.
|
|
308
|
+
*/
|
|
309
|
+
const redirectUri = String(ctx.oidc.params?.redirect_uri ?? '');
|
|
310
|
+
const approval = store.getApproval(clientId);
|
|
311
|
+
const approved = approval?.redirectUris.some(uri => redirectUriMatches(redirectUri, uri)) ?? false;
|
|
312
|
+
if (!approved && !ctx.oidc.result?.consent)
|
|
313
|
+
return undefined;
|
|
314
|
+
const existing = ctx.oidc.result?.consent?.grantId ?? ctx.oidc.session?.grantIdFor(clientId);
|
|
315
|
+
const grant = existing
|
|
316
|
+
? ((await ctx.oidc.provider.Grant.find(existing)) ??
|
|
317
|
+
new ctx.oidc.provider.Grant({ clientId, accountId: ctx.oidc.session?.accountId }))
|
|
318
|
+
: new ctx.oidc.provider.Grant({ clientId, accountId: ctx.oidc.session?.accountId });
|
|
319
|
+
/**
|
|
320
|
+
* Whatever scope the request asked for has to end up ON the grant, even
|
|
321
|
+
* though the hub issues nothing for it.
|
|
322
|
+
*
|
|
323
|
+
* The hub advertises `scopes_supported: ["openid"]` and cannot stop:
|
|
324
|
+
* oidc-provider's configuration.js adds it unconditionally. A client that
|
|
325
|
+
* reads the discovery document therefore asks for `openid` — claude.ai
|
|
326
|
+
* does — and a requested scope that the grant does not carry leaves the
|
|
327
|
+
* consent prompt unsatisfied. oidc-provider then sends the browser back
|
|
328
|
+
* to the interaction, the consent page is rendered again, approving it
|
|
329
|
+
* finishes the interaction, the resume finds the same missing scope, and
|
|
330
|
+
* round it goes. What that looks like from the outside is an approve
|
|
331
|
+
* button that reloads the page and does nothing, for ever.
|
|
332
|
+
*
|
|
333
|
+
* Granting it is bookkeeping and nothing more: no id_token is ever
|
|
334
|
+
* issued and `features.userinfo` is off, so `openid` on the grant buys
|
|
335
|
+
* the client no claim it could read. The scope set has already been
|
|
336
|
+
* validated against `scopes_supported` by the time this runs, so there is
|
|
337
|
+
* nothing here to widen.
|
|
338
|
+
*
|
|
339
|
+
* This is also why the grant is now *amended* rather than returned as
|
|
340
|
+
* found: a session that already holds a grant from an earlier flow would
|
|
341
|
+
* otherwise keep whatever scopes it was minted with, and a client that
|
|
342
|
+
* later asks for one more would loop the same way with no way out but
|
|
343
|
+
* clearing cookies.
|
|
344
|
+
*/
|
|
345
|
+
const scopes = String(ctx.oidc.params?.scope ?? '')
|
|
346
|
+
.split(' ')
|
|
347
|
+
.filter(Boolean);
|
|
348
|
+
if (scopes.length > 0)
|
|
349
|
+
grant.addOIDCScope(scopes.join(' '));
|
|
350
|
+
const requested = ctx.oidc.params?.resource;
|
|
351
|
+
for (const resource of [requested ?? []].flat()) {
|
|
352
|
+
grant.addResourceScope(String(resource), HUB_SCOPE);
|
|
353
|
+
}
|
|
354
|
+
await grant.save();
|
|
355
|
+
return grant;
|
|
356
|
+
},
|
|
357
|
+
findAccount: (_ctx, sub) => ({ accountId: sub, claims: async () => ({ sub }) }),
|
|
358
|
+
/**
|
|
359
|
+
* Caps how long a client assertion may live.
|
|
360
|
+
*
|
|
361
|
+
* oidc-provider only checks that it has not expired, so a client could sign
|
|
362
|
+
* one valid for a year and anything that captured it would hold a
|
|
363
|
+
* reusable credential for that long. The hub has always required five
|
|
364
|
+
* minutes, which is what RFC 7523 §3 recommends and what keeps a leaked
|
|
365
|
+
* assertion close to worthless.
|
|
366
|
+
*/
|
|
367
|
+
assertJwtClientAuthClaimsAndHeader: async (_ctx, claims) => {
|
|
368
|
+
const lifetime = Number(claims.exp) - Number(claims.iat ?? claims.exp);
|
|
369
|
+
if (!Number.isFinite(lifetime) || lifetime > MAX_ASSERTION_LIFETIME_S) {
|
|
370
|
+
throw new errors.InvalidClientAuth('assertion is valid for too long');
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
/**
|
|
374
|
+
* JSON, not the library's HTML page.
|
|
375
|
+
*
|
|
376
|
+
* This is reached when an authorization request cannot be redirected back
|
|
377
|
+
* -- an unknown client, a redirect_uri that does not match -- which is
|
|
378
|
+
* precisely when the caller is a program rather than a person. The hub has
|
|
379
|
+
* always answered those with a machine-readable body, and a connector that
|
|
380
|
+
* gets HTML has nothing to log but a wall of markup.
|
|
381
|
+
*/
|
|
382
|
+
renderError: (ctx, out) => {
|
|
383
|
+
ctx.type = 'json';
|
|
384
|
+
ctx.body = out;
|
|
385
|
+
},
|
|
386
|
+
interactions: {
|
|
387
|
+
url: (_ctx, interaction) => `${options.interactionPath ?? '/interaction'}/${interaction.uid}/`
|
|
388
|
+
},
|
|
389
|
+
// The hub's own signing key. Supplying it keeps oidc-provider from falling
|
|
390
|
+
// back to the development keys it warns about; with opaque access tokens
|
|
391
|
+
// nothing user-facing is signed with it.
|
|
392
|
+
jwks: { keys: [store.privateKey.export({ format: 'jwk' })] },
|
|
393
|
+
cookies: { keys: [store.cookieSecret] },
|
|
394
|
+
ttl: {
|
|
395
|
+
AccessToken: ACCESS_TOKEN_TTL_S,
|
|
396
|
+
RefreshToken: REFRESH_TOKEN_TTL_S,
|
|
397
|
+
AuthorizationCode: CODE_TTL_S,
|
|
398
|
+
Session: SESSION_TTL_S,
|
|
399
|
+
Interaction: CODE_TTL_S,
|
|
400
|
+
Grant: REFRESH_TOKEN_TTL_S
|
|
401
|
+
},
|
|
402
|
+
/**
|
|
403
|
+
* Every outbound request oidc-provider makes goes through the hub's pinned
|
|
404
|
+
* client. With the metadata-document path closed above, that leaves
|
|
405
|
+
* `jwks_uri` for private_key_jwt clients — and that one needs it most:
|
|
406
|
+
* unlike its own CIMD fetch, oidc-provider requests jwks_uri with neither
|
|
407
|
+
* `redirect: 'manual'` nor a body cap (the default limit is Infinity).
|
|
408
|
+
* `safeFetch` refuses non-https, pins the resolved public address so a
|
|
409
|
+
* second DNS answer cannot redirect it inward, and caps the body.
|
|
410
|
+
*/
|
|
411
|
+
...(options.cimd ? { fetch: options.cimd.safeFetch } : {}),
|
|
412
|
+
// Defence in depth behind `safeFetch`: two of the three defaults are
|
|
413
|
+
// Infinity, and an unbounded fetch is not something to discover later.
|
|
414
|
+
fetchResponseBodyLimits: {
|
|
415
|
+
'client_id metadata document': 5 * 1024,
|
|
416
|
+
jwks_uri: 64 * 1024,
|
|
417
|
+
sector_identifier_uri: 8 * 1024
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
const provider = new Provider(issuer, configuration);
|
|
421
|
+
/**
|
|
422
|
+
* RFC 8252 §7.3: a native client listens on an ephemeral loopback port and
|
|
423
|
+
* cannot know it in advance, so `http://127.0.0.1:1234/cb` and
|
|
424
|
+
* `http://127.0.0.1:51234/cb` are the same redirect target. oidc-provider
|
|
425
|
+
* compares exactly and would refuse every run after the first.
|
|
426
|
+
*
|
|
427
|
+
* Uses the same matcher the approval check does, so "approved for this
|
|
428
|
+
* target" and "allowed to redirect there" cannot drift apart.
|
|
429
|
+
*/
|
|
430
|
+
const exactlyAllowed = provider.Client.prototype.redirectUriAllowed;
|
|
431
|
+
provider.Client.prototype.redirectUriAllowed = function redirectUriAllowed(value) {
|
|
432
|
+
if (exactlyAllowed.call(this, value))
|
|
433
|
+
return true;
|
|
434
|
+
return (this.redirectUris ?? []).some(uri => redirectUriMatches(value, uri));
|
|
435
|
+
};
|
|
436
|
+
installThrowawaySecret(provider, store);
|
|
437
|
+
installDiscoveryFixups(provider);
|
|
438
|
+
return provider;
|
|
439
|
+
}
|
|
440
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../src/auth/oidc/provider.ts"],"names":[],"mappings":"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"}
|