@n24q02m/hull 0.0.0-stage → 0.4.0-beta.10
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/LICENSE +201 -0
- package/README.md +166 -2
- package/build/auth/credential-form.d.ts +163 -0
- package/build/auth/credential-form.d.ts.map +1 -0
- package/build/auth/credential-form.js +1869 -0
- package/build/auth/credential-form.js.map +1 -0
- package/build/auth/delegated-oauth-app.d.ts +90 -0
- package/build/auth/delegated-oauth-app.d.ts.map +1 -0
- package/build/auth/delegated-oauth-app.js +881 -0
- package/build/auth/delegated-oauth-app.js.map +1 -0
- package/build/auth/index.d.ts +12 -0
- package/build/auth/index.d.ts.map +1 -0
- package/build/auth/index.js +12 -0
- package/build/auth/index.js.map +1 -0
- package/build/auth/local-oauth-app.d.ts +152 -0
- package/build/auth/local-oauth-app.d.ts.map +1 -0
- package/build/auth/local-oauth-app.js +975 -0
- package/build/auth/local-oauth-app.js.map +1 -0
- package/build/auth/relay-login.d.ts +46 -0
- package/build/auth/relay-login.d.ts.map +1 -0
- package/build/auth/relay-login.js +197 -0
- package/build/auth/relay-login.js.map +1 -0
- package/build/auth/router.d.ts +18 -0
- package/build/auth/router.d.ts.map +1 -0
- package/build/auth/router.js +62 -0
- package/build/auth/router.js.map +1 -0
- package/build/auth/session-store.d.ts +49 -0
- package/build/auth/session-store.d.ts.map +1 -0
- package/build/auth/session-store.js +94 -0
- package/build/auth/session-store.js.map +1 -0
- package/build/auth/stable-sub.d.ts +27 -0
- package/build/auth/stable-sub.d.ts.map +1 -0
- package/build/auth/stable-sub.js +38 -0
- package/build/auth/stable-sub.js.map +1 -0
- package/build/auth/well-known.d.ts +4 -0
- package/build/auth/well-known.d.ts.map +1 -0
- package/build/auth/well-known.js +26 -0
- package/build/auth/well-known.js.map +1 -0
- package/build/cli/build-cli.d.ts +97 -0
- package/build/cli/build-cli.d.ts.map +1 -0
- package/build/cli/build-cli.js +309 -0
- package/build/cli/build-cli.js.map +1 -0
- package/build/cli/index.d.ts +2 -0
- package/build/cli/index.d.ts.map +1 -0
- package/build/cli/index.js +2 -0
- package/build/cli/index.js.map +1 -0
- package/build/crypto/aes.d.ts +8 -0
- package/build/crypto/aes.d.ts.map +1 -0
- package/build/crypto/aes.js +27 -0
- package/build/crypto/aes.js.map +1 -0
- package/build/crypto/ecdh.d.ts +5 -0
- package/build/crypto/ecdh.d.ts.map +1 -0
- package/build/crypto/ecdh.js +16 -0
- package/build/crypto/ecdh.js.map +1 -0
- package/build/crypto/index.d.ts +4 -0
- package/build/crypto/index.d.ts.map +1 -0
- package/build/crypto/index.js +4 -0
- package/build/crypto/index.js.map +1 -0
- package/build/crypto/kdf.d.ts +15 -0
- package/build/crypto/kdf.d.ts.map +1 -0
- package/build/crypto/kdf.js +26 -0
- package/build/crypto/kdf.js.map +1 -0
- package/build/index.d.ts +19 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +28 -0
- package/build/index.js.map +1 -0
- package/build/lifecycle/lock.d.ts +68 -0
- package/build/lifecycle/lock.d.ts.map +1 -0
- package/build/lifecycle/lock.js +173 -0
- package/build/lifecycle/lock.js.map +1 -0
- package/build/oauth/cache.d.ts +22 -0
- package/build/oauth/cache.d.ts.map +1 -0
- package/build/oauth/cache.js +24 -0
- package/build/oauth/cache.js.map +1 -0
- package/build/oauth/index.d.ts +6 -0
- package/build/oauth/index.d.ts.map +1 -0
- package/build/oauth/index.js +6 -0
- package/build/oauth/index.js.map +1 -0
- package/build/oauth/jwt-issuer.d.ts +87 -0
- package/build/oauth/jwt-issuer.d.ts.map +1 -0
- package/build/oauth/jwt-issuer.js +205 -0
- package/build/oauth/jwt-issuer.js.map +1 -0
- package/build/oauth/pkce.d.ts +10 -0
- package/build/oauth/pkce.d.ts.map +1 -0
- package/build/oauth/pkce.js +34 -0
- package/build/oauth/pkce.js.map +1 -0
- package/build/oauth/provider.d.ts +52 -0
- package/build/oauth/provider.d.ts.map +1 -0
- package/build/oauth/provider.js +98 -0
- package/build/oauth/provider.js.map +1 -0
- package/build/oauth/user-store.d.ts +25 -0
- package/build/oauth/user-store.d.ts.map +1 -0
- package/build/oauth/user-store.js +85 -0
- package/build/oauth/user-store.js.map +1 -0
- package/build/relay/browser.d.ts +15 -0
- package/build/relay/browser.d.ts.map +1 -0
- package/build/relay/browser.js +121 -0
- package/build/relay/browser.js.map +1 -0
- package/build/relay/client.d.ts +36 -0
- package/build/relay/client.d.ts.map +1 -0
- package/build/relay/client.js +135 -0
- package/build/relay/client.js.map +1 -0
- package/build/relay/session.d.ts +12 -0
- package/build/relay/session.d.ts.map +1 -0
- package/build/relay/session.js +55 -0
- package/build/relay/session.js.map +1 -0
- package/build/relay/tool-helpers.d.ts +85 -0
- package/build/relay/tool-helpers.d.ts.map +1 -0
- package/build/relay/tool-helpers.js +103 -0
- package/build/relay/tool-helpers.js.map +1 -0
- package/build/relay/wordlist.d.ts +2 -0
- package/build/relay/wordlist.d.ts.map +1 -0
- package/build/relay/wordlist.js +7782 -0
- package/build/relay/wordlist.js.map +1 -0
- package/build/schema/types.d.ts +67 -0
- package/build/schema/types.d.ts.map +1 -0
- package/build/schema/types.js +2 -0
- package/build/schema/types.js.map +1 -0
- package/build/storage/atomic-write.d.ts +8 -0
- package/build/storage/atomic-write.d.ts.map +1 -0
- package/build/storage/atomic-write.js +26 -0
- package/build/storage/atomic-write.js.map +1 -0
- package/build/storage/backends.d.ts +74 -0
- package/build/storage/backends.d.ts.map +1 -0
- package/build/storage/backends.js +193 -0
- package/build/storage/backends.js.map +1 -0
- package/build/storage/config-file.d.ts +35 -0
- package/build/storage/config-file.d.ts.map +1 -0
- package/build/storage/config-file.js +259 -0
- package/build/storage/config-file.js.map +1 -0
- package/build/storage/credential-store.d.ts +71 -0
- package/build/storage/credential-store.d.ts.map +1 -0
- package/build/storage/credential-store.js +91 -0
- package/build/storage/credential-store.js.map +1 -0
- package/build/storage/data-dir.d.ts +58 -0
- package/build/storage/data-dir.d.ts.map +1 -0
- package/build/storage/data-dir.js +79 -0
- package/build/storage/data-dir.js.map +1 -0
- package/build/storage/encryption.d.ts +9 -0
- package/build/storage/encryption.d.ts.map +1 -0
- package/build/storage/encryption.js +27 -0
- package/build/storage/encryption.js.map +1 -0
- package/build/storage/home-dir.d.ts +11 -0
- package/build/storage/home-dir.d.ts.map +1 -0
- package/build/storage/home-dir.js +18 -0
- package/build/storage/home-dir.js.map +1 -0
- package/build/storage/index.d.ts +12 -0
- package/build/storage/index.d.ts.map +1 -0
- package/build/storage/index.js +15 -0
- package/build/storage/index.js.map +1 -0
- package/build/storage/machine-id.d.ts +4 -0
- package/build/storage/machine-id.d.ts.map +1 -0
- package/build/storage/machine-id.js +73 -0
- package/build/storage/machine-id.js.map +1 -0
- package/build/storage/mode.d.ts +23 -0
- package/build/storage/mode.d.ts.map +1 -0
- package/build/storage/mode.js +46 -0
- package/build/storage/mode.js.map +1 -0
- package/build/storage/per-plugin-store.d.ts +65 -0
- package/build/storage/per-plugin-store.d.ts.map +1 -0
- package/build/storage/per-plugin-store.js +194 -0
- package/build/storage/per-plugin-store.js.map +1 -0
- package/build/storage/resolver.d.ts +7 -0
- package/build/storage/resolver.d.ts.map +1 -0
- package/build/storage/resolver.js +38 -0
- package/build/storage/resolver.js.map +1 -0
- package/build/storage/session-lock.d.ts +19 -0
- package/build/storage/session-lock.d.ts.map +1 -0
- package/build/storage/session-lock.js +105 -0
- package/build/storage/session-lock.js.map +1 -0
- package/build/token-auth/authenticator.d.ts +70 -0
- package/build/token-auth/authenticator.d.ts.map +1 -0
- package/build/token-auth/authenticator.js +146 -0
- package/build/token-auth/authenticator.js.map +1 -0
- package/build/token-auth/cli.d.ts +38 -0
- package/build/token-auth/cli.d.ts.map +1 -0
- package/build/token-auth/cli.js +192 -0
- package/build/token-auth/cli.js.map +1 -0
- package/build/token-auth/context.d.ts +30 -0
- package/build/token-auth/context.d.ts.map +1 -0
- package/build/token-auth/context.js +32 -0
- package/build/token-auth/context.js.map +1 -0
- package/build/token-auth/http-guard.d.ts +26 -0
- package/build/token-auth/http-guard.d.ts.map +1 -0
- package/build/token-auth/http-guard.js +51 -0
- package/build/token-auth/http-guard.js.map +1 -0
- package/build/token-auth/index.d.ts +14 -0
- package/build/token-auth/index.d.ts.map +1 -0
- package/build/token-auth/index.js +14 -0
- package/build/token-auth/index.js.map +1 -0
- package/build/token-auth/limiter.d.ts +20 -0
- package/build/token-auth/limiter.d.ts.map +1 -0
- package/build/token-auth/limiter.js +41 -0
- package/build/token-auth/limiter.js.map +1 -0
- package/build/token-auth/loopback.d.ts +17 -0
- package/build/token-auth/loopback.d.ts.map +1 -0
- package/build/token-auth/loopback.js +62 -0
- package/build/token-auth/loopback.js.map +1 -0
- package/build/token-auth/py-compat.d.ts +24 -0
- package/build/token-auth/py-compat.d.ts.map +1 -0
- package/build/token-auth/py-compat.js +76 -0
- package/build/token-auth/py-compat.js.map +1 -0
- package/build/token-auth/tokens.d.ts +36 -0
- package/build/token-auth/tokens.d.ts.map +1 -0
- package/build/token-auth/tokens.js +115 -0
- package/build/token-auth/tokens.js.map +1 -0
- package/build/token-auth/users.d.ts +62 -0
- package/build/token-auth/users.d.ts.map +1 -0
- package/build/token-auth/users.js +174 -0
- package/build/token-auth/users.js.map +1 -0
- package/build/transport/cache.d.ts +19 -0
- package/build/transport/cache.d.ts.map +1 -0
- package/build/transport/cache.js +76 -0
- package/build/transport/cache.js.map +1 -0
- package/build/transport/index.d.ts +5 -0
- package/build/transport/index.d.ts.map +1 -0
- package/build/transport/index.js +4 -0
- package/build/transport/index.js.map +1 -0
- package/build/transport/local-server.d.ts +173 -0
- package/build/transport/local-server.d.ts.map +1 -0
- package/build/transport/local-server.js +457 -0
- package/build/transport/local-server.js.map +1 -0
- package/build/transport/oauth-middleware.d.ts +45 -0
- package/build/transport/oauth-middleware.d.ts.map +1 -0
- package/build/transport/oauth-middleware.js +86 -0
- package/build/transport/oauth-middleware.js.map +1 -0
- package/build/transport/streamable-http.d.ts +54 -0
- package/build/transport/streamable-http.d.ts.map +1 -0
- package/build/transport/streamable-http.js +57 -0
- package/build/transport/streamable-http.js.map +1 -0
- package/package.json +107 -4
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local MCP server entry point combining OAuth 2.1 AS + Streamable HTTP transport.
|
|
3
|
+
*
|
|
4
|
+
* Composes:
|
|
5
|
+
* 1. OAuth 2.1 AS (credential form + token exchange) -- serves /authorize,
|
|
6
|
+
* /token, /otp, /setup-status, /.well-known/*
|
|
7
|
+
* 2. MCP Streamable HTTP transport -- serves /mcp with optional Bearer auth
|
|
8
|
+
* 3. /health endpoint -- liveness probe
|
|
9
|
+
*
|
|
10
|
+
* For servers without credential input (e.g. godot) ``relaySchema`` may be
|
|
11
|
+
* omitted: only /mcp (unauthenticated) and /health are served.
|
|
12
|
+
*
|
|
13
|
+
* This is a TypeScript port of ``core-py``'s ``local_server.py``. Route layout,
|
|
14
|
+
* Bearer enforcement, and lifecycle semantics are kept identical.
|
|
15
|
+
*/
|
|
16
|
+
import { randomUUID } from 'node:crypto';
|
|
17
|
+
import * as fs from 'node:fs';
|
|
18
|
+
import { createServer } from 'node:http';
|
|
19
|
+
import * as os from 'node:os';
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
22
|
+
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
|
|
23
|
+
import { isSchemaComplete } from '../auth/credential-form.js';
|
|
24
|
+
import { createDelegatedOAuthApp } from '../auth/delegated-oauth-app.js';
|
|
25
|
+
import { createLocalOAuthApp } from '../auth/local-oauth-app.js';
|
|
26
|
+
import { jsonResponse } from '../auth/router.js';
|
|
27
|
+
import { refreshLockTimestamp, sweepStaleLocks, writeLockFile } from '../lifecycle/lock.js';
|
|
28
|
+
import { tryOpenBrowser } from '../relay/browser.js';
|
|
29
|
+
import { readStoredConfig } from '../storage/credential-store.js';
|
|
30
|
+
import { Authenticator } from '../token-auth/authenticator.js';
|
|
31
|
+
import { guardRequest } from '../token-auth/http-guard.js';
|
|
32
|
+
import { assertNoAuthLoopback } from '../token-auth/loopback.js';
|
|
33
|
+
import { extractBearerToken } from './oauth-middleware.js';
|
|
34
|
+
/**
|
|
35
|
+
* Start an HTTP server with optional local OAuth AS + MCP Streamable HTTP transport.
|
|
36
|
+
*
|
|
37
|
+
* Behavior:
|
|
38
|
+
* - If ``relaySchema`` is provided, serves OAuth routes (/authorize, /token,
|
|
39
|
+
* /otp, /setup-status, /.well-known/*) AND /mcp with Bearer auth.
|
|
40
|
+
* - If ``relaySchema`` is undefined (e.g., godot), serves ONLY /mcp without
|
|
41
|
+
* auth (plus /health).
|
|
42
|
+
* - Binds to ``host:port``. Port 0 auto-assigns via the OS.
|
|
43
|
+
* - Returns a handle for lifecycle management; the server runs in the
|
|
44
|
+
* background until ``close()`` is called.
|
|
45
|
+
*/
|
|
46
|
+
export async function runHttpServer(serverFactory, options) {
|
|
47
|
+
const host = options.host ?? '127.0.0.1';
|
|
48
|
+
const wantedPort = options.port ?? 0;
|
|
49
|
+
// Token-table auth replaces the OAuth/relay stack entirely; combining them
|
|
50
|
+
// would leave two competing Bearer schemes on /mcp.
|
|
51
|
+
let tokenAuth = null;
|
|
52
|
+
if (options.auth !== undefined) {
|
|
53
|
+
const conflicting = ['relaySchema', 'delegatedOAuth', 'jwtIssuer', 'authDisabled', 'authScope'].filter((key) => options[key] !== undefined && options[key] !== false);
|
|
54
|
+
if (conflicting.length > 0) {
|
|
55
|
+
throw new Error(`\`auth\` is mutually exclusive with ${conflicting.map((key) => `\`${key}\``).join(', ')}`);
|
|
56
|
+
}
|
|
57
|
+
tokenAuth = new Authenticator(options.auth);
|
|
58
|
+
if (tokenAuth.mode === 'no-auth')
|
|
59
|
+
assertNoAuthLoopback(host);
|
|
60
|
+
}
|
|
61
|
+
// Edge auth deployment warning (per spec 2026-05-01-stdio-pure-http-multiuser
|
|
62
|
+
// §4.2.1). When ``PUBLIC_URL`` points to a non-localhost host but
|
|
63
|
+
// ``MCP_RELAY_PASSWORD`` is empty, the relay form is reachable from the
|
|
64
|
+
// public Internet without authentication — that's the wedge this gate
|
|
65
|
+
// closes. Operators running single-user dev on localhost intentionally
|
|
66
|
+
// skip the password; everyone else gets a startup warning so the misconfig
|
|
67
|
+
// doesn't pass silently.
|
|
68
|
+
const publicUrl = process.env.PUBLIC_URL ?? '';
|
|
69
|
+
const relayPassword = process.env.MCP_RELAY_PASSWORD ?? '';
|
|
70
|
+
const isLocalhost = /^https?:\/\/(localhost|127\.0\.0\.1)/.test(publicUrl);
|
|
71
|
+
if (tokenAuth === null && publicUrl && !isLocalhost && !relayPassword) {
|
|
72
|
+
console.warn('[hull] WARNING: HTTP mode public deployment without MCP_RELAY_PASSWORD — relay form is open to Internet');
|
|
73
|
+
}
|
|
74
|
+
let oauthApp = null;
|
|
75
|
+
let jwtIssuer = null;
|
|
76
|
+
if (options.relaySchema && options.delegatedOAuth) {
|
|
77
|
+
throw new Error('`relaySchema` and `delegatedOAuth` are mutually exclusive');
|
|
78
|
+
}
|
|
79
|
+
if (options.delegatedOAuth) {
|
|
80
|
+
oauthApp = await createDelegatedOAuthApp({
|
|
81
|
+
serverName: options.serverName,
|
|
82
|
+
flow: options.delegatedOAuth.flow,
|
|
83
|
+
upstream: options.delegatedOAuth.upstream,
|
|
84
|
+
onTokenReceived: options.delegatedOAuth.onTokenReceived,
|
|
85
|
+
sessionKv: options.delegatedOAuth.sessionKv,
|
|
86
|
+
jwtIssuer: options.jwtIssuer
|
|
87
|
+
});
|
|
88
|
+
jwtIssuer = oauthApp.jwtIssuer;
|
|
89
|
+
}
|
|
90
|
+
else if (options.relaySchema) {
|
|
91
|
+
oauthApp = await createLocalOAuthApp({
|
|
92
|
+
serverName: options.serverName,
|
|
93
|
+
relaySchema: options.relaySchema,
|
|
94
|
+
onCredentialsSaved: options.onCredentialsSaved,
|
|
95
|
+
onStepSubmitted: options.onStepSubmitted,
|
|
96
|
+
customCredentialFormHtml: options.customCredentialFormHtml,
|
|
97
|
+
stableSubEnabled: options.stableSubEnabled,
|
|
98
|
+
jwtIssuer: options.jwtIssuer
|
|
99
|
+
});
|
|
100
|
+
jwtIssuer = oauthApp.jwtIssuer;
|
|
101
|
+
}
|
|
102
|
+
// MCP per-session pattern (mirrors Python's StreamableHTTPSessionManager):
|
|
103
|
+
//
|
|
104
|
+
// - Each MCP client gets its own ``StreamableHTTPServerTransport`` keyed by
|
|
105
|
+
// Mcp-Session-Id, plus its own ``McpServer`` instance.
|
|
106
|
+
// - The first POST (initialize) has no session header; we mint a UUID via
|
|
107
|
+
// ``sessionIdGenerator`` and register the transport in ``onsessioninitialized``.
|
|
108
|
+
// - Subsequent POSTs (notifications/initialized, tools/list, tools/call)
|
|
109
|
+
// carry Mcp-Session-Id and are routed to the same transport, so the SDK's
|
|
110
|
+
// ``_initialized`` flag is in the right state for ``validateSession`` /
|
|
111
|
+
// ``validateProtocolVersion`` to accept the request.
|
|
112
|
+
// - Anything else -- an ID we never issued, or no ID on a request that
|
|
113
|
+
// cannot open a session -- is refused without building anything. Only
|
|
114
|
+
// ``initialize`` may open a session, which is what keeps the count of
|
|
115
|
+
// live ``McpServer`` instances tied to sessions rather than to requests.
|
|
116
|
+
//
|
|
117
|
+
// Stateless mode (sessionIdGenerator: undefined) was an earlier attempt; it
|
|
118
|
+
// returned HTTP 500 on the SDK-mandatory ``notifications/initialized`` POST
|
|
119
|
+
// because each request landed on a fresh transport with ``_initialized=false``
|
|
120
|
+
// and the SDK had no session manager to bridge them. Per-session routing is
|
|
121
|
+
// the same architecture the SDK examples + Python core-py use.
|
|
122
|
+
const transports = new Map();
|
|
123
|
+
const servers = new Map();
|
|
124
|
+
/** JSON-RPC error envelope, shaped like core-py's session manager replies. */
|
|
125
|
+
function jsonRpcError(res, status, code, message) {
|
|
126
|
+
jsonResponse(res, status, {
|
|
127
|
+
jsonrpc: '2.0',
|
|
128
|
+
id: 'server-error',
|
|
129
|
+
error: { code, message }
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Buffer and parse the body of a request that carries no session ID.
|
|
134
|
+
*
|
|
135
|
+
* Only the bootstrap path pays this cost. Once a session exists the request
|
|
136
|
+
* stream is handed to the SDK untouched, as before -- we need the body here
|
|
137
|
+
* solely to tell an ``initialize`` apart from a request that has no business
|
|
138
|
+
* opening a session, and the SDK cannot re-read a stream we consumed, so the
|
|
139
|
+
* parsed value is passed on to ``handleRequest``.
|
|
140
|
+
*
|
|
141
|
+
* The size ceiling matches the SDK transport's own ``maximumMessageSize``
|
|
142
|
+
* default; without one, this buffer is an unbounded allocation reachable
|
|
143
|
+
* before any session exists.
|
|
144
|
+
*/
|
|
145
|
+
async function readBootstrapBody(req) {
|
|
146
|
+
const MAX_BYTES = 4 * 1024 * 1024;
|
|
147
|
+
const chunks = [];
|
|
148
|
+
let size = 0;
|
|
149
|
+
for await (const chunk of req) {
|
|
150
|
+
const buf = chunk;
|
|
151
|
+
size += buf.length;
|
|
152
|
+
if (size > MAX_BYTES) {
|
|
153
|
+
return { ok: false, status: 413, code: -32600, message: 'Request body too large' };
|
|
154
|
+
}
|
|
155
|
+
chunks.push(buf);
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
return { ok: true, body: JSON.parse(Buffer.concat(chunks).toString('utf8')) };
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return { ok: false, status: 400, code: -32700, message: 'Parse error' };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function handleSessionRequest(req, res) {
|
|
165
|
+
const sessionHeader = req.headers['mcp-session-id'];
|
|
166
|
+
const incomingSessionId = Array.isArray(sessionHeader) ? sessionHeader[0] : sessionHeader;
|
|
167
|
+
if (incomingSessionId) {
|
|
168
|
+
const known = transports.get(incomingSessionId);
|
|
169
|
+
if (!known) {
|
|
170
|
+
// 404 is the spec's signal for "that session is gone" and the status
|
|
171
|
+
// core-py returns. A client that lost its session to a restart reads
|
|
172
|
+
// it as "open a new one"; a 400 would read as "your request is
|
|
173
|
+
// malformed" and it would retry the dead ID.
|
|
174
|
+
jsonRpcError(res, 404, -32600, 'Session not found');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
await known.handleRequest(req, res);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// No session ID. GET (opens the server-to-client SSE stream) and DELETE
|
|
181
|
+
// (closes a session) both presuppose one, so only a POST can get further.
|
|
182
|
+
if (req.method !== 'POST') {
|
|
183
|
+
jsonRpcError(res, 400, -32600, 'Bad Request: Mcp-Session-Id header is required');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const parsed = await readBootstrapBody(req);
|
|
187
|
+
if (!parsed.ok) {
|
|
188
|
+
jsonRpcError(res, parsed.status, parsed.code, parsed.message);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (!isInitializeRequest(parsed.body)) {
|
|
192
|
+
jsonRpcError(res, 400, -32600, 'Bad Request: no valid session ID provided');
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const server = serverFactory();
|
|
196
|
+
let transport;
|
|
197
|
+
transport = new StreamableHTTPServerTransport({
|
|
198
|
+
sessionIdGenerator: () => randomUUID(),
|
|
199
|
+
onsessioninitialized: (sessionId) => {
|
|
200
|
+
transports.set(sessionId, transport);
|
|
201
|
+
servers.set(sessionId, server);
|
|
202
|
+
},
|
|
203
|
+
onsessionclosed: (sessionId) => {
|
|
204
|
+
transports.delete(sessionId);
|
|
205
|
+
servers.delete(sessionId);
|
|
206
|
+
server.close().catch(() => {
|
|
207
|
+
/* best-effort cleanup */
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
await server.connect(transport);
|
|
212
|
+
await transport.handleRequest(req, res, parsed.body);
|
|
213
|
+
}
|
|
214
|
+
// Derive the RFC 9728 protected-resource-metadata URL for the Bearer
|
|
215
|
+
// challenge. Mirrors ``local-oauth-app.ts``'s ``getBaseUrl`` PUBLIC_URL-first
|
|
216
|
+
// convention: the deployed servers (oci-vm-prod behind CF Tunnel -> Caddy)
|
|
217
|
+
// set ``PUBLIC_URL`` so the advertised metadata URL is the public HTTPS host
|
|
218
|
+
// rather than the internal HTTP socket address. When unset, fall back to the
|
|
219
|
+
// request Host + ``X-Forwarded-Proto`` (or the socket ``encrypted`` flag).
|
|
220
|
+
function resourceMetadataUrl(req) {
|
|
221
|
+
const envPublicUrl = process.env.PUBLIC_URL;
|
|
222
|
+
let base;
|
|
223
|
+
if (envPublicUrl !== undefined && envPublicUrl.length > 0) {
|
|
224
|
+
base = envPublicUrl.replace(/\/+$/, '');
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
const reqHost = req.headers.host ?? 'localhost';
|
|
228
|
+
const encrypted = req.socket.encrypted === true;
|
|
229
|
+
const forwardedProto = req.headers['x-forwarded-proto'];
|
|
230
|
+
let protocol = encrypted ? 'https' : 'http';
|
|
231
|
+
if (typeof forwardedProto === 'string' && forwardedProto.length > 0) {
|
|
232
|
+
const commaIdx = forwardedProto.indexOf(',');
|
|
233
|
+
protocol = (commaIdx !== -1 ? forwardedProto.substring(0, commaIdx) : forwardedProto).trim();
|
|
234
|
+
}
|
|
235
|
+
base = `${protocol}://${reqHost}`;
|
|
236
|
+
}
|
|
237
|
+
return `${base}/.well-known/oauth-protected-resource`;
|
|
238
|
+
}
|
|
239
|
+
async function mcpHandler(req, res) {
|
|
240
|
+
// Bearer auth bypass for deployments behind an external auth boundary.
|
|
241
|
+
// Caller (reverse proxy, API gateway) is trusted to enforce auth upstream.
|
|
242
|
+
if (jwtIssuer && options.authDisabled) {
|
|
243
|
+
const anonymousClaims = { sub: 'anonymous', anonymous: true };
|
|
244
|
+
if (options.authScope) {
|
|
245
|
+
await options.authScope(anonymousClaims, async () => {
|
|
246
|
+
await handleSessionRequest(req, res);
|
|
247
|
+
});
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
await handleSessionRequest(req, res);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// Bearer auth if configured.
|
|
254
|
+
if (jwtIssuer) {
|
|
255
|
+
const authHeader = req.headers.authorization;
|
|
256
|
+
const token = extractBearerToken(authHeader);
|
|
257
|
+
if (!token) {
|
|
258
|
+
res.writeHead(401, {
|
|
259
|
+
'WWW-Authenticate': `Bearer resource_metadata="${resourceMetadataUrl(req)}"`
|
|
260
|
+
});
|
|
261
|
+
res.end();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
let claims;
|
|
265
|
+
try {
|
|
266
|
+
claims = await jwtIssuer.verifyAccessToken(token);
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
res.writeHead(401, {
|
|
270
|
+
'WWW-Authenticate': `Bearer resource_metadata="${resourceMetadataUrl(req)}", error="invalid_token"`
|
|
271
|
+
});
|
|
272
|
+
res.end();
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (options.authScope) {
|
|
276
|
+
await options.authScope(claims, async () => {
|
|
277
|
+
await handleSessionRequest(req, res);
|
|
278
|
+
});
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Delegate to per-session MCP transport.
|
|
283
|
+
await handleSessionRequest(req, res);
|
|
284
|
+
}
|
|
285
|
+
async function dispatch(req, res, pathname) {
|
|
286
|
+
// Route /mcp to MCP transport (with optional Bearer auth).
|
|
287
|
+
if (pathname === '/mcp') {
|
|
288
|
+
await mcpHandler(req, res);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
// Consumer-registered routes. After the built-ins (which win) and before
|
|
292
|
+
// the OAuth app, whose handler is a catch-all for the remaining paths.
|
|
293
|
+
if (options.extraRoutes) {
|
|
294
|
+
for (const route of options.extraRoutes) {
|
|
295
|
+
if (route.path === pathname && route.method === req.method) {
|
|
296
|
+
await route.handler(req, res);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Route everything else to OAuth app if present.
|
|
302
|
+
if (oauthApp) {
|
|
303
|
+
await oauthApp.handler(req, res);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// No OAuth app and no match -- 404.
|
|
307
|
+
jsonResponse(res, 404, { error: 'not_found' });
|
|
308
|
+
}
|
|
309
|
+
const handler = async (req, res) => {
|
|
310
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
311
|
+
const pathname = url.pathname;
|
|
312
|
+
// Liveness probe. Always available, never behind token auth.
|
|
313
|
+
if (pathname === '/health') {
|
|
314
|
+
jsonResponse(res, 200, { status: 'ok', server: options.serverName });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
// Token-table auth runs before any routing and binds the caller's
|
|
318
|
+
// AuthContext for the rest of the request (read via currentUser()).
|
|
319
|
+
if (tokenAuth !== null) {
|
|
320
|
+
await guardRequest(tokenAuth, req, res, () => dispatch(req, res, pathname));
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
await dispatch(req, res, pathname);
|
|
324
|
+
};
|
|
325
|
+
const httpServer = createServer((req, res) => {
|
|
326
|
+
handler(req, res).catch(() => {
|
|
327
|
+
if (!res.headersSent) {
|
|
328
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
329
|
+
res.end(JSON.stringify({ error: 'internal_error' }));
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
await new Promise((resolve, reject) => {
|
|
334
|
+
httpServer.once('error', reject);
|
|
335
|
+
httpServer.listen(wantedPort, host, () => {
|
|
336
|
+
httpServer.removeListener('error', reject);
|
|
337
|
+
resolve();
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
const addr = httpServer.address();
|
|
341
|
+
const actualPort = addr.port;
|
|
342
|
+
// Sweep stale locks for our server name BEFORE writing our own. Without
|
|
343
|
+
// this, abnormal-exit residue (Windows OOM, taskkill, signal) can pile up
|
|
344
|
+
// dozens of `<server>-<port>.lock` files — see 2026-04-28 wet-mcp 11-stale-lock incident.
|
|
345
|
+
const swept = sweepStaleLocks(options.serverName);
|
|
346
|
+
if (swept > 0) {
|
|
347
|
+
console.error(`[runHttpServer] cleaned ${swept} stale lock(s) for ${options.serverName}`);
|
|
348
|
+
}
|
|
349
|
+
const proxyToken = jwtIssuer ? await jwtIssuer.issueAccessToken('proxy', 31536000) : '';
|
|
350
|
+
const lockDirPath = path.join(os.homedir(), '.config', 'mcp', 'locks');
|
|
351
|
+
fs.mkdirSync(lockDirPath, { recursive: true, mode: 0o700 });
|
|
352
|
+
if (process.platform !== 'win32') {
|
|
353
|
+
fs.chmodSync(lockDirPath, 0o700);
|
|
354
|
+
}
|
|
355
|
+
const lockFile = writeLockFile(options.serverName, actualPort, proxyToken, lockDirPath);
|
|
356
|
+
// Refresh the lock timestamp hourly so the 24h TTL sweep does not kill
|
|
357
|
+
// long-running daemons. Cancelled in `close()`.
|
|
358
|
+
const lockRefreshInterval = setInterval(() => refreshLockTimestamp(lockFile), 3600 * 1000);
|
|
359
|
+
if (typeof lockRefreshInterval.unref === 'function') {
|
|
360
|
+
lockRefreshInterval.unref();
|
|
361
|
+
}
|
|
362
|
+
// Auto-open the credential form in the user's browser when no creds exist
|
|
363
|
+
// yet. We open the root URL ("/") which auto-bootstraps PKCE and redirects
|
|
364
|
+
// to /authorize with valid params; opening /authorize directly returns
|
|
365
|
+
// invalid_request because it requires client_id/redirect_uri/state/
|
|
366
|
+
// code_challenge. See `local-oauth-app.ts` root handler docstring.
|
|
367
|
+
// Best-effort: any failure surfaces via tryOpenBrowser's ASCII fallback banner.
|
|
368
|
+
// ``openBrowser: false`` opts out entirely -- see the option's docstring for
|
|
369
|
+
// why a consumer that already published the URL must not get a second tab.
|
|
370
|
+
if (oauthApp && options.openBrowser !== false) {
|
|
371
|
+
try {
|
|
372
|
+
const existingConfig = await readStoredConfig(options.serverName);
|
|
373
|
+
// Use schema completeness instead of "config === null" so peer-share
|
|
374
|
+
// paths writing partial entries (e.g. wet inheriting CRG cloud keys)
|
|
375
|
+
// do not suppress the relay form when required fields are missing.
|
|
376
|
+
const configComplete = options.relaySchema
|
|
377
|
+
? isSchemaComplete(existingConfig, options.relaySchema)
|
|
378
|
+
: existingConfig !== null;
|
|
379
|
+
if (!configComplete && process.env.NODE_ENV !== 'test') {
|
|
380
|
+
const setupUrl = `http://${host}:${actualPort}/`;
|
|
381
|
+
await tryOpenBrowser(setupUrl);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
/* best-effort: never crash startup on browser-open failure */
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Invoke setup hook. Supports legacy 1-arg (success-only) and new 2-arg
|
|
389
|
+
// (success + failure) signatures via Function.prototype.length so upstream
|
|
390
|
+
// errors (invalid_grant / expired_token / access_denied) propagate to the
|
|
391
|
+
// browser form instead of leaving the spinner waiting forever.
|
|
392
|
+
if (options.setupCompleteHook && oauthApp) {
|
|
393
|
+
const markSetupFailed = 'markSetupFailed' in oauthApp && typeof oauthApp.markSetupFailed === 'function'
|
|
394
|
+
? oauthApp.markSetupFailed
|
|
395
|
+
: undefined;
|
|
396
|
+
if (options.setupCompleteHook.length >= 2 && markSetupFailed !== undefined) {
|
|
397
|
+
;
|
|
398
|
+
options.setupCompleteHook(oauthApp.markSetupComplete, markSetupFailed);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
;
|
|
402
|
+
options.setupCompleteHook(oauthApp.markSetupComplete);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
port: actualPort,
|
|
407
|
+
host,
|
|
408
|
+
close: async () => {
|
|
409
|
+
if (oauthApp && 'shutdown' in oauthApp) {
|
|
410
|
+
await oauthApp.shutdown();
|
|
411
|
+
}
|
|
412
|
+
// Drain per-session transports + servers so their open SSE streams
|
|
413
|
+
// and tool-call timers don't keep the event loop alive after the
|
|
414
|
+
// HTTP server closes.
|
|
415
|
+
for (const transport of transports.values()) {
|
|
416
|
+
try {
|
|
417
|
+
await transport.close();
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
/* best-effort cleanup */
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
for (const server of servers.values()) {
|
|
424
|
+
try {
|
|
425
|
+
await server.close();
|
|
426
|
+
}
|
|
427
|
+
catch {
|
|
428
|
+
/* best-effort cleanup */
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
transports.clear();
|
|
432
|
+
servers.clear();
|
|
433
|
+
if (lockRefreshInterval !== null) {
|
|
434
|
+
clearInterval(lockRefreshInterval);
|
|
435
|
+
}
|
|
436
|
+
return new Promise((resolve, reject) => {
|
|
437
|
+
httpServer.close(async (err) => {
|
|
438
|
+
if (lockFile) {
|
|
439
|
+
try {
|
|
440
|
+
await fs.promises.unlink(lockFile);
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
/* best-effort cleanup */
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
if (err) {
|
|
447
|
+
reject(err);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
resolve();
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
//# sourceMappingURL=local-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-server.js","sourceRoot":"","sources":["../../src/transport/local-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,YAAY,EAA0D,MAAM,WAAW,CAAA;AAEhG,OAAO,KAAK,EAAE,MAAM,SAAS,CAAA;AAC7B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AAGjC,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAA;AAClG,OAAO,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAA;AAGxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,EACL,uBAAuB,EAGxB,MAAM,gCAAgC,CAAA;AACvC,OAAO,EAEL,mBAAmB,EAGpB,MAAM,4BAA4B,CAAA;AACnC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAE3F,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACjE,OAAO,EAAE,aAAa,EAAuB,MAAM,gCAAgC,CAAA;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AA8I1D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,aAA8B,EAC9B,OAA6B;IAE7B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,WAAW,CAAA;IACxC,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,IAAI,CAAC,CAAA;IAEpC,2EAA2E;IAC3E,oDAAoD;IACpD,IAAI,SAAS,GAAyB,IAAI,CAAA;IAC1C,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAI,CAAC,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,cAAc,EAAE,WAAW,CAAW,CAAC,MAAM,CAC/G,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAC9D,CAAA;QACD,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,uCAAuC,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAC7G,CAAC;QACD,SAAS,GAAG,IAAI,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QAC3C,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS;YAAE,oBAAoB,CAAC,IAAI,CAAC,CAAA;IAC9D,CAAC;IAED,8EAA8E;IAC9E,kEAAkE;IAClE,wEAAwE;IACxE,sEAAsE;IACtE,uEAAuE;IACvE,2EAA2E;IAC3E,yBAAyB;IACzB,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAA;IAC9C,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAA;IAC1D,MAAM,WAAW,GAAG,sCAAsC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IAC1E,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,IAAI,CAAC,WAAW,IAAI,CAAC,aAAa,EAAE,CAAC;QACtE,OAAO,CAAC,IAAI,CACV,yGAAyG,CAC1G,CAAA;IACH,CAAC;IAED,IAAI,QAAQ,GAAyD,IAAI,CAAA;IACzE,IAAI,SAAS,GAAqB,IAAI,CAAA;IAEtC,IAAI,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAA;IAC9E,CAAC;IAED,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;QAC3B,QAAQ,GAAG,MAAM,uBAAuB,CAAC;YACvC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,IAAI;YACjC,QAAQ,EAAE,OAAO,CAAC,cAAc,CAAC,QAAQ;YACzC,eAAe,EAAE,OAAO,CAAC,cAAc,CAAC,eAAe;YACvD,SAAS,EAAE,OAAO,CAAC,cAAc,CAAC,SAAS;YAC3C,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC,CAAA;QACF,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAA;IAChC,CAAC;SAAM,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QAC/B,QAAQ,GAAG,MAAM,mBAAmB,CAAC;YACnC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,WAAW,EAAE,OAAO,CAAC,WAAW;YAChC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,wBAAwB,EAAE,OAAO,CAAC,wBAAwB;YAC1D,gBAAgB,EAAE,OAAO,CAAC,gBAAgB;YAC1C,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC,CAAA;QACF,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAA;IAChC,CAAC;IAED,2EAA2E;IAC3E,EAAE;IACF,6EAA6E;IAC7E,0DAA0D;IAC1D,2EAA2E;IAC3E,oFAAoF;IACpF,0EAA0E;IAC1E,6EAA6E;IAC7E,2EAA2E;IAC3E,wDAAwD;IACxD,wEAAwE;IACxE,yEAAyE;IACzE,yEAAyE;IACzE,4EAA4E;IAC5E,EAAE;IACF,4EAA4E;IAC5E,4EAA4E;IAC5E,+EAA+E;IAC/E,4EAA4E;IAC5E,+DAA+D;IAC/D,MAAM,UAAU,GAAG,IAAI,GAAG,EAAyC,CAAA;IACnE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAqB,CAAA;IAE5C,8EAA8E;IAC9E,SAAS,YAAY,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAY,EAAE,OAAe;QACtF,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE;YACxB,OAAO,EAAE,KAAK;YACd,EAAE,EAAE,cAAc;YAClB,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;SACzB,CAAC,CAAA;IACJ,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,KAAK,UAAU,iBAAiB,CAC9B,GAAoB;QAEpB,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAA;QACjC,MAAM,MAAM,GAAa,EAAE,CAAA;QAC3B,IAAI,IAAI,GAAG,CAAC,CAAA;QACZ,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,KAAe,CAAA;YAC3B,IAAI,IAAI,GAAG,CAAC,MAAM,CAAA;YAClB,IAAI,IAAI,GAAG,SAAS,EAAE,CAAC;gBACrB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAA;YACpF,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAClB,CAAC;QACD,IAAI,CAAC;YACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAA;QAC/E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,CAAA;QACzE,CAAC;IACH,CAAC;IAED,KAAK,UAAU,oBAAoB,CAAC,GAAoB,EAAE,GAAmB;QAC3E,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAA;QACnD,MAAM,iBAAiB,GAAG,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAA;QAEzF,IAAI,iBAAiB,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;YAC/C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,qEAAqE;gBACrE,qEAAqE;gBACrE,+DAA+D;gBAC/D,6CAA6C;gBAC7C,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAA;gBACnD,OAAM;YACR,CAAC;YACD,MAAM,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;YACnC,OAAM;QACR,CAAC;QAED,wEAAwE;QACxE,0EAA0E;QAC1E,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1B,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,gDAAgD,CAAC,CAAA;YAChF,OAAM;QACR,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAA;QAC3C,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;YAC7D,OAAM;QACR,CAAC;QACD,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,2CAA2C,CAAC,CAAA;YAC3E,OAAM;QACR,CAAC;QAED,MAAM,MAAM,GAAG,aAAa,EAAE,CAAA;QAC9B,IAAI,SAAwC,CAAA;QAC5C,SAAS,GAAG,IAAI,6BAA6B,CAAC;YAC5C,kBAAkB,EAAE,GAAG,EAAE,CAAC,UAAU,EAAE;YACtC,oBAAoB,EAAE,CAAC,SAAS,EAAE,EAAE;gBAClC,UAAU,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;gBACpC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;YAChC,CAAC;YACD,eAAe,EAAE,CAAC,SAAS,EAAE,EAAE;gBAC7B,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;gBAC5B,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;gBACzB,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;oBACxB,yBAAyB;gBAC3B,CAAC,CAAC,CAAA;YACJ,CAAC;SACF,CAAC,CAAA;QACF,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;QAC/B,MAAM,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;IACtD,CAAC;IAED,qEAAqE;IACrE,8EAA8E;IAC9E,2EAA2E;IAC3E,6EAA6E;IAC7E,6EAA6E;IAC7E,2EAA2E;IAC3E,SAAS,mBAAmB,CAAC,GAAoB;QAC/C,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAA;QAC3C,IAAI,IAAY,CAAA;QAChB,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1D,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QACzC,CAAC;aAAM,CAAC;YACN,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,CAAA;YAC/C,MAAM,SAAS,GAAI,GAAG,CAAC,MAAkC,CAAC,SAAS,KAAK,IAAI,CAAA;YAC5E,MAAM,cAAc,GAAG,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAA;YACvD,IAAI,QAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAA;YAC3C,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpE,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;gBAC5C,QAAQ,GAAG,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,IAAI,EAAE,CAAA;YAC9F,CAAC;YACD,IAAI,GAAG,GAAG,QAAQ,MAAM,OAAO,EAAE,CAAA;QACnC,CAAC;QACD,OAAO,GAAG,IAAI,uCAAuC,CAAA;IACvD,CAAC;IAED,KAAK,UAAU,UAAU,CAAC,GAAoB,EAAE,GAAmB;QACjE,uEAAuE;QACvE,2EAA2E;QAC3E,IAAI,SAAS,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;YACtC,MAAM,eAAe,GAAc,EAAE,GAAG,EAAE,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,CAAA;YACxE,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBACtB,MAAM,OAAO,CAAC,SAAS,CAAC,eAAe,EAAE,KAAK,IAAI,EAAE;oBAClD,MAAM,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;gBACtC,CAAC,CAAC,CAAA;gBACF,OAAM;YACR,CAAC;YACD,MAAM,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;YACpC,OAAM;QACR,CAAC;QACD,6BAA6B;QAC7B,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAA;YAC5C,MAAM,KAAK,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAA;YAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;oBACjB,kBAAkB,EAAE,6BAA6B,mBAAmB,CAAC,GAAG,CAAC,GAAG;iBAC7E,CAAC,CAAA;gBACF,GAAG,CAAC,GAAG,EAAE,CAAA;gBACT,OAAM;YACR,CAAC;YACD,IAAI,MAAiB,CAAA;YACrB,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,SAAS,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAA;YACnD,CAAC;YAAC,MAAM,CAAC;gBACP,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;oBACjB,kBAAkB,EAAE,6BAA6B,mBAAmB,CAAC,GAAG,CAAC,0BAA0B;iBACpG,CAAC,CAAA;gBACF,GAAG,CAAC,GAAG,EAAE,CAAA;gBACT,OAAM;YACR,CAAC;YACD,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBACtB,MAAM,OAAO,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;oBACzC,MAAM,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;gBACtC,CAAC,CAAC,CAAA;gBACF,OAAM;YACR,CAAC;QACH,CAAC;QACD,yCAAyC;QACzC,MAAM,oBAAoB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACtC,CAAC;IAED,KAAK,UAAU,QAAQ,CAAC,GAAoB,EAAE,GAAmB,EAAE,QAAgB;QACjF,2DAA2D;QAC3D,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;YACxB,MAAM,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;YAC1B,OAAM;QACR,CAAC;QAED,yEAAyE;QACzE,uEAAuE;QACvE,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACxB,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;gBACxC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC;oBAC3D,MAAM,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;oBAC7B,OAAM;gBACR,CAAC;YACH,CAAC;QACH,CAAC;QAED,iDAAiD;QACjD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;YAChC,OAAM;QACR,CAAC;QAED,oCAAoC;QACpC,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAA;IAChD,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,EAAE,GAAoB,EAAE,GAAmB,EAAiB,EAAE;QACjF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC,CAAA;QAChF,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAA;QAE7B,6DAA6D;QAC7D,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;YACpE,OAAM;QACR,CAAC;QAED,kEAAkE;QAClE,oEAAoE;QACpE,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,MAAM,YAAY,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAA;YAC3E,OAAM;QACR,CAAC;QACD,MAAM,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAA;IACpC,CAAC,CAAA;IAED,MAAM,UAAU,GAAW,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACnD,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YAC3B,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAA;gBAC1D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAA;YACtD,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAChC,UAAU,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE;YACvC,UAAU,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;YAC1C,OAAO,EAAE,CAAA;QACX,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,EAAiB,CAAA;IAChD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAA;IAE5B,wEAAwE;IACxE,0EAA0E;IAC1E,0FAA0F;IAC1F,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;IACjD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,2BAA2B,KAAK,sBAAsB,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACvF,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,CAAC,CAAA;IACtE,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC3D,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;IAClC,CAAC;IACD,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,CAAC,CAAA;IAEvF,uEAAuE;IACvE,gDAAgD;IAChD,MAAM,mBAAmB,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,oBAAoB,CAAC,QAAQ,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,CAAA;IAC1F,IAAI,OAAO,mBAAmB,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACpD,mBAAmB,CAAC,KAAK,EAAE,CAAA;IAC7B,CAAC;IAED,0EAA0E;IAC1E,2EAA2E;IAC3E,uEAAuE;IACvE,oEAAoE;IACpE,mEAAmE;IACnE,gFAAgF;IAChF,6EAA6E;IAC7E,2EAA2E;IAC3E,IAAI,QAAQ,IAAI,OAAO,CAAC,WAAW,KAAK,KAAK,EAAE,CAAC;QAC9C,IAAI,CAAC;YACH,MAAM,cAAc,GAAG,MAAM,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;YACjE,qEAAqE;YACrE,qEAAqE;YACrE,mEAAmE;YACnE,MAAM,cAAc,GAAG,OAAO,CAAC,WAAW;gBACxC,CAAC,CAAC,gBAAgB,CAAC,cAAc,EAAE,OAAO,CAAC,WAAW,CAAC;gBACvD,CAAC,CAAC,cAAc,KAAK,IAAI,CAAA;YAE3B,IAAI,CAAC,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;gBACvD,MAAM,QAAQ,GAAG,UAAU,IAAI,IAAI,UAAU,GAAG,CAAA;gBAChD,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAA;YAChC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,8DAA8D;QAChE,CAAC;IACH,CAAC;IAED,wEAAwE;IACxE,2EAA2E;IAC3E,0EAA0E;IAC1E,+DAA+D;IAC/D,IAAI,OAAO,CAAC,iBAAiB,IAAI,QAAQ,EAAE,CAAC;QAC1C,MAAM,eAAe,GACnB,iBAAiB,IAAI,QAAQ,IAAI,OAAO,QAAQ,CAAC,eAAe,KAAK,UAAU;YAC7E,CAAC,CAAC,QAAQ,CAAC,eAAe;YAC1B,CAAC,CAAC,SAAS,CAAA;QACf,IAAI,OAAO,CAAC,iBAAiB,CAAC,MAAM,IAAI,CAAC,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;YAC3E,CAAC;YAAC,OAAO,CAAC,iBAAsG,CAC9G,QAAQ,CAAC,iBAAiB,EAC1B,eAAe,CAChB,CAAA;QACH,CAAC;aAAM,CAAC;YACN,CAAC;YAAC,OAAO,CAAC,iBAA0D,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAA;QAClG,CAAC;IACH,CAAC;IAED,OAAO;QACL,IAAI,EAAE,UAAU;QAChB,IAAI;QACJ,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,IAAI,QAAQ,IAAI,UAAU,IAAI,QAAQ,EAAE,CAAC;gBACvC,MAAM,QAAQ,CAAC,QAAQ,EAAE,CAAA;YAC3B,CAAC;YAED,mEAAmE;YACnE,iEAAiE;YACjE,sBAAsB;YACtB,KAAK,MAAM,SAAS,IAAI,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC5C,IAAI,CAAC;oBACH,MAAM,SAAS,CAAC,KAAK,EAAE,CAAA;gBACzB,CAAC;gBAAC,MAAM,CAAC;oBACP,yBAAyB;gBAC3B,CAAC;YACH,CAAC;YACD,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;gBACtC,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,KAAK,EAAE,CAAA;gBACtB,CAAC;gBAAC,MAAM,CAAC;oBACP,yBAAyB;gBAC3B,CAAC;YACH,CAAC;YACD,UAAU,CAAC,KAAK,EAAE,CAAA;YAClB,OAAO,CAAC,KAAK,EAAE,CAAA;YAEf,IAAI,mBAAmB,KAAK,IAAI,EAAE,CAAC;gBACjC,aAAa,CAAC,mBAAmB,CAAC,CAAA;YACpC,CAAC;YAED,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC3C,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;oBAC7B,IAAI,QAAQ,EAAE,CAAC;wBACb,IAAI,CAAC;4BACH,MAAM,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;wBACpC,CAAC;wBAAC,MAAM,CAAC;4BACP,yBAAyB;wBAC3B,CAAC;oBACH,CAAC;oBACD,IAAI,GAAG,EAAE,CAAC;wBACR,MAAM,CAAC,GAAG,CAAC,CAAA;oBACb,CAAC;yBAAM,CAAC;wBACN,OAAO,EAAE,CAAA;oBACX,CAAC;gBACH,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 Bearer token validation middleware for Streamable HTTP.
|
|
3
|
+
*
|
|
4
|
+
* Validates `Authorization: Bearer <token>` headers on incoming requests
|
|
5
|
+
* against a `JWTIssuer`. On missing or invalid token it writes a 401
|
|
6
|
+
* with `WWW-Authenticate: Bearer resource_metadata="..."` per RFC 6750 +
|
|
7
|
+
* RFC 9728 (OAuth 2.1 protected resource metadata discovery).
|
|
8
|
+
*
|
|
9
|
+
* Success attaches the validated claims to `AuthenticatedRequest.user` so
|
|
10
|
+
* downstream handlers can read the subject without re-validating.
|
|
11
|
+
*/
|
|
12
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
13
|
+
import type { JWTIssuer } from '../oauth/jwt-issuer.js';
|
|
14
|
+
export interface OAuthMiddlewareOptions {
|
|
15
|
+
jwtIssuer: JWTIssuer;
|
|
16
|
+
resourceMetadataUrl: string;
|
|
17
|
+
/**
|
|
18
|
+
* When `true`, skip Bearer token validation and attach an anonymous user
|
|
19
|
+
* claim. Intended for deployments behind an external auth boundary
|
|
20
|
+
* (reverse proxy, API gateway like agentgateway/Zitadel) where the caller
|
|
21
|
+
* has already verified identity. Logs a warning on construction; the
|
|
22
|
+
* deployer is responsible for ensuring upstream auth is in place.
|
|
23
|
+
*/
|
|
24
|
+
authDisabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface AuthenticatedRequest extends IncomingMessage {
|
|
27
|
+
user?: Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export declare function extractBearerToken(authHeader: string | undefined): string | null;
|
|
30
|
+
export declare class OAuthMiddleware {
|
|
31
|
+
private readonly _issuer;
|
|
32
|
+
private readonly _resourceMetadataUrl;
|
|
33
|
+
private readonly _authDisabled;
|
|
34
|
+
constructor(options: OAuthMiddlewareOptions);
|
|
35
|
+
/**
|
|
36
|
+
* Validate the request. Returns `true` if the request should proceed
|
|
37
|
+
* (and attaches `req.user` on success). Returns `false` if the
|
|
38
|
+
* middleware has already written a 401 response.
|
|
39
|
+
*
|
|
40
|
+
* When constructed with `authDisabled: true`, this short-circuits to
|
|
41
|
+
* `true` and attaches `{ sub: 'anonymous', anonymous: true }` to `req.user`.
|
|
42
|
+
*/
|
|
43
|
+
validate(req: AuthenticatedRequest, res: ServerResponse): Promise<boolean>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=oauth-middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-middleware.d.ts","sourceRoot":"","sources":["../../src/transport/oauth-middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAEvD,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,SAAS,CAAA;IACpB,mBAAmB,EAAE,MAAM,CAAA;IAC3B;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,oBAAqB,SAAQ,eAAe;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B;AAiBD,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAsBhF;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAW;IACnC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAQ;IAC7C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,YAAY,OAAO,EAAE,sBAAsB,EAU1C;IAED;;;;;;;OAOG;IACG,QAAQ,CAAC,GAAG,EAAE,oBAAoB,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAkB/E;CACF"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 Bearer token validation middleware for Streamable HTTP.
|
|
3
|
+
*
|
|
4
|
+
* Validates `Authorization: Bearer <token>` headers on incoming requests
|
|
5
|
+
* against a `JWTIssuer`. On missing or invalid token it writes a 401
|
|
6
|
+
* with `WWW-Authenticate: Bearer resource_metadata="..."` per RFC 6750 +
|
|
7
|
+
* RFC 9728 (OAuth 2.1 protected resource metadata discovery).
|
|
8
|
+
*
|
|
9
|
+
* Success attaches the validated claims to `AuthenticatedRequest.user` so
|
|
10
|
+
* downstream handlers can read the subject without re-validating.
|
|
11
|
+
*/
|
|
12
|
+
function writeChallenge(res, resourceMetadataUrl, error) {
|
|
13
|
+
const params = [`resource_metadata="${resourceMetadataUrl}"`];
|
|
14
|
+
if (error)
|
|
15
|
+
params.push(`error="${error}"`);
|
|
16
|
+
res.writeHead(401, {
|
|
17
|
+
'WWW-Authenticate': `Bearer ${params.join(', ')}`,
|
|
18
|
+
'Content-Type': 'application/json'
|
|
19
|
+
});
|
|
20
|
+
res.end(JSON.stringify({
|
|
21
|
+
error: error ?? 'unauthorized',
|
|
22
|
+
error_description: error ? 'The access token is missing or invalid' : 'Authentication required'
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
export function extractBearerToken(authHeader) {
|
|
26
|
+
if (!authHeader)
|
|
27
|
+
return null;
|
|
28
|
+
// Fast path for strictly "Bearer " or "bearer " (exact case & single space)
|
|
29
|
+
if (authHeader.startsWith('Bearer ') || authHeader.startsWith('bearer ')) {
|
|
30
|
+
const token = authHeader.substring(7).trim();
|
|
31
|
+
return token.length > 0 ? token : null;
|
|
32
|
+
}
|
|
33
|
+
// Fallback for tricky cases (mixed case, leading whitespace, etc.)
|
|
34
|
+
const trimmed = authHeader.trim();
|
|
35
|
+
if (trimmed.length <= 6)
|
|
36
|
+
return null;
|
|
37
|
+
const prefix = trimmed.substring(0, 6);
|
|
38
|
+
if (prefix.toLowerCase() !== 'bearer')
|
|
39
|
+
return null;
|
|
40
|
+
const char6 = trimmed.charCodeAt(6);
|
|
41
|
+
// Check if character 6 is whitespace (space=32, tab=9, newline=10, return=13)
|
|
42
|
+
if (char6 !== 32 && char6 !== 9 && char6 !== 10 && char6 !== 13)
|
|
43
|
+
return null;
|
|
44
|
+
const token = trimmed.substring(7).trim();
|
|
45
|
+
return token.length > 0 ? token : null;
|
|
46
|
+
}
|
|
47
|
+
export class OAuthMiddleware {
|
|
48
|
+
constructor(options) {
|
|
49
|
+
this._issuer = options.jwtIssuer;
|
|
50
|
+
this._resourceMetadataUrl = options.resourceMetadataUrl;
|
|
51
|
+
this._authDisabled = options.authDisabled === true;
|
|
52
|
+
if (this._authDisabled) {
|
|
53
|
+
console.warn('[hull OAuthMiddleware] auth_disabled=true — Bearer token validation skipped. ' +
|
|
54
|
+
'Caller must enforce authentication at the network boundary (e.g. reverse proxy, API gateway).');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validate the request. Returns `true` if the request should proceed
|
|
59
|
+
* (and attaches `req.user` on success). Returns `false` if the
|
|
60
|
+
* middleware has already written a 401 response.
|
|
61
|
+
*
|
|
62
|
+
* When constructed with `authDisabled: true`, this short-circuits to
|
|
63
|
+
* `true` and attaches `{ sub: 'anonymous', anonymous: true }` to `req.user`.
|
|
64
|
+
*/
|
|
65
|
+
async validate(req, res) {
|
|
66
|
+
if (this._authDisabled) {
|
|
67
|
+
req.user = { sub: 'anonymous', anonymous: true };
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
const token = extractBearerToken(req.headers.authorization);
|
|
71
|
+
if (!token) {
|
|
72
|
+
writeChallenge(res, this._resourceMetadataUrl);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const claims = await this._issuer.verifyAccessToken(token);
|
|
77
|
+
req.user = claims;
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
writeChallenge(res, this._resourceMetadataUrl, 'invalid_token');
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=oauth-middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-middleware.js","sourceRoot":"","sources":["../../src/transport/oauth-middleware.ts"],"names":[],"mappings":"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"}
|