@mcp-guardian/server 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -8
- package/dist/auth/auth-types.d.ts +40 -0
- package/dist/auth/auth-types.d.ts.map +1 -0
- package/dist/auth/auth-types.js +5 -0
- package/dist/auth/auth-types.js.map +1 -0
- package/dist/auth/dashboard-auth.d.ts +97 -0
- package/dist/auth/dashboard-auth.d.ts.map +1 -0
- package/dist/auth/dashboard-auth.js +319 -0
- package/dist/auth/dashboard-auth.js.map +1 -0
- package/dist/auth/dpop.d.ts +38 -0
- package/dist/auth/dpop.d.ts.map +1 -0
- package/dist/auth/dpop.js +72 -0
- package/dist/auth/dpop.js.map +1 -0
- package/dist/auth/oauth.d.ts +25 -0
- package/dist/auth/oauth.d.ts.map +1 -0
- package/dist/auth/oauth.js +96 -0
- package/dist/auth/oauth.js.map +1 -0
- package/dist/auth/redis-session-cache.d.ts +21 -0
- package/dist/auth/redis-session-cache.d.ts.map +1 -0
- package/dist/auth/redis-session-cache.js +74 -0
- package/dist/auth/redis-session-cache.js.map +1 -0
- package/dist/auth/session-cache.d.ts +47 -0
- package/dist/auth/session-cache.d.ts.map +1 -0
- package/dist/auth/session-cache.js +91 -0
- package/dist/auth/session-cache.js.map +1 -0
- package/dist/cli.js +23 -5
- package/dist/cli.js.map +1 -1
- package/dist/database/database-interface.d.ts +17 -0
- package/dist/database/database-interface.d.ts.map +1 -0
- package/dist/database/database-interface.js +2 -0
- package/dist/database/database-interface.js.map +1 -0
- package/dist/database/postgres-db.d.ts +18 -0
- package/dist/database/postgres-db.d.ts.map +1 -0
- package/dist/database/postgres-db.js +118 -0
- package/dist/database/postgres-db.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/policy/policy-watcher.d.ts +24 -0
- package/dist/policy/policy-watcher.d.ts.map +1 -0
- package/dist/policy/policy-watcher.js +68 -0
- package/dist/policy/policy-watcher.js.map +1 -0
- package/dist/policy/shell-tokenizer.d.ts +92 -0
- package/dist/policy/shell-tokenizer.d.ts.map +1 -0
- package/dist/policy/shell-tokenizer.js +300 -0
- package/dist/policy/shell-tokenizer.js.map +1 -0
- package/dist/proxy/http-proxy-server.d.ts +26 -0
- package/dist/proxy/http-proxy-server.d.ts.map +1 -0
- package/dist/proxy/http-proxy-server.js +172 -0
- package/dist/proxy/http-proxy-server.js.map +1 -0
- package/dist/proxy/proxy-manager.d.ts +3 -1
- package/dist/proxy/proxy-manager.d.ts.map +1 -1
- package/dist/proxy/proxy-manager.js +10 -3
- package/dist/proxy/proxy-manager.js.map +1 -1
- package/dist/proxy/proxy-server.d.ts +15 -8
- package/dist/proxy/proxy-server.d.ts.map +1 -1
- package/dist/proxy/proxy-server.js +80 -26
- package/dist/proxy/proxy-server.js.map +1 -1
- package/dist/utils/circuit-breaker.d.ts +29 -0
- package/dist/utils/circuit-breaker.d.ts.map +1 -0
- package/dist/utils/circuit-breaker.js +81 -0
- package/dist/utils/circuit-breaker.js.map +1 -0
- package/dist/utils/dashboard-server.d.ts +19 -0
- package/dist/utils/dashboard-server.d.ts.map +1 -0
- package/dist/utils/dashboard-server.js +258 -0
- package/dist/utils/dashboard-server.js.map +1 -0
- package/dist/utils/metrics.d.ts +17 -0
- package/dist/utils/metrics.d.ts.map +1 -0
- package/dist/utils/metrics.js +79 -0
- package/dist/utils/metrics.js.map +1 -0
- package/dist/utils/mtls-config.d.ts +27 -0
- package/dist/utils/mtls-config.d.ts.map +1 -0
- package/dist/utils/mtls-config.js +82 -0
- package/dist/utils/mtls-config.js.map +1 -0
- package/dist/utils/payload-normalizer.d.ts +62 -0
- package/dist/utils/payload-normalizer.d.ts.map +1 -0
- package/dist/utils/payload-normalizer.js +240 -0
- package/dist/utils/payload-normalizer.js.map +1 -0
- package/dist/utils/policy-auditor.d.ts +24 -0
- package/dist/utils/policy-auditor.d.ts.map +1 -0
- package/dist/utils/policy-auditor.js +58 -0
- package/dist/utils/policy-auditor.js.map +1 -0
- package/dist/utils/redis-rate-limiter.d.ts +22 -0
- package/dist/utils/redis-rate-limiter.d.ts.map +1 -0
- package/dist/utils/redis-rate-limiter.js +61 -0
- package/dist/utils/redis-rate-limiter.js.map +1 -0
- package/dist/utils/structured-logger.d.ts +1 -1
- package/dist/utils/structured-logger.d.ts.map +1 -1
- package/dist/utils/tracing.d.ts +7 -0
- package/dist/utils/tracing.d.ts.map +1 -0
- package/dist/utils/tracing.js +34 -0
- package/dist/utils/tracing.js.map +1 -0
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy-server.d.ts","sourceRoot":"","sources":["../../src/proxy/proxy-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy-server.d.ts","sourceRoot":"","sources":["../../src/proxy/proxy-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAG1D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD;;;;;;;GAOG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,EAAE,CAAkB;IAC5B,OAAO,CAAC,gBAAgB,CAAuB;IAC/C,OAAO,CAAC,gBAAgB,CAAa;IACrC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,gBAAgB,CAAsC;IAC9D,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,aAAa,CAAwB;gBAG3C,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,EAAE,EAAE,eAAe,EACnB,UAAU,CAAC,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,YAAY,EAC3B,aAAa,CAAC,EAAE,cAAc;IAsBhC,IAAI,KAAK,IAAI,MAAM,CAAC,cAAc,GAAG,IAAI,CAExC;IAED,OAAO,CAAC,WAAW;IAiCnB,OAAO,CAAC,WAAW;IAMnB;;OAEG;IACH,OAAO,CAAC,SAAS;IASjB;;;;;OAKG;IACG,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAmHnD,IAAI,IAAI,IAAI;CAOb"}
|
|
@@ -3,13 +3,14 @@ import { createInterface } from 'readline';
|
|
|
3
3
|
import { TokenCounter } from '../utils/token-counter.js';
|
|
4
4
|
import { Logger } from '../utils/logger.js';
|
|
5
5
|
import { StructuredLogger } from '../utils/structured-logger.js';
|
|
6
|
+
import { OAuthValidator } from '../auth/oauth.js';
|
|
6
7
|
/**
|
|
7
|
-
* MCP Proxy Interceptor — sits between the AI client and an MCP server
|
|
8
|
-
* capturing every JSON-RPC call's token usage for real cost auditing.
|
|
8
|
+
* MCP Proxy Interceptor — sits between the AI client and an MCP server.
|
|
9
9
|
*
|
|
10
10
|
* v0.4: Integrated PolicyEngine for active blocking of malicious tool calls.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* v0.5: OAuth 2.1 JWT validation — validates bearer tokens before policy evaluation.
|
|
12
|
+
* If authValidator is provided, every tools/call requires a valid JWT.
|
|
13
|
+
* Unauthenticated calls are blocked with a JSON-RPC auth error.
|
|
13
14
|
*/
|
|
14
15
|
export class McpProxyServer {
|
|
15
16
|
child;
|
|
@@ -22,9 +23,11 @@ export class McpProxyServer {
|
|
|
22
23
|
requestArguments;
|
|
23
24
|
serverName;
|
|
24
25
|
policyEngine;
|
|
25
|
-
|
|
26
|
+
authValidator;
|
|
27
|
+
constructor(command, args, env, db, serverName, policyEngine, authValidator) {
|
|
26
28
|
this.serverName = serverName || command.split('/').pop() || command;
|
|
27
29
|
this.policyEngine = policyEngine || null;
|
|
30
|
+
this.authValidator = authValidator || null;
|
|
28
31
|
this.child = spawn(command, args, {
|
|
29
32
|
env: { ...process.env, ...env },
|
|
30
33
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -37,6 +40,7 @@ export class McpProxyServer {
|
|
|
37
40
|
event: 'proxy_started',
|
|
38
41
|
serverName: this.serverName,
|
|
39
42
|
blockingMode: this.policyEngine ? this.policyEngine.getMode() : 'audit',
|
|
43
|
+
authEnabled: this.authValidator ? this.authValidator.getConfig().required : false,
|
|
40
44
|
});
|
|
41
45
|
}
|
|
42
46
|
get stdin() {
|
|
@@ -48,7 +52,6 @@ export class McpProxyServer {
|
|
|
48
52
|
try {
|
|
49
53
|
const msg = JSON.parse(line);
|
|
50
54
|
if (msg.id && msg.id === this.currentRequestId) {
|
|
51
|
-
// Response to our tracked tools/call request
|
|
52
55
|
const responseTokens = this.tokenCounter.count(line);
|
|
53
56
|
const record = {
|
|
54
57
|
serverName: this.serverName,
|
|
@@ -63,11 +66,9 @@ export class McpProxyServer {
|
|
|
63
66
|
this.currentRequestId = null;
|
|
64
67
|
this.requestToolName = null;
|
|
65
68
|
}
|
|
66
|
-
// Forward response to client
|
|
67
69
|
process.stdout.write(line + '\n');
|
|
68
70
|
}
|
|
69
71
|
catch {
|
|
70
|
-
// Non-JSON line — forward as-is
|
|
71
72
|
process.stdout.write(line + '\n');
|
|
72
73
|
}
|
|
73
74
|
});
|
|
@@ -80,12 +81,24 @@ export class McpProxyServer {
|
|
|
80
81
|
process.stderr.write(data);
|
|
81
82
|
});
|
|
82
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Send a JSON-RPC 2.0 error response to the client.
|
|
86
|
+
*/
|
|
87
|
+
sendError(id, code, message, data) {
|
|
88
|
+
const errorResponse = JSON.stringify({
|
|
89
|
+
jsonrpc: '2.0',
|
|
90
|
+
id,
|
|
91
|
+
error: { code, message, data },
|
|
92
|
+
});
|
|
93
|
+
process.stdout.write(errorResponse + '\n');
|
|
94
|
+
}
|
|
83
95
|
/**
|
|
84
96
|
* Called when the AI client writes a request to be proxied.
|
|
85
|
-
*
|
|
86
|
-
*
|
|
97
|
+
* 1. Validate OAuth 2.1 JWT (if configured)
|
|
98
|
+
* 2. Evaluate against policy engine
|
|
99
|
+
* 3. Forward or block
|
|
87
100
|
*/
|
|
88
|
-
handleClientInput(raw) {
|
|
101
|
+
async handleClientInput(raw) {
|
|
89
102
|
try {
|
|
90
103
|
const msg = JSON.parse(raw);
|
|
91
104
|
if (msg.method === 'tools/call' && msg.id) {
|
|
@@ -94,9 +107,60 @@ export class McpProxyServer {
|
|
|
94
107
|
this.requestToolName = msg.params?.name || 'unknown';
|
|
95
108
|
this.requestTokens = this.tokenCounter.count(raw);
|
|
96
109
|
this.requestArguments = msg.params?.arguments;
|
|
110
|
+
const toolName = this.requestToolName || 'unknown';
|
|
111
|
+
// ── v0.5: OAuth 2.1 JWT validation ──────────────────
|
|
112
|
+
if (this.authValidator) {
|
|
113
|
+
const authHeader = msg.params?._meta?.auth?.Authorization
|
|
114
|
+
|| msg.Authorization
|
|
115
|
+
|| msg.params?.Authorization
|
|
116
|
+
|| undefined;
|
|
117
|
+
const token = OAuthValidator.extractToken(authHeader);
|
|
118
|
+
if (!token) {
|
|
119
|
+
if (this.authValidator.getConfig().required) {
|
|
120
|
+
StructuredLogger.info({
|
|
121
|
+
event: 'auth_required',
|
|
122
|
+
serverName: this.serverName,
|
|
123
|
+
toolName,
|
|
124
|
+
requestId: msg.id,
|
|
125
|
+
});
|
|
126
|
+
this.sendError(msg.id, -32002, 'Authentication required. Provide a valid Bearer token in the Authorization header.');
|
|
127
|
+
this.currentRequestId = null;
|
|
128
|
+
this.requestToolName = null;
|
|
129
|
+
this.requestArguments = undefined;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const result = await this.authValidator.validate(token);
|
|
135
|
+
if (!result.valid) {
|
|
136
|
+
StructuredLogger.logError({
|
|
137
|
+
event: 'oidc_auth_error',
|
|
138
|
+
serverName: this.serverName,
|
|
139
|
+
error: `JWT validation failed: ${result.error}`,
|
|
140
|
+
requestId: msg.id,
|
|
141
|
+
});
|
|
142
|
+
if (this.authValidator.getConfig().required) {
|
|
143
|
+
this.sendError(msg.id, -32003, `Authentication failed: ${result.error}`);
|
|
144
|
+
this.currentRequestId = null;
|
|
145
|
+
this.requestToolName = null;
|
|
146
|
+
this.requestArguments = undefined;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
StructuredLogger.info({
|
|
152
|
+
event: 'auth_success',
|
|
153
|
+
serverName: this.serverName,
|
|
154
|
+
toolName,
|
|
155
|
+
requestId: msg.id,
|
|
156
|
+
agent: result.identity?.sub,
|
|
157
|
+
clientId: result.identity?.clientId,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
97
162
|
// ── v0.4: Active policy enforcement ──────────────────
|
|
98
163
|
if (this.policyEngine) {
|
|
99
|
-
const toolName = this.requestToolName || 'unknown';
|
|
100
164
|
const context = {
|
|
101
165
|
serverName: this.serverName,
|
|
102
166
|
toolName,
|
|
@@ -106,7 +170,6 @@ export class McpProxyServer {
|
|
|
106
170
|
timestamp: new Date().toISOString(),
|
|
107
171
|
};
|
|
108
172
|
const decision = this.policyEngine.evaluate(context);
|
|
109
|
-
// Log every decision for SIEM audit trail
|
|
110
173
|
StructuredLogger.logPolicyDecision({
|
|
111
174
|
event: 'policy_decision',
|
|
112
175
|
requestId: msg.id,
|
|
@@ -124,22 +187,14 @@ export class McpProxyServer {
|
|
|
124
187
|
reason: decision.reason,
|
|
125
188
|
rule: decision.rule,
|
|
126
189
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
id: msg.id,
|
|
131
|
-
error: {
|
|
132
|
-
code: -32001,
|
|
133
|
-
message: `Blocked by MCP Guardian policy: ${decision.reason}`,
|
|
134
|
-
data: { rule: decision.rule, policy: this.policyEngine.getMode() },
|
|
135
|
-
},
|
|
190
|
+
this.sendError(msg.id, -32001, `Blocked by MCP Guardian policy: ${decision.reason}`, {
|
|
191
|
+
rule: decision.rule,
|
|
192
|
+
policy: this.policyEngine.getMode(),
|
|
136
193
|
});
|
|
137
|
-
process.stdout.write(errorResponse + '\n');
|
|
138
|
-
// Reset state
|
|
139
194
|
this.currentRequestId = null;
|
|
140
195
|
this.requestToolName = null;
|
|
141
196
|
this.requestArguments = undefined;
|
|
142
|
-
return;
|
|
197
|
+
return;
|
|
143
198
|
}
|
|
144
199
|
}
|
|
145
200
|
}
|
|
@@ -147,7 +202,6 @@ export class McpProxyServer {
|
|
|
147
202
|
catch {
|
|
148
203
|
// Non-JSON input — forward as-is
|
|
149
204
|
}
|
|
150
|
-
// Forward to the underlying MCP server
|
|
151
205
|
this.child.stdin?.write(raw + '\n');
|
|
152
206
|
}
|
|
153
207
|
kill() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy-server.js","sourceRoot":"","sources":["../../src/proxy/proxy-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAgB,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAGzD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAG5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy-server.js","sourceRoot":"","sources":["../../src/proxy/proxy-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
|
|
2
|
+
export declare class CircuitBreaker {
|
|
3
|
+
private state;
|
|
4
|
+
private failureCount;
|
|
5
|
+
private successCount;
|
|
6
|
+
private lastFailureTime;
|
|
7
|
+
private readonly resetTimeout;
|
|
8
|
+
private readonly failureThreshold;
|
|
9
|
+
private readonly successThreshold;
|
|
10
|
+
private readonly name;
|
|
11
|
+
constructor(name: string, options?: {
|
|
12
|
+
failureThreshold?: number;
|
|
13
|
+
successThreshold?: number;
|
|
14
|
+
resetTimeoutMs?: number;
|
|
15
|
+
});
|
|
16
|
+
/** Check if the circuit allows a request through */
|
|
17
|
+
allowRequest(): boolean;
|
|
18
|
+
/** Record a successful request */
|
|
19
|
+
recordSuccess(): void;
|
|
20
|
+
/** Record a failed request */
|
|
21
|
+
recordFailure(): void;
|
|
22
|
+
getState(): CircuitState;
|
|
23
|
+
getStats(): {
|
|
24
|
+
state: CircuitState;
|
|
25
|
+
failureCount: number;
|
|
26
|
+
successCount: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=circuit-breaker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../../src/utils/circuit-breaker.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;gBAG5B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAO;IAQjG,oDAAoD;IACpD,YAAY,IAAI,OAAO;IAcvB,kCAAkC;IAClC,aAAa,IAAI,IAAI;IAcrB,8BAA8B;IAC9B,aAAa,IAAI,IAAI;IAerB,QAAQ,IAAI,YAAY;IAIxB,QAAQ,IAAI;QAAE,KAAK,EAAE,YAAY,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE;CAOhF"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circuit Breaker for MCP Proxy Server.
|
|
3
|
+
* Implements the classic 3-state circuit breaker pattern:
|
|
4
|
+
* CLOSED → OPEN → HALF_OPEN → CLOSED (or OPEN again).
|
|
5
|
+
*
|
|
6
|
+
* Used to protect upstream MCP servers from cascading failures.
|
|
7
|
+
*/
|
|
8
|
+
import { Logger } from './logger.js';
|
|
9
|
+
export class CircuitBreaker {
|
|
10
|
+
state = 'CLOSED';
|
|
11
|
+
failureCount = 0;
|
|
12
|
+
successCount = 0;
|
|
13
|
+
lastFailureTime = 0;
|
|
14
|
+
resetTimeout;
|
|
15
|
+
failureThreshold;
|
|
16
|
+
successThreshold;
|
|
17
|
+
name;
|
|
18
|
+
constructor(name, options = {}) {
|
|
19
|
+
this.name = name;
|
|
20
|
+
this.failureThreshold = options.failureThreshold || 5;
|
|
21
|
+
this.successThreshold = options.successThreshold || 2;
|
|
22
|
+
this.resetTimeout = options.resetTimeoutMs || 30000;
|
|
23
|
+
}
|
|
24
|
+
/** Check if the circuit allows a request through */
|
|
25
|
+
allowRequest() {
|
|
26
|
+
if (this.state === 'CLOSED')
|
|
27
|
+
return true;
|
|
28
|
+
if (this.state === 'OPEN') {
|
|
29
|
+
if (Date.now() - this.lastFailureTime >= this.resetTimeout) {
|
|
30
|
+
this.state = 'HALF_OPEN';
|
|
31
|
+
Logger.debug(`[circuit-breaker:${this.name}] Transitioned to HALF_OPEN`);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
// HALF_OPEN: allow probes
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
/** Record a successful request */
|
|
40
|
+
recordSuccess() {
|
|
41
|
+
if (this.state === 'HALF_OPEN') {
|
|
42
|
+
this.successCount++;
|
|
43
|
+
if (this.successCount >= this.successThreshold) {
|
|
44
|
+
this.state = 'CLOSED';
|
|
45
|
+
this.failureCount = 0;
|
|
46
|
+
this.successCount = 0;
|
|
47
|
+
Logger.info(`[circuit-breaker:${this.name}] Circuit CLOSED — service healthy`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else if (this.state === 'CLOSED') {
|
|
51
|
+
this.failureCount = 0;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Record a failed request */
|
|
55
|
+
recordFailure() {
|
|
56
|
+
this.lastFailureTime = Date.now();
|
|
57
|
+
if (this.state === 'HALF_OPEN') {
|
|
58
|
+
this.state = 'OPEN';
|
|
59
|
+
this.successCount = 0;
|
|
60
|
+
Logger.warn(`[circuit-breaker:${this.name}] Circuit OPEN — half-open probe failed`);
|
|
61
|
+
}
|
|
62
|
+
else if (this.state === 'CLOSED') {
|
|
63
|
+
this.failureCount++;
|
|
64
|
+
if (this.failureCount >= this.failureThreshold) {
|
|
65
|
+
this.state = 'OPEN';
|
|
66
|
+
Logger.warn(`[circuit-breaker:${this.name}] Circuit OPEN — ${this.failureCount} consecutive failures`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
getState() {
|
|
71
|
+
return this.state;
|
|
72
|
+
}
|
|
73
|
+
getStats() {
|
|
74
|
+
return {
|
|
75
|
+
state: this.state,
|
|
76
|
+
failureCount: this.failureCount,
|
|
77
|
+
successCount: this.successCount,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=circuit-breaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuit-breaker.js","sourceRoot":"","sources":["../../src/utils/circuit-breaker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { PolicyWatcher } from '../policy/policy-watcher.js';
|
|
3
|
+
import { DashboardAuth } from '../auth/dashboard-auth.js';
|
|
4
|
+
/**
|
|
5
|
+
* Lightweight dashboard server that serves:
|
|
6
|
+
* - / — the dashboard HTML (requires auth if enabled)
|
|
7
|
+
* - /login — login page (when JWT auth is enabled)
|
|
8
|
+
* - /api/login — POST login endpoint
|
|
9
|
+
* - /api/policy — current policy (JSON, requires auth)
|
|
10
|
+
* - /api/policy/reload — trigger policy reload (requires auth)
|
|
11
|
+
* - /metrics — Prometheus metrics (can be auth-gated or public via DASHBOARD_METRICS_PUBLIC=true)
|
|
12
|
+
*
|
|
13
|
+
* v1.2: Integrated DashboardAuth for JWT/API key authentication, CSRF protection.
|
|
14
|
+
*/
|
|
15
|
+
export declare function startDashboardServer(port?: number, policyWatcher?: PolicyWatcher, dashboardAuth?: DashboardAuth): Promise<{
|
|
16
|
+
auth: DashboardAuth;
|
|
17
|
+
server: ReturnType<typeof createServer>;
|
|
18
|
+
}>;
|
|
19
|
+
//# sourceMappingURL=dashboard-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-server.d.ts","sourceRoot":"","sources":["../../src/utils/dashboard-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAmC,MAAM,MAAM,CAAC;AAKrE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAM1D;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,GAAE,MAAa,EACnB,aAAa,CAAC,EAAE,aAAa,EAC7B,aAAa,CAAC,EAAE,aAAa,GAC5B,OAAO,CAAC;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,UAAU,CAAC,OAAO,YAAY,CAAC,CAAA;CAAE,CAAC,CA2P3E"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { resolve, dirname } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { Logger } from './logger.js';
|
|
6
|
+
import { DashboardAuth } from '../auth/dashboard-auth.js';
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
/**
|
|
10
|
+
* Lightweight dashboard server that serves:
|
|
11
|
+
* - / — the dashboard HTML (requires auth if enabled)
|
|
12
|
+
* - /login — login page (when JWT auth is enabled)
|
|
13
|
+
* - /api/login — POST login endpoint
|
|
14
|
+
* - /api/policy — current policy (JSON, requires auth)
|
|
15
|
+
* - /api/policy/reload — trigger policy reload (requires auth)
|
|
16
|
+
* - /metrics — Prometheus metrics (can be auth-gated or public via DASHBOARD_METRICS_PUBLIC=true)
|
|
17
|
+
*
|
|
18
|
+
* v1.2: Integrated DashboardAuth for JWT/API key authentication, CSRF protection.
|
|
19
|
+
*/
|
|
20
|
+
export async function startDashboardServer(port = 4000, policyWatcher, dashboardAuth) {
|
|
21
|
+
if (process.env['DASHBOARD_ENABLED'] !== 'true') {
|
|
22
|
+
Logger.debug('[dashboard] Dashboard server not enabled (set DASHBOARD_ENABLED=true)');
|
|
23
|
+
return { auth: dashboardAuth || new DashboardAuth({ enabled: false }), server: createServer((_req, res) => {
|
|
24
|
+
res.writeHead(200);
|
|
25
|
+
res.end();
|
|
26
|
+
}) };
|
|
27
|
+
}
|
|
28
|
+
const auth = dashboardAuth || new DashboardAuth();
|
|
29
|
+
const authEnabled = auth.isEnabled();
|
|
30
|
+
if (authEnabled) {
|
|
31
|
+
Logger.info('[dashboard] Dashboard authentication enabled');
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
Logger.info('[dashboard] Dashboard running without authentication (set DASHBOARD_AUTH_ENABLED=true)');
|
|
35
|
+
}
|
|
36
|
+
const dashboardHtml = readFileSync(resolve(__dirname, '..', '..', 'deploy', 'dashboard.html'), 'utf-8');
|
|
37
|
+
/** Read JSON body from request */
|
|
38
|
+
async function readBody(req) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
let data = '';
|
|
41
|
+
req.on('data', (chunk) => { data += chunk.toString(); });
|
|
42
|
+
req.on('end', () => {
|
|
43
|
+
try {
|
|
44
|
+
resolve(data ? JSON.parse(data) : {});
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
resolve({});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
req.on('error', reject);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** Parse form-encoded body from request */
|
|
54
|
+
async function readFormBody(req) {
|
|
55
|
+
return new Promise((resolve) => {
|
|
56
|
+
let data = '';
|
|
57
|
+
req.on('data', (chunk) => { data += chunk.toString(); });
|
|
58
|
+
req.on('end', () => {
|
|
59
|
+
const result = {};
|
|
60
|
+
if (data) {
|
|
61
|
+
try {
|
|
62
|
+
const params = new URLSearchParams(data);
|
|
63
|
+
for (const [key, value] of params) {
|
|
64
|
+
result[key] = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Ignore parse errors
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
resolve(result);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** Get client IP for rate limiting */
|
|
76
|
+
function getClientIp(req) {
|
|
77
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
78
|
+
if (forwarded) {
|
|
79
|
+
const first = Array.isArray(forwarded) ? forwarded[0] : forwarded.split(',')[0];
|
|
80
|
+
return (first || '').trim();
|
|
81
|
+
}
|
|
82
|
+
return req.socket?.remoteAddress || 'unknown';
|
|
83
|
+
}
|
|
84
|
+
/** Write JSON response */
|
|
85
|
+
function writeJson(res, status, data) {
|
|
86
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
87
|
+
res.end(JSON.stringify(data));
|
|
88
|
+
}
|
|
89
|
+
const server = createServer(async (req, res) => {
|
|
90
|
+
const url = req.url || '/';
|
|
91
|
+
const method = req.method || 'GET';
|
|
92
|
+
// ── CORS preflight ─────────────────────────────────────
|
|
93
|
+
if (method === 'OPTIONS') {
|
|
94
|
+
res.writeHead(204, {
|
|
95
|
+
'Access-Control-Allow-Origin': '*',
|
|
96
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
97
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-API-Key',
|
|
98
|
+
});
|
|
99
|
+
res.end();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// ── CORS headers on all responses ─────────────────────
|
|
103
|
+
const setCors = () => {
|
|
104
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
// ── Login page (only when JWT auth is enabled, no API key set) ──
|
|
108
|
+
if (url === '/login' && method === 'GET') {
|
|
109
|
+
setCors();
|
|
110
|
+
if (auth.isEnabled() && auth.hasJwtSessionAuth()) {
|
|
111
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
112
|
+
res.end(auth.getLoginPageHtml());
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
res.writeHead(302, { 'Location': '/' });
|
|
116
|
+
res.end();
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// ── Login API endpoint ──────────────────────────────
|
|
121
|
+
if (url === '/api/login' && method === 'POST') {
|
|
122
|
+
setCors();
|
|
123
|
+
const ip = getClientIp(req);
|
|
124
|
+
const contentType = req.headers['content-type'] || '';
|
|
125
|
+
let body;
|
|
126
|
+
if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
127
|
+
body = await readFormBody(req);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
body = await readBody(req);
|
|
131
|
+
}
|
|
132
|
+
const result = auth.login({
|
|
133
|
+
url,
|
|
134
|
+
headers: req.headers,
|
|
135
|
+
body: {
|
|
136
|
+
username: body.username,
|
|
137
|
+
password: body.password,
|
|
138
|
+
api_key: body.api_key,
|
|
139
|
+
},
|
|
140
|
+
ip,
|
|
141
|
+
});
|
|
142
|
+
if (result.success && req.headers['content-type']?.includes('form')) {
|
|
143
|
+
// Form submission — redirect to dashboard with token
|
|
144
|
+
res.writeHead(302, {
|
|
145
|
+
'Location': `/?api_key=${encodeURIComponent(result.token)}`,
|
|
146
|
+
'Set-Cookie': `mcp_guardian_session=${result.token}; Path=/; HttpOnly; SameSite=Strict; Max-Age=3600`,
|
|
147
|
+
});
|
|
148
|
+
res.end();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (result.success) {
|
|
152
|
+
writeJson(res, 200, { success: true, token: result.token });
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
writeJson(res, 401, { success: false, error: result.error });
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// ── Auth check for all other routes ─────────────────
|
|
160
|
+
const authResult = auth.authenticate({ url, headers: req.headers, method });
|
|
161
|
+
if (!authResult.authenticated) {
|
|
162
|
+
setCors();
|
|
163
|
+
if (req.headers['accept']?.includes('text/html')) {
|
|
164
|
+
// Browser request — redirect to login
|
|
165
|
+
res.writeHead(302, { 'Location': '/login' });
|
|
166
|
+
res.end();
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
writeJson(res, 401, { error: 'Authentication required', reason: authResult.reason });
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
// ── Dashboard HTML ──────────────────────────────────
|
|
174
|
+
if (url === '/' || url === '/dashboard.html') {
|
|
175
|
+
setCors();
|
|
176
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
177
|
+
res.end(dashboardHtml);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// ── Policy API ──────────────────────────────────────
|
|
181
|
+
if (url === '/api/policy' && method === 'GET') {
|
|
182
|
+
setCors();
|
|
183
|
+
if (!policyWatcher || !policyWatcher.get()) {
|
|
184
|
+
writeJson(res, 404, { error: 'No active policy. Start proxy with --policy flag.' });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
writeJson(res, 200, { mode: policyWatcher.get().getMode(), rules: 'Policy engine active (YAML view available on filesystem)' });
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (url === '/api/policy/reload' && method === 'POST') {
|
|
191
|
+
setCors();
|
|
192
|
+
if (!policyWatcher) {
|
|
193
|
+
writeJson(res, 404, { error: 'Policy watcher not configured' });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
writeJson(res, 200, { status: 'ok', message: 'Policy watcher is active. File changes are auto-detected.' });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// ── Prometheus /metrics proxy ──────────────────────
|
|
200
|
+
if (url === '/metrics') {
|
|
201
|
+
setCors();
|
|
202
|
+
const metricsPublic = process.env['DASHBOARD_METRICS_PUBLIC'] === 'true';
|
|
203
|
+
// Re-check auth for metrics unless public
|
|
204
|
+
if (metricsPublic || authResult.authenticated) {
|
|
205
|
+
try {
|
|
206
|
+
const metricsPort = process.env['METRICS_PORT'] || '9090';
|
|
207
|
+
const metricsRes = await fetch(`http://localhost:${metricsPort}/metrics`);
|
|
208
|
+
if (!metricsRes.ok)
|
|
209
|
+
throw new Error(`Metrics server returned ${metricsRes.status}`);
|
|
210
|
+
const text = await metricsRes.text();
|
|
211
|
+
res.writeHead(200, {
|
|
212
|
+
'Content-Type': 'text/plain; version=0.0.4; charset=utf-8',
|
|
213
|
+
'Access-Control-Allow-Origin': '*',
|
|
214
|
+
});
|
|
215
|
+
res.end(text);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
writeJson(res, 200, { error: 'Metrics not available. Ensure METRICS_ENABLED=true and proxy is running.' });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
writeJson(res, 401, { error: 'Authentication required for metrics' });
|
|
223
|
+
}
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// ── Auth status check ───────────────────────────────
|
|
227
|
+
if (url === '/api/auth/status' && method === 'GET') {
|
|
228
|
+
setCors();
|
|
229
|
+
writeJson(res, 200, { authenticated: true, identity: authResult.identity, authEnabled });
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// ── Logout ──────────────────────────────────────────
|
|
233
|
+
if (url === '/api/logout' && method === 'POST') {
|
|
234
|
+
setCors();
|
|
235
|
+
const authHeader = req.headers['authorization'];
|
|
236
|
+
if (authHeader) {
|
|
237
|
+
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
|
238
|
+
if (match)
|
|
239
|
+
auth.logout(match[1]);
|
|
240
|
+
}
|
|
241
|
+
writeJson(res, 200, { status: 'ok', message: 'Logged out' });
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// ── 404 ──────────────────────────────────────────────
|
|
245
|
+
setCors();
|
|
246
|
+
writeJson(res, 404, { error: 'Not found' });
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
setCors();
|
|
250
|
+
writeJson(res, 500, { error: err?.message || 'Internal error' });
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
server.listen(port, () => {
|
|
254
|
+
Logger.info(`[dashboard] Dashboard available at http://localhost:${port}${authEnabled ? ' (auth enabled)' : ''}`);
|
|
255
|
+
});
|
|
256
|
+
return { auth, server };
|
|
257
|
+
}
|
|
258
|
+
//# sourceMappingURL=dashboard-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-server.js","sourceRoot":"","sources":["../../src/utils/dashboard-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAmC,MAAM,MAAM,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG1D,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAe,IAAI,EACnB,aAA6B,EAC7B,aAA6B;IAE7B,IAAI,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,KAAK,MAAM,EAAE,CAAC;QAChD,MAAM,CAAC,KAAK,CAAC,uEAAuE,CAAC,CAAC;QACtF,OAAO,EAAE,IAAI,EAAE,aAAa,IAAI,IAAI,aAAa,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;gBACxG,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,EAAE,CAAC;YACZ,CAAC,CAAC,EAAE,CAAC;IACP,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,IAAI,IAAI,aAAa,EAAE,CAAC;IAClD,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAErC,IAAI,WAAW,EAAE,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;IAC9D,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,IAAI,CAAC,wFAAwF,CAAC,CAAC;IACxG,CAAC;IAED,MAAM,aAAa,GAAG,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC,CAAC;IAExG,kCAAkC;IAClC,KAAK,UAAU,QAAQ,CAAC,GAAoB;QAC1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YACjE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,IAAI,CAAC;oBACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACxC,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,CAAC,EAAE,CAAC,CAAC;gBACd,CAAC;YACH,CAAC,CAAC,CAAC;YACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAED,2CAA2C;IAC3C,KAAK,UAAU,YAAY,CAAC,GAAoB;QAC9C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,IAAI,IAAI,GAAG,EAAE,CAAC;YACd,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YACjE,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;gBACjB,MAAM,MAAM,GAA2B,EAAE,CAAC;gBAC1C,IAAI,IAAI,EAAE,CAAC;oBACT,IAAI,CAAC;wBACH,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC;wBACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;4BAClC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;wBACtB,CAAC;oBACH,CAAC;oBAAC,MAAM,CAAC;wBACP,sBAAsB;oBACxB,CAAC;gBACH,CAAC;gBACD,OAAO,CAAC,MAAM,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,sCAAsC;IACtC,SAAS,WAAW,CAAC,GAAoB;QACvC,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACjD,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAChF,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9B,CAAC;QACD,OAAO,GAAG,CAAC,MAAM,EAAE,aAAa,IAAI,SAAS,CAAC;IAChD,CAAC;IAED,0BAA0B;IAC1B,SAAS,SAAS,CAAC,GAAmB,EAAE,MAAc,EAAE,IAAa;QACnE,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC9D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAChC,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC7C,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC;QAC3B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;QAEnC,0DAA0D;QAC1D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;gBACjB,6BAA6B,EAAE,GAAG;gBAClC,8BAA8B,EAAE,oBAAoB;gBACpD,8BAA8B,EAAE,wCAAwC;aACzE,CAAC,CAAC;YACH,GAAG,CAAC,GAAG,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QAED,yDAAyD;QACzD,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,GAAG,CAAC,SAAS,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;QACpD,CAAC,CAAC;QAEF,IAAI,CAAC;YACH,mEAAmE;YACnE,IAAI,GAAG,KAAK,QAAQ,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;gBACzC,OAAO,EAAE,CAAC;gBACV,IAAI,IAAI,CAAC,SAAS,EAAE,IAAI,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC;oBACjD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;oBACpD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;gBACnC,CAAC;qBAAM,CAAC;oBACN,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;oBACxC,GAAG,CAAC,GAAG,EAAE,CAAC;gBACZ,CAAC;gBACD,OAAO;YACT,CAAC;YAED,uDAAuD;YACvD,IAAI,GAAG,KAAK,YAAY,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBAC9C,OAAO,EAAE,CAAC;gBACV,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;gBAC5B,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;gBAEtD,IAAI,IAA4B,CAAC;gBACjC,IAAI,WAAW,CAAC,QAAQ,CAAC,mCAAmC,CAAC,EAAE,CAAC;oBAC9D,IAAI,GAAG,MAAM,YAAY,CAAC,GAAG,CAAC,CAAC;gBACjC,CAAC;qBAAM,CAAC;oBACN,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAsC,CAAC;gBAClE,CAAC;gBAED,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC;oBACxB,GAAG;oBACH,OAAO,EAAE,GAAG,CAAC,OAAwD;oBACrE,IAAI,EAAE;wBACJ,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,OAAO,EAAE,IAAI,CAAC,OAAO;qBACtB;oBACD,EAAE;iBACH,CAAC,CAAC;gBAEH,IAAI,MAAM,CAAC,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;oBACpE,qDAAqD;oBACrD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;wBACjB,UAAU,EAAE,aAAa,kBAAkB,CAAC,MAAM,CAAC,KAAM,CAAC,EAAE;wBAC5D,YAAY,EAAE,wBAAwB,MAAM,CAAC,KAAK,mDAAmD;qBACtG,CAAC,CAAC;oBACH,GAAG,CAAC,GAAG,EAAE,CAAC;oBACV,OAAO;gBACT,CAAC;gBAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;oBACnB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC9D,CAAC;qBAAM,CAAC;oBACN,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC/D,CAAC;gBACD,OAAO;YACT,CAAC;YAED,uDAAuD;YACvD,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YAC5E,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;gBAC9B,OAAO,EAAE,CAAC;gBACV,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;oBACjD,sCAAsC;oBACtC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC;oBAC7C,GAAG,CAAC,GAAG,EAAE,CAAC;gBACZ,CAAC;qBAAM,CAAC;oBACN,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;gBACvF,CAAC;gBACD,OAAO;YACT,CAAC;YAED,uDAAuD;YACvD,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,iBAAiB,EAAE,CAAC;gBAC7C,OAAO,EAAE,CAAC;gBACV,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC,CAAC;gBACpD,GAAG,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;gBACvB,OAAO;YACT,CAAC;YAED,uDAAuD;YACvD,IAAI,GAAG,KAAK,aAAa,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;gBAC9C,OAAO,EAAE,CAAC;gBACV,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,EAAE,CAAC;oBAC3C,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,mDAAmD,EAAE,CAAC,CAAC;oBACpF,OAAO;gBACT,CAAC;gBACD,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,aAAa,CAAC,GAAG,EAAG,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,0DAA0D,EAAE,CAAC,CAAC;gBACjI,OAAO;YACT,CAAC;YAED,IAAI,GAAG,KAAK,oBAAoB,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACtD,OAAO,EAAE,CAAC;gBACV,IAAI,CAAC,aAAa,EAAE,CAAC;oBACnB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC,CAAC;oBAChE,OAAO;gBACT,CAAC;gBACD,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,2DAA2D,EAAE,CAAC,CAAC;gBAC5G,OAAO;YACT,CAAC;YAED,sDAAsD;YACtD,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;gBACvB,OAAO,EAAE,CAAC;gBACV,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,KAAK,MAAM,CAAC;gBACzE,0CAA0C;gBAC1C,IAAI,aAAa,IAAI,UAAU,CAAC,aAAa,EAAE,CAAC;oBAC9C,IAAI,CAAC;wBACH,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,MAAM,CAAC;wBAC1D,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,oBAAoB,WAAW,UAAU,CAAC,CAAC;wBAC1E,IAAI,CAAC,UAAU,CAAC,EAAE;4BAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;wBACpF,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC;wBACrC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;4BACjB,cAAc,EAAE,0CAA0C;4BAC1D,6BAA6B,EAAE,GAAG;yBACnC,CAAC,CAAC;wBACH,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;oBAChB,CAAC;oBAAC,MAAM,CAAC;wBACP,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,0EAA0E,EAAE,CAAC,CAAC;oBAC7G,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAC,CAAC;gBACxE,CAAC;gBACD,OAAO;YACT,CAAC;YAED,uDAAuD;YACvD,IAAI,GAAG,KAAK,kBAAkB,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;gBACnD,OAAO,EAAE,CAAC;gBACV,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC;gBACzF,OAAO;YACT,CAAC;YAED,uDAAuD;YACvD,IAAI,GAAG,KAAK,aAAa,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBAC/C,OAAO,EAAE,CAAC;gBACV,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;gBAChD,IAAI,UAAU,EAAE,CAAC;oBACf,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;oBACnD,IAAI,KAAK;wBAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;gBACnC,CAAC;gBACD,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;gBAC7D,OAAO;YACT,CAAC;YAED,wDAAwD;YACxD,OAAO,EAAE,CAAC;YACV,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,EAAE,CAAC;YACV,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,IAAI,gBAAgB,EAAE,CAAC,CAAC;QACnE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACvB,MAAM,CAAC,IAAI,CAAC,uDAAuD,IAAI,GAAG,WAAW,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpH,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC"}
|