@inso_web/els-mcp 0.1.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 +482 -0
- package/dist/audit/prisma.d.ts +67 -0
- package/dist/audit/prisma.d.ts.map +1 -0
- package/dist/audit/prisma.js +65 -0
- package/dist/audit/prisma.js.map +1 -0
- package/dist/audit/service.d.ts +72 -0
- package/dist/audit/service.d.ts.map +1 -0
- package/dist/audit/service.js +137 -0
- package/dist/audit/service.js.map +1 -0
- package/dist/billing/limits.d.ts +34 -0
- package/dist/billing/limits.d.ts.map +1 -0
- package/dist/billing/limits.js +51 -0
- package/dist/billing/limits.js.map +1 -0
- package/dist/billing/tracker.d.ts +39 -0
- package/dist/billing/tracker.d.ts.map +1 -0
- package/dist/billing/tracker.js +92 -0
- package/dist/billing/tracker.js.map +1 -0
- package/dist/cache/cachedElsClient.d.ts +71 -0
- package/dist/cache/cachedElsClient.d.ts.map +1 -0
- package/dist/cache/cachedElsClient.js +167 -0
- package/dist/cache/cachedElsClient.js.map +1 -0
- package/dist/cache/index.d.ts +10 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +6 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/policies.d.ts +60 -0
- package/dist/cache/policies.d.ts.map +1 -0
- package/dist/cache/policies.js +90 -0
- package/dist/cache/policies.js.map +1 -0
- package/dist/cache/redis.d.ts +52 -0
- package/dist/cache/redis.d.ts.map +1 -0
- package/dist/cache/redis.js +134 -0
- package/dist/cache/redis.js.map +1 -0
- package/dist/cache/types.d.ts +32 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +32 -0
- package/dist/cache/types.js.map +1 -0
- package/dist/cache/wrapper.d.ts +38 -0
- package/dist/cache/wrapper.d.ts.map +1 -0
- package/dist/cache/wrapper.js +109 -0
- package/dist/cache/wrapper.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +86 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +105 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +211 -0
- package/dist/config.js.map +1 -0
- package/dist/elsClient.d.ts +137 -0
- package/dist/elsClient.d.ts.map +1 -0
- package/dist/elsClient.js +285 -0
- package/dist/elsClient.js.map +1 -0
- package/dist/http/app.d.ts +40 -0
- package/dist/http/app.d.ts.map +1 -0
- package/dist/http/app.js +135 -0
- package/dist/http/app.js.map +1 -0
- package/dist/http/jwks.d.ts +8 -0
- package/dist/http/jwks.d.ts.map +1 -0
- package/dist/http/jwks.js +34 -0
- package/dist/http/jwks.js.map +1 -0
- package/dist/http/middleware/auth.d.ts +11 -0
- package/dist/http/middleware/auth.d.ts.map +1 -0
- package/dist/http/middleware/auth.js +225 -0
- package/dist/http/middleware/auth.js.map +1 -0
- package/dist/http/middleware/dcrRateLimit.d.ts +29 -0
- package/dist/http/middleware/dcrRateLimit.d.ts.map +1 -0
- package/dist/http/middleware/dcrRateLimit.js +59 -0
- package/dist/http/middleware/dcrRateLimit.js.map +1 -0
- package/dist/http/middleware/errorHandler.d.ts +12 -0
- package/dist/http/middleware/errorHandler.d.ts.map +1 -0
- package/dist/http/middleware/errorHandler.js +26 -0
- package/dist/http/middleware/errorHandler.js.map +1 -0
- package/dist/http/middleware/originGuard.d.ts +28 -0
- package/dist/http/middleware/originGuard.d.ts.map +1 -0
- package/dist/http/middleware/originGuard.js +55 -0
- package/dist/http/middleware/originGuard.js.map +1 -0
- package/dist/http/middleware/requestId.d.ts +19 -0
- package/dist/http/middleware/requestId.d.ts.map +1 -0
- package/dist/http/middleware/requestId.js +23 -0
- package/dist/http/middleware/requestId.js.map +1 -0
- package/dist/http/routes/health.d.ts +24 -0
- package/dist/http/routes/health.d.ts.map +1 -0
- package/dist/http/routes/health.js +73 -0
- package/dist/http/routes/health.js.map +1 -0
- package/dist/http/routes/metrics.d.ts +18 -0
- package/dist/http/routes/metrics.d.ts.map +1 -0
- package/dist/http/routes/metrics.js +42 -0
- package/dist/http/routes/metrics.js.map +1 -0
- package/dist/http/routes/wellKnown.d.ts +15 -0
- package/dist/http/routes/wellKnown.d.ts.map +1 -0
- package/dist/http/routes/wellKnown.js +43 -0
- package/dist/http/routes/wellKnown.js.map +1 -0
- package/dist/http/types.d.ts +40 -0
- package/dist/http/types.d.ts.map +1 -0
- package/dist/http/types.js +9 -0
- package/dist/http/types.js.map +1 -0
- package/dist/instrumentation.d.ts +22 -0
- package/dist/instrumentation.d.ts.map +1 -0
- package/dist/instrumentation.js +38 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/lib/cursor.d.ts +22 -0
- package/dist/lib/cursor.d.ts.map +1 -0
- package/dist/lib/cursor.js +95 -0
- package/dist/lib/cursor.js.map +1 -0
- package/dist/lib/errors.d.ts +49 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +83 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/responseFormat.d.ts +14 -0
- package/dist/lib/responseFormat.d.ts.map +1 -0
- package/dist/lib/responseFormat.js +74 -0
- package/dist/lib/responseFormat.js.map +1 -0
- package/dist/middleware/withMiddleware.d.ts +53 -0
- package/dist/middleware/withMiddleware.d.ts.map +1 -0
- package/dist/middleware/withMiddleware.js +190 -0
- package/dist/middleware/withMiddleware.js.map +1 -0
- package/dist/observability/health.d.ts +51 -0
- package/dist/observability/health.d.ts.map +1 -0
- package/dist/observability/health.js +77 -0
- package/dist/observability/health.js.map +1 -0
- package/dist/observability/index.d.ts +8 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +5 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/logger.d.ts +45 -0
- package/dist/observability/logger.d.ts.map +1 -0
- package/dist/observability/logger.js +75 -0
- package/dist/observability/logger.js.map +1 -0
- package/dist/observability/metrics.d.ts +49 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/metrics.js +184 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/observability/tracing.d.ts +28 -0
- package/dist/observability/tracing.d.ts.map +1 -0
- package/dist/observability/tracing.js +56 -0
- package/dist/observability/tracing.js.map +1 -0
- package/dist/prompts/index.d.ts +20 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +202 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/redaction/argsRedactor.d.ts +22 -0
- package/dist/redaction/argsRedactor.d.ts.map +1 -0
- package/dist/redaction/argsRedactor.js +97 -0
- package/dist/redaction/argsRedactor.js.map +1 -0
- package/dist/redaction/fields.d.ts +64 -0
- package/dist/redaction/fields.d.ts.map +1 -0
- package/dist/redaction/fields.js +155 -0
- package/dist/redaction/fields.js.map +1 -0
- package/dist/redaction/index.d.ts +52 -0
- package/dist/redaction/index.d.ts.map +1 -0
- package/dist/redaction/index.js +160 -0
- package/dist/redaction/index.js.map +1 -0
- package/dist/redaction/promptInjection.d.ts +32 -0
- package/dist/redaction/promptInjection.d.ts.map +1 -0
- package/dist/redaction/promptInjection.js +68 -0
- package/dist/redaction/promptInjection.js.map +1 -0
- package/dist/redaction/url.d.ts +8 -0
- package/dist/redaction/url.d.ts.map +1 -0
- package/dist/redaction/url.js +26 -0
- package/dist/redaction/url.js.map +1 -0
- package/dist/redaction/userAgent.d.ts +9 -0
- package/dist/redaction/userAgent.d.ts.map +1 -0
- package/dist/redaction/userAgent.js +39 -0
- package/dist/redaction/userAgent.js.map +1 -0
- package/dist/resources/index.d.ts +24 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +150 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/server.d.ts +37 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +35 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/baselineCompare.d.ts +36 -0
- package/dist/tools/baselineCompare.d.ts.map +1 -0
- package/dist/tools/baselineCompare.js +69 -0
- package/dist/tools/baselineCompare.js.map +1 -0
- package/dist/tools/errorHeatmap.d.ts +40 -0
- package/dist/tools/errorHeatmap.d.ts.map +1 -0
- package/dist/tools/errorHeatmap.js +69 -0
- package/dist/tools/errorHeatmap.js.map +1 -0
- package/dist/tools/errorHistogram.d.ts +39 -0
- package/dist/tools/errorHistogram.d.ts.map +1 -0
- package/dist/tools/errorHistogram.js +61 -0
- package/dist/tools/errorHistogram.js.map +1 -0
- package/dist/tools/errorStatsBreakdown.d.ts +43 -0
- package/dist/tools/errorStatsBreakdown.d.ts.map +1 -0
- package/dist/tools/errorStatsBreakdown.js +77 -0
- package/dist/tools/errorStatsBreakdown.js.map +1 -0
- package/dist/tools/errorsInSession.d.ts +44 -0
- package/dist/tools/errorsInSession.d.ts.map +1 -0
- package/dist/tools/errorsInSession.js +91 -0
- package/dist/tools/errorsInSession.js.map +1 -0
- package/dist/tools/explainError.d.ts +35 -0
- package/dist/tools/explainError.d.ts.map +1 -0
- package/dist/tools/explainError.js +98 -0
- package/dist/tools/explainError.js.map +1 -0
- package/dist/tools/findCorrelatedErrors.d.ts +43 -0
- package/dist/tools/findCorrelatedErrors.d.ts.map +1 -0
- package/dist/tools/findCorrelatedErrors.js +59 -0
- package/dist/tools/findCorrelatedErrors.js.map +1 -0
- package/dist/tools/findSimilarErrors.d.ts +44 -0
- package/dist/tools/findSimilarErrors.d.ts.map +1 -0
- package/dist/tools/findSimilarErrors.js +59 -0
- package/dist/tools/findSimilarErrors.js.map +1 -0
- package/dist/tools/getLogDetails.d.ts +30 -0
- package/dist/tools/getLogDetails.d.ts.map +1 -0
- package/dist/tools/getLogDetails.js +49 -0
- package/dist/tools/getLogDetails.js.map +1 -0
- package/dist/tools/groupedErrors.d.ts +46 -0
- package/dist/tools/groupedErrors.d.ts.map +1 -0
- package/dist/tools/groupedErrors.js +71 -0
- package/dist/tools/groupedErrors.js.map +1 -0
- package/dist/tools/impactAnalysis.d.ts +42 -0
- package/dist/tools/impactAnalysis.d.ts.map +1 -0
- package/dist/tools/impactAnalysis.js +61 -0
- package/dist/tools/impactAnalysis.js.map +1 -0
- package/dist/tools/index.d.ts +41 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +83 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/listApps.d.ts +27 -0
- package/dist/tools/listApps.d.ts.map +1 -0
- package/dist/tools/listApps.js +78 -0
- package/dist/tools/listApps.js.map +1 -0
- package/dist/tools/queryLogsJql.d.ts +44 -0
- package/dist/tools/queryLogsJql.d.ts.map +1 -0
- package/dist/tools/queryLogsJql.js +107 -0
- package/dist/tools/queryLogsJql.js.map +1 -0
- package/dist/tools/searchLogs.d.ts +60 -0
- package/dist/tools/searchLogs.d.ts.map +1 -0
- package/dist/tools/searchLogs.js +127 -0
- package/dist/tools/searchLogs.js.map +1 -0
- package/dist/tools/topErrorMessages.d.ts +42 -0
- package/dist/tools/topErrorMessages.d.ts.map +1 -0
- package/dist/tools/topErrorMessages.js +63 -0
- package/dist/tools/topErrorMessages.js.map +1 -0
- package/dist/tools/trafficStats.d.ts +39 -0
- package/dist/tools/trafficStats.d.ts.map +1 -0
- package/dist/tools/trafficStats.js +57 -0
- package/dist/tools/trafficStats.js.map +1 -0
- package/dist/tools/triageRecentCritical.d.ts +26 -0
- package/dist/tools/triageRecentCritical.d.ts.map +1 -0
- package/dist/tools/triageRecentCritical.js +81 -0
- package/dist/tools/triageRecentCritical.js.map +1 -0
- package/dist/tools/versionRegression.d.ts +29 -0
- package/dist/tools/versionRegression.d.ts.map +1 -0
- package/dist/tools/versionRegression.js +80 -0
- package/dist/tools/versionRegression.js.map +1 -0
- package/dist/transports/http-server.d.ts +25 -0
- package/dist/transports/http-server.d.ts.map +1 -0
- package/dist/transports/http-server.js +84 -0
- package/dist/transports/http-server.js.map +1 -0
- package/dist/transports/http.d.ts +71 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +315 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/transports/stdio.d.ts +13 -0
- package/dist/transports/stdio.d.ts.map +1 -0
- package/dist/transports/stdio.js +16 -0
- package/dist/transports/stdio.js.map +1 -0
- package/dist/types.d.ts +150 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +95 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
2
|
+
/**
|
|
3
|
+
* JWKS resolver через `jose.createRemoteJWKSet`.
|
|
4
|
+
*
|
|
5
|
+
* `createRemoteJWKSet` сам кеширует ключи (`cacheMaxAge`) и реализует cooldown
|
|
6
|
+
* между retry'ами при cache-miss (`cooldownDuration`). Мы используем:
|
|
7
|
+
* - cacheMaxAge: 10 минут — TTL ключа в кеше
|
|
8
|
+
* - cooldownDuration: 10 секунд — минимальный интервал между miss-fetch'ами
|
|
9
|
+
* - timeoutDuration: 5 секунд — HTTP timeout на fetch JWKS
|
|
10
|
+
*
|
|
11
|
+
* Для dev: переменная `MCP_OIDC_JWKS_URL` уже подхватывается через config —
|
|
12
|
+
* можно указать локальный auth URL (`http://localhost:4002/oidc/.well-known/jwks.json`).
|
|
13
|
+
*/
|
|
14
|
+
const CACHE_MAX_AGE_MS = 10 * 60 * 1000;
|
|
15
|
+
const COOLDOWN_MS = 10 * 1000;
|
|
16
|
+
const TIMEOUT_MS = 5 * 1000;
|
|
17
|
+
export function createJwksResolver(config) {
|
|
18
|
+
const jwks = createRemoteJWKSet(new URL(config.oidcJwksUrl), {
|
|
19
|
+
cacheMaxAge: CACHE_MAX_AGE_MS,
|
|
20
|
+
cooldownDuration: COOLDOWN_MS,
|
|
21
|
+
timeoutDuration: TIMEOUT_MS,
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
async verify(token) {
|
|
25
|
+
const { payload } = await jwtVerify(token, jwks, {
|
|
26
|
+
issuer: config.oidcIssuer,
|
|
27
|
+
audience: config.oidcAudience,
|
|
28
|
+
algorithms: ['RS256'],
|
|
29
|
+
});
|
|
30
|
+
return payload;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=jwks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwks.js","sourceRoot":"","sources":["../../src/http/jwks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAmB,SAAS,EAAE,MAAM,MAAM,CAAC;AAGtE;;;;;;;;;;;GAWG;AAEH,MAAM,gBAAgB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACxC,MAAM,WAAW,GAAG,EAAE,GAAG,IAAI,CAAC;AAC9B,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,CAAC;AAO5B,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE;QAC3D,WAAW,EAAE,gBAAgB;QAC7B,gBAAgB,EAAE,WAAW;QAC7B,eAAe,EAAE,UAAU;KAC5B,CAAC,CAAC;IAEH,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,KAAa;YACxB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE;gBAC/C,MAAM,EAAE,MAAM,CAAC,UAAU;gBACzB,QAAQ,EAAE,MAAM,CAAC,YAAY;gBAC7B,UAAU,EAAE,CAAC,OAAO,CAAC;aACtB,CAAC,CAAC;YACH,OAAO,OAAO,CAAC;QACjB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RequestHandler } from 'express';
|
|
2
|
+
import type { Logger } from 'pino';
|
|
3
|
+
import type { Config } from '../../config.js';
|
|
4
|
+
import type { JwksResolver } from '../jwks.js';
|
|
5
|
+
export interface CreateAuthOptions {
|
|
6
|
+
config: Config;
|
|
7
|
+
jwks: JwksResolver;
|
|
8
|
+
log?: Logger;
|
|
9
|
+
}
|
|
10
|
+
export declare function createAuthMiddleware(opts: CreateAuthOptions): RequestHandler;
|
|
11
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/http/middleware/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAC;AAC/E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAoB/C,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,YAAY,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,GAAG,cAAc,CAiI5E"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { recordAuthRejection } from '../../observability/metrics.js';
|
|
2
|
+
/**
|
|
3
|
+
* Auth middleware для HTTP transport.
|
|
4
|
+
*
|
|
5
|
+
* Поддерживает два path:
|
|
6
|
+
* A. ELS-key Bearer (`Bearer els_(live|test)_...`) — passthrough в ELS как есть.
|
|
7
|
+
* B. OIDC JWT Bearer — локально валидируем через JWKS (INSO Auth).
|
|
8
|
+
*
|
|
9
|
+
* Если оба заголовка не подходят (или их нет) — 401 с `WWW-Authenticate`,
|
|
10
|
+
* указывающим на RFC 9728 resource metadata.
|
|
11
|
+
*
|
|
12
|
+
* Маршруты `/healthz`, `/readyz` и `/.well-known/*` исключены из этого
|
|
13
|
+
* middleware на уровне роутинга — здесь мы их не проверяем.
|
|
14
|
+
*/
|
|
15
|
+
const ELS_KEY_REGEX = /^els_(live|test)_[\w-]{30,}$/;
|
|
16
|
+
export function createAuthMiddleware(opts) {
|
|
17
|
+
const { config, jwks, log } = opts;
|
|
18
|
+
const wwwAuthHeader = buildWwwAuthenticate(config.publicUrl);
|
|
19
|
+
return async function authMiddleware(req, res, next) {
|
|
20
|
+
const authHeader = readAuthorization(req);
|
|
21
|
+
if (!authHeader) {
|
|
22
|
+
reject(res, log, wwwAuthHeader, 'missing_authorization', 401, {
|
|
23
|
+
error: 'unauthorized',
|
|
24
|
+
error_description: 'Missing Authorization header',
|
|
25
|
+
resource_metadata: `${config.publicUrl}/.well-known/oauth-protected-resource`,
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const token = authHeader.slice('Bearer '.length).trim();
|
|
30
|
+
if (token.length === 0) {
|
|
31
|
+
reject(res, log, wwwAuthHeader, 'empty_bearer', 401, {
|
|
32
|
+
error: 'invalid_token',
|
|
33
|
+
error_description: 'Empty Bearer token',
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Path A: ELS-key (priority — phase 3)
|
|
38
|
+
if (ELS_KEY_REGEX.test(token)) {
|
|
39
|
+
const ctx = buildElsKeyContext(req, token, config);
|
|
40
|
+
req.context = ctx;
|
|
41
|
+
log?.debug?.({ keyId: ctx.keyId, requestId: ctx.requestId }, 'auth: els-key');
|
|
42
|
+
next();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Path B: OIDC JWT
|
|
46
|
+
if (looksLikeJwt(token)) {
|
|
47
|
+
try {
|
|
48
|
+
const payload = await jwks.verify(token);
|
|
49
|
+
const sub = typeof payload.sub === 'string' ? payload.sub : '';
|
|
50
|
+
if (!sub) {
|
|
51
|
+
recordAuthRejection('oidc_missing_sub');
|
|
52
|
+
log?.warn?.({ requestId: pickRequestId(req) }, 'OIDC reject: missing sub');
|
|
53
|
+
reject(res, log, wwwAuthHeader, null, 401, {
|
|
54
|
+
error: 'invalid_token',
|
|
55
|
+
error_description: 'JWT missing sub claim',
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// Phase 6: scope-claim обязателен (не просто пустой).
|
|
60
|
+
if (!hasScopeClaim(payload)) {
|
|
61
|
+
recordAuthRejection('oidc_missing_scope_claim');
|
|
62
|
+
log?.warn?.({ sub, requestId: pickRequestId(req) }, 'OIDC reject: scope claim absent');
|
|
63
|
+
reject(res, log, wwwAuthHeader, null, 403, {
|
|
64
|
+
error: 'insufficient_scope',
|
|
65
|
+
error_description: 'Token has no scope claim (scope / scp)',
|
|
66
|
+
required_scope: 'errors:mcp-read',
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const scopes = extractScopes(payload);
|
|
71
|
+
if (!scopes.includes('errors:mcp-read')) {
|
|
72
|
+
recordAuthRejection('oidc_insufficient_scope');
|
|
73
|
+
log?.warn?.({ sub, scopes, requestId: pickRequestId(req) }, 'OIDC reject: insufficient scope');
|
|
74
|
+
reject(res, log, wwwAuthHeader, null, 403, {
|
|
75
|
+
error: 'insufficient_scope',
|
|
76
|
+
error_description: 'Token lacks required scope "errors:mcp-read"',
|
|
77
|
+
required_scope: 'errors:mcp-read',
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Phase 6: проверка audience — JWT должен содержать ожидаемую audience.
|
|
82
|
+
if (!hasAudience(payload, config.oidcAudience)) {
|
|
83
|
+
recordAuthRejection('oidc_invalid_audience');
|
|
84
|
+
log?.warn?.({ sub, aud: payload.aud, expected: config.oidcAudience, requestId: pickRequestId(req) }, 'OIDC reject: invalid audience');
|
|
85
|
+
reject(res, log, wwwAuthHeader, null, 401, {
|
|
86
|
+
error: 'invalid_token',
|
|
87
|
+
error_description: `Token audience does not include "${config.oidcAudience}"`,
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// TODO Phase 4: реальный resolver sub → доступные appSlugs через LK API.
|
|
92
|
+
// Phase 3 workaround: либо ENV MCP_OIDC_DEMO_APP_SLUG, либо отказ.
|
|
93
|
+
const appSlug = config.oidcDemoAppSlug ?? '';
|
|
94
|
+
if (!appSlug) {
|
|
95
|
+
recordAuthRejection('oidc_no_app_resolver');
|
|
96
|
+
res.status(503).json({
|
|
97
|
+
error: 'oidc_app_resolver_unavailable',
|
|
98
|
+
error_description: 'OIDC sub → appSlug resolver not configured. Set MCP_OIDC_DEMO_APP_SLUG for Phase 3.',
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const ctx = buildOidcContext(req, sub, scopes, appSlug);
|
|
103
|
+
req.context = ctx;
|
|
104
|
+
log?.debug?.({ sub, requestId: ctx.requestId, appSlug }, 'auth: oidc');
|
|
105
|
+
next();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
recordAuthRejection('oidc_verify_failed');
|
|
110
|
+
log?.warn?.({ err: errMsg(err) }, 'OIDC token verification failed');
|
|
111
|
+
reject(res, log, wwwAuthHeader, null, 401, {
|
|
112
|
+
error: 'invalid_token',
|
|
113
|
+
error_description: 'Failed to verify OIDC token',
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Не похоже ни на ELS-key, ни на JWT
|
|
119
|
+
reject(res, log, wwwAuthHeader, 'unrecognized_token_shape', 401, {
|
|
120
|
+
error: 'invalid_token',
|
|
121
|
+
error_description: 'Token format not recognized',
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function reject(res, _log, wwwAuthHeader, metricReason, status, body) {
|
|
126
|
+
if (metricReason !== null) {
|
|
127
|
+
try {
|
|
128
|
+
recordAuthRejection(metricReason);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// ignore
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
res.setHeader('WWW-Authenticate', wwwAuthHeader);
|
|
135
|
+
res.status(status).json(body);
|
|
136
|
+
}
|
|
137
|
+
function hasScopeClaim(payload) {
|
|
138
|
+
return payload.scope !== undefined || payload.scp !== undefined;
|
|
139
|
+
}
|
|
140
|
+
function hasAudience(payload, expected) {
|
|
141
|
+
const aud = payload.aud;
|
|
142
|
+
if (typeof aud === 'string')
|
|
143
|
+
return aud === expected;
|
|
144
|
+
if (Array.isArray(aud))
|
|
145
|
+
return aud.includes(expected);
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
function readAuthorization(req) {
|
|
149
|
+
const raw = req.headers.authorization;
|
|
150
|
+
if (!raw || typeof raw !== 'string')
|
|
151
|
+
return null;
|
|
152
|
+
if (!raw.toLowerCase().startsWith('bearer '))
|
|
153
|
+
return null;
|
|
154
|
+
return raw;
|
|
155
|
+
}
|
|
156
|
+
function looksLikeJwt(s) {
|
|
157
|
+
// header.payload.signature — три base64url-сегмента через точку.
|
|
158
|
+
return /^[\w-]+\.[\w-]+\.[\w-]+$/.test(s);
|
|
159
|
+
}
|
|
160
|
+
function extractScopes(payload) {
|
|
161
|
+
const raw = payload.scope ?? payload.scp;
|
|
162
|
+
if (typeof raw === 'string')
|
|
163
|
+
return raw.split(/\s+/).filter(Boolean);
|
|
164
|
+
if (Array.isArray(raw))
|
|
165
|
+
return raw.filter((x) => typeof x === 'string');
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
function buildElsKeyContext(req, token, _config) {
|
|
169
|
+
return {
|
|
170
|
+
authMethod: 'els-key',
|
|
171
|
+
elsApiKey: token,
|
|
172
|
+
appSlug: '', // не известен на этом этапе — резолвится в elsClient через ELS upstream
|
|
173
|
+
keyId: token.slice(0, 12), // els_live_xxxx — visibility для логов
|
|
174
|
+
ip: pickIp(req),
|
|
175
|
+
userAgent: pickUserAgent(req),
|
|
176
|
+
sessionId: pickSessionId(req),
|
|
177
|
+
requestId: pickRequestId(req),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function buildOidcContext(req, sub, scopes, appSlug) {
|
|
181
|
+
return {
|
|
182
|
+
authMethod: 'oidc',
|
|
183
|
+
oidcSub: sub,
|
|
184
|
+
scopes,
|
|
185
|
+
appSlug,
|
|
186
|
+
keyId: `oidc:${sub.slice(0, 8)}`,
|
|
187
|
+
ip: pickIp(req),
|
|
188
|
+
userAgent: pickUserAgent(req),
|
|
189
|
+
sessionId: pickSessionId(req),
|
|
190
|
+
requestId: pickRequestId(req),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function pickIp(req) {
|
|
194
|
+
const xff = req.headers['x-forwarded-for'];
|
|
195
|
+
if (typeof xff === 'string') {
|
|
196
|
+
const first = xff.split(',')[0]?.trim();
|
|
197
|
+
if (first)
|
|
198
|
+
return first;
|
|
199
|
+
}
|
|
200
|
+
return req.socket.remoteAddress ?? '';
|
|
201
|
+
}
|
|
202
|
+
function pickUserAgent(req) {
|
|
203
|
+
const ua = req.headers['user-agent'];
|
|
204
|
+
return typeof ua === 'string' ? ua : '';
|
|
205
|
+
}
|
|
206
|
+
function pickSessionId(req) {
|
|
207
|
+
const sid = req.headers['mcp-session-id'];
|
|
208
|
+
if (typeof sid === 'string' && sid.length > 0)
|
|
209
|
+
return sid;
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
function pickRequestId(req) {
|
|
213
|
+
const existing = req.headers['x-request-id'];
|
|
214
|
+
if (typeof existing === 'string' && existing.length > 0)
|
|
215
|
+
return existing;
|
|
216
|
+
// Запасной id, если request-id middleware не отработал.
|
|
217
|
+
return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
218
|
+
}
|
|
219
|
+
function buildWwwAuthenticate(publicUrl) {
|
|
220
|
+
return `Bearer realm="els-mcp", resource_metadata="${publicUrl}/.well-known/oauth-protected-resource"`;
|
|
221
|
+
}
|
|
222
|
+
function errMsg(err) {
|
|
223
|
+
return err instanceof Error ? err.message : String(err);
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/http/middleware/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RequestHandler } from 'express';
|
|
2
|
+
import type { Logger } from 'pino';
|
|
3
|
+
import type { RedisService } from '../../cache/redis.js';
|
|
4
|
+
/**
|
|
5
|
+
* Phase 6: Rate-limit middleware для **будущего** Dynamic Client Registration
|
|
6
|
+
* endpoint (`POST /oauth/register`). DCR ещё не реализован в auth-сервисе —
|
|
7
|
+
* этот файл — stub'а, готовая к подключению.
|
|
8
|
+
*
|
|
9
|
+
* Алгоритм:
|
|
10
|
+
* - Redis-based sliding window: один счётчик per IP, ключ `mcp:dcr:rl:{ip}`.
|
|
11
|
+
* - INCR + EXPIRE (на первый INCR) на window секунд.
|
|
12
|
+
* - Если current > limit → 429 + `Retry-After`.
|
|
13
|
+
* - Если Redis недоступен → fail-open (предупреждаем в log).
|
|
14
|
+
*
|
|
15
|
+
* Default лимит: 10 регистраций / час / IP. Можно тюнить через опции.
|
|
16
|
+
*
|
|
17
|
+
* NB: Подключать middleware ТОЛЬКО когда `/oauth/register` появится.
|
|
18
|
+
* Сейчас файл существует, но не используется в `http/app.ts`.
|
|
19
|
+
*/
|
|
20
|
+
export interface CreateDcrRateLimitOptions {
|
|
21
|
+
redis?: RedisService | null;
|
|
22
|
+
/** Лимит запросов на окно. Default 10. */
|
|
23
|
+
limit?: number;
|
|
24
|
+
/** Длина окна в секундах. Default 3600 (1 час). */
|
|
25
|
+
windowSec?: number;
|
|
26
|
+
log?: Logger;
|
|
27
|
+
}
|
|
28
|
+
export declare function createDcrRateLimit(opts?: CreateDcrRateLimitOptions): RequestHandler;
|
|
29
|
+
//# sourceMappingURL=dcrRateLimit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dcrRateLimit.d.ts","sourceRoot":"","sources":["../../../src/http/middleware/dcrRateLimit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAC;AAC/E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,0CAA0C;IAC1C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAMD,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,yBAA8B,GAAG,cAAc,CAoDvF"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const DEFAULT_LIMIT = 10;
|
|
2
|
+
const DEFAULT_WINDOW_SEC = 3600;
|
|
3
|
+
const KEY_PREFIX = 'mcp:dcr:rl:';
|
|
4
|
+
export function createDcrRateLimit(opts = {}) {
|
|
5
|
+
const redis = opts.redis;
|
|
6
|
+
const limit = opts.limit ?? DEFAULT_LIMIT;
|
|
7
|
+
const windowSec = opts.windowSec ?? DEFAULT_WINDOW_SEC;
|
|
8
|
+
const log = opts.log;
|
|
9
|
+
return async function dcrRateLimit(req, res, next) {
|
|
10
|
+
const ip = pickIp(req);
|
|
11
|
+
if (!ip) {
|
|
12
|
+
// не определили IP — пропускаем
|
|
13
|
+
next();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (!redis || redis.unavailable) {
|
|
17
|
+
log?.warn?.({ ip }, 'dcrRateLimit: Redis unavailable, fail-open');
|
|
18
|
+
next();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const key = `${KEY_PREFIX}${ip}`;
|
|
22
|
+
try {
|
|
23
|
+
const raw = redis.raw;
|
|
24
|
+
if (!raw) {
|
|
25
|
+
next();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const current = await raw.incr(key);
|
|
29
|
+
if (current === 1) {
|
|
30
|
+
// первый INCR — выставляем TTL окна.
|
|
31
|
+
await raw.expire(key, windowSec);
|
|
32
|
+
}
|
|
33
|
+
if (current > limit) {
|
|
34
|
+
const ttl = await raw.ttl(key);
|
|
35
|
+
res.setHeader('Retry-After', String(Math.max(1, ttl)));
|
|
36
|
+
res.status(429).json({
|
|
37
|
+
error: 'rate_limited',
|
|
38
|
+
error_description: `DCR rate limit exceeded (${limit}/${windowSec}s)`,
|
|
39
|
+
});
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
// На ошибках Redis — fail-open.
|
|
45
|
+
log?.warn?.({ err: err.message, ip }, 'dcrRateLimit: Redis error, fail-open');
|
|
46
|
+
}
|
|
47
|
+
next();
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function pickIp(req) {
|
|
51
|
+
const xff = req.headers['x-forwarded-for'];
|
|
52
|
+
if (typeof xff === 'string') {
|
|
53
|
+
const first = xff.split(',')[0]?.trim();
|
|
54
|
+
if (first)
|
|
55
|
+
return first;
|
|
56
|
+
}
|
|
57
|
+
return req.socket?.remoteAddress ?? '';
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=dcrRateLimit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dcrRateLimit.js","sourceRoot":"","sources":["../../../src/http/middleware/dcrRateLimit.ts"],"names":[],"mappings":"AA8BA,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAChC,MAAM,UAAU,GAAG,aAAa,CAAC;AAEjC,MAAM,UAAU,kBAAkB,CAAC,OAAkC,EAAE;IACrE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACzB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,aAAa,CAAC;IAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,kBAAkB,CAAC;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;IAErB,OAAO,KAAK,UAAU,YAAY,CAChC,GAAY,EACZ,GAAa,EACb,IAAkB;QAElB,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,gCAAgC;YAChC,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;YAChC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,4CAA4C,CAAC,CAAC;YAClE,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,MAAM,GAAG,GAAG,GAAG,UAAU,GAAG,EAAE,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC;YACtB,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,IAAI,EAAE,CAAC;gBACP,OAAO;YACT,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACpC,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;gBAClB,qCAAqC;gBACrC,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACnC,CAAC;YACD,IAAI,OAAO,GAAG,KAAK,EAAE,CAAC;gBACpB,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC/B,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;gBACvD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnB,KAAK,EAAE,cAAc;oBACrB,iBAAiB,EAAE,4BAA4B,KAAK,IAAI,SAAS,IAAI;iBACtE,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,gCAAgC;YAChC,GAAG,EAAE,IAAI,EAAE,CACT,EAAE,GAAG,EAAG,GAAa,CAAC,OAAO,EAAE,EAAE,EAAE,EACnC,sCAAsC,CACvC,CAAC;QACJ,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,GAAY;IAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC3C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QACxC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,GAAG,CAAC,MAAM,EAAE,aAAa,IAAI,EAAE,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ErrorRequestHandler } from 'express';
|
|
2
|
+
import type { Logger } from 'pino';
|
|
3
|
+
/**
|
|
4
|
+
* Глобальный error handler для Express. Логирует исключение, отдаёт JSON 500.
|
|
5
|
+
*
|
|
6
|
+
* Должен быть зарегистрирован последним (`app.use(errorHandler(log))`).
|
|
7
|
+
*
|
|
8
|
+
* Phase 6: предпочитает per-request `req.log` (если установлен в requestId
|
|
9
|
+
* middleware) — иначе fallback на global logger.
|
|
10
|
+
*/
|
|
11
|
+
export declare function errorHandler(log?: Logger): ErrorRequestHandler;
|
|
12
|
+
//# sourceMappingURL=errorHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../../src/http/middleware/errorHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAmB9D"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Глобальный error handler для Express. Логирует исключение, отдаёт JSON 500.
|
|
3
|
+
*
|
|
4
|
+
* Должен быть зарегистрирован последним (`app.use(errorHandler(log))`).
|
|
5
|
+
*
|
|
6
|
+
* Phase 6: предпочитает per-request `req.log` (если установлен в requestId
|
|
7
|
+
* middleware) — иначе fallback на global logger.
|
|
8
|
+
*/
|
|
9
|
+
export function errorHandler(log) {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
11
|
+
return function expressErrorHandler(err, req, res, _next) {
|
|
12
|
+
const requestId = typeof req.headers['x-request-id'] === 'string'
|
|
13
|
+
? req.headers['x-request-id']
|
|
14
|
+
: undefined;
|
|
15
|
+
const reqLog = req.log ?? log;
|
|
16
|
+
reqLog?.error?.({ err: err instanceof Error ? err.message : String(err), path: req.path }, 'Unhandled error in HTTP handler');
|
|
17
|
+
if (res.headersSent)
|
|
18
|
+
return;
|
|
19
|
+
res.status(500).json({
|
|
20
|
+
error: 'internal',
|
|
21
|
+
error_description: 'Internal server error',
|
|
22
|
+
requestId,
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=errorHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../../src/http/middleware/errorHandler.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,GAAY;IACvC,6DAA6D;IAC7D,OAAO,SAAS,mBAAmB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK;QACtD,MAAM,SAAS,GACb,OAAO,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK,QAAQ;YAC7C,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC;YAC7B,CAAC,CAAC,SAAS,CAAC;QAChB,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC;QAC9B,MAAM,EAAE,KAAK,EAAE,CACb,EAAE,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,EACzE,iCAAiC,CAClC,CAAC;QACF,IAAI,GAAG,CAAC,WAAW;YAAE,OAAO;QAC5B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,KAAK,EAAE,UAAU;YACjB,iBAAiB,EAAE,uBAAuB;YAC1C,SAAS;SACV,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RequestHandler } from 'express';
|
|
2
|
+
import type { Logger } from 'pino';
|
|
3
|
+
/**
|
|
4
|
+
* Phase 6 origin-validation middleware для /els/mcp.
|
|
5
|
+
*
|
|
6
|
+
* Семантика:
|
|
7
|
+
* - Если запрос пришёл с `Origin` header (browser-based MCP клиент или
|
|
8
|
+
* EventSource из веб-приложения) — origin валидируется против whitelist.
|
|
9
|
+
* При несовпадении → 403 + `forbidden_origin`.
|
|
10
|
+
* - Если `Origin` отсутствует (server-to-server: curl, MCP SDK из node,
|
|
11
|
+
* Claude Desktop через stdio-proxy и т. п.) — middleware пропускает.
|
|
12
|
+
*
|
|
13
|
+
* Это дополнительная защита над cors():
|
|
14
|
+
* - cors() блокирует preflight; но non-preflight POST без credentials
|
|
15
|
+
* может пройти.
|
|
16
|
+
* - originGuard явно отказывает unknown-origin'ам на пути /els/mcp.
|
|
17
|
+
*
|
|
18
|
+
* Wildcard `*` в whitelist: dev-only режим (NODE_ENV !== 'production').
|
|
19
|
+
*/
|
|
20
|
+
export interface CreateOriginGuardOptions {
|
|
21
|
+
/** Список разрешённых origin'ов. Пустой — guard отключён. */
|
|
22
|
+
allowed: string[];
|
|
23
|
+
/** Если true — `*` в whitelist означает «пропускать любой origin». */
|
|
24
|
+
allowWildcard?: boolean;
|
|
25
|
+
log?: Logger;
|
|
26
|
+
}
|
|
27
|
+
export declare function createOriginGuard(opts: CreateOriginGuardOptions): RequestHandler;
|
|
28
|
+
//# sourceMappingURL=originGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"originGuard.d.ts","sourceRoot":"","sources":["../../../src/http/middleware/originGuard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAC;AAC/E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAGnC;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,wBAAwB;IACvC,6DAA6D;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sEAAsE;IACtE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,wBAAwB,GAAG,cAAc,CAoDhF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { recordAuthRejection } from '../../observability/metrics.js';
|
|
2
|
+
export function createOriginGuard(opts) {
|
|
3
|
+
const exact = new Set();
|
|
4
|
+
let wildcard = false;
|
|
5
|
+
for (const o of opts.allowed) {
|
|
6
|
+
if (o === '*') {
|
|
7
|
+
if (opts.allowWildcard)
|
|
8
|
+
wildcard = true;
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (o === 'http://localhost' || o === 'http://127.0.0.1')
|
|
12
|
+
continue;
|
|
13
|
+
exact.add(o);
|
|
14
|
+
}
|
|
15
|
+
// dev fallback — допускаем любой localhost-порт если http://localhost есть в списке.
|
|
16
|
+
const allowLocalhost = opts.allowed.includes('http://localhost');
|
|
17
|
+
return function originGuard(req, res, next) {
|
|
18
|
+
const origin = readOrigin(req);
|
|
19
|
+
// Нет origin — пропускаем (server-to-server).
|
|
20
|
+
if (!origin) {
|
|
21
|
+
next();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (wildcard) {
|
|
25
|
+
next();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (exact.has(origin)) {
|
|
29
|
+
next();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (allowLocalhost && /^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin)) {
|
|
33
|
+
next();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
recordAuthRejection('forbidden_origin');
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// не блокируем основной flow
|
|
41
|
+
}
|
|
42
|
+
opts.log?.warn?.({ origin, path: req.path }, 'originGuard rejection');
|
|
43
|
+
res.status(403).json({
|
|
44
|
+
error: 'forbidden_origin',
|
|
45
|
+
error_description: `Origin "${origin}" is not allowed`,
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function readOrigin(req) {
|
|
50
|
+
const raw = req.headers.origin;
|
|
51
|
+
if (typeof raw === 'string' && raw.length > 0)
|
|
52
|
+
return raw;
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=originGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"originGuard.js","sourceRoot":"","sources":["../../../src/http/middleware/originGuard.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AA4BrE,MAAM,UAAU,iBAAiB,CAAC,IAA8B;IAC9D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;YACd,IAAI,IAAI,CAAC,aAAa;gBAAE,QAAQ,GAAG,IAAI,CAAC;YACxC,SAAS;QACX,CAAC;QACD,IAAI,CAAC,KAAK,kBAAkB,IAAI,CAAC,KAAK,kBAAkB;YAAE,SAAS;QACnE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACf,CAAC;IAED,qFAAqF;IACrF,MAAM,cAAc,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IAEjE,OAAO,SAAS,WAAW,CACzB,GAAY,EACZ,GAAa,EACb,IAAkB;QAElB,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QAE/B,8CAA8C;QAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QAED,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACtB,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,cAAc,IAAI,4CAA4C,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAChF,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,mBAAmB,CAAC,kBAAkB,CAAC,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,6BAA6B;QAC/B,CAAC;QACD,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,uBAAuB,CAAC,CAAC;QACtE,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,KAAK,EAAE,kBAAkB;YACzB,iBAAiB,EAAE,WAAW,MAAM,kBAAkB;SACvD,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,GAAY;IAC9B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;IAC/B,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IAC1D,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RequestHandler } from 'express';
|
|
2
|
+
import type { Logger } from 'pino';
|
|
3
|
+
/**
|
|
4
|
+
* Простой request-id middleware. Принимает существующий `X-Request-Id`, либо
|
|
5
|
+
* генерирует новый UUIDv4. Кладёт в `req.headers['x-request-id']`, чтобы
|
|
6
|
+
* downstream middleware (auth) могли подхватить его в context.
|
|
7
|
+
*
|
|
8
|
+
* Также ставит response header `X-Request-Id` для клиента (полезно при триаже).
|
|
9
|
+
*
|
|
10
|
+
* Phase 6: если передан base logger — добавляем `req.log = base.child({...})`
|
|
11
|
+
* с per-request полями (`requestId`, `sessionId`). Downstream middleware
|
|
12
|
+
* (auth, errorHandler) могут использовать `req.log` для лучшей корреляции.
|
|
13
|
+
*/
|
|
14
|
+
export interface RequestIdOptions {
|
|
15
|
+
/** Базовый pino-логгер. Если задан — каждому запросу создаётся child. */
|
|
16
|
+
logger?: Logger;
|
|
17
|
+
}
|
|
18
|
+
export declare function requestId(opts?: RequestIdOptions): RequestHandler;
|
|
19
|
+
//# sourceMappingURL=requestId.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestId.d.ts","sourceRoot":"","sources":["../../../src/http/middleware/requestId.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAC;AAE/E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,SAAS,CAAC,IAAI,GAAE,gBAAqB,GAAG,cAAc,CA0BrE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
export function requestId(opts = {}) {
|
|
3
|
+
const baseLogger = opts.logger;
|
|
4
|
+
return function requestIdMiddleware(req, res, next) {
|
|
5
|
+
let id = req.headers['x-request-id'];
|
|
6
|
+
if (typeof id !== 'string' || id.length === 0) {
|
|
7
|
+
id = randomUUID();
|
|
8
|
+
req.headers['x-request-id'] = id;
|
|
9
|
+
}
|
|
10
|
+
res.setHeader('X-Request-Id', id);
|
|
11
|
+
if (baseLogger) {
|
|
12
|
+
const sid = typeof req.headers['mcp-session-id'] === 'string'
|
|
13
|
+
? req.headers['mcp-session-id']
|
|
14
|
+
: undefined;
|
|
15
|
+
req.log = baseLogger.child({
|
|
16
|
+
requestId: id,
|
|
17
|
+
...(sid !== undefined ? { sessionId: sid } : {}),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
next();
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=requestId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestId.js","sourceRoot":"","sources":["../../../src/http/middleware/requestId.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAmBzC,MAAM,UAAU,SAAS,CAAC,OAAyB,EAAE;IACnD,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/B,OAAO,SAAS,mBAAmB,CACjC,GAAY,EACZ,GAAa,EACb,IAAkB;QAElB,IAAI,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACrC,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9C,EAAE,GAAG,UAAU,EAAE,CAAC;YAClB,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,CAAC;QACnC,CAAC;QACD,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;QAElC,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,GAAG,GACP,OAAO,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,QAAQ;gBAC/C,CAAC,CAAE,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAY;gBAC3C,CAAC,CAAC,SAAS,CAAC;YAChB,GAAG,CAAC,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC;gBACzB,SAAS,EAAE,EAAE;gBACb,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACjD,CAAC,CAAC;QACL,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import type { Logger } from 'pino';
|
|
3
|
+
import type { ElsClient } from '../../elsClient.js';
|
|
4
|
+
import type { RedisService } from '../../cache/redis.js';
|
|
5
|
+
/**
|
|
6
|
+
* Health endpoints:
|
|
7
|
+
* - `GET /health` — INSO Uptime-совместимый формат: `{status, uptime, version, hostname}`.
|
|
8
|
+
* Используется LK Uptime Service (health-monitor) для мониторинга,
|
|
9
|
+
* парсит поле `version` (BUILD_VERSION из CI).
|
|
10
|
+
* - `GET /healthz` — liveness (legacy alias). Всегда 200.
|
|
11
|
+
* - `GET /readyz` — readiness. Проверяет upstream ELS И Redis.
|
|
12
|
+
*/
|
|
13
|
+
export interface CreateHealthOptions {
|
|
14
|
+
elsClient?: ElsClient;
|
|
15
|
+
redis?: RedisService;
|
|
16
|
+
/** Дополнительные probe'ы (БД, etc.) — будут вызваны последовательно. */
|
|
17
|
+
probes?: Array<{
|
|
18
|
+
name: string;
|
|
19
|
+
check: () => Promise<void>;
|
|
20
|
+
}>;
|
|
21
|
+
log?: Logger;
|
|
22
|
+
}
|
|
23
|
+
export declare function createHealthRouter(opts?: CreateHealthOptions): Router;
|
|
24
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../../src/http/routes/health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAuB,MAAM,SAAS,CAAC;AAEtD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAMzD;;;;;;;GAOG;AAEH,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,yEAAyE;IACzE,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC,CAAC;IAC7D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,mBAAwB,GAAG,MAAM,CAOzE"}
|