@lensmcp/cluster 1.18.3 → 1.18.6
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/basic-ssl.d.ts +0 -1
- package/basic-ssl.js +1 -241
- package/build-scope-patterns.d.ts +0 -1
- package/build-scope-patterns.js +1 -40
- package/create-webpack-dev.d.ts +0 -1
- package/create-webpack-dev.js +1 -186
- package/create-webpack-prod.d.ts +0 -1
- package/create-webpack-prod.js +1 -169
- package/executors/build/build.impl.d.ts +0 -1
- package/executors/build/build.impl.js +1 -98
- package/executors/gateway/gateway-errors.d.ts +0 -1
- package/executors/gateway/gateway-errors.js +1 -43
- package/executors/gateway/gateway.impl.d.ts +0 -1
- package/executors/gateway/gateway.impl.js +1 -53
- package/executors/gateway/gateway.lib.d.ts +0 -1
- package/executors/gateway/gateway.lib.js +1 -29
- package/executors/gateway/health-check.d.ts +0 -1
- package/executors/gateway/health-check.js +1 -66
- package/executors/gateway/jwks-verify.d.ts +0 -1
- package/executors/gateway/jwks-verify.js +1 -121
- package/executors/gateway/main.prod-gateway.d.ts +0 -1
- package/executors/gateway/main.prod-gateway.js +2 -573
- package/executors/gateway/main.rollout.d.ts +0 -1
- package/executors/gateway/main.rollout.js +11 -117
- package/executors/gateway/manifest.d.ts +0 -1
- package/executors/gateway/manifest.js +1 -374
- package/executors/gateway/metrics.d.ts +0 -1
- package/executors/gateway/metrics.js +1 -56
- package/executors/gateway/otel-tracing.d.ts +0 -1
- package/executors/gateway/otel-tracing.js +1 -74
- package/executors/gateway/prod-gateway.lib.d.ts +0 -1
- package/executors/gateway/prod-gateway.lib.js +1 -22
- package/executors/gateway/prod-runtime/access-log.d.ts +0 -1
- package/executors/gateway/prod-runtime/access-log.js +1 -24
- package/executors/gateway/prod-runtime/app.d.ts +0 -1
- package/executors/gateway/prod-runtime/app.js +1 -123
- package/executors/gateway/prod-runtime/auth.d.ts +0 -1
- package/executors/gateway/prod-runtime/auth.js +1 -51
- package/executors/gateway/prod-runtime/cors.d.ts +0 -1
- package/executors/gateway/prod-runtime/cors.js +1 -40
- package/executors/gateway/prod-runtime/edge.d.ts +0 -1
- package/executors/gateway/prod-runtime/edge.js +1 -65
- package/executors/gateway/prod-runtime/handler.d.ts +0 -1
- package/executors/gateway/prod-runtime/handler.js +1 -226
- package/executors/gateway/prod-runtime/hooks.d.ts +0 -1
- package/executors/gateway/prod-runtime/hooks.js +1 -42
- package/executors/gateway/prod-runtime/observability.d.ts +0 -1
- package/executors/gateway/prod-runtime/observability.js +1 -125
- package/executors/gateway/prod-runtime/rollout.d.ts +0 -1
- package/executors/gateway/prod-runtime/rollout.js +1 -103
- package/executors/gateway/prod-runtime/routing.d.ts +0 -1
- package/executors/gateway/prod-runtime/routing.js +1 -40
- package/executors/gateway/prod-runtime/server.d.ts +0 -1
- package/executors/gateway/prod-runtime/server.js +1 -79
- package/executors/gateway/prod-runtime/trust.d.ts +0 -1
- package/executors/gateway/prod-runtime/trust.js +1 -32
- package/executors/gateway/prod-runtime/types.d.ts +0 -1
- package/executors/gateway/prod-runtime/types.js +1 -2
- package/executors/gateway/prod-runtime/upgrade.d.ts +0 -1
- package/executors/gateway/prod-runtime/upgrade.js +4 -116
- package/executors/gateway/prod-runtime/upstream.d.ts +0 -1
- package/executors/gateway/prod-runtime/upstream.js +1 -21
- package/executors/gateway/providers-prod.d.ts +0 -1
- package/executors/gateway/providers-prod.js +1 -232
- package/executors/gateway/rate-limit.d.ts +0 -1
- package/executors/gateway/rate-limit.js +2 -75
- package/executors/gateway/registry-source.d.ts +0 -1
- package/executors/gateway/registry-source.js +1 -131
- package/executors/gateway/rollout-ops.d.ts +0 -1
- package/executors/gateway/rollout-ops.js +2 -167
- package/executors/gateway/runtime/auth.d.ts +0 -1
- package/executors/gateway/runtime/auth.js +1 -64
- package/executors/gateway/runtime/chooser.d.ts +0 -1
- package/executors/gateway/runtime/chooser.js +12 -45
- package/executors/gateway/runtime/control.d.ts +0 -1
- package/executors/gateway/runtime/control.js +1 -128
- package/executors/gateway/runtime/dev-auth.d.ts +0 -1
- package/executors/gateway/runtime/dev-auth.js +1 -108
- package/executors/gateway/runtime/discovery.d.ts +0 -1
- package/executors/gateway/runtime/discovery.js +1 -123
- package/executors/gateway/runtime/edge.d.ts +0 -1
- package/executors/gateway/runtime/edge.js +1 -47
- package/executors/gateway/runtime/handler.d.ts +0 -1
- package/executors/gateway/runtime/handler.js +1 -183
- package/executors/gateway/runtime/hooks.d.ts +0 -1
- package/executors/gateway/runtime/hooks.js +1 -55
- package/executors/gateway/runtime/lens-children.d.ts +0 -1
- package/executors/gateway/runtime/lens-children.js +1 -651
- package/executors/gateway/runtime/lifecycle.d.ts +0 -1
- package/executors/gateway/runtime/lifecycle.js +3 -842
- package/executors/gateway/runtime/observability.d.ts +0 -1
- package/executors/gateway/runtime/observability.js +2 -148
- package/executors/gateway/runtime/pod-env.d.ts +0 -1
- package/executors/gateway/runtime/pod-env.js +2 -89
- package/executors/gateway/runtime/proxy.d.ts +0 -1
- package/executors/gateway/runtime/proxy.js +1 -457
- package/executors/gateway/runtime/route-registry.d.ts +0 -1
- package/executors/gateway/runtime/route-registry.js +1 -72
- package/executors/gateway/runtime/scope.d.ts +0 -1
- package/executors/gateway/runtime/scope.js +1 -117
- package/executors/gateway/runtime/server.d.ts +0 -1
- package/executors/gateway/runtime/server.js +3 -487
- package/executors/gateway/runtime/service-keys.d.ts +0 -1
- package/executors/gateway/runtime/service-keys.js +1 -49
- package/executors/gateway/runtime/types.d.ts +0 -1
- package/executors/gateway/runtime/types.js +1 -151
- package/executors/gateway/runtime/upgrade.d.ts +0 -1
- package/executors/gateway/runtime/upgrade.js +1 -71
- package/executors/gateway/runtime/workspace-registry.d.ts +0 -1
- package/executors/gateway/runtime/workspace-registry.js +1 -99
- package/executors/gateway/ssrf-guard.d.ts +0 -1
- package/executors/gateway/ssrf-guard.js +1 -190
- package/executors/serve/serve.impl.d.ts +0 -1
- package/executors/serve/serve.impl.js +1 -280
- package/executors/trust/trust.impl.d.ts +0 -1
- package/executors/trust/trust.impl.js +4 -162
- package/gateway.d.ts +0 -1
- package/gateway.js +1 -35
- package/index.d.ts +0 -1
- package/index.js +1 -16
- package/main.devserver.d.ts +0 -1
- package/main.devserver.js +10 -1117
- package/package.json +4 -3
- package/tsgo-check-plugin.d.ts +0 -1
- package/tsgo-check-plugin.js +4 -364
- package/typecheck-bus.d.ts +0 -1
- package/typecheck-bus.js +4 -256
- package/basic-ssl.d.ts.map +0 -1
- package/build-scope-patterns.d.ts.map +0 -1
- package/create-webpack-dev.d.ts.map +0 -1
- package/create-webpack-prod.d.ts.map +0 -1
- package/executors/build/build.impl.d.ts.map +0 -1
- package/executors/gateway/gateway-errors.d.ts.map +0 -1
- package/executors/gateway/gateway.impl.d.ts.map +0 -1
- package/executors/gateway/gateway.lib.d.ts.map +0 -1
- package/executors/gateway/health-check.d.ts.map +0 -1
- package/executors/gateway/jwks-verify.d.ts.map +0 -1
- package/executors/gateway/main.prod-gateway.d.ts.map +0 -1
- package/executors/gateway/main.rollout.d.ts.map +0 -1
- package/executors/gateway/manifest.d.ts.map +0 -1
- package/executors/gateway/metrics.d.ts.map +0 -1
- package/executors/gateway/otel-tracing.d.ts.map +0 -1
- package/executors/gateway/prod-gateway.lib.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/access-log.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/app.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/auth.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/cors.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/edge.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/handler.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/hooks.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/observability.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/rollout.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/routing.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/server.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/trust.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/types.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/upgrade.d.ts.map +0 -1
- package/executors/gateway/prod-runtime/upstream.d.ts.map +0 -1
- package/executors/gateway/providers-prod.d.ts.map +0 -1
- package/executors/gateway/rate-limit.d.ts.map +0 -1
- package/executors/gateway/registry-source.d.ts.map +0 -1
- package/executors/gateway/rollout-ops.d.ts.map +0 -1
- package/executors/gateway/runtime/auth.d.ts.map +0 -1
- package/executors/gateway/runtime/chooser.d.ts.map +0 -1
- package/executors/gateway/runtime/control.d.ts.map +0 -1
- package/executors/gateway/runtime/dev-auth.d.ts.map +0 -1
- package/executors/gateway/runtime/discovery.d.ts.map +0 -1
- package/executors/gateway/runtime/edge.d.ts.map +0 -1
- package/executors/gateway/runtime/handler.d.ts.map +0 -1
- package/executors/gateway/runtime/hooks.d.ts.map +0 -1
- package/executors/gateway/runtime/lens-children.d.ts.map +0 -1
- package/executors/gateway/runtime/lifecycle.d.ts.map +0 -1
- package/executors/gateway/runtime/observability.d.ts.map +0 -1
- package/executors/gateway/runtime/pod-env.d.ts.map +0 -1
- package/executors/gateway/runtime/proxy.d.ts.map +0 -1
- package/executors/gateway/runtime/route-registry.d.ts.map +0 -1
- package/executors/gateway/runtime/scope.d.ts.map +0 -1
- package/executors/gateway/runtime/server.d.ts.map +0 -1
- package/executors/gateway/runtime/service-keys.d.ts.map +0 -1
- package/executors/gateway/runtime/types.d.ts.map +0 -1
- package/executors/gateway/runtime/upgrade.d.ts.map +0 -1
- package/executors/gateway/runtime/workspace-registry.d.ts.map +0 -1
- package/executors/gateway/ssrf-guard.d.ts.map +0 -1
- package/executors/serve/serve.impl.d.ts.map +0 -1
- package/executors/trust/trust.impl.d.ts.map +0 -1
- package/gateway.d.ts.map +0 -1
- package/index.d.ts.map +0 -1
- package/main.devserver.d.ts.map +0 -1
- package/tsgo-check-plugin.d.ts.map +0 -1
- package/typecheck-bus.d.ts.map +0 -1
|
@@ -1,108 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.devAttrs = void 0;
|
|
4
|
-
exports.verifyDevJwt = verifyDevJwt;
|
|
5
|
-
exports.stampIdentity = stampIdentity;
|
|
6
|
-
/**
|
|
7
|
-
* Dev edge auth: verify the end-user token, then stamp the trusted identity headers the services read
|
|
8
|
-
* (via GatewayTrustGuard). It MIRRORS the prod gateway's posture — a real JWKS/RS256 verifier when one is
|
|
9
|
-
* wired (see `../jwks-verify` + the composition in `server.ts`), an HS256 shared-secret path as the legacy
|
|
10
|
-
* fallback. Pure functions (the verifier is injected, so unit tests supply their own JWKS).
|
|
11
|
-
*
|
|
12
|
-
* SECURITY (the invariant this file must hold): a `jwt` route MUST NOT accept a token whose signature the
|
|
13
|
-
* edge cannot verify. When a JWKS verifier is wired it is the SOLE acceptor — every alg goes through it, so
|
|
14
|
-
* an unknown `kid`, a bad signature, `alg:none`, an HS256-confusion token, and an expired token ALL fail
|
|
15
|
-
* closed (rejected). Without a verifier, an asymmetric (RS256/ES*) token cannot be checked (we don't hold
|
|
16
|
-
* the issuer's public keys), so it also fails closed — unless an operator EXPLICITLY opts into the old
|
|
17
|
-
* insecure decode-only path (`LENSMCP_GW_DEV_JWT_INSECURE_DECODE=1`) for local bring-up. The HS256
|
|
18
|
-
* shared-secret path stays for services using that convention (and is itself bypassed once JWKS is wired).
|
|
19
|
-
*/
|
|
20
|
-
const node_crypto_1 = require("node:crypto");
|
|
21
|
-
/** The compact JWS from the `Authorization: Bearer …` header, or undefined. */
|
|
22
|
-
function bearerToken(req) {
|
|
23
|
-
const raw = Array.isArray(req.headers['authorization']) ? req.headers['authorization'][0] : req.headers['authorization'];
|
|
24
|
-
const auth = String(raw ?? '');
|
|
25
|
-
return auth.startsWith('Bearer ') ? auth.slice(7) : undefined;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Verify the end-user JWT and return its claims, or undefined.
|
|
29
|
-
*
|
|
30
|
-
* @param verifier when provided (the RS256/JWKS verifier wired by the composition root), it is the ONLY
|
|
31
|
-
* acceptor — the token is verified against the issuer's published keys by `kid`, so a rogue key / unknown
|
|
32
|
-
* kid / bad signature / `alg:none` / HS256-confusion / expired token all return undefined (fail closed).
|
|
33
|
-
*/
|
|
34
|
-
function verifyDevJwt(req, verifier) {
|
|
35
|
-
const token = bearerToken(req);
|
|
36
|
-
if (!token)
|
|
37
|
-
return undefined;
|
|
38
|
-
const [h, p, s] = token.split('.');
|
|
39
|
-
if (!h || !p || !s)
|
|
40
|
-
return undefined;
|
|
41
|
-
// Preferred: a real JWKS/RS256 verifier (mirrors the prod gateway's `jwks ? jwks.verify(tok) : hs256`).
|
|
42
|
-
// When wired it is the SOLE acceptor, so every forgery vector below is closed at once.
|
|
43
|
-
if (verifier)
|
|
44
|
-
return verifier.verify(token);
|
|
45
|
-
// ── No verifier wired (legacy dev) ──────────────────────────────────────────────────────────────────
|
|
46
|
-
let alg;
|
|
47
|
-
try {
|
|
48
|
-
alg = JSON.parse(Buffer.from(h, 'base64url').toString())['alg'];
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
if (typeof alg === 'string' && alg !== 'HS256') {
|
|
54
|
-
// An asymmetric IdP (RS256 + JWKS) does NOT use the HS256 dev shared secret. Without the issuer's public
|
|
55
|
-
// keys we CANNOT verify the signature, so — unlike the old code, which decode-accepted any RS256 token
|
|
56
|
-
// (a full edge auth bypass: a rogue key or a forged `alg:none` sailed through) — we FAIL CLOSED. Wire a
|
|
57
|
-
// JWKS verifier (LENSMCP_GW_JWKS_URL, or the auto-derived auth-host JWKS) to accept RS256 tokens.
|
|
58
|
-
if (process.env['LENSMCP_GW_DEV_JWT_INSECURE_DECODE'] !== '1')
|
|
59
|
-
return undefined; // fail closed
|
|
60
|
-
// Explicit, dev-only opt-in to the legacy decode + exp-check (NO signature check) — bring-up escape hatch.
|
|
61
|
-
try {
|
|
62
|
-
const rsClaims = JSON.parse(Buffer.from(p, 'base64url').toString());
|
|
63
|
-
if (typeof rsClaims['exp'] === 'number' && Date.now() / 1000 > rsClaims['exp'])
|
|
64
|
-
return undefined;
|
|
65
|
-
return rsClaims;
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// HS256 shared-secret convention (only reachable when NO JWKS verifier is wired).
|
|
72
|
-
const secret = process.env['AUTH_JWT_SECRET'] ?? 'tetros-dev-secret';
|
|
73
|
-
const expected = (0, node_crypto_1.createHmac)('sha256', secret).update(`${h}.${p}`).digest('base64url');
|
|
74
|
-
const a = Buffer.from(s);
|
|
75
|
-
const b = Buffer.from(expected);
|
|
76
|
-
if (a.length !== b.length || !(0, node_crypto_1.timingSafeEqual)(a, b))
|
|
77
|
-
return undefined;
|
|
78
|
-
try {
|
|
79
|
-
const claims = JSON.parse(Buffer.from(p, 'base64url').toString());
|
|
80
|
-
if (typeof claims['exp'] === 'number' && Date.now() / 1000 > claims['exp'])
|
|
81
|
-
return undefined;
|
|
82
|
-
return claims;
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
return undefined;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
/** Stamp verified identity onto the forwarded request — tenant from the `tid` CLAIM, never a client header. */
|
|
89
|
-
function stampIdentity(req, claims) {
|
|
90
|
-
const set = (k, v) => { if (v !== undefined && v !== null && v !== '')
|
|
91
|
-
req.headers[k] = String(v); };
|
|
92
|
-
set('x-user-id', claims['sub']);
|
|
93
|
-
set('x-tenant-id', claims['tid']);
|
|
94
|
-
const roles = claims['roles'];
|
|
95
|
-
if (Array.isArray(roles) && roles.length)
|
|
96
|
-
req.headers['x-user-roles'] = roles.map(String).join(',');
|
|
97
|
-
const perms = claims['perms'];
|
|
98
|
-
if (Array.isArray(perms) && perms.length)
|
|
99
|
-
req.headers['x-user-permissions'] = perms.map(String).join(',');
|
|
100
|
-
}
|
|
101
|
-
/** Build the ABAC attribute bag from the verified claims + request envelope. */
|
|
102
|
-
const devAttrs = (req, claims) => ({
|
|
103
|
-
...claims,
|
|
104
|
-
ip: req.socket.remoteAddress ?? '',
|
|
105
|
-
method: (req.method ?? 'GET').toUpperCase(),
|
|
106
|
-
path: (req.url ?? '/').split('?')[0],
|
|
107
|
-
});
|
|
108
|
-
exports.devAttrs = devAttrs;
|
|
1
|
+
"use strict";var y=Object.defineProperty;var a=(e,r)=>y(e,"name",{value:r,configurable:!0});var h=Object.defineProperty,u=a((e,r)=>h(e,"name",{value:r,configurable:!0}),"u");Object.defineProperty(exports,"__esModule",{value:!0}),exports.devAttrs=void 0,exports.verifyDevJwt=verifyDevJwt,exports.stampIdentity=stampIdentity;const node_crypto_1=require("node:crypto");function bearerToken(e){const r=Array.isArray(e.headers.authorization)?e.headers.authorization[0]:e.headers.authorization,t=String(r??"");return t.startsWith("Bearer ")?t.slice(7):void 0}a(bearerToken,"bearerToken"),u(bearerToken,"bearerToken");function verifyDevJwt(e,r){const t=bearerToken(e);if(!t)return;const[n,s,f]=t.split(".");if(!n||!s||!f)return;if(r)return r.verify(t);let o;try{o=JSON.parse(Buffer.from(n,"base64url").toString()).alg}catch{return}if(typeof o=="string"&&o!=="HS256"){if(process.env.LENSMCP_GW_DEV_JWT_INSECURE_DECODE!=="1")return;try{const i=JSON.parse(Buffer.from(s,"base64url").toString());return typeof i.exp=="number"&&Date.now()/1e3>i.exp?void 0:i}catch{return}}const d=process.env.AUTH_JWT_SECRET??"tetros-dev-secret",l=(0,node_crypto_1.createHmac)("sha256",d).update(`${n}.${s}`).digest("base64url"),p=Buffer.from(f),c=Buffer.from(l);if(!(p.length!==c.length||!(0,node_crypto_1.timingSafeEqual)(p,c)))try{const i=JSON.parse(Buffer.from(s,"base64url").toString());return typeof i.exp=="number"&&Date.now()/1e3>i.exp?void 0:i}catch{return}}a(verifyDevJwt,"verifyDevJwt"),u(verifyDevJwt,"verifyDevJwt");function stampIdentity(e,r){const t=u((f,o)=>{o!=null&&o!==""&&(e.headers[f]=String(o))},"set");t("x-user-id",r.sub),t("x-tenant-id",r.tid);const n=r.roles;Array.isArray(n)&&n.length&&(e.headers["x-user-roles"]=n.map(String).join(","));const s=r.perms;Array.isArray(s)&&s.length&&(e.headers["x-user-permissions"]=s.map(String).join(","))}a(stampIdentity,"stampIdentity"),u(stampIdentity,"stampIdentity");const devAttrs=u((e,r)=>({...r,ip:e.socket.remoteAddress??"",method:(e.method??"GET").toUpperCase(),path:(e.url??"/").split("?")[0]}),"devAttrs");exports.devAttrs=devAttrs;
|
|
@@ -1,123 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.pickSock = pickSock;
|
|
4
|
-
exports.lensPortFromUpstream = lensPortFromUpstream;
|
|
5
|
-
exports.discoverRoutes = discoverRoutes;
|
|
6
|
-
const tslib_1 = require("tslib");
|
|
7
|
-
/**
|
|
8
|
-
* Route discovery + socket-pool round-robin. Pure (fs reads only): reads every
|
|
9
|
-
* project's `cluster` (or legacy `davnx`) declaration and builds the route table.
|
|
10
|
-
*/
|
|
11
|
-
const fs = tslib_1.__importStar(require("node:fs"));
|
|
12
|
-
const os = tslib_1.__importStar(require("node:os"));
|
|
13
|
-
const path = tslib_1.__importStar(require("node:path"));
|
|
14
|
-
const manifest_1 = require("../manifest");
|
|
15
|
-
const scope_1 = require("./scope");
|
|
16
|
-
/** Round-robin over the pool's sockets, rescanning the dir when the cache ages out. */
|
|
17
|
-
function pickSock(pool, scanTtlMs = 1500) {
|
|
18
|
-
const now = Date.now();
|
|
19
|
-
if (now - pool.scannedAt > scanTtlMs) {
|
|
20
|
-
try {
|
|
21
|
-
pool.socks = fs.readdirSync(pool.dir)
|
|
22
|
-
.filter((f) => /^child-\d+\.sock$/.test(f))
|
|
23
|
-
.map((f) => path.join(pool.dir, f));
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
pool.socks = [];
|
|
27
|
-
}
|
|
28
|
-
pool.scannedAt = now;
|
|
29
|
-
}
|
|
30
|
-
if (pool.socks.length === 0)
|
|
31
|
-
return undefined;
|
|
32
|
-
pool.idx = (pool.idx + 1) % pool.socks.length;
|
|
33
|
-
return pool.socks[pool.idx];
|
|
34
|
-
}
|
|
35
|
-
/** Extract the port from an `upstream` URL like `http://localhost:5173`. */
|
|
36
|
-
function lensPortFromUpstream(upstream) {
|
|
37
|
-
if (!upstream)
|
|
38
|
-
return undefined;
|
|
39
|
-
const m = /:(\d+)/.exec(upstream);
|
|
40
|
-
return m ? Number(m[1]) : undefined;
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Read every project's `cluster` (or legacy `davnx`) declaration and build
|
|
44
|
-
* the route table: host routes first (plus auto `internal.<host>` for pod
|
|
45
|
-
* services), the `default` catch-all last.
|
|
46
|
-
*/
|
|
47
|
-
function discoverRoutes(rootDir, projects, sockBaseDir = os.tmpdir(),
|
|
48
|
-
// The workspace key namespaces the pod sock dir (planning/multi-workspace-gateway.md) — MUST match
|
|
49
|
-
// `main.devserver.ts`'s `SOCK_DIR` derivation so the gateway scans the same dir the devserver writes.
|
|
50
|
-
// Empty → the legacy un-namespaced path (single-workspace / pre-multi-workspace).
|
|
51
|
-
wsKey = '') {
|
|
52
|
-
const sockDirFor = (service) => wsKey ? path.join(sockBaseDir, wsKey, `${service}-devserver`) : path.join(sockBaseDir, `${service}-devserver`);
|
|
53
|
-
const routes = [];
|
|
54
|
-
const services = [];
|
|
55
|
-
const hosts = []; // every declared host — the default's apex is derived from these
|
|
56
|
-
let defaultRoute;
|
|
57
|
-
for (const [name, p] of Object.entries(projects)) {
|
|
58
|
-
let decl;
|
|
59
|
-
try {
|
|
60
|
-
const raw = JSON.parse(fs.readFileSync(path.join(rootDir, p.root, 'project.json'), 'utf8'));
|
|
61
|
-
decl = raw.cluster ?? raw.davnx;
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (!decl)
|
|
67
|
-
continue;
|
|
68
|
-
let pool;
|
|
69
|
-
let svc;
|
|
70
|
-
let target;
|
|
71
|
-
let lens;
|
|
72
|
-
if (decl.service) {
|
|
73
|
-
// `lens` applies to FRONTEND apps only; a pod service is already
|
|
74
|
-
// instrumented by the serve executor's node-instrumentation graft.
|
|
75
|
-
if (decl.lens)
|
|
76
|
-
console.warn(`[gateway] ${name}: cluster.lens is ignored for pod (service) decls — backends are instrumented by serve-hmr.`);
|
|
77
|
-
pool = { dir: sockDirFor(decl.service), socks: [], idx: -1, scannedAt: 0 };
|
|
78
|
-
svc = { project: name, decl, root: rootDir, wsKey, pool, state: 'down', lastUsed: Date.now(), inflight: 0, lastScaleAt: 0 };
|
|
79
|
-
services.push(svc);
|
|
80
|
-
}
|
|
81
|
-
else if (decl.lens) {
|
|
82
|
-
// Lens-mode frontend: the gateway SPAWNS this app's Vite + instrumentation
|
|
83
|
-
// (deterministically on `port`) and upstreams to it. One LensFrontend per
|
|
84
|
-
// project is SHARED across its host/default routes (so the spawn fires once).
|
|
85
|
-
const port = decl.port ?? lensPortFromUpstream(decl.upstream) ?? 5173;
|
|
86
|
-
target = `http://localhost:${port}`;
|
|
87
|
-
lens = { project: name, projectRoot: path.join(rootDir, p.root), port };
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
target = decl.upstream ?? (decl.port ? `http://localhost:${decl.port}` : undefined);
|
|
91
|
-
if (!target) {
|
|
92
|
-
console.warn(`[gateway] ${name}: no service/upstream/port — skipped.`);
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
const base = { project: name, prependPrefix: decl.prependPrefix, target, pool, svc, prefix: decl.path, auth: decl.auth, lens };
|
|
97
|
-
if (decl.host) {
|
|
98
|
-
routes.push({ ...base, host: decl.host });
|
|
99
|
-
hosts.push(decl.host);
|
|
100
|
-
// internal.<host>: east-west traffic, same pods, NO middleware.
|
|
101
|
-
if (pool && decl.internalHost !== false) {
|
|
102
|
-
routes.push({
|
|
103
|
-
...base,
|
|
104
|
-
host: typeof decl.internalHost === 'string' ? decl.internalHost : `internal.${decl.host}`,
|
|
105
|
-
internal: true,
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
if (decl.default)
|
|
110
|
-
defaultRoute = { ...base, prefix: undefined };
|
|
111
|
-
}
|
|
112
|
-
if (defaultRoute) {
|
|
113
|
-
// Stamp the apex so a shared multi-workspace gateway routes an unclaimed host to THIS workspace's
|
|
114
|
-
// default (see matchRoute). Harmless for a single workspace — one default ignores its apex.
|
|
115
|
-
defaultRoute.apex = (0, scope_1.baseDomainOf)(hosts);
|
|
116
|
-
routes.push(defaultRoute);
|
|
117
|
-
}
|
|
118
|
-
// specificity sort (shared with prod): host+path beats host-only beats
|
|
119
|
-
// catch-all — so a path-mounted service (api.x/channels) wins over the
|
|
120
|
-
// host's owner (api.x → api).
|
|
121
|
-
(0, manifest_1.sortBySpecificity)(routes);
|
|
122
|
-
return { routes, services };
|
|
123
|
-
}
|
|
1
|
+
"use strict";var S=Object.defineProperty;var p=(e,r)=>S(e,"name",{value:r,configurable:!0});var k=Object.defineProperty,a=p((e,r)=>k(e,"name",{value:r,configurable:!0}),"a");Object.defineProperty(exports,"__esModule",{value:!0}),exports.pickSock=pickSock,exports.lensPortFromUpstream=lensPortFromUpstream,exports.discoverRoutes=discoverRoutes;const tslib_1=require("tslib"),fs=tslib_1.__importStar(require("node:fs")),os=tslib_1.__importStar(require("node:os")),path=tslib_1.__importStar(require("node:path")),manifest_1=require("../manifest"),scope_1=require("./scope");function pickSock(e,r=1500){const i=Date.now();if(i-e.scannedAt>r){try{e.socks=fs.readdirSync(e.dir).filter(s=>/^child-\d+\.sock$/.test(s)).map(s=>path.join(e.dir,s))}catch{e.socks=[]}e.scannedAt=i}if(e.socks.length!==0)return e.idx=(e.idx+1)%e.socks.length,e.socks[e.idx]}p(pickSock,"pickSock"),a(pickSock,"pickSock");function lensPortFromUpstream(e){if(!e)return;const r=/:(\d+)/.exec(e);return r?Number(r[1]):void 0}p(lensPortFromUpstream,"lensPortFromUpstream"),a(lensPortFromUpstream,"lensPortFromUpstream");function discoverRoutes(e,r,i=os.tmpdir(),s=""){const y=a(t=>s?path.join(i,s,`${t}-devserver`):path.join(i,`${t}-devserver`),"sockDirFor"),n=[],v=[],m=[];let l;for(const[t,x]of Object.entries(r)){let o;try{const c=JSON.parse(fs.readFileSync(path.join(e,x.root,"project.json"),"utf8"));o=c.cluster??c.davnx}catch{continue}if(!o)continue;let d,f,u,j;if(o.service)o.lens&&console.warn(`[gateway] ${t}: cluster.lens is ignored for pod (service) decls \u2014 backends are instrumented by serve-hmr.`),d={dir:y(o.service),socks:[],idx:-1,scannedAt:0},f={project:t,decl:o,root:e,wsKey:s,pool:d,state:"down",lastUsed:Date.now(),inflight:0,lastScaleAt:0},v.push(f);else if(o.lens){const c=o.port??lensPortFromUpstream(o.upstream)??5173;u=`http://localhost:${c}`,j={project:t,projectRoot:path.join(e,x.root),port:c}}else if(u=o.upstream??(o.port?`http://localhost:${o.port}`:void 0),!u){console.warn(`[gateway] ${t}: no service/upstream/port \u2014 skipped.`);continue}const h={project:t,prependPrefix:o.prependPrefix,target:u,pool:d,svc:f,prefix:o.path,auth:o.auth,lens:j};o.host&&(n.push({...h,host:o.host}),m.push(o.host),d&&o.internalHost!==!1&&n.push({...h,host:typeof o.internalHost=="string"?o.internalHost:`internal.${o.host}`,internal:!0})),o.default&&(l={...h,prefix:void 0})}return l&&(l.apex=(0,scope_1.baseDomainOf)(m),n.push(l)),(0,manifest_1.sortBySpecificity)(n),{routes:n,services:v}}p(discoverRoutes,"discoverRoutes"),a(discoverRoutes,"discoverRoutes");
|
|
@@ -28,4 +28,3 @@ export declare const corsPreflight: (req: http.IncomingMessage, res: http.Server
|
|
|
28
28
|
/** Allowed-methods lists for the two preflight sites. */
|
|
29
29
|
export declare const PREFLIGHT_METHODS_NOROUTE = "GET,HEAD,OPTIONS";
|
|
30
30
|
export declare const PREFLIGHT_METHODS_ROUTED = "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS";
|
|
31
|
-
//# sourceMappingURL=edge.d.ts.map
|
|
@@ -1,47 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PREFLIGHT_METHODS_ROUTED = exports.PREFLIGHT_METHODS_NOROUTE = exports.corsPreflight = exports.cors = exports.GATEWAY_OWNED_HEADERS = exports.sendError = void 0;
|
|
4
|
-
const gateway_errors_1 = require("../gateway-errors");
|
|
5
|
-
/**
|
|
6
|
-
* Send a gateway-generated edge error as the JSON envelope `{ key, status, traceId, message? }` (NOT
|
|
7
|
-
* `text/plain`) + an `x-request-id` header — the SAME localized contract a service returns, so a browser
|
|
8
|
-
* app renders `t(key)` and surfaces the trace id. Mirrors the prod gateway's `sendError`.
|
|
9
|
-
*/
|
|
10
|
-
const sendError = (res, req, status, reason, detail) => {
|
|
11
|
-
const traceId = (0, gateway_errors_1.ensureRequestId)(req);
|
|
12
|
-
if (!res.headersSent)
|
|
13
|
-
res.writeHead(status, { 'content-type': gateway_errors_1.GATEWAY_ERROR_CONTENT_TYPE, 'x-request-id': traceId });
|
|
14
|
-
res.end((0, gateway_errors_1.gatewayErrorBody)(status, reason, traceId, detail));
|
|
15
|
-
};
|
|
16
|
-
exports.sendError = sendError;
|
|
17
|
-
/**
|
|
18
|
-
* The gateway OWNS these headers — strip any client-supplied copy at ingress so identity + trust can
|
|
19
|
-
* only originate from the gateway (mirrors the prod gateway's stripTrust). Applied at the very top of
|
|
20
|
-
* BOTH the HTTP handler and the WS upgrade.
|
|
21
|
-
*/
|
|
22
|
-
exports.GATEWAY_OWNED_HEADERS = ['x-internal-token', 'x-api-key-id', 'x-user-id', 'x-tenant-id', 'x-user-roles', 'x-user-permissions'];
|
|
23
|
-
/** Reflect the request Origin back with credentials — the gateway is the edge CORS authority. */
|
|
24
|
-
const cors = (req, res) => {
|
|
25
|
-
const origin = String(req.headers.origin ?? '*');
|
|
26
|
-
res.setHeader('access-control-allow-origin', origin);
|
|
27
|
-
res.setHeader('access-control-allow-credentials', 'true');
|
|
28
|
-
res.setHeader('vary', 'Origin');
|
|
29
|
-
};
|
|
30
|
-
exports.cors = cors;
|
|
31
|
-
/**
|
|
32
|
-
* Answer a CORS preflight (OPTIONS) at the edge: reflect CORS + 204 with the allowed `methods` and the
|
|
33
|
-
* requested headers echoed back. `methods` differs by caller — the no-route host-root uses the narrow
|
|
34
|
-
* `GET,HEAD,OPTIONS`; a routed path uses the wide verb list.
|
|
35
|
-
*/
|
|
36
|
-
const corsPreflight = (req, res, methods) => {
|
|
37
|
-
(0, exports.cors)(req, res);
|
|
38
|
-
res.writeHead(204, {
|
|
39
|
-
'access-control-allow-methods': methods,
|
|
40
|
-
'access-control-allow-headers': String(req.headers['access-control-request-headers'] ?? '*'),
|
|
41
|
-
});
|
|
42
|
-
res.end();
|
|
43
|
-
};
|
|
44
|
-
exports.corsPreflight = corsPreflight;
|
|
45
|
-
/** Allowed-methods lists for the two preflight sites. */
|
|
46
|
-
exports.PREFLIGHT_METHODS_NOROUTE = 'GET,HEAD,OPTIONS';
|
|
47
|
-
exports.PREFLIGHT_METHODS_ROUTED = 'GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS';
|
|
1
|
+
"use strict";var i=Object.defineProperty;var a=(r,e)=>i(r,"name",{value:e,configurable:!0});var c=Object.defineProperty,s=a((r,e)=>c(r,"name",{value:e,configurable:!0}),"s");Object.defineProperty(exports,"__esModule",{value:!0}),exports.PREFLIGHT_METHODS_ROUTED=exports.PREFLIGHT_METHODS_NOROUTE=exports.corsPreflight=exports.cors=exports.GATEWAY_OWNED_HEADERS=exports.sendError=void 0;const gateway_errors_1=require("../gateway-errors"),sendError=s((r,e,o,n,E)=>{const t=(0,gateway_errors_1.ensureRequestId)(e);r.headersSent||r.writeHead(o,{"content-type":gateway_errors_1.GATEWAY_ERROR_CONTENT_TYPE,"x-request-id":t}),r.end((0,gateway_errors_1.gatewayErrorBody)(o,n,t,E))},"sendError");exports.sendError=sendError,exports.GATEWAY_OWNED_HEADERS=["x-internal-token","x-api-key-id","x-user-id","x-tenant-id","x-user-roles","x-user-permissions"];const cors=s((r,e)=>{const o=String(r.headers.origin??"*");e.setHeader("access-control-allow-origin",o),e.setHeader("access-control-allow-credentials","true"),e.setHeader("vary","Origin")},"cors");exports.cors=cors;const corsPreflight=s((r,e,o)=>{(0,exports.cors)(r,e),e.writeHead(204,{"access-control-allow-methods":o,"access-control-allow-headers":String(r.headers["access-control-request-headers"]??"*")}),e.end()},"corsPreflight");exports.corsPreflight=corsPreflight,exports.PREFLIGHT_METHODS_NOROUTE="GET,HEAD,OPTIONS",exports.PREFLIGHT_METHODS_ROUTED="GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS";
|
|
@@ -1,183 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createHandler = createHandler;
|
|
4
|
-
const manifest_1 = require("../manifest");
|
|
5
|
-
const gateway_errors_1 = require("../gateway-errors");
|
|
6
|
-
const discovery_1 = require("./discovery");
|
|
7
|
-
const edge_1 = require("./edge");
|
|
8
|
-
const observability_1 = require("./observability");
|
|
9
|
-
const chooser_1 = require("./chooser");
|
|
10
|
-
function createHandler(deps) {
|
|
11
|
-
const { rt, obs, auth, proxy, hooks } = deps;
|
|
12
|
-
const match = (req) => (0, manifest_1.matchRoute)(rt.routes, (req.headers.host || '').split(':')[0], req.url ?? '/');
|
|
13
|
-
// Host-root infra: when a host only has PATH-mounted services (pure
|
|
14
|
-
// microservices under api.<domain>/{prefix}), the gateway itself owns the
|
|
15
|
-
// root — CORS, health aggregation + service discovery, like a real edge.
|
|
16
|
-
const handleNoRoute = (req, res) => {
|
|
17
|
-
// gateway-owned endpoints are cross-origin by design (the app calls
|
|
18
|
-
// api.<domain>/health from app.<domain>) — answer CORS here; routed
|
|
19
|
-
// paths inherit CORS from the services themselves.
|
|
20
|
-
(0, edge_1.cors)(req, res);
|
|
21
|
-
if (req.method === 'OPTIONS') {
|
|
22
|
-
res.writeHead(204, {
|
|
23
|
-
'access-control-allow-methods': edge_1.PREFLIGHT_METHODS_NOROUTE,
|
|
24
|
-
'access-control-allow-headers': String(req.headers['access-control-request-headers'] ?? '*'),
|
|
25
|
-
});
|
|
26
|
-
res.end();
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const host = (req.headers.host || '').split(':')[0];
|
|
30
|
-
const mounted = rt.routes.filter((r) => r.host && (0, manifest_1.hostMatches)(r.host, host) && r.prefix);
|
|
31
|
-
const url = (req.url ?? '/').split('?')[0];
|
|
32
|
-
// The lens dashboard HOST ROOT → the multi-workspace chooser. The dashboard itself mounts at `/<key>`,
|
|
33
|
-
// so the bare `/` is unrouted and lands here; serve the landing page listing every known workspace.
|
|
34
|
-
if ((url === '/' || url === '/index.html' || url === '') &&
|
|
35
|
-
rt.routes.some((r) => r.host && (0, manifest_1.hostMatches)(r.host, host) && r.project === observability_1.LENS_DASHBOARD_PROJECT)) {
|
|
36
|
-
(0, chooser_1.serveChooser)(res, rt.wsKey);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
if (mounted.length > 0 && (url === '/health' || url === '/healthz')) {
|
|
40
|
-
const services = mounted.map((r) => ({
|
|
41
|
-
project: r.project,
|
|
42
|
-
prefix: r.prefix,
|
|
43
|
-
pods: r.pool ? ((0, discovery_1.pickSock)(r.pool, 0), r.pool.socks.length) : undefined,
|
|
44
|
-
state: r.svc?.state ?? (r.target ? 'up' : 'unknown'),
|
|
45
|
-
}));
|
|
46
|
-
const ok = services.every((s) => s.state !== 'down' || true);
|
|
47
|
-
res.writeHead(ok ? 200 : 503, { 'content-type': 'application/json' });
|
|
48
|
-
res.end(JSON.stringify({ status: 'ok', host, services }, null, 2));
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (mounted.length > 0 && url === '/.well-known/services') {
|
|
52
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
53
|
-
res.end(JSON.stringify({ host, services: mounted.map((r) => ({ prefix: r.prefix, project: r.project, internal: !!r.internal })) }, null, 2));
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
(0, edge_1.sendError)(res, req, 404, 'not_found', mounted.length > 0
|
|
57
|
-
? `No service mounted at ${url}. Mounted on ${host}: ${[...new Set(mounted.map((r) => r.prefix))].join(', ')} (+ /health, /.well-known/services)`
|
|
58
|
-
: 'No gateway route for this hostname.');
|
|
59
|
-
};
|
|
60
|
-
const runPipeline = async (req, res) => {
|
|
61
|
-
// 0. HTTP/2 carries the host in the `:authority` pseudo-header, NOT `host` — the
|
|
62
|
-
// http2 compat layer exposes it as `req.authority` and omits `host` from `req.headers`.
|
|
63
|
-
// Every downstream step (route match, trace, CORS) reads `req.headers.host`, so
|
|
64
|
-
// normalize it here before anything else. No-op for HTTP/1.1 (host is already set).
|
|
65
|
-
if (!req.headers.host) {
|
|
66
|
-
const authority = req.authority ?? req.headers[':authority'];
|
|
67
|
-
if (authority)
|
|
68
|
-
req.headers.host = authority;
|
|
69
|
-
}
|
|
70
|
-
// 1. STRIP client-supplied trust headers before anything else: the gateway is
|
|
71
|
-
// the ONLY party allowed to assert `x-internal-token` (came through me) and
|
|
72
|
-
// `x-api-key-id` (validated caller identity). Without this, a keyless/TCP route
|
|
73
|
-
// would leak a spoofed token and any public route would forward a spoofed caller
|
|
74
|
-
// id to downstream audit. The gateway re-stamps them below where earned.
|
|
75
|
-
for (const h of edge_1.GATEWAY_OWNED_HEADERS)
|
|
76
|
-
delete req.headers[h];
|
|
77
|
-
// 2. Mint a trace id for EVERY request (not just lens-traced ones): forwarded to the
|
|
78
|
-
// pod (adopted as its traceId) and returned on the response — any request is traceable.
|
|
79
|
-
(0, gateway_errors_1.ensureRequestId)(req);
|
|
80
|
-
const trace = obs.beginTrace(req);
|
|
81
|
-
if (trace) {
|
|
82
|
-
obs.traceStep(trace, 'received', { host: trace.host, method: trace.method, url: trace.url });
|
|
83
|
-
req.__lensmcpTrace = trace;
|
|
84
|
-
}
|
|
85
|
-
const ctx = hooks.httpContext(req, res, trace);
|
|
86
|
-
try {
|
|
87
|
-
// 3. onRequest (sees already-stripped headers; may short-circuit)
|
|
88
|
-
if (hooks.has('onRequest')) {
|
|
89
|
-
await hooks.run('onRequest', ctx);
|
|
90
|
-
if (ctx.responded())
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
// 4. route match
|
|
94
|
-
const route = match(req);
|
|
95
|
-
if (!route) {
|
|
96
|
-
handleNoRoute(req, res);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
ctx.route = route;
|
|
100
|
-
obs.traceStep(trace, 'route-match', {
|
|
101
|
-
project: route.project,
|
|
102
|
-
...(route.prefix ? { prefix: route.prefix } : {}),
|
|
103
|
-
...(route.host ? { routeHost: route.host } : {}),
|
|
104
|
-
...(route.internal ? { internal: true } : {}),
|
|
105
|
-
});
|
|
106
|
-
// 7. CORS preflight on a ROUTED path: answer it HERE, BEFORE edge auth — an OPTIONS preflight
|
|
107
|
-
// carries no token by spec, so the JWT (or x-api-key) enforcement below would 401 it and the
|
|
108
|
-
// browser's CORS check fails. The matching real request still goes through auth normally.
|
|
109
|
-
if (req.method === 'OPTIONS') {
|
|
110
|
-
(0, edge_1.cors)(req, res);
|
|
111
|
-
res.writeHead(204, {
|
|
112
|
-
'access-control-allow-methods': edge_1.PREFLIGHT_METHODS_ROUTED,
|
|
113
|
-
'access-control-allow-headers': String(req.headers['access-control-request-headers'] ?? '*'),
|
|
114
|
-
});
|
|
115
|
-
res.end();
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
// 8. CORS on EVERY routed response — including the 401/403/5xx the gateway generates ITSELF
|
|
119
|
-
// below. Without this a gateway-generated error short-circuits before the upstream (which
|
|
120
|
-
// carries CORS on success), so the browser sees the error WITHOUT an Access-Control-Allow-Origin
|
|
121
|
-
// and reports a CORS failure that MASKS the real status. On success the upstream's CORS header
|
|
122
|
-
// overwrites this one (http-proxy setHeader), so there's no duplicate.
|
|
123
|
-
(0, edge_1.cors)(req, res);
|
|
124
|
-
// 9. beforeAuth
|
|
125
|
-
if (hooks.has('beforeAuth')) {
|
|
126
|
-
await hooks.run('beforeAuth', ctx);
|
|
127
|
-
if (ctx.responded())
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
// 10/11. auth + authz. internal.* validates the caller's api-key; public verifies the end-user
|
|
131
|
-
// JWT + enforces per-route rules. Custom middleware (afterAuth) runs AFTER core auth, public ONLY.
|
|
132
|
-
if (route.internal) {
|
|
133
|
-
const v = auth.internal(req, route, trace);
|
|
134
|
-
if (!v.ok) {
|
|
135
|
-
obs.finishTrace(trace, route.project, v.status);
|
|
136
|
-
(0, edge_1.sendError)(res, req, v.status, v.reason, v.detail);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
ctx.caller = v.caller;
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
const v = auth.public(req, route, trace);
|
|
143
|
-
if (!v.ok) {
|
|
144
|
-
obs.finishTrace(trace, route.project, v.status);
|
|
145
|
-
(0, edge_1.sendError)(res, req, v.status, v.reason, v.detail);
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
ctx.claims = v.claims;
|
|
149
|
-
if (hooks.has('afterAuth')) {
|
|
150
|
-
await hooks.run('afterAuth', ctx);
|
|
151
|
-
if (ctx.responded())
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
// 12. inject the target service's key so pods trust the gateway (pool routes, both branches)
|
|
156
|
-
if (route.pool && rt.serviceKeys[route.project]) {
|
|
157
|
-
req.headers['x-internal-token'] = rt.serviceKeys[route.project];
|
|
158
|
-
}
|
|
159
|
-
// 13. beforeForward (last chance to mutate headers/url)
|
|
160
|
-
if (hooks.has('beforeForward')) {
|
|
161
|
-
await hooks.run('beforeForward', ctx);
|
|
162
|
-
if (ctx.responded())
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
// 15. onResponse — fire user handlers once the response closes (the built-in
|
|
166
|
-
// recordEdge/finishTrace live in proxy.forward's own close handler).
|
|
167
|
-
if (hooks.has('onResponse')) {
|
|
168
|
-
res.on('close', () => { ctx.status = res.statusCode ?? 0; void hooks.run('onResponse', ctx); });
|
|
169
|
-
}
|
|
170
|
-
// 14. forward
|
|
171
|
-
void proxy.forward(route, req, res);
|
|
172
|
-
}
|
|
173
|
-
catch (e) {
|
|
174
|
-
// A hook (or the pipeline) threw — narrate via onError, then 500 if nobody responded.
|
|
175
|
-
ctx.error = e;
|
|
176
|
-
if (hooks.has('onError'))
|
|
177
|
-
await hooks.run('onError', ctx);
|
|
178
|
-
if (!ctx.responded())
|
|
179
|
-
(0, edge_1.sendError)(res, req, 500, 'internal', e.message);
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
return (req, res) => { void runPipeline(req, res); };
|
|
183
|
-
}
|
|
1
|
+
"use strict";var E=Object.defineProperty;var d=(l,i)=>E(l,"name",{value:i,configurable:!0});var v=Object.defineProperty,h=d((l,i)=>v(l,"name",{value:i,configurable:!0}),"h");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createHandler=createHandler;const manifest_1=require("../manifest"),gateway_errors_1=require("../gateway-errors"),discovery_1=require("./discovery"),edge_1=require("./edge"),observability_1=require("./observability"),chooser_1=require("./chooser");function createHandler(l){const{rt:i,obs:u,auth:p,proxy:f,hooks:a}=l,w=h(e=>(0,manifest_1.matchRoute)(i.routes,(e.headers.host||"").split(":")[0],e.url??"/"),"match"),m=h((e,o)=>{if((0,edge_1.cors)(e,o),e.method==="OPTIONS"){o.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_NOROUTE,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),o.end();return}const n=(e.headers.host||"").split(":")[0],s=i.routes.filter(r=>r.host&&(0,manifest_1.hostMatches)(r.host,n)&&r.prefix),t=(e.url??"/").split("?")[0];if((t==="/"||t==="/index.html"||t==="")&&i.routes.some(r=>r.host&&(0,manifest_1.hostMatches)(r.host,n)&&r.project===observability_1.LENS_DASHBOARD_PROJECT)){(0,chooser_1.serveChooser)(o,i.wsKey);return}if(s.length>0&&(t==="/health"||t==="/healthz")){const r=s.map(c=>({project:c.project,prefix:c.prefix,pods:c.pool?((0,discovery_1.pickSock)(c.pool,0),c.pool.socks.length):void 0,state:c.svc?.state??(c.target?"up":"unknown")})),_=r.every(c=>c.state!=="down"||!0);o.writeHead(_?200:503,{"content-type":"application/json"}),o.end(JSON.stringify({status:"ok",host:n,services:r},null,2));return}if(s.length>0&&t==="/.well-known/services"){o.writeHead(200,{"content-type":"application/json"}),o.end(JSON.stringify({host:n,services:s.map(r=>({prefix:r.prefix,project:r.project,internal:!!r.internal}))},null,2));return}(0,edge_1.sendError)(o,e,404,"not_found",s.length>0?`No service mounted at ${t}. Mounted on ${n}: ${[...new Set(s.map(r=>r.prefix))].join(", ")} (+ /health, /.well-known/services)`:"No gateway route for this hostname.")},"handleNoRoute"),y=h(async(e,o)=>{if(!e.headers.host){const t=e.authority??e.headers[":authority"];t&&(e.headers.host=t)}for(const t of edge_1.GATEWAY_OWNED_HEADERS)delete e.headers[t];(0,gateway_errors_1.ensureRequestId)(e);const n=u.beginTrace(e);n&&(u.traceStep(n,"received",{host:n.host,method:n.method,url:n.url}),e.__lensmcpTrace=n);const s=a.httpContext(e,o,n);try{if(a.has("onRequest")&&(await a.run("onRequest",s),s.responded()))return;const t=w(e);if(!t){m(e,o);return}if(s.route=t,u.traceStep(n,"route-match",{project:t.project,...t.prefix?{prefix:t.prefix}:{},...t.host?{routeHost:t.host}:{},...t.internal?{internal:!0}:{}}),e.method==="OPTIONS"){(0,edge_1.cors)(e,o),o.writeHead(204,{"access-control-allow-methods":edge_1.PREFLIGHT_METHODS_ROUTED,"access-control-allow-headers":String(e.headers["access-control-request-headers"]??"*")}),o.end();return}if((0,edge_1.cors)(e,o),a.has("beforeAuth")&&(await a.run("beforeAuth",s),s.responded()))return;if(t.internal){const r=p.internal(e,t,n);if(!r.ok){u.finishTrace(n,t.project,r.status),(0,edge_1.sendError)(o,e,r.status,r.reason,r.detail);return}s.caller=r.caller}else{const r=p.public(e,t,n);if(!r.ok){u.finishTrace(n,t.project,r.status),(0,edge_1.sendError)(o,e,r.status,r.reason,r.detail);return}if(s.claims=r.claims,a.has("afterAuth")&&(await a.run("afterAuth",s),s.responded()))return}if(t.pool&&i.serviceKeys[t.project]&&(e.headers["x-internal-token"]=i.serviceKeys[t.project]),a.has("beforeForward")&&(await a.run("beforeForward",s),s.responded()))return;a.has("onResponse")&&o.on("close",()=>{s.status=o.statusCode??0,a.run("onResponse",s)}),f.forward(t,e,o)}catch(t){s.error=t,a.has("onError")&&await a.run("onError",s),s.responded()||(0,edge_1.sendError)(o,e,500,"internal",t.message)}},"runPipeline");return(e,o)=>{y(e,o)}}d(createHandler,"createHandler"),h(createHandler,"createHandler");
|
|
@@ -1,55 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createHooks = createHooks;
|
|
4
|
-
const edge_1 = require("./edge");
|
|
5
|
-
// Terminal phases observe a request whose response is ALREADY complete, so
|
|
6
|
-
// `responded()` is true by definition — they must run every handler regardless.
|
|
7
|
-
// The request-flow phases short-circuit the moment a handler responds.
|
|
8
|
-
const TERMINAL_PHASES = new Set(['onResponse', 'onError']);
|
|
9
|
-
function createHooks(hooks) {
|
|
10
|
-
const run = async (phase, ctx) => {
|
|
11
|
-
const list = hooks[phase];
|
|
12
|
-
if (!list || list.length === 0)
|
|
13
|
-
return;
|
|
14
|
-
const terminal = TERMINAL_PHASES.has(phase);
|
|
15
|
-
for (const h of list) {
|
|
16
|
-
if (!terminal && ctx.responded())
|
|
17
|
-
return; // a request-flow hook already responded → stop
|
|
18
|
-
await h(ctx);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
const has = (phase) => (hooks[phase]?.length ?? 0) > 0;
|
|
22
|
-
const httpContext = (req, res, trace) => ({
|
|
23
|
-
req,
|
|
24
|
-
res,
|
|
25
|
-
socket: undefined,
|
|
26
|
-
trace,
|
|
27
|
-
route: undefined,
|
|
28
|
-
claims: undefined,
|
|
29
|
-
caller: undefined,
|
|
30
|
-
pod: undefined,
|
|
31
|
-
error: undefined,
|
|
32
|
-
status: undefined,
|
|
33
|
-
sendError: (status, reason, detail) => (0, edge_1.sendError)(res, req, status, reason, detail),
|
|
34
|
-
responded: () => res.writableEnded || res.headersSent,
|
|
35
|
-
});
|
|
36
|
-
const upgradeContext = (req, socket, trace) => ({
|
|
37
|
-
req,
|
|
38
|
-
res: undefined,
|
|
39
|
-
socket,
|
|
40
|
-
trace,
|
|
41
|
-
route: undefined,
|
|
42
|
-
claims: undefined,
|
|
43
|
-
caller: undefined,
|
|
44
|
-
pod: undefined,
|
|
45
|
-
error: undefined,
|
|
46
|
-
status: undefined,
|
|
47
|
-
// No HTTP response on an upgrade — short-circuiting means destroying the socket.
|
|
48
|
-
sendError: () => { try {
|
|
49
|
-
socket.destroy();
|
|
50
|
-
}
|
|
51
|
-
catch { /* already gone */ } },
|
|
52
|
-
responded: () => socket.destroyed,
|
|
53
|
-
});
|
|
54
|
-
return { run, has, httpContext, upgradeContext };
|
|
55
|
-
}
|
|
1
|
+
"use strict";var u=Object.defineProperty;var i=(t,e)=>u(t,"name",{value:e,configurable:!0});var a=Object.defineProperty,n=i((t,e)=>a(t,"name",{value:e,configurable:!0}),"n");Object.defineProperty(exports,"__esModule",{value:!0}),exports.createHooks=createHooks;const edge_1=require("./edge"),TERMINAL_PHASES=new Set(["onResponse","onError"]);function createHooks(t){return{run:n(async(e,r)=>{const o=t[e];if(!o||o.length===0)return;const d=TERMINAL_PHASES.has(e);for(const s of o){if(!d&&r.responded())return;await s(r)}},"run"),has:n(e=>(t[e]?.length??0)>0,"has"),httpContext:n((e,r,o)=>({req:e,res:r,socket:void 0,trace:o,route:void 0,claims:void 0,caller:void 0,pod:void 0,error:void 0,status:void 0,sendError:n((d,s,c)=>(0,edge_1.sendError)(r,e,d,s,c),"sendError"),responded:n(()=>r.writableEnded||r.headersSent,"responded")}),"httpContext"),upgradeContext:n((e,r,o)=>({req:e,res:void 0,socket:r,trace:o,route:void 0,claims:void 0,caller:void 0,pod:void 0,error:void 0,status:void 0,sendError:n(()=>{try{r.destroy()}catch{}},"sendError"),responded:n(()=>r.destroyed,"responded")}),"upgradeContext")}}i(createHooks,"createHooks"),n(createHooks,"createHooks");
|
|
@@ -57,4 +57,3 @@ export interface LensChildren {
|
|
|
57
57
|
export declare function createLensChildren(rt: GatewayRuntime, obs: Observability, options: GatewayRuntimeOptions, deps?: {
|
|
58
58
|
isPortFree?: (port: number) => Promise<boolean>;
|
|
59
59
|
}): LensChildren;
|
|
60
|
-
//# sourceMappingURL=lens-children.d.ts.map
|