@winstonsayno/mcp-gateway 1.1.0 → 1.2.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +32 -6
  3. package/dashboard/README.md +4 -0
  4. package/dashboard/index.html +42 -3
  5. package/dist/auth/middleware.d.ts +29 -0
  6. package/dist/auth/middleware.d.ts.map +1 -1
  7. package/dist/auth/middleware.js +142 -18
  8. package/dist/auth/middleware.js.map +1 -1
  9. package/dist/cli.js +54 -1
  10. package/dist/cli.js.map +1 -1
  11. package/dist/config/loader.d.ts.map +1 -1
  12. package/dist/config/loader.js +110 -2
  13. package/dist/config/loader.js.map +1 -1
  14. package/dist/gateway/api.d.ts +5 -0
  15. package/dist/gateway/api.d.ts.map +1 -1
  16. package/dist/gateway/api.js +86 -4
  17. package/dist/gateway/api.js.map +1 -1
  18. package/dist/gateway/index.d.ts +9 -1
  19. package/dist/gateway/index.d.ts.map +1 -1
  20. package/dist/gateway/index.js +76 -5
  21. package/dist/gateway/index.js.map +1 -1
  22. package/dist/index.d.ts +11 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +7 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/mcp/catalog.d.ts +1 -1
  27. package/dist/mcp/endpoint.d.ts +46 -1
  28. package/dist/mcp/endpoint.d.ts.map +1 -1
  29. package/dist/mcp/endpoint.js +312 -10
  30. package/dist/mcp/endpoint.js.map +1 -1
  31. package/dist/middleware/error-handler.d.ts +10 -1
  32. package/dist/middleware/error-handler.d.ts.map +1 -1
  33. package/dist/middleware/error-handler.js +17 -3
  34. package/dist/middleware/error-handler.js.map +1 -1
  35. package/dist/monitor/index.d.ts.map +1 -1
  36. package/dist/monitor/index.js +5 -0
  37. package/dist/monitor/index.js.map +1 -1
  38. package/dist/proxy/index.d.ts +15 -0
  39. package/dist/proxy/index.d.ts.map +1 -1
  40. package/dist/proxy/index.js +44 -4
  41. package/dist/proxy/index.js.map +1 -1
  42. package/dist/security/headers.d.ts +23 -0
  43. package/dist/security/headers.d.ts.map +1 -0
  44. package/dist/security/headers.js +71 -0
  45. package/dist/security/headers.js.map +1 -0
  46. package/dist/security/lockout.d.ts +41 -0
  47. package/dist/security/lockout.d.ts.map +1 -0
  48. package/dist/security/lockout.js +117 -0
  49. package/dist/security/lockout.js.map +1 -0
  50. package/dist/security/network.d.ts +31 -0
  51. package/dist/security/network.d.ts.map +1 -0
  52. package/dist/security/network.js +147 -0
  53. package/dist/security/network.js.map +1 -0
  54. package/dist/security/posture.d.ts +18 -0
  55. package/dist/security/posture.d.ts.map +1 -0
  56. package/dist/security/posture.js +73 -0
  57. package/dist/security/posture.js.map +1 -0
  58. package/dist/security/redact.d.ts +34 -0
  59. package/dist/security/redact.d.ts.map +1 -0
  60. package/dist/security/redact.js +115 -0
  61. package/dist/security/redact.js.map +1 -0
  62. package/dist/utils/logger.d.ts.map +1 -1
  63. package/dist/utils/logger.js +4 -2
  64. package/dist/utils/logger.js.map +1 -1
  65. package/dist/utils/types.d.ts +81 -2
  66. package/dist/utils/types.d.ts.map +1 -1
  67. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -9,6 +9,91 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [1.2.0] - 2026-10-07
13
+
14
+ Security hardening and more of the MCP spec on `/mcp`. Every new protection that could reject traffic that 1.1
15
+ accepted is **opt-in**; see *Upgrade notes*.
16
+
17
+ ### Security
18
+ - **Hashed API keys**: `auth.apiKeys` entries (plain strings or `key`) may be `sha256:<64 hex>` digests, so the config
19
+ file never holds a usable key. Digest and plain form of a key share the same client id. New CLI commands
20
+ `mcp-gateway gen-key` (random `mgw_…` key + digest, `--bytes`, `--prefix`, `--json`) and
21
+ `mcp-gateway hash-key [key]` (reads stdin when no argument is given). Malformed `sha256:` values are a config error.
22
+ - **Key expiry / disabling**: `expiresAt` (ISO 8601) and `disabled` on object keys → `401`; open `/mcp` sessions of
23
+ such keys end on the next reload. Keys expiring within 7 days produce a warning.
24
+ - **JWT hardening** (`auth.jwt`): `issuer`, `audience` (string or list), `algorithms` allowlist, `clockToleranceSeconds`,
25
+ `requireExp`, `maxTokenAgeSeconds`; verification keys from `jwtSecret` (HS*), a PEM `publicKey` (RS/PS/ES/EdDSA) or
26
+ a `jwksUrl` (HTTPS, cached `jwksCacheSeconds`, refetch on unknown `kid`). HMAC and asymmetric algorithms are never
27
+ mixed (algorithm-confusion protection); exactly one key source must be configured.
28
+ - **Security headers** (`security.headers`, default on): `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`,
29
+ `Referrer-Policy: no-referrer`, `Cross-Origin-Opener-Policy`, `Cross-Origin-Resource-Policy`, a deny-all CSP on API
30
+ responses and a dashboard CSP that allows exactly its inline script by SHA-256 hash. Optional `security.hsts`.
31
+ - **Network guards**: `security.ipAllowlist` (IPv4 / IPv6 / CIDR, via `net.BlockList`), `security.allowedHosts` (Host
32
+ header allowlist with `*.domain` wildcards), `security.trustProxy` (Express "trust proxy": decides `req.ip` for rate
33
+ limits, lockout, allowlist and logs). Liveness / readiness probes stay reachable.
34
+ - **DNS-rebinding protection** (`security.dnsRebindingProtection`): Host must be a loopback name / the bind address
35
+ (or `allowedHosts`), and `/mcp` accepts browser `Origin`s only when same-origin, loopback or explicitly listed.
36
+ - **Size limits**: `security.maxBodyBytes` (default 10 MiB as before, now configurable for REST and `/mcp`) and
37
+ `security.maxToolArgumentsBytes` for `tools/call`, `prompts/get` and `completion/complete` arguments (`413` on REST,
38
+ `-32602` on `/mcp`).
39
+ - **Brute-force lockout** (`security.authLockout`): after `maxFailures` (10) failed authentications from one IP within
40
+ `windowSeconds` (300), the IP gets `429` + `Retry-After` for `lockoutSeconds` (900) on every authenticated route,
41
+ including `/mcp` and `/api/v1/events`.
42
+ - **Secret redaction**: log lines and metadata, recorded `errorMessage`s (request log, audit log, `/requests`,
43
+ `/stats`, `/events`, dashboard) mask Bearer / Basic tokens, JWTs, common provider keys (OpenAI, Anthropic, GitHub,
44
+ GitLab, Slack, AWS, Google), `password=` / `token=` / `api_key=` pairs, URL credentials and values under
45
+ secret-looking keys. `GET /servers` now also masks secret-looking stdio `args` (`--token x`, `--api-key=x`). Extra
46
+ patterns via `security.redactPatterns`.
47
+ - **Secure-defaults check**: startup warnings / hints (auth off on a public bind, DNS rebinding, `/mcp` open to any
48
+ origin, plain-text / short / expiring keys, JWT without iss / aud / exp, no lockout, `corsOrigins: ["*"]` with auth,
49
+ headers disabled, error details exposed). `mcp-gateway validate` prints them; `--strict` exits with code 2.
50
+ - `GET /api/v1/security`: auth strategy, warnings, key hygiene counts and upcoming expiries, JWT settings, effective
51
+ security settings and lockout state — never key material; scoped clients get `403`. The dashboard's *Connect*
52
+ page shows it as a *Security posture* card (demo mock updated).
53
+ - `SECURITY.md`: hardening table and scope; `docs/deployment.md` security checklist extended.
54
+
55
+ ### Fixed (security)
56
+ - Unexpected errors (500) no longer return their message and stack trace whenever `NODE_ENV` was not `production`
57
+ (the default for `npm i -g` installs). They are shown only with `NODE_ENV=development` or the new
58
+ `security.exposeErrorDetails: true`. Deliberate `GatewayError` details are unchanged.
59
+
60
+ ### Added (MCP)
61
+ - **Progress notifications**: a single `tools/call` with `params._meta.progressToken` from a client that accepts
62
+ `text/event-stream` is forwarded with a gateway-generated token; upstream `notifications/progress` are mapped back
63
+ and the reply becomes an SSE stream (progress events, then the result). Plain JSON otherwise.
64
+ - **Logging**: `logging` capability, `logging/setLevel` per session; upstream `notifications/message` are forwarded to
65
+ sessions in scope at or above their level (`logger: "<serverId>/<logger>"`), and upstream servers announcing
66
+ `logging` are set to the most verbose level any session requested (re-applied after reconnects).
67
+ - **Completion**: `completions` capability and `completion/complete` routed by prompt (exposed name translated back)
68
+ or resource template / URI; servers without the capability answer an empty completion.
69
+ - **Resource subscriptions**: `resources.subscribe` capability, `resources/subscribe` / `resources/unsubscribe`
70
+ (routed like `resources/read`; `-32601` when the owning server does not support subscriptions) and
71
+ `notifications/resources/updated` forwarding. One upstream subscription per (server, URI) is shared and
72
+ reference-counted across sessions, released when the last session unsubscribes or ends, and restored after an
73
+ upstream reconnect.
74
+ - Proxy: `RequestOptions.onProgress`, `connected` and `notification` events.
75
+ - Library exports: `hashApiKey`, `isHashedKey`, `buildJwtVerifier`, `HMAC_ALGORITHMS`, `ASYMMETRIC_ALGORITHMS`,
76
+ `redactString`, `redactValue`, `redactArgs`, `configureRedaction`, `securityWarnings`, `AuthLockout`,
77
+ `createIpMatcher`, `hostAllowed`, `dashboardCsp`, `inlineScriptHashes`, `LOG_LEVELS`; types `SecurityConfig`,
78
+ `JwtConfig`, `AuthLockoutConfig`, `SecurityWarning`, `McpLogLevel`, `RequestOptions`, `ProgressUpdate`.
79
+
80
+ ### Changed
81
+ - `auth.jwtSecret` is no longer required for `strategy: jwt` when `auth.jwt.publicKey` or `auth.jwt.jwksUrl` is set.
82
+ - `initialize` on `/mcp` now also announces `logging`, `completions` and `resources.subscribe` (clients ignore
83
+ capabilities they do not use).
84
+ - JWT verification failures are logged with the client IP.
85
+
86
+ ### Upgrade notes
87
+ - No configuration changes are required. New behaviour that is on by default: security headers (disable with
88
+ `security.headers: false`), secret redaction in logs / recorded error messages, hidden 500 error details, and the
89
+ extra capabilities on `/mcp`.
90
+ - If you embedded the dashboard in an `<iframe>` on another origin, set `security.headers: false` (the new
91
+ `frame-ancestors 'none'` / `X-Frame-Options: DENY` block that).
92
+ - Tests or tooling that relied on 500 responses carrying `error.message` / `details` outside production need
93
+ `NODE_ENV=development` or `security.exposeErrorDetails: true`.
94
+ - Everything else (`ipAllowlist`, `allowedHosts`, `dnsRebindingProtection`, `authLockout`, `maxToolArgumentsBytes`,
95
+ `hsts`, `trustProxy`, JWT checks, key expiry) is opt-in.
96
+
12
97
  ## [1.1.0] - 2026-10-07
13
98
 
14
99
  ### Added
package/README.md CHANGED
@@ -21,6 +21,8 @@ Route · Authenticate · Rate-limit · Monitor — all your [Model Context Proto
21
21
 
22
22
  ---
23
23
 
24
+ > **Live demo:** try the dashboard with simulated traffic — <https://harrisoncn.github.io/mcp-gateway/> (runs entirely in your browser).
25
+
24
26
  ## The Problem
25
27
 
26
28
  As [MCP](https://modelcontextprotocol.io) becomes the standard protocol for AI agents to interact with tools, teams are running **dozens of MCP servers** — filesystem, GitHub, databases, Slack, search, and more. Managing them is chaos:
@@ -63,10 +65,11 @@ As [MCP](https://modelcontextprotocol.io) becomes the standard protocol for AI a
63
65
  ## Features
64
66
 
65
67
  - **Unified API endpoint** — one URL for all your MCP tools, auto-routed by tool name
66
- - **MCP endpoint for clients** — `/mcp` speaks MCP Streamable HTTP (2025-06-18 / 2025-03-26), so Claude Code, Cursor or any MCP client sees every upstream tool through one server, with the same auth, limits and metrics
68
+ - **MCP endpoint for clients** — `/mcp` speaks MCP Streamable HTTP (2025-06-18 / 2025-03-26), so Claude Code, Cursor or any MCP client sees every upstream tool through one server, with the same auth, limits and metrics — including progress notifications, cancellation, logging, completions and resource subscriptions
67
69
  - **Every MCP transport** — `stdio`, `streamable-http` (current spec), legacy `sse` (HTTP+SSE) and `websocket` upstream servers, with per-server headers for upstream auth
68
70
  - **Automatic reconnect** — crashed or disconnected servers are reconnected with exponential backoff + jitter; state is visible in `/servers`, `/health`, the dashboard and Prometheus
69
- - **Authentication** — API key (constant-time compare), JWT (HS256/384/512), or no-auth; misconfiguration fails closed
71
+ - **Authentication** — API keys (constant-time compare, storable as `sha256:` digests, with expiry), JWT (HMAC secret, PEM public key or JWKS URL; issuer / audience / exp checks), or no-auth; misconfiguration fails closed
72
+ - **Hardening** — security headers + hash-based CSP, IP allowlist, Host / Origin checks against DNS rebinding, body and argument size limits, brute-force lockout, secret redaction in logs and history, startup security warnings (`mcp-gateway validate --strict`)
70
73
  - **Rate limiting** — per-key sliding-window counter, with standard `X-RateLimit-*` headers
71
74
  - **Per-key scopes** — restrict an API key (or a JWT via claims) to some servers / tools and give it its own rate limit; enforced on REST and `/mcp`
72
75
  - **Concurrency limits** — per-server `maxConcurrency`, queued requests count against `timeout`
@@ -199,9 +202,9 @@ What the endpoint does:
199
202
 
200
203
  | | |
201
204
  |---|---|
202
- | `POST /mcp` | JSON-RPC: `initialize`, `ping`, `tools/list` (paginated), `tools/call`, `resources/list`, `resources/templates/list`, `resources/read`, `prompts/list`, `prompts/get`, notifications (incl. `notifications/cancelled`). Batches are accepted. Responses are `application/json`. |
203
- | `GET /mcp` | SSE stream for server→client notifications: `notifications/tools/list_changed`, `notifications/resources/list_changed` and `notifications/prompts/list_changed` are sent when the aggregated list a session sees changes (a server announces changes, connects, is removed by hot reload, …). |
204
- | Resources & prompts | Resource URIs are passed through unchanged; when two servers list the same URI the lowest server id wins. `resources/read` is routed by exact URI, then by resource template, then to the only server with resources. Prompt names follow `toolNaming` like tools. `resources/subscribe` is not offered. |
205
+ | `POST /mcp` | JSON-RPC: `initialize`, `ping`, `tools/list` (paginated), `tools/call`, `resources/list`, `resources/templates/list`, `resources/read`, `resources/subscribe` / `unsubscribe`, `prompts/list`, `prompts/get`, `logging/setLevel`, `completion/complete`, notifications (incl. `notifications/cancelled`). Batches are accepted. Responses are `application/json`; a single `tools/call` with `_meta.progressToken` switches to an SSE reply when the upstream reports progress (`notifications/progress`, then the result). |
206
+ | `GET /mcp` | SSE stream for server→client notifications: `notifications/tools/list_changed`, `notifications/resources/list_changed` and `notifications/prompts/list_changed` are sent when the aggregated list a session sees changes (a server announces changes, connects, is removed by hot reload, …); `notifications/resources/updated` for subscribed URIs; upstream `notifications/message` at or above the session's `logging/setLevel` level (`logger` = `<serverId>/<logger>`). |
207
+ | Resources & prompts | Resource URIs are passed through unchanged; when two servers list the same URI the lowest server id wins. `resources/read` is routed by exact URI, then by resource template, then to the only server with resources. Prompt names follow `toolNaming` like tools. `resources/subscribe` is routed the same way; sessions share one upstream subscription per URI, restored after reconnects. `completion/complete` is routed by prompt name or resource template. |
205
208
  | `DELETE /mcp` | Ends the session. |
206
209
  | Sessions | `initialize` returns `Mcp-Session-Id`; later requests must send it (`400` if missing, `404` if unknown or expired). A session is bound to the API key / JWT subject that created it. Idle sessions expire after `mcp.sessionIdleTimeoutSeconds`. |
207
210
  | Tool names | `toolNaming: auto` (default) keeps a tool's name unless two servers expose the same name; then every copy becomes `<serverId>__<tool>`. `prefix` always uses `<serverId>__<tool>`. Ordering is deterministic (server id, then tool name). In `auto` mode the prefixed form is also accepted by `tools/call`. |
@@ -292,11 +295,13 @@ auth:
292
295
  strategy: api-key # none | api-key | jwt (oauth2 is not implemented and is rejected)
293
296
  apiKeys:
294
297
  - "your-secret-key" # full access
298
+ - "sha256:…" # a key stored as its digest (mcp-gateway gen-key / hash-key)
295
299
  - key: "${APP_KEY}" # scoped key (see "Per-key scopes")
296
300
  name: app
297
301
  servers: ["github"]
298
302
  tools: ["read_*"]
299
303
  rateLimit: { limit: 30, windowSeconds: 60 }
304
+ expiresAt: "2027-01-01" # optional expiry; disabled: true switches a key off
300
305
  protect:
301
306
  health: false # true → /api/v1/health requires auth
302
307
  metrics: false # true → /api/v1/metrics requires auth (configure your scraper)
@@ -323,6 +328,12 @@ monitor:
323
328
  prometheus: true # Enable Prometheus /metrics
324
329
  retentionHours: 24 # Metrics retention
325
330
 
331
+ security: # hardening (see docs/configuration.md#security)
332
+ authLockout: true # 429 for IPs with repeated auth failures
333
+ dnsRebindingProtection: false # true for a local gateway without auth
334
+ ipAllowlist: ["10.0.0.0/8"]
335
+ maxToolArgumentsBytes: 262144
336
+
326
337
  corsOrigins:
327
338
  - "https://your-app.com"
328
339
 
@@ -568,6 +579,18 @@ await gateway.start();
568
579
  process.on('SIGTERM', () => gateway.stop());
569
580
  ```
570
581
 
582
+ ## What's New in v1.2
583
+
584
+ | Feature | Description |
585
+ |---------|-------------|
586
+ | **Hashed & expiring keys** | `auth.apiKeys` entries can be `sha256:<hex>` digests (`mcp-gateway gen-key`, `hash-key`) and carry `expiresAt` / `disabled` |
587
+ | **JWT hardening** | `auth.jwt`: `issuer`, `audience`, `algorithms` (HMAC / asymmetric never mixed), `clockToleranceSeconds`, `requireExp`, `maxTokenAgeSeconds`, PEM `publicKey` or cached `jwksUrl` (works with OAuth 2.0 / OIDC providers) |
588
+ | **`security` block** | headers + dashboard CSP, `hsts`, `trustProxy`, `ipAllowlist`, `allowedHosts`, `dnsRebindingProtection`, `maxBodyBytes`, `maxToolArgumentsBytes`, `authLockout`, `redactPatterns` — all hot reloadable |
589
+ | **Secure-defaults check** | startup warnings, `validate --strict`, `GET /api/v1/security` and a *Security posture* card in the dashboard |
590
+ | **More of MCP on `/mcp`** | `notifications/progress` (SSE replies), `logging/setLevel` + forwarded `notifications/message`, `completion/complete`, `resources/subscribe` / `unsubscribe` + `notifications/resources/updated` |
591
+
592
+ Details and upgrade notes: [CHANGELOG](CHANGELOG.md#120---2026-10-07).
593
+
571
594
  ## What's New in v1.0
572
595
 
573
596
  | Feature | Description |
@@ -617,8 +640,11 @@ unknown fields). Deep imports, log format, the dashboard and the audit database
617
640
  | JS / Kotlin clients, OpenAI / Anthropic tool schemas | ✅ Done (v1.0) |
618
641
  | Resources & prompts passthrough, persistent audit log | ✅ Done (v1.0) |
619
642
  | Stable API, docs, container image | ✅ Done (v1.0) |
643
+ | Security hardening (hashed keys, JWKS, lockout, DNS-rebinding guard, CSP) | ✅ Done (v1.2) |
644
+ | Progress, logging, completions, resource subscriptions on `/mcp` | ✅ Done (v1.2) |
620
645
  | Redis-backed rate limiting | 📋 Planned |
621
- | OAuth2 / OIDC auth | 📋 Planned |
646
+ | OAuth2 / OIDC auth | 🟡 JWT access tokens via `auth.jwt.jwksUrl` (v1.2); discovery / token introspection planned |
647
+ | Forwarding sampling / elicitation / roots requests to downstream clients | 📋 Planned |
622
648
  | Tool-level access control | ✅ Done via per-key scopes (v0.6) |
623
649
  | Request replay & debugging | 📋 Planned (history is available via the audit log) |
624
650
  | Multi-tenant mode | 📋 Planned |
@@ -70,3 +70,7 @@ servers and tools.
70
70
  |---|---|
71
71
  | ![Onboarding](../docs/images/dashboard-onboarding-try-tool.png) | ![Playground (中文)](../docs/images/dashboard-playground-zh.png) |
72
72
  | ![Light theme](../docs/images/dashboard-overview-light.png) | ![Mobile](../docs/images/dashboard-mobile-zh.png) |
73
+
74
+ ## Demo build
75
+
76
+ `demo/mock.js` replaces `fetch` for the gateway API with an in-browser simulation (servers, tools, request stream, SSE events). The GitHub Pages workflow (`.github/workflows/pages.yml`) injects it before the dashboard script and publishes the result to <https://harrisoncn.github.io/mcp-gateway/>. It is not shipped in the npm package or Docker image.
@@ -196,6 +196,12 @@ main { max-width: 1320px; margin: 0 auto; padding: 20px 16px 96px; }
196
196
  .kv b { font-size: 13.5px; font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
197
197
  .srv .err { color: var(--bad); font-size: 12px; overflow-wrap: anywhere; }
198
198
  .chips { display: flex; flex-wrap: wrap; gap: 6px; }
199
+ .sec-card { margin-top: 14px; }
200
+ .sec-card .kv { margin-bottom: 12px; }
201
+ .sec-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
202
+ .sec-list li { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; line-height: 1.45; color: var(--text); }
203
+ .sec-list li .badge { flex: none; margin-top: 1px; }
204
+ @media (max-width: 560px) { .sec-card .kv { grid-template-columns: repeat(2, 1fr); } }
199
205
  .chip { font-family: var(--mono); font-size: 11.5px; padding: 3px 8px; border-radius: 7px; background: var(--surface-2); border: 1px solid var(--border); cursor: pointer; transition: border-color .2s, transform .15s var(--ease); }
200
206
  .chip:hover { border-color: var(--accent); }
201
207
  .chip:active { transform: scale(.95); }
@@ -478,6 +484,7 @@ tbody tr:hover { background: var(--surface-2); }
478
484
  <section class="view" id="view-connect" role="tabpanel" aria-labelledby="tab-connect">
479
485
  <div class="page-head"><div><h1 data-i18n="cnTitle">Connect a client</h1><p data-i18n="cnSub">Point any MCP client at this gateway</p></div></div>
480
486
  <div class="card" id="connectCard"></div>
487
+ <div class="card sec-card" id="securityCard" hidden></div>
481
488
  </section>
482
489
  </main>
483
490
 
@@ -567,6 +574,7 @@ const I18N = {
567
574
  via: 'Via', kind: 'Kind', any: 'Any', ok: 'OK', failed: 'Failed', apply: 'Apply', reset: 'Reset', time: 'Time', duration: 'Duration',
568
575
  prev: '← Newer', next: 'Older →', page: 'Page {n}', srcAudit: 'Persistent audit log', srcMemory: 'In-memory log',
569
576
  cnTitle: 'Connect a client', cnSub: 'Point any MCP client at this gateway',
577
+ secTitle: 'Security posture', secOk: 'No issues', secIssues: '{n} to review', secAuth: 'Auth', secKeys: 'Hashed keys', secLockout: 'Lockout', secRebind: 'DNS-rebinding guard', secHeaders: 'Security headers', secLocked: 'Locked IPs', secOn: 'on', secOff: 'off', secWarn: 'warning', secHint: 'hint', secNone: 'Nothing to report.',
570
578
  ob1Eye: 'Step 1 · Connect', ob1Title: 'Welcome to your MCP gateway',
571
579
  ob1Lead: "One endpoint for every MCP server — with auth, rate limits and live metrics. Let's get you connected in under a minute.",
572
580
  hero1: 'Many servers', hero1s: 'stdio, SSE, WebSocket, HTTP', hero2: 'One secure door', hero2s: 'keys, scopes, limits', hero3: 'Live insight', hero3s: 'latency, errors, usage',
@@ -611,6 +619,7 @@ const I18N = {
611
619
  via: '通道', kind: '类型', any: '全部', ok: '成功', failed: '失败', apply: '应用', reset: '重置', time: '时间', duration: '耗时',
612
620
  prev: '← 较新', next: '较早 →', page: '第 {n} 页', srcAudit: '持久化审计日志', srcMemory: '内存日志',
613
621
  cnTitle: '接入客户端', cnSub: '让任意 MCP 客户端连接到本网关',
622
+ secTitle: '安全状态', secOk: '无问题', secIssues: '{n} 项待处理', secAuth: '认证', secKeys: '已哈希的密钥', secLockout: '失败锁定', secRebind: 'DNS 重绑定防护', secHeaders: '安全响应头', secLocked: '已锁定 IP', secOn: '开', secOff: '关', secWarn: '警告', secHint: '建议', secNone: '暂无可报告的问题。',
614
623
  ob1Eye: '第 1 步 · 连接', ob1Title: '欢迎使用 MCP 网关',
615
624
  ob1Lead: '一个入口接入所有 MCP 服务器——自带鉴权、限流与实时监控。一分钟内即可完成连接。',
616
625
  hero1: '多个服务器', hero1s: 'stdio、SSE、WebSocket、HTTP', hero2: '统一安全入口', hero2s: '密钥、权限范围、限流', hero3: '实时洞察', hero3s: '延迟、错误、用量',
@@ -672,7 +681,7 @@ const fmtNum = (v, d = 0) => (v == null || !isFinite(v) ? '—' : v >= 1e6 ? (v
672
681
  const fmtTime = (ts) => new Date(ts).toLocaleTimeString(lang === 'zh' ? 'zh-CN' : undefined, { hour12: false });
673
682
  const fmtDateTime = (ts) => new Date(ts).toLocaleString(lang === 'zh' ? 'zh-CN' : undefined, { hour12: false, month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' });
674
683
  const statusCls = (s) => ({ online: 'ok', degraded: 'warn', reconnecting: 'info', offline: 'bad' })[s] || '';
675
- const ORIGIN = location.origin;
684
+ const ORIGIN = window.__MCP_GATEWAY_DEMO__ ? 'http://localhost:4000' : location.origin;
676
685
  const ICON = {
677
686
  overview: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 13h4l3-8 4 14 3-6h4"/></svg>',
678
687
  servers: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="6" rx="2"/><rect x="3" y="14" width="18" height="6" rx="2"/><path d="M7 7h.01M7 17h.01"/></svg>',
@@ -828,7 +837,7 @@ function go(view, push = true) {
828
837
  if (view === 'servers') loadServers();
829
838
  if (view === 'playground') renderPlayground($('#pgMain'), 'main');
830
839
  if (view === 'history') loadHistory();
831
- if (view === 'connect') renderSnippets($('#connectCard'), 'main');
840
+ if (view === 'connect') { renderSnippets($('#connectCard'), 'main'); loadSecurity(); }
832
841
  if (view === 'overview') requestAnimationFrame(renderCharts);
833
842
  }
834
843
  window.addEventListener('hashchange', () => go(location.hash.slice(1), false));
@@ -1537,6 +1546,36 @@ document.addEventListener('keydown', (e) => {
1537
1546
  }
1538
1547
  });
1539
1548
 
1549
+ // ─── Security posture (Connect page) ─────────────────────────────────────────
1550
+ async function loadSecurity() {
1551
+ try { state.security = await api('/security'); }
1552
+ catch { state.security = null; } // older gateway, scoped key or no access: hide the card
1553
+ renderSecurity();
1554
+ }
1555
+ function renderSecurity() {
1556
+ const card = $('#securityCard');
1557
+ const s = state.security;
1558
+ if (!s || !Array.isArray(s.warnings)) { card.hidden = true; return; }
1559
+ card.hidden = false;
1560
+ const warns = s.warnings.filter((w) => w.level === 'warn').length;
1561
+ const onOff = (v) => esc(v ? t('secOn') : t('secOff'));
1562
+ const k = s.apiKeys || { total: 0, hashed: 0 };
1563
+ const facts = [
1564
+ [t('secAuth'), esc(s.authStrategy)],
1565
+ [t('secKeys'), k.total ? `${k.hashed}/${k.total}` : '—'],
1566
+ [t('secLockout'), onOff(s.settings?.authLockout)],
1567
+ [t('secRebind'), onOff(s.settings?.dnsRebindingProtection)],
1568
+ [t('secHeaders'), onOff(s.settings?.headers)],
1569
+ [t('secLocked'), s.lockout ? String(s.lockout.lockedClients) : '—'],
1570
+ ];
1571
+ card.innerHTML =
1572
+ `<h2><span>${esc(t('secTitle'))}</span><span class="badge ${warns ? 'warn' : 'ok'}" style="margin-left:auto">${esc(warns ? t('secIssues').replace('{n}', warns) : t('secOk'))}</span></h2>` +
1573
+ `<div class="kv">${facts.map(([a, b]) => `<div><span>${esc(a)}</span><b>${b}</b></div>`).join('')}</div>` +
1574
+ (s.warnings.length
1575
+ ? `<ul class="sec-list">${s.warnings.map((w) => `<li><span class="badge ${w.level === 'warn' ? 'warn' : 'info'}">${esc(w.level === 'warn' ? t('secWarn') : t('secHint'))}</span><span>${esc(w.message)}</span></li>`).join('')}</ul>`
1576
+ : `<div class="empty">${esc(t('secNone'))}</div>`);
1577
+ }
1578
+
1540
1579
  // ─── Language ────────────────────────────────────────────────────────────────
1541
1580
  $('#langBtn').addEventListener('click', () => transition(() => {
1542
1581
  lang = lang === 'zh' ? 'en' : 'zh';
@@ -1545,7 +1584,7 @@ $('#langBtn').addEventListener('click', () => transition(() => {
1545
1584
  if (state.servers.length) renderServers();
1546
1585
  delete $('#pgMain').dataset.built; delete $('#obPg').dataset.built;
1547
1586
  if (state.view === 'playground') renderPlayground($('#pgMain'), 'main');
1548
- if (state.view === 'connect') renderSnippets($('#connectCard'), 'main');
1587
+ if (state.view === 'connect') { renderSnippets($('#connectCard'), 'main'); renderSecurity(); }
1549
1588
  if (state.view === 'history') loadHistory();
1550
1589
  if (!$('#ob').hidden) showStep(obState.step);
1551
1590
  }));
@@ -10,8 +10,17 @@
10
10
  * - The client id attached to a request is a hash fingerprint of the key,
11
11
  * never a prefix of the key itself (prefixes were leaking into logs and the
12
12
  * /requests endpoint, and keys sharing a prefix shared a rate-limit bucket).
13
+ * - Keys may be stored as SHA-256 digests (`sha256:<hex>`), so the config
14
+ * file never holds a usable key. A digest key and its plain form produce
15
+ * the same client id.
16
+ * - Keys can expire (`expiresAt`) or be switched off (`disabled`).
17
+ * - JWTs: HMAC secret, PEM public key or JWKS URL; algorithm allowlist that
18
+ * never mixes HMAC and asymmetric algorithms; optional issuer / audience /
19
+ * exp / max-age checks and clock tolerance.
13
20
  */
21
+ import { type KeyObject } from 'crypto';
14
22
  import type { Request, RequestHandler } from 'express';
23
+ import { type JWTVerifyGetKey, type JWTVerifyOptions } from 'jose';
15
24
  import type { ApiKeyConfig, AuthConfig } from '../utils/types.js';
16
25
  import { type AccessScope } from './scopes.js';
17
26
  export type AuthedRequest = Request & {
@@ -34,10 +43,30 @@ export type AuthMiddleware = RequestHandler & {
34
43
  };
35
44
  /** Normalise `auth.apiKeys` entries (plain strings or objects). */
36
45
  export declare function normalizeApiKeys(keys: AuthConfig['apiKeys']): ApiKeyConfig[];
46
+ export declare const HMAC_ALGORITHMS: string[];
47
+ export declare const ASYMMETRIC_ALGORITHMS: string[];
48
+ /** Whether a configured key is a `sha256:<hex>` digest rather than the key itself. */
49
+ export declare function isHashedKey(key: string): boolean;
50
+ /** `sha256:<hex>` digest of a key, the form to store in the config file. */
51
+ export declare function hashApiKey(key: string): string;
52
+ /** Expiry of a key entry as epoch ms (undefined = never; NaN for an unparsable date). */
53
+ export declare function keyExpiry(entry: ApiKeyConfig): number | undefined;
54
+ /** Why a key is currently unusable, or undefined when it is active. */
55
+ export declare function keyInactiveReason(entry: ApiKeyConfig, now?: number): 'disabled' | 'expired' | undefined;
37
56
  export declare function createAuthMiddleware(config?: AuthConfig): AuthMiddleware;
38
57
  export declare function fingerprint(key: string): string;
39
58
  /** Constant-time membership check against a pre-hashed key list. */
40
59
  export declare function matchesAnyKey(candidate: string, keyDigests: Buffer[]): boolean;
41
60
  /** Constant-time lookup: index of the matching key digest, or -1. */
42
61
  export declare function matchKeyIndex(candidate: string, keyDigests: Buffer[]): number;
62
+ export interface JwtVerifier {
63
+ key: Uint8Array | KeyObject | JWTVerifyGetKey;
64
+ options: JWTVerifyOptions;
65
+ }
66
+ /**
67
+ * Key source + verification options for the `jwt` strategy. Throws on
68
+ * misconfiguration (missing / conflicting key sources, algorithms that do not
69
+ * fit the key type, non-HTTPS JWKS URL).
70
+ */
71
+ export declare function buildJwtVerifier(config: AuthConfig): JwtVerifier;
43
72
  //# sourceMappingURL=middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,OAAO,EAA0B,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/E,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,kGAAkG;AAClG,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG;IAC5C;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,KAAK;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,CAAC;CAC3F,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,GAAG,YAAY,EAAE,CAI5E;AAaD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,cAAc,CAmCxE;AAMD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAE9E;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAQ7E"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAgD,KAAK,SAAS,EAAE,MAAM,QAAQ,CAAC;AACtF,OAAO,KAAK,EAAE,OAAO,EAA0B,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,EAAiC,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,MAAM,CAAC;AAClG,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAa,MAAM,mBAAmB,CAAC;AAE7E,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,kGAAkG;AAClG,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG;IAC5C;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,KAAK;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,CAAC;CAC3F,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,GAAG,YAAY,EAAE,CAI5E;AAWD,eAAO,MAAM,eAAe,UAA8B,CAAC;AAC3D,eAAO,MAAM,qBAAqB,UAKjC,CAAC;AAIF,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,yFAAyF;AACzF,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAGjE;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,SAAa,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,CAK3G;AAED,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,cAAc,CA4BxE;AAYD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAE9E;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAQ7E;AAsDD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,UAAU,GAAG,SAAS,GAAG,eAAe,CAAC;IAC9C,OAAO,EAAE,gBAAgB,CAAC;CAC3B;AAID;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,CAyDhE"}
@@ -10,9 +10,16 @@
10
10
  * - The client id attached to a request is a hash fingerprint of the key,
11
11
  * never a prefix of the key itself (prefixes were leaking into logs and the
12
12
  * /requests endpoint, and keys sharing a prefix shared a rate-limit bucket).
13
+ * - Keys may be stored as SHA-256 digests (`sha256:<hex>`), so the config
14
+ * file never holds a usable key. A digest key and its plain form produce
15
+ * the same client id.
16
+ * - Keys can expire (`expiresAt`) or be switched off (`disabled`).
17
+ * - JWTs: HMAC secret, PEM public key or JWKS URL; algorithm allowlist that
18
+ * never mixes HMAC and asymmetric algorithms; optional issuer / audience /
19
+ * exp / max-age checks and clock tolerance.
13
20
  */
14
- import { createHash, timingSafeEqual } from 'crypto';
15
- import { jwtVerify } from 'jose';
21
+ import { createHash, createPublicKey, timingSafeEqual } from 'crypto';
22
+ import { createRemoteJWKSet, jwtVerify } from 'jose';
16
23
  import { logger } from '../utils/logger.js';
17
24
  import { scopeFromJwt } from './scopes.js';
18
25
  /** Normalise `auth.apiKeys` entries (plain strings or objects). */
@@ -33,7 +40,37 @@ function scopeOf(entry) {
33
40
  scope.rateLimit = { ...entry.rateLimit, perKey: true };
34
41
  return Object.keys(scope).length > 0 ? scope : undefined;
35
42
  }
36
- const JWT_ALGORITHMS = ['HS256', 'HS384', 'HS512'];
43
+ export const HMAC_ALGORITHMS = ['HS256', 'HS384', 'HS512'];
44
+ export const ASYMMETRIC_ALGORITHMS = [
45
+ 'RS256', 'RS384', 'RS512',
46
+ 'PS256', 'PS384', 'PS512',
47
+ 'ES256', 'ES384', 'ES512',
48
+ 'EdDSA',
49
+ ];
50
+ const HASHED_KEY_RE = /^sha256:([0-9a-f]{64})$/i;
51
+ /** Whether a configured key is a `sha256:<hex>` digest rather than the key itself. */
52
+ export function isHashedKey(key) {
53
+ return HASHED_KEY_RE.test(key);
54
+ }
55
+ /** `sha256:<hex>` digest of a key, the form to store in the config file. */
56
+ export function hashApiKey(key) {
57
+ return `sha256:${createHash('sha256').update(key, 'utf8').digest('hex')}`;
58
+ }
59
+ /** Expiry of a key entry as epoch ms (undefined = never; NaN for an unparsable date). */
60
+ export function keyExpiry(entry) {
61
+ if (entry.expiresAt === undefined)
62
+ return undefined;
63
+ return Date.parse(entry.expiresAt);
64
+ }
65
+ /** Why a key is currently unusable, or undefined when it is active. */
66
+ export function keyInactiveReason(entry, now = Date.now()) {
67
+ if (entry.disabled)
68
+ return 'disabled';
69
+ const exp = keyExpiry(entry);
70
+ if (exp !== undefined && !(exp > now))
71
+ return 'expired';
72
+ return undefined;
73
+ }
37
74
  export function createAuthMiddleware(config) {
38
75
  if (!config || config.strategy === 'none') {
39
76
  const none = (_req, _res, next) => next();
@@ -56,15 +93,8 @@ export function createAuthMiddleware(config) {
56
93
  }
57
94
  return apiKeyMiddleware(keys);
58
95
  }
59
- case 'jwt': {
60
- if (!config.jwtSecret) {
61
- throw new Error('auth.strategy is "jwt" but auth.jwtSecret is not configured');
62
- }
63
- if (config.jwtSecret.length < 32) {
64
- logger.warn('auth.jwtSecret is shorter than 32 characters; use a longer random secret');
65
- }
66
- return jwtMiddleware(config.jwtSecret);
67
- }
96
+ case 'jwt':
97
+ return jwtMiddleware(buildJwtVerifier(config));
68
98
  default:
69
99
  // Fail closed. Previously this fell back to *no auth*, so configuring the
70
100
  // advertised-but-unimplemented "oauth2" strategy exposed every endpoint.
@@ -74,7 +104,15 @@ export function createAuthMiddleware(config) {
74
104
  function digest(value) {
75
105
  return createHash('sha256').update(value, 'utf8').digest();
76
106
  }
107
+ /** Digest of a configured key: the stored digest for `sha256:` keys, else SHA-256 of the key. */
108
+ function configuredDigest(key) {
109
+ const m = HASHED_KEY_RE.exec(key);
110
+ return m ? Buffer.from(m[1], 'hex') : digest(key);
111
+ }
77
112
  export function fingerprint(key) {
113
+ const m = HASHED_KEY_RE.exec(key);
114
+ if (m)
115
+ return m[1].toLowerCase().slice(0, 12);
78
116
  return createHash('sha256').update(key, 'utf8').digest('hex').slice(0, 12);
79
117
  }
80
118
  /** Constant-time membership check against a pre-hashed key list. */
@@ -103,7 +141,12 @@ function extractKey(req) {
103
141
  return undefined;
104
142
  }
105
143
  function apiKeyMiddleware(entries) {
106
- const keyDigests = entries.map((e) => digest(e.key));
144
+ for (const e of entries) {
145
+ if (e.expiresAt !== undefined && Number.isNaN(keyExpiry(e))) {
146
+ throw new Error(`auth.apiKeys: invalid expiresAt "${e.expiresAt}"${e.name ? ` (key "${e.name}")` : ''}`);
147
+ }
148
+ }
149
+ const keyDigests = entries.map((e) => configuredDigest(e.key));
107
150
  const clients = entries.map((e) => ({
108
151
  clientId: e.name ? `key:${e.name}` : `key:${fingerprint(e.key)}`,
109
152
  scope: scopeOf(e),
@@ -118,6 +161,12 @@ function apiKeyMiddleware(entries) {
118
161
  return;
119
162
  }
120
163
  const client = clients[index];
164
+ const inactive = keyInactiveReason(entries[index]);
165
+ if (inactive) {
166
+ logger.warn(`Unauthorized request from ${req.ip}: API key ${client.clientId} is ${inactive}`);
167
+ res.status(401).json({ error: 'Unauthorized', message: 'Valid API key required' });
168
+ return;
169
+ }
121
170
  req.clientId = client.clientId;
122
171
  if (client.scope)
123
172
  req.scope = client.scope;
@@ -125,12 +174,87 @@ function apiKeyMiddleware(entries) {
125
174
  };
126
175
  mw.resolveClient = (clientId) => {
127
176
  const c = clientId ? byClientId.get(clientId) : undefined;
128
- return c ? { known: true, scope: c.scope } : { known: false };
177
+ if (!c || keyInactiveReason(entries[clients.indexOf(c)]))
178
+ return { known: false };
179
+ return { known: true, scope: c.scope };
129
180
  };
130
181
  return mw;
131
182
  }
132
- function jwtMiddleware(secret) {
133
- const secretKey = new TextEncoder().encode(secret);
183
+ const asList = (v) => (v === undefined ? undefined : Array.isArray(v) ? v : [v]);
184
+ /**
185
+ * Key source + verification options for the `jwt` strategy. Throws on
186
+ * misconfiguration (missing / conflicting key sources, algorithms that do not
187
+ * fit the key type, non-HTTPS JWKS URL).
188
+ */
189
+ export function buildJwtVerifier(config) {
190
+ const jwt = config.jwt ?? {};
191
+ const sources = [config.jwtSecret ? 'jwtSecret' : '', jwt.publicKey ? 'jwt.publicKey' : '', jwt.jwksUrl ? 'jwt.jwksUrl' : ''].filter(Boolean);
192
+ if (sources.length === 0) {
193
+ throw new Error('auth.strategy is "jwt" but none of auth.jwtSecret, auth.jwt.publicKey or auth.jwt.jwksUrl is configured');
194
+ }
195
+ if (sources.length > 1)
196
+ throw new Error(`auth: configure only one of ${sources.join(', ')}`);
197
+ const hmac = !!config.jwtSecret;
198
+ const allowed = hmac ? HMAC_ALGORITHMS : ASYMMETRIC_ALGORITHMS;
199
+ const algorithms = jwt.algorithms ?? allowed;
200
+ if (algorithms.length === 0)
201
+ throw new Error('auth.jwt.algorithms must not be empty');
202
+ const bad = algorithms.filter((a) => !allowed.includes(a));
203
+ if (bad.length > 0) {
204
+ throw new Error(`auth.jwt.algorithms: ${bad.join(', ')} cannot be used with ${sources[0]} (allowed: ${allowed.join(', ')})`);
205
+ }
206
+ let key;
207
+ if (config.jwtSecret) {
208
+ if (config.jwtSecret.length < 32) {
209
+ logger.warn('auth.jwtSecret is shorter than 32 characters; use a longer random secret');
210
+ }
211
+ key = new TextEncoder().encode(config.jwtSecret);
212
+ }
213
+ else if (jwt.publicKey) {
214
+ try {
215
+ key = createPublicKey(jwt.publicKey);
216
+ }
217
+ catch (err) {
218
+ throw new Error(`auth.jwt.publicKey is not a valid PEM public key: ${err instanceof Error ? err.message : String(err)}`);
219
+ }
220
+ }
221
+ else {
222
+ let url;
223
+ try {
224
+ url = new URL(jwt.jwksUrl);
225
+ }
226
+ catch {
227
+ throw new Error(`auth.jwt.jwksUrl is not a valid URL: ${jwt.jwksUrl}`);
228
+ }
229
+ const local = ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
230
+ if (url.protocol !== 'https:' && !(url.protocol === 'http:' && local)) {
231
+ throw new Error('auth.jwt.jwksUrl must use https:// (http:// is only allowed for localhost)');
232
+ }
233
+ key = createRemoteJWKSet(url, {
234
+ cacheMaxAge: (jwt.jwksCacheSeconds ?? 600) * 1000,
235
+ cooldownDuration: 30_000,
236
+ timeoutDuration: 5_000,
237
+ });
238
+ }
239
+ const options = { algorithms };
240
+ const issuer = asList(jwt.issuer);
241
+ const audience = asList(jwt.audience);
242
+ if (issuer)
243
+ options.issuer = issuer;
244
+ if (audience)
245
+ options.audience = audience;
246
+ if (jwt.clockToleranceSeconds)
247
+ options.clockTolerance = jwt.clockToleranceSeconds;
248
+ if (jwt.requireExp)
249
+ options.requiredClaims = ['exp'];
250
+ if (jwt.maxTokenAgeSeconds)
251
+ options.maxTokenAge = jwt.maxTokenAgeSeconds;
252
+ return { key, options };
253
+ }
254
+ function jwtMiddleware(verifier) {
255
+ const verify = (token) => typeof verifier.key === 'function'
256
+ ? jwtVerify(token, verifier.key, verifier.options)
257
+ : jwtVerify(token, verifier.key, verifier.options);
134
258
  return (req, res, next) => {
135
259
  const authHeader = req.headers.authorization;
136
260
  if (!authHeader || !/^Bearer /i.test(authHeader)) {
@@ -140,7 +264,7 @@ function jwtMiddleware(secret) {
140
264
  const token = authHeader.slice(7).trim();
141
265
  // Two-argument then(): an error thrown downstream of next() must not be
142
266
  // misreported as an auth failure.
143
- jwtVerify(token, secretKey, { algorithms: JWT_ALGORITHMS }).then(({ payload }) => {
267
+ verify(token).then(({ payload }) => {
144
268
  req.jwtPayload = payload;
145
269
  req.clientId = `jwt:${String(payload.sub ?? 'unknown')}`;
146
270
  const scope = scopeFromJwt(payload);
@@ -148,7 +272,7 @@ function jwtMiddleware(secret) {
148
272
  req.scope = scope;
149
273
  next();
150
274
  }, (err) => {
151
- logger.warn(`JWT verification failed: ${err instanceof Error ? err.message : String(err)}`);
275
+ logger.warn(`JWT verification failed from ${req.ip}: ${err instanceof Error ? err.message : String(err)}`);
152
276
  if (!res.headersSent) {
153
277
  res.status(401).json({ error: 'Unauthorized', message: 'Invalid or expired token' });
154
278
  }
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe,EAAkB,MAAM,QAAQ,CAAC;AAEtF,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAA+C,MAAM,MAAM,CAAC;AAElG,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAoB,MAAM,aAAa,CAAC;AAmB7D,mEAAmE;AACnE,MAAM,UAAU,gBAAgB,CAAC,IAA2B;IAC1D,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;SAChB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SACpD,MAAM,CAAC,CAAC,CAAC,EAAqB,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAC5F,CAAC;AAED,SAAS,OAAO,CAAC,KAAmB;IAClC,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,IAAI,KAAK,CAAC,IAAI;QAAE,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxC,IAAI,KAAK,CAAC,OAAO;QAAE,KAAK,CAAC,OAAO,GAAG,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,KAAK,CAAC,KAAK;QAAE,KAAK,CAAC,KAAK,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IAChD,IAAI,KAAK,CAAC,SAAS;QAAE,KAAK,CAAC,SAAS,GAAG,EAAE,GAAG,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC5E,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3D,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC3D,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,OAAO,EAAE,OAAO,EAAE,OAAO;IACzB,OAAO,EAAE,OAAO,EAAE,OAAO;IACzB,OAAO,EAAE,OAAO,EAAE,OAAO;IACzB,OAAO;CACR,CAAC;AAEF,MAAM,aAAa,GAAG,0BAA0B,CAAC;AAEjD,sFAAsF;AACtF,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,OAAO,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjC,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,SAAS,CAAC,KAAmB;IAC3C,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IACpD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;AACrC,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,iBAAiB,CAAC,KAAmB,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IACrE,IAAI,KAAK,CAAC,QAAQ;QAAE,OAAO,UAAU,CAAC;IACtC,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAC7B,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IACxD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,MAAmB;IACtD,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAmB,CAAC,IAAa,EAAE,IAAc,EAAE,IAAkB,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;QAC3F,IAAI,CAAC,aAAa,GAAG,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,QAAQ,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxB,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC9C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtB,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;YACnF,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;YAChC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBACrB,IAAI,CAAC,CAAC,CAAC,IAAI;oBAAE,SAAS;gBACtB,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;oBAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;gBACvF,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;YACD,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;QACD,KAAK,KAAK;YACR,OAAO,aAAa,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAC;QACjD;YACE,0EAA0E;YAC1E,yEAAyE;YACzE,MAAM,IAAI,KAAK,CAAC,kBAAkB,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAC,CAAC;IACvF,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,KAAa;IAC3B,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;AAC7D,CAAC;AAED,iGAAiG;AACjG,SAAS,gBAAgB,CAAC,GAAW;IACnC,MAAM,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/C,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC7E,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,UAAoB;IACnE,OAAO,aAAa,CAAC,SAAS,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,UAAoB;IACnE,MAAM,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5B,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;IACf,oFAAoF;IACpF,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC1B,IAAI,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC;YAAE,KAAK,GAAG,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IACH,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,UAAU,CAAC,GAAY;IAC9B,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IAC7C,IAAI,UAAU,IAAI,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/C,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;IACjD,CAAC;IACD,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9C,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,YAAY,CAAC;IACrF,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAuB;IAC/C,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3G,CAAC;IACH,CAAC;IACD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAClC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE;QAChE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;KAClB,CAAC,CAAC,CAAC;IACJ,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAEhE,MAAM,EAAE,GAAmB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QAC7E,MAAM,GAAG,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAExD,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,CAAC,6BAA6B,GAAG,CAAC,EAAE,8BAA8B,CAAC,CAAC;YAC/E,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC,CAAC;YACnF,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAE,CAAC,CAAC;QACpD,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,6BAA6B,GAAG,CAAC,EAAE,aAAa,MAAM,CAAC,QAAQ,OAAO,QAAQ,EAAE,CAAC,CAAC;YAC9F,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC,CAAC;YACnF,OAAO;QACT,CAAC;QACA,GAAqB,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAClD,IAAI,MAAM,CAAC,KAAK;YAAG,GAAqB,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC9D,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;IACF,EAAE,CAAC,aAAa,GAAG,CAAC,QAAQ,EAAE,EAAE;QAC9B,MAAM,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1D,IAAI,CAAC,CAAC,IAAI,iBAAiB,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAE,CAAC;YAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACnF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IACzC,CAAC,CAAC;IACF,OAAO,EAAE,CAAC;AACZ,CAAC;AAOD,MAAM,MAAM,GAAG,CAAC,CAAgC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhH;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAkB;IACjD,MAAM,GAAG,GAAc,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9I,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,yGAAyG,CAAC,CAAC;IAC7H,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7F,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,qBAAqB,CAAC;IAC/D,MAAM,UAAU,GAAG,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC;IAC7C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IACtF,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,wBAAwB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,OAAO,CAAC,CAAC,CAAC,cAAc,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC5G,CAAC;IACJ,CAAC;IAED,IAAI,GAAuB,CAAC;IAC5B,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;QACrB,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,0EAA0E,CAAC,CAAC;QAC1F,CAAC;QACD,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACnD,CAAC;SAAM,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,qDAAqD,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC3H,CAAC;IACH,CAAC;SAAM,CAAC;QACN,IAAI,GAAQ,CAAC;QACb,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,OAAQ,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,wCAAwC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,KAAK,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACzE,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,KAAK,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,KAAK,CAAC,4EAA4E,CAAC,CAAC;QAChG,CAAC;QACD,GAAG,GAAG,kBAAkB,CAAC,GAAG,EAAE;YAC5B,WAAW,EAAE,CAAC,GAAG,CAAC,gBAAgB,IAAI,GAAG,CAAC,GAAG,IAAI;YACjD,gBAAgB,EAAE,MAAM;YACxB,eAAe,EAAE,KAAK;SACvB,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAqB,EAAE,UAAU,EAAE,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,MAAM;QAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC;IACpC,IAAI,QAAQ;QAAE,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC1C,IAAI,GAAG,CAAC,qBAAqB;QAAE,OAAO,CAAC,cAAc,GAAG,GAAG,CAAC,qBAAqB,CAAC;IAClF,IAAI,GAAG,CAAC,UAAU;QAAE,OAAO,CAAC,cAAc,GAAG,CAAC,KAAK,CAAC,CAAC;IACrD,IAAI,GAAG,CAAC,kBAAkB;QAAE,OAAO,CAAC,WAAW,GAAG,GAAG,CAAC,kBAAkB,CAAC;IACzE,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC;AAC1B,CAAC;AAED,SAAS,aAAa,CAAC,QAAqB;IAC1C,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE,CAC/B,OAAO,QAAQ,CAAC,GAAG,KAAK,UAAU;QAChC,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC;QAClD,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAEvD,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;QACzD,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;QAC7C,IAAI,CAAC,UAAU,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YACjD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,uBAAuB,EAAE,CAAC,CAAC;YAClF,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAEzC,wEAAwE;QACxE,kCAAkC;QAClC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAChB,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;YACb,GAAqB,CAAC,UAAU,GAAG,OAAO,CAAC;YAC3C,GAAqB,CAAC,QAAQ,GAAG,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,SAAS,CAAC,EAAE,CAAC;YAC5E,MAAM,KAAK,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;YACpC,IAAI,KAAK;gBAAG,GAAqB,CAAC,KAAK,GAAG,KAAK,CAAC;YAChD,IAAI,EAAE,CAAC;QACT,CAAC,EACD,CAAC,GAAY,EAAE,EAAE;YACf,MAAM,CAAC,IAAI,CAAC,gCAAgC,GAAG,CAAC,EAAE,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3G,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;gBACrB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,0BAA0B,EAAE,CAAC,CAAC;YACvF,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}