@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
package/dist/auth/provider.js
DELETED
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import { SignJWT, jwtVerify } from 'jose';
|
|
3
|
-
import { InvalidGrantError, InvalidTargetError, InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
|
|
4
|
-
import { redirectUriMatches } from '@modelcontextprotocol/sdk/server/auth/handlers/authorize.js';
|
|
5
|
-
import { renderLoginPage } from './login-page.js';
|
|
6
|
-
import { renderConsentPage } from './consent-page.js';
|
|
7
|
-
import { allowFormActionTo } from './headers.js';
|
|
8
|
-
const ACCESS_TOKEN_TTL_S = 15 * 60;
|
|
9
|
-
const REFRESH_TOKEN_TTL_S = 30 * 24 * 3600;
|
|
10
|
-
const CODE_TTL_MS = 10 * 60_000;
|
|
11
|
-
export const SESSION_TTL_MS = 30 * 60_000;
|
|
12
|
-
export const SESSION_COOKIE = 'mcp_hub_session';
|
|
13
|
-
/**
|
|
14
|
-
* The `__Host-` prefix pins the cookie to this exact origin, so no neighbouring
|
|
15
|
-
* subdomain can plant one. It requires Secure, which rules it out over plain
|
|
16
|
-
* http (local development and the test suite).
|
|
17
|
-
*/
|
|
18
|
-
export function sessionCookieName(externalUrl) {
|
|
19
|
-
return new URL(externalUrl).protocol === 'https:' ? `__Host-${SESSION_COOKIE}` : SESSION_COOKIE;
|
|
20
|
-
}
|
|
21
|
-
function sign(value, secret) {
|
|
22
|
-
return crypto.createHmac('sha256', secret).update(value).digest('base64url');
|
|
23
|
-
}
|
|
24
|
-
/** Distinguishes admin-minted API tokens from interactive OAuth tokens. */
|
|
25
|
-
export const API_TOKEN_SUBJECT = 'mcp-hub-token';
|
|
26
|
-
/**
|
|
27
|
-
* Mint a long-lived, resource-bound API token for clients that cannot do
|
|
28
|
-
* OAuth (OpenAI Responses API, xAI API, Gemini API, plain-header clients).
|
|
29
|
-
* The JWT is returned exactly once; only its record (jti) is persisted, which
|
|
30
|
-
* is what `tokens list` shows and `tokens revoke` deletes.
|
|
31
|
-
*/
|
|
32
|
-
export async function mintApiToken(store, externalUrl, resource, days, label) {
|
|
33
|
-
const id = crypto.randomBytes(8).toString('base64url');
|
|
34
|
-
const now = Math.floor(Date.now() / 1000);
|
|
35
|
-
const expiresAt = now + days * 86_400;
|
|
36
|
-
const token = await new SignJWT({})
|
|
37
|
-
.setProtectedHeader({ alg: 'EdDSA' })
|
|
38
|
-
.setIssuer(new URL(externalUrl).href)
|
|
39
|
-
.setAudience(resource.href)
|
|
40
|
-
.setSubject(API_TOKEN_SUBJECT)
|
|
41
|
-
.setIssuedAt(now)
|
|
42
|
-
.setExpirationTime(expiresAt)
|
|
43
|
-
.setJti(id)
|
|
44
|
-
.sign(store.privateKey);
|
|
45
|
-
store.saveApiToken(id, { label, resource: resource.href, createdAt: now, expiresAt });
|
|
46
|
-
return { id, token, expiresAt };
|
|
47
|
-
}
|
|
48
|
-
export class HubOAuthProvider {
|
|
49
|
-
store;
|
|
50
|
-
externalUrl;
|
|
51
|
-
options;
|
|
52
|
-
/** Short-lived authorization codes; in-memory on purpose (10 min lifetime). */
|
|
53
|
-
codes = new Map();
|
|
54
|
-
sessionCookieName;
|
|
55
|
-
constructor(store, externalUrl, options = {}) {
|
|
56
|
-
this.store = store;
|
|
57
|
-
this.externalUrl = externalUrl;
|
|
58
|
-
this.options = options;
|
|
59
|
-
this.sessionCookieName = sessionCookieName(externalUrl);
|
|
60
|
-
}
|
|
61
|
-
get clientsStore() {
|
|
62
|
-
const store = this.store;
|
|
63
|
-
return {
|
|
64
|
-
getClient: clientId => store.getClient(clientId),
|
|
65
|
-
registerClient: client => {
|
|
66
|
-
const isPublic = client.token_endpoint_auth_method === 'none';
|
|
67
|
-
const stored = {
|
|
68
|
-
...client,
|
|
69
|
-
client_id: crypto.randomBytes(16).toString('base64url'),
|
|
70
|
-
client_id_issued_at: Math.floor(Date.now() / 1000),
|
|
71
|
-
// Secrets never expire: ChatGPT registers once per connector and
|
|
72
|
-
// never re-registers, so an expiring secret bricks the connector
|
|
73
|
-
// when it runs out (the SDK default is 30 days).
|
|
74
|
-
client_secret_expires_at: 0
|
|
75
|
-
};
|
|
76
|
-
// A stored secret makes the SDK demand it on every token request, so a
|
|
77
|
-
// public client must be persisted without one — but ChatGPT expects a
|
|
78
|
-
// client_secret in the registration response even for "none" and
|
|
79
|
-
// refuses its own registration otherwise. Hand it one in the response
|
|
80
|
-
// only; token requests from public clients ignore it.
|
|
81
|
-
if (isPublic)
|
|
82
|
-
delete stored.client_secret;
|
|
83
|
-
store.saveClient(stored);
|
|
84
|
-
console.log(`mcp-hub: registered OAuth client ${stored.client_id} (${stored.client_name ?? 'unnamed'})`);
|
|
85
|
-
return isPublic ? { ...stored, client_secret: crypto.randomBytes(32).toString('hex'), client_secret_expires_at: 0 } : stored;
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
// --- Authorization endpoint ---------------------------------------------
|
|
90
|
-
/**
|
|
91
|
-
* A live session alone must not be enough to hand out a code: the cookie
|
|
92
|
-
* rides along on cross-site top-level navigations, so any page could walk a
|
|
93
|
-
* logged-in user through /authorize with a client it registered itself and
|
|
94
|
-
* collect the code. Only clients the user confirmed once are silent; for
|
|
95
|
-
* those the code can only ever land on their own registered redirect_uri.
|
|
96
|
-
*
|
|
97
|
-
* The SDK re-sends on `res` if this method throws, so nothing may throw
|
|
98
|
-
* after the response has gone out.
|
|
99
|
-
*/
|
|
100
|
-
async authorize(client, params, res) {
|
|
101
|
-
const resource = this.resolveResource(params.resource);
|
|
102
|
-
const session = this.readSessionCookie(res.req.headers.cookie);
|
|
103
|
-
if (session && this.isApproved(client, params.redirectUri)) {
|
|
104
|
-
this.redirectWithCode(client, { ...params, resource }, res);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const request = this.signPending(client, { ...params, resource });
|
|
108
|
-
const page = session
|
|
109
|
-
? renderConsentPage(request, this.csrfToken(session), params.redirectUri, client.client_name, resource?.href)
|
|
110
|
-
: renderLoginPage(request, params.redirectUri, client.client_name, undefined, resource?.href);
|
|
111
|
-
// Submitting either form ends in a redirect to this client's redirect_uri,
|
|
112
|
-
// which the SDK has already matched against its registration.
|
|
113
|
-
allowFormActionTo(res, params.redirectUri);
|
|
114
|
-
res.status(200).type('html').send(page);
|
|
115
|
-
}
|
|
116
|
-
/** Everything the login/consent form has to carry across, signed so it
|
|
117
|
-
* cannot be tampered with while it sits in the browser. */
|
|
118
|
-
signPending(client, params) {
|
|
119
|
-
const resource = this.resolveResource(params.resource);
|
|
120
|
-
const pending = {
|
|
121
|
-
clientId: client.client_id,
|
|
122
|
-
redirectUri: params.redirectUri,
|
|
123
|
-
codeChallenge: params.codeChallenge,
|
|
124
|
-
state: params.state,
|
|
125
|
-
scopes: params.scopes ?? [],
|
|
126
|
-
exp: Date.now() + CODE_TTL_MS,
|
|
127
|
-
resource: resource?.href
|
|
128
|
-
};
|
|
129
|
-
const payload = Buffer.from(JSON.stringify(pending)).toString('base64url');
|
|
130
|
-
return `${payload}.${sign(payload, this.store.cookieSecret)}`;
|
|
131
|
-
}
|
|
132
|
-
isApproved(client, redirectUri) {
|
|
133
|
-
const approval = this.store.getApproval(client.client_id);
|
|
134
|
-
// Same matching the SDK applies at registration, so a loopback client that
|
|
135
|
-
// picks a fresh port every run stays approved.
|
|
136
|
-
return approval?.redirectUris.some(uri => redirectUriMatches(redirectUri, uri)) ?? false;
|
|
137
|
-
}
|
|
138
|
-
approve(client, redirectUri) {
|
|
139
|
-
this.store.saveApproval(client.client_id, redirectUri, client.client_name);
|
|
140
|
-
console.log(`mcp-hub: approved OAuth client ${client.client_id} (${client.client_name ?? 'unnamed'}) for ${redirectUri}`);
|
|
141
|
-
}
|
|
142
|
-
decodePendingAuthorization(token) {
|
|
143
|
-
const [payload, signature] = token.split('.');
|
|
144
|
-
if (!payload || !signature)
|
|
145
|
-
return undefined;
|
|
146
|
-
const expected = sign(payload, this.store.cookieSecret);
|
|
147
|
-
if (signature.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)))
|
|
148
|
-
return undefined;
|
|
149
|
-
const pending = JSON.parse(Buffer.from(payload, 'base64url').toString());
|
|
150
|
-
if (pending.exp < Date.now())
|
|
151
|
-
return undefined;
|
|
152
|
-
return pending;
|
|
153
|
-
}
|
|
154
|
-
redirectWithCode(client, params, res) {
|
|
155
|
-
const resource = typeof params.resource === 'string' ? params.resource : this.resolveResource(params.resource)?.href;
|
|
156
|
-
const code = crypto.randomBytes(32).toString('base64url');
|
|
157
|
-
this.sweepExpiredCodes();
|
|
158
|
-
this.codes.set(code, {
|
|
159
|
-
clientId: client.client_id,
|
|
160
|
-
codeChallenge: params.codeChallenge,
|
|
161
|
-
redirectUri: params.redirectUri,
|
|
162
|
-
scopes: params.scopes ?? [],
|
|
163
|
-
expiresAt: Date.now() + CODE_TTL_MS,
|
|
164
|
-
resource
|
|
165
|
-
});
|
|
166
|
-
const target = new URL(params.redirectUri);
|
|
167
|
-
target.searchParams.set('code', code);
|
|
168
|
-
if (params.state !== undefined)
|
|
169
|
-
target.searchParams.set('state', params.state);
|
|
170
|
-
res.redirect(target.toString());
|
|
171
|
-
}
|
|
172
|
-
/** RFC 6749 §4.1.2.1 — tell the client it was turned down instead of
|
|
173
|
-
* leaving it to time out. */
|
|
174
|
-
redirectWithError(redirectUri, state, error, res) {
|
|
175
|
-
const target = new URL(redirectUri);
|
|
176
|
-
target.searchParams.set('error', error);
|
|
177
|
-
if (state !== undefined)
|
|
178
|
-
target.searchParams.set('state', state);
|
|
179
|
-
res.redirect(target.toString());
|
|
180
|
-
}
|
|
181
|
-
/** Codes are otherwise only dropped on redemption, so unredeemed ones from
|
|
182
|
-
* anyone who can register a client would accumulate for the process' life. */
|
|
183
|
-
sweepExpiredCodes() {
|
|
184
|
-
const now = Date.now();
|
|
185
|
-
for (const [code, pending] of this.codes) {
|
|
186
|
-
if (pending.expiresAt < now)
|
|
187
|
-
this.codes.delete(code);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
// --- Session cookie ------------------------------------------------------
|
|
191
|
-
createSessionCookie() {
|
|
192
|
-
const expires = String(Date.now() + SESSION_TTL_MS);
|
|
193
|
-
return `${expires}.${sign(expires, this.store.cookieSecret)}`;
|
|
194
|
-
}
|
|
195
|
-
/** The verified cookie value, which doubles as the handle the consent
|
|
196
|
-
* form's CSRF token is bound to. */
|
|
197
|
-
readSessionCookie(cookieHeader) {
|
|
198
|
-
const match = cookieHeader?.match(new RegExp(`(?:^|;\\s*)${this.sessionCookieName}=([^;]+)`));
|
|
199
|
-
if (!match)
|
|
200
|
-
return undefined;
|
|
201
|
-
const value = decodeURIComponent(match[1]);
|
|
202
|
-
const [expires, signature] = value.split('.');
|
|
203
|
-
if (!expires || !signature)
|
|
204
|
-
return undefined;
|
|
205
|
-
const expected = sign(expires, this.store.cookieSecret);
|
|
206
|
-
if (signature.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)))
|
|
207
|
-
return undefined;
|
|
208
|
-
return Number(expires) > Date.now() ? value : undefined;
|
|
209
|
-
}
|
|
210
|
-
hasValidSession(cookieHeader) {
|
|
211
|
-
return this.readSessionCookie(cookieHeader) !== undefined;
|
|
212
|
-
}
|
|
213
|
-
csrfToken(sessionValue) {
|
|
214
|
-
return sign(`csrf:${sessionValue}`, this.store.cookieSecret);
|
|
215
|
-
}
|
|
216
|
-
verifyCsrfToken(sessionValue, token) {
|
|
217
|
-
if (typeof token !== 'string')
|
|
218
|
-
return false;
|
|
219
|
-
const expected = this.csrfToken(sessionValue);
|
|
220
|
-
return token.length === expected.length && crypto.timingSafeEqual(Buffer.from(token), Buffer.from(expected));
|
|
221
|
-
}
|
|
222
|
-
// --- Token endpoint ------------------------------------------------------
|
|
223
|
-
async challengeForAuthorizationCode(_client, authorizationCode) {
|
|
224
|
-
const pending = this.codes.get(authorizationCode);
|
|
225
|
-
if (!pending || pending.expiresAt < Date.now())
|
|
226
|
-
throw new InvalidGrantError('Invalid or expired authorization code');
|
|
227
|
-
return pending.codeChallenge;
|
|
228
|
-
}
|
|
229
|
-
async exchangeAuthorizationCode(client, authorizationCode, _codeVerifier, redirectUri, resource) {
|
|
230
|
-
const pending = this.codes.get(authorizationCode);
|
|
231
|
-
if (!pending || pending.expiresAt < Date.now() || pending.clientId !== client.client_id) {
|
|
232
|
-
throw new InvalidGrantError('Invalid or expired authorization code');
|
|
233
|
-
}
|
|
234
|
-
if (redirectUri !== undefined && redirectUri !== pending.redirectUri) {
|
|
235
|
-
throw new InvalidGrantError('redirect_uri does not match the authorization request');
|
|
236
|
-
}
|
|
237
|
-
const requestedResource = resource ? this.resolveResource(resource) : undefined;
|
|
238
|
-
if (resource && requestedResource?.href !== pending.resource) {
|
|
239
|
-
throw new InvalidGrantError('resource does not match the authorization request');
|
|
240
|
-
}
|
|
241
|
-
this.codes.delete(authorizationCode); // single use
|
|
242
|
-
return this.issueTokens(client.client_id, pending.scopes, undefined, pending.resource);
|
|
243
|
-
}
|
|
244
|
-
async exchangeRefreshToken(client, refreshToken, scopes, resource) {
|
|
245
|
-
const record = this.store.getRefreshToken(refreshToken);
|
|
246
|
-
if (!record || record.clientId !== client.client_id) {
|
|
247
|
-
// Seeing a token that was already rotated away means the chain leaked:
|
|
248
|
-
// the legitimate holder and someone else both have one. Drop them all.
|
|
249
|
-
const consumed = this.store.wasConsumed(refreshToken);
|
|
250
|
-
if (consumed) {
|
|
251
|
-
const revoked = this.store.revokeFamily(consumed.familyId);
|
|
252
|
-
console.warn(`mcp-hub: refresh token replay from client ${client.client_id}, revoked ${revoked} token(s)`);
|
|
253
|
-
}
|
|
254
|
-
throw new InvalidGrantError('Invalid refresh token');
|
|
255
|
-
}
|
|
256
|
-
if (scopes && !scopes.every(scope => record.scopes.includes(scope))) {
|
|
257
|
-
throw new InvalidGrantError('Requested scopes exceed the original grant');
|
|
258
|
-
}
|
|
259
|
-
const requestedResource = resource ? this.resolveResource(resource) : undefined;
|
|
260
|
-
if (resource && !requestedResource)
|
|
261
|
-
throw new InvalidTargetError('Unknown or invalid resource');
|
|
262
|
-
if (record.resource && requestedResource && record.resource !== requestedResource.href) {
|
|
263
|
-
throw new InvalidGrantError('resource does not match the original grant');
|
|
264
|
-
}
|
|
265
|
-
const tokenResource = requestedResource?.href ?? record.resource;
|
|
266
|
-
if (this.options.requireResource && !tokenResource)
|
|
267
|
-
throw new InvalidTargetError('A resource indicator is required');
|
|
268
|
-
// Tokens issued before families existed adopt one on their first rotation.
|
|
269
|
-
const familyId = record.familyId ?? crypto.randomBytes(16).toString('base64url');
|
|
270
|
-
this.store.consumeRefreshToken(refreshToken, familyId, record.expiresAt);
|
|
271
|
-
return this.issueTokens(client.client_id, scopes ?? record.scopes, familyId, tokenResource);
|
|
272
|
-
}
|
|
273
|
-
async issueTokens(clientId, scopes, familyId = crypto.randomBytes(16).toString('base64url'), resource) {
|
|
274
|
-
const issuedMs = Date.now();
|
|
275
|
-
const accessToken = await new SignJWT({ client_id: clientId, scope: scopes.join(' '), issued_ms: issuedMs })
|
|
276
|
-
.setProtectedHeader({ alg: 'EdDSA' })
|
|
277
|
-
.setIssuer(this.externalUrl)
|
|
278
|
-
.setAudience(resource ?? this.externalUrl)
|
|
279
|
-
.setSubject('mcp-hub-user')
|
|
280
|
-
.setIssuedAt()
|
|
281
|
-
.setExpirationTime(`${ACCESS_TOKEN_TTL_S}s`)
|
|
282
|
-
.setJti(crypto.randomBytes(8).toString('base64url'))
|
|
283
|
-
.sign(this.store.privateKey);
|
|
284
|
-
const refreshToken = `rt_${crypto.randomBytes(32).toString('base64url')}`;
|
|
285
|
-
this.store.saveRefreshToken(refreshToken, {
|
|
286
|
-
clientId,
|
|
287
|
-
scopes,
|
|
288
|
-
expiresAt: Math.floor(Date.now() / 1000) + REFRESH_TOKEN_TTL_S,
|
|
289
|
-
familyId,
|
|
290
|
-
resource
|
|
291
|
-
});
|
|
292
|
-
return {
|
|
293
|
-
access_token: accessToken,
|
|
294
|
-
token_type: 'Bearer',
|
|
295
|
-
expires_in: ACCESS_TOKEN_TTL_S,
|
|
296
|
-
refresh_token: refreshToken,
|
|
297
|
-
scope: scopes.join(' ') || undefined
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
async verifyAccessToken(token) {
|
|
301
|
-
let payload;
|
|
302
|
-
try {
|
|
303
|
-
({ payload } = await jwtVerify(token, this.store.publicKey, {
|
|
304
|
-
issuer: this.externalUrl,
|
|
305
|
-
algorithms: ['EdDSA']
|
|
306
|
-
}));
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
throw new InvalidTokenError('Invalid or expired access token');
|
|
310
|
-
}
|
|
311
|
-
// Admin-minted API token: same signature and audience rules as an OAuth
|
|
312
|
-
// access token, but revocation lives in its state record — a jti whose
|
|
313
|
-
// record is gone (revoked or expired) is refused even though the JWT
|
|
314
|
-
// itself still verifies.
|
|
315
|
-
if (payload.sub === API_TOKEN_SUBJECT) {
|
|
316
|
-
if (typeof payload.jti !== 'string' || !this.store.getApiToken(payload.jti)) {
|
|
317
|
-
throw new InvalidTokenError('Access token has been revoked');
|
|
318
|
-
}
|
|
319
|
-
const audience = typeof payload.aud === 'string' ? payload.aud : undefined;
|
|
320
|
-
const resource = audience ? this.resolveResourceStrict(audience) : undefined;
|
|
321
|
-
if (!resource)
|
|
322
|
-
throw new InvalidTokenError('Invalid token audience');
|
|
323
|
-
return { token, clientId: `token:${payload.jti}`, scopes: [], expiresAt: payload.exp, resource };
|
|
324
|
-
}
|
|
325
|
-
if (payload.sub !== 'mcp-hub-user' || typeof payload.client_id !== 'string') {
|
|
326
|
-
throw new InvalidTokenError('Invalid access token claims');
|
|
327
|
-
}
|
|
328
|
-
const audience = typeof payload.aud === 'string' ? payload.aud : undefined;
|
|
329
|
-
const resource = audience && audience !== this.externalUrl ? this.resolveResourceStrict(audience) : undefined;
|
|
330
|
-
if (!audience || (audience !== this.externalUrl && !resource))
|
|
331
|
-
throw new InvalidTokenError('Invalid token audience');
|
|
332
|
-
const issuedMs = typeof payload.issued_ms === 'number' ? payload.issued_ms : (payload.iat ?? 0) * 1000;
|
|
333
|
-
const revokedBefore = this.store.getRevokedBefore(payload.client_id);
|
|
334
|
-
if (revokedBefore !== undefined && issuedMs <= revokedBefore)
|
|
335
|
-
throw new InvalidTokenError('Access token has been revoked');
|
|
336
|
-
return {
|
|
337
|
-
token,
|
|
338
|
-
clientId: payload.client_id,
|
|
339
|
-
scopes: typeof payload.scope === 'string' && payload.scope.length > 0 ? payload.scope.split(' ') : [],
|
|
340
|
-
expiresAt: payload.exp,
|
|
341
|
-
resource
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
/** Audience check for already-issued tokens: never falls back to a default. */
|
|
345
|
-
resolveResourceStrict(audience) {
|
|
346
|
-
let url;
|
|
347
|
-
try {
|
|
348
|
-
url = new URL(audience);
|
|
349
|
-
}
|
|
350
|
-
catch {
|
|
351
|
-
return undefined;
|
|
352
|
-
}
|
|
353
|
-
return this.options.resolveResource?.(url);
|
|
354
|
-
}
|
|
355
|
-
async revokeToken(client, request) {
|
|
356
|
-
const record = this.store.getRefreshToken(request.token);
|
|
357
|
-
if (record && record.clientId === client.client_id)
|
|
358
|
-
this.store.deleteRefreshToken(request.token);
|
|
359
|
-
// Individual JWTs are not stored; the offline admin command revokes all
|
|
360
|
-
// access and refresh tokens for a client through the revokedBefore marker.
|
|
361
|
-
}
|
|
362
|
-
resolveResource(resource) {
|
|
363
|
-
if (!resource) {
|
|
364
|
-
// Real clients omit the RFC 8707 parameter (older Codex logins, Google
|
|
365
|
-
// ADK, Gemini Enterprise). DEFAULT_RESOURCE lets the operator route
|
|
366
|
-
// those onto one chosen resource instead of turning them away — the
|
|
367
|
-
// token is still bound, never global.
|
|
368
|
-
if (this.options.defaultResource)
|
|
369
|
-
return this.options.defaultResource;
|
|
370
|
-
if (this.options.requireResource)
|
|
371
|
-
throw new InvalidTargetError('A resource indicator is required');
|
|
372
|
-
return undefined;
|
|
373
|
-
}
|
|
374
|
-
const resolved = this.options.resolveResource?.(resource);
|
|
375
|
-
if (!resolved)
|
|
376
|
-
throw new InvalidTargetError('Unknown or invalid resource');
|
|
377
|
-
return resolved;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
//# sourceMappingURL=provider.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/auth/provider.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/routes.js
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import express, { Router } from 'express';
|
|
3
|
-
import bcrypt from 'bcryptjs';
|
|
4
|
-
import { mcpAuthRouter, createOAuthMetadata } from '@modelcontextprotocol/sdk/server/auth/router.js';
|
|
5
|
-
import { SESSION_TTL_MS } from './provider.js';
|
|
6
|
-
import { renderLoginPage } from './login-page.js';
|
|
7
|
-
import { allowFormActionTo, contentSecurityPolicy } from './headers.js';
|
|
8
|
-
const LOGIN_MAX_ATTEMPTS = 10;
|
|
9
|
-
const LOGIN_WINDOW_MS = 15 * 60_000;
|
|
10
|
-
const LOGIN_MAX_ATTEMPTS_TOTAL = 100;
|
|
11
|
-
function earlyRateLimit(windowMs, maxPerIp, maxTotal) {
|
|
12
|
-
const byIp = new Map();
|
|
13
|
-
let total = { count: 0, resetAt: 0 };
|
|
14
|
-
return (req, res, next) => {
|
|
15
|
-
const now = Date.now();
|
|
16
|
-
if (total.resetAt <= now)
|
|
17
|
-
total = { count: 0, resetAt: now + windowMs };
|
|
18
|
-
const ip = req.ip ?? 'unknown';
|
|
19
|
-
let entry = byIp.get(ip);
|
|
20
|
-
if (entry && entry.resetAt <= now) {
|
|
21
|
-
byIp.delete(ip);
|
|
22
|
-
entry = undefined;
|
|
23
|
-
}
|
|
24
|
-
// Reject before inserting anything: a flood of distinct rejected IPs must
|
|
25
|
-
// not grow the map. Accepted requests bound it at maxTotal per window.
|
|
26
|
-
if ((entry?.count ?? 0) >= maxPerIp || total.count >= maxTotal) {
|
|
27
|
-
res.set('Retry-After', String(Math.max(1, Math.ceil((Math.min(entry?.resetAt ?? Infinity, total.resetAt) - now) / 1000))));
|
|
28
|
-
res.status(429).json({ error: 'too_many_requests', error_description: 'Request rate limit exceeded' });
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
if (!entry) {
|
|
32
|
-
if (byIp.size >= maxTotal) {
|
|
33
|
-
for (const [candidateIp, candidate] of byIp)
|
|
34
|
-
if (candidate.resetAt <= now)
|
|
35
|
-
byIp.delete(candidateIp);
|
|
36
|
-
}
|
|
37
|
-
entry = { count: 0, resetAt: now + windowMs };
|
|
38
|
-
byIp.set(ip, entry);
|
|
39
|
-
}
|
|
40
|
-
entry.count++;
|
|
41
|
-
total.count++;
|
|
42
|
-
next();
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
export class LoginRateLimiter {
|
|
46
|
-
attempts = new Map();
|
|
47
|
-
total = { count: 0, resetAt: 0 };
|
|
48
|
-
isBlocked(ip) {
|
|
49
|
-
if (this.total.resetAt > Date.now() && this.total.count >= LOGIN_MAX_ATTEMPTS_TOTAL)
|
|
50
|
-
return true;
|
|
51
|
-
const entry = this.attempts.get(ip);
|
|
52
|
-
if (!entry || entry.resetAt < Date.now())
|
|
53
|
-
return false;
|
|
54
|
-
return entry.count >= LOGIN_MAX_ATTEMPTS;
|
|
55
|
-
}
|
|
56
|
-
recordFailure(ip) {
|
|
57
|
-
this.sweepExpired(); // entries are otherwise only dropped on a successful login from that exact IP
|
|
58
|
-
const now = Date.now();
|
|
59
|
-
// Behind a reverse proxy req.ip is the proxy for every request, and a
|
|
60
|
-
// spoofable X-Forwarded-For makes the per-IP counter meaningless — this
|
|
61
|
-
// caps the total either way.
|
|
62
|
-
if (this.total.resetAt < now)
|
|
63
|
-
this.total = { count: 1, resetAt: now + LOGIN_WINDOW_MS };
|
|
64
|
-
else
|
|
65
|
-
this.total.count++;
|
|
66
|
-
const entry = this.attempts.get(ip);
|
|
67
|
-
if (!entry || entry.resetAt < now) {
|
|
68
|
-
this.attempts.set(ip, { count: 1, resetAt: now + LOGIN_WINDOW_MS });
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
entry.count++;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
sweepExpired() {
|
|
75
|
-
const now = Date.now();
|
|
76
|
-
for (const [ip, entry] of this.attempts) {
|
|
77
|
-
if (entry.resetAt < now)
|
|
78
|
-
this.attempts.delete(ip);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
reset(ip) {
|
|
82
|
-
this.attempts.delete(ip);
|
|
83
|
-
this.total = { count: 0, resetAt: 0 };
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
export function createAuthRoutes(options) {
|
|
87
|
-
const { provider, externalUrl } = options;
|
|
88
|
-
if (!options.passwordHash && !options.password) {
|
|
89
|
-
throw new Error('Either PASSWORD_HASH (bcrypt) or PASSWORD must be set');
|
|
90
|
-
}
|
|
91
|
-
const issuerUrl = new URL(externalUrl);
|
|
92
|
-
const router = Router();
|
|
93
|
-
const rateLimiter = new LoginRateLimiter();
|
|
94
|
-
// Keep the password login/consent pages and the OAuth token responses out of
|
|
95
|
-
// any shared or browser cache (RFC 6749 §5.1 requires no-store on token
|
|
96
|
-
// responses; the login form has no business being cached either). The CSP
|
|
97
|
-
// and legacy frame header keep an attacker from clickjacking approval or
|
|
98
|
-
// password entry. This application has no legitimate framing use case.
|
|
99
|
-
router.use((_req, res, next) => {
|
|
100
|
-
res.set('Cache-Control', 'no-store');
|
|
101
|
-
res.set('Content-Security-Policy', contentSecurityPolicy());
|
|
102
|
-
res.set('X-Frame-Options', 'DENY');
|
|
103
|
-
res.set('X-Content-Type-Options', 'nosniff');
|
|
104
|
-
res.set('Referrer-Policy', 'no-referrer');
|
|
105
|
-
next();
|
|
106
|
-
});
|
|
107
|
-
// These run before the SDK's JSON/form parsers. The SDK applies its own
|
|
108
|
-
// endpoint limits too, but those otherwise happen only after body parsing.
|
|
109
|
-
router.use('/register', earlyRateLimit(60 * 60_000, 20, 200));
|
|
110
|
-
router.use('/authorize', earlyRateLimit(15 * 60_000, 100, 1_000));
|
|
111
|
-
router.use('/token', earlyRateLimit(15 * 60_000, 50, 500));
|
|
112
|
-
router.use('/login', earlyRateLimit(15 * 60_000, 100, 500));
|
|
113
|
-
router.use('/consent', earlyRateLimit(15 * 60_000, 100, 500));
|
|
114
|
-
// Standard AS endpoints: /.well-known/*, /authorize, /token, /register, /revoke
|
|
115
|
-
router.use(mcpAuthRouter({ provider, issuerUrl, resourceName: 'mcp-hub' }));
|
|
116
|
-
// Path-scoped metadata (RFC 9728 §3.1): clients connected to /<name>/mcp look
|
|
117
|
-
// up /.well-known/oauth-protected-resource/<name>/mcp before falling back to
|
|
118
|
-
// the root document, and RFC 8414 has an equivalent path-insertion form for
|
|
119
|
-
// AS metadata. Serve both for any suffix; one AS covers all resources.
|
|
120
|
-
const asMetadata = createOAuthMetadata({ provider, issuerUrl });
|
|
121
|
-
router.get('/.well-known/oauth-authorization-server/{*splat}', (_req, res) => {
|
|
122
|
-
res.json(asMetadata);
|
|
123
|
-
});
|
|
124
|
-
// OIDC Discovery alias: some clients (ChatGPT among them) probe
|
|
125
|
-
// /.well-known/openid-configuration instead of — or before — the RFC 8414
|
|
126
|
-
// path, and the MCP spec requires clients to support both. The RFC 8414
|
|
127
|
-
// document is a compatible answer for the fields such clients read.
|
|
128
|
-
router.get('/.well-known/openid-configuration', (_req, res) => {
|
|
129
|
-
res.json(asMetadata);
|
|
130
|
-
});
|
|
131
|
-
router.get('/.well-known/openid-configuration/{*splat}', (_req, res) => {
|
|
132
|
-
res.json(asMetadata);
|
|
133
|
-
});
|
|
134
|
-
router.get('/.well-known/oauth-protected-resource/{*splat}', (req, res) => {
|
|
135
|
-
const suffix = req.path.replace('/.well-known/oauth-protected-resource', '');
|
|
136
|
-
res.json({
|
|
137
|
-
resource: issuerUrl.origin + suffix,
|
|
138
|
-
authorization_servers: [externalUrl],
|
|
139
|
-
bearer_methods_supported: ['header'],
|
|
140
|
-
resource_name: 'mcp-hub'
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
const checkPassword = (password) => {
|
|
144
|
-
if (options.passwordHash)
|
|
145
|
-
return bcrypt.compareSync(password, options.passwordHash);
|
|
146
|
-
const expected = Buffer.from(options.password);
|
|
147
|
-
const given = Buffer.from(password);
|
|
148
|
-
return expected.length === given.length && crypto.timingSafeEqual(expected, given);
|
|
149
|
-
};
|
|
150
|
-
router.post('/login', express.urlencoded({ extended: false }), async (req, res) => {
|
|
151
|
-
const ip = req.ip ?? 'unknown';
|
|
152
|
-
const { password, request } = req.body;
|
|
153
|
-
const pending = typeof request === 'string' ? provider.decodePendingAuthorization(request) : undefined;
|
|
154
|
-
if (!pending) {
|
|
155
|
-
res.status(400).type('html').send('<p>Authorization request expired. Close this window and connect again.</p>');
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
if (rateLimiter.isBlocked(ip)) {
|
|
159
|
-
console.warn(`mcp-hub: login rate limit exceeded from ${ip}`);
|
|
160
|
-
res.status(429).type('html').send('<p>Too many attempts. Try again later.</p>');
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
if (typeof password !== 'string' || !checkPassword(password)) {
|
|
164
|
-
rateLimiter.recordFailure(ip);
|
|
165
|
-
console.warn(`mcp-hub: authentication failure from ${ip}`);
|
|
166
|
-
// The retry form has to reach the same redirect as the first attempt.
|
|
167
|
-
allowFormActionTo(res, pending.redirectUri);
|
|
168
|
-
res.status(401).type('html').send(renderLoginPage(request, pending.redirectUri, undefined, 'Wrong password', pending.resource));
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
rateLimiter.reset(ip);
|
|
172
|
-
console.log(`mcp-hub: successful login from ${ip}`);
|
|
173
|
-
res.cookie(provider.sessionCookieName, provider.createSessionCookie(), {
|
|
174
|
-
httpOnly: true,
|
|
175
|
-
secure: issuerUrl.protocol === 'https:',
|
|
176
|
-
sameSite: 'lax',
|
|
177
|
-
maxAge: SESSION_TTL_MS,
|
|
178
|
-
path: '/'
|
|
179
|
-
});
|
|
180
|
-
const client = await provider.clientsStore.getClient(pending.clientId);
|
|
181
|
-
if (!client) {
|
|
182
|
-
res.status(400).type('html').send('<p>Unknown client. Close this window and connect again.</p>');
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
provider.approve(client, pending.redirectUri); // typing the password is the consent
|
|
186
|
-
provider.redirectWithCode(client, pending, res);
|
|
187
|
-
});
|
|
188
|
-
// Reached from the consent page when a signed-in user authorizes a client
|
|
189
|
-
// for the first time. Everything here has to hold even though the request
|
|
190
|
-
// may have been triggered by a page the user did not expect to land on.
|
|
191
|
-
router.post('/consent', express.urlencoded({ extended: false }), async (req, res) => {
|
|
192
|
-
const { request, csrf, action } = req.body;
|
|
193
|
-
const pending = typeof request === 'string' ? provider.decodePendingAuthorization(request) : undefined;
|
|
194
|
-
if (!pending) {
|
|
195
|
-
res.status(400).type('html').send('<p>Authorization request expired. Close this window and connect again.</p>');
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
const session = provider.readSessionCookie(req.headers.cookie);
|
|
199
|
-
if (!session) {
|
|
200
|
-
res.status(401).type('html').send('<p>Session expired. Close this window and connect again.</p>');
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
if (!provider.verifyCsrfToken(session, csrf)) {
|
|
204
|
-
console.warn(`mcp-hub: consent with an invalid CSRF token from ${req.ip ?? 'unknown'}`);
|
|
205
|
-
res.status(403).type('html').send('<p>Invalid request. Close this window and connect again.</p>');
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (action !== 'approve') {
|
|
209
|
-
console.log(`mcp-hub: authorization denied for client ${pending.clientId}`);
|
|
210
|
-
provider.redirectWithError(pending.redirectUri, pending.state, 'access_denied', res);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
const client = await provider.clientsStore.getClient(pending.clientId);
|
|
214
|
-
if (!client) {
|
|
215
|
-
res.status(400).type('html').send('<p>Unknown client. Close this window and connect again.</p>');
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
provider.approve(client, pending.redirectUri);
|
|
219
|
-
provider.redirectWithCode(client, pending, res);
|
|
220
|
-
});
|
|
221
|
-
return router;
|
|
222
|
-
}
|
|
223
|
-
//# sourceMappingURL=routes.js.map
|
package/dist/auth/routes.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/auth/routes.ts"],"names":[],"mappings":"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"}
|