@suveren/gateway 0.2.12 → 0.3.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 +21 -7
- package/dist/control-plane/index.mjs +146 -2
- package/dist/mcp-server/http.mjs +80 -38
- package/dist/ui/assets/index-RkZBhIxU.js +105 -0
- package/dist/ui/index.html +1 -1
- package/node_modules/@hap/core/dist/index.d.mts +147 -3
- package/node_modules/@hap/core/dist/index.d.ts +147 -3
- package/node_modules/@hap/core/dist/index.js +74 -10
- package/node_modules/@hap/core/dist/index.mjs +69 -10
- package/node_modules/@hap/core/package.json +1 -1
- package/node_modules/@hap/core/src/gatekeeper.ts +4 -4
- package/node_modules/@hap/core/src/identity.ts +77 -0
- package/node_modules/@hap/core/src/index.ts +2 -0
- package/node_modules/@hap/core/src/intent-disclosure.ts +69 -0
- package/node_modules/@hap/core/src/types.ts +62 -2
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/README.md +16 -1
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/index.js +4 -20
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/read.js +17 -17
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/json.js +60 -32
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/raw.js +3 -3
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/text.js +3 -3
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/types/urlencoded.js +16 -20
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/lib/utils.js +18 -16
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/LICENSE +22 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/README.md +69 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.d.ts +26 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.js +170 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/dist/index.js.map +1 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/node_modules/content-type/package.json +52 -0
- package/node_modules/@modelcontextprotocol/sdk/node_modules/body-parser/package.json +23 -10
- package/node_modules/@types/node/README.md +1 -1
- package/node_modules/@types/node/assert/strict.d.ts +10 -1
- package/node_modules/@types/node/assert.d.ts +144 -17
- package/node_modules/@types/node/async_hooks.d.ts +9 -9
- package/node_modules/@types/node/buffer.d.ts +0 -5
- package/node_modules/@types/node/child_process.d.ts +17 -21
- package/node_modules/@types/node/cluster.d.ts +13 -13
- package/node_modules/@types/node/console.d.ts +1 -1
- package/node_modules/@types/node/crypto.d.ts +454 -571
- package/node_modules/@types/node/diagnostics_channel.d.ts +37 -29
- package/node_modules/@types/node/dns.d.ts +14 -14
- package/node_modules/@types/node/events.d.ts +3 -3
- package/node_modules/@types/node/fs/promises.d.ts +4 -4
- package/node_modules/@types/node/fs.d.ts +3 -12
- package/node_modules/@types/node/globals.d.ts +4 -4
- package/node_modules/@types/node/http.d.ts +5 -5
- package/node_modules/@types/node/http2.d.ts +5 -1
- package/node_modules/@types/node/index.d.ts +0 -3
- package/node_modules/@types/node/inspector.d.ts +1 -1
- package/node_modules/@types/node/module.d.ts +16 -45
- package/node_modules/@types/node/net.d.ts +1 -1
- package/node_modules/@types/node/os.d.ts +2 -2
- package/node_modules/@types/node/package.json +4 -4
- package/node_modules/@types/node/perf_hooks.d.ts +1 -9
- package/node_modules/@types/node/process.d.ts +11 -11
- package/node_modules/@types/node/readline.d.ts +12 -11
- package/node_modules/@types/node/repl.d.ts +10 -10
- package/node_modules/@types/node/sqlite.d.ts +1 -1
- package/node_modules/@types/node/stream/promises.d.ts +2 -2
- package/node_modules/@types/node/stream/web.d.ts +1 -1
- package/node_modules/@types/node/stream.d.ts +5 -5
- package/node_modules/@types/node/test/reporters.d.ts +1 -1
- package/node_modules/@types/node/test.d.ts +8 -8
- package/node_modules/@types/node/tls.d.ts +1 -1
- package/node_modules/@types/node/ts5.6/index.d.ts +1 -4
- package/node_modules/@types/node/ts5.7/index.d.ts +0 -3
- package/node_modules/@types/node/url.d.ts +3 -3
- package/node_modules/@types/node/util/types.d.ts +1 -1
- package/node_modules/@types/node/util.d.ts +3 -20
- package/node_modules/@types/node/v8.d.ts +1 -1
- package/node_modules/@types/node/vm.d.ts +9 -9
- package/node_modules/@types/node/web-globals/domexception.d.ts +15 -0
- package/node_modules/@types/node/worker_threads.d.ts +2 -5
- package/node_modules/@types/node/zlib.d.ts +3 -3
- package/node_modules/fast-uri/index.js +1 -1
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/fast-uri/test/security.test.js +28 -0
- package/node_modules/hono/dist/adapter/aws-lambda/handler.js +14 -20
- package/node_modules/hono/dist/adapter/lambda-edge/handler.js +8 -2
- package/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +14 -20
- package/node_modules/hono/dist/cjs/adapter/lambda-edge/handler.js +8 -2
- package/node_modules/hono/dist/cjs/helper/css/common.js +3 -1
- package/node_modules/hono/dist/cjs/helper/css/index.js +9 -1
- package/node_modules/hono/dist/cjs/jsx/base.js +8 -14
- package/node_modules/hono/dist/cjs/jsx/components.js +41 -21
- package/node_modules/hono/dist/cjs/jsx/context.js +131 -5
- package/node_modules/hono/dist/cjs/jsx/streaming.js +9 -7
- package/node_modules/hono/dist/cjs/middleware/bearer-auth/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -5
- package/node_modules/hono/dist/cjs/middleware/language/language.js +10 -32
- package/node_modules/hono/dist/cjs/middleware/serve-static/index.js +1 -1
- package/node_modules/hono/dist/cjs/middleware/timing/timing.js +3 -1
- package/node_modules/hono/dist/cjs/utils/ipaddr.js +6 -1
- package/node_modules/hono/dist/helper/css/common.js +3 -1
- package/node_modules/hono/dist/helper/css/index.js +9 -1
- package/node_modules/hono/dist/jsx/base.js +15 -15
- package/node_modules/hono/dist/jsx/components.js +42 -22
- package/node_modules/hono/dist/jsx/context.js +129 -5
- package/node_modules/hono/dist/jsx/streaming.js +10 -8
- package/node_modules/hono/dist/middleware/bearer-auth/index.js +1 -1
- package/node_modules/hono/dist/middleware/cors/index.js +2 -5
- package/node_modules/hono/dist/middleware/language/language.js +10 -32
- package/node_modules/hono/dist/middleware/serve-static/index.js +1 -1
- package/node_modules/hono/dist/middleware/timing/timing.js +3 -1
- package/node_modules/hono/dist/tsconfig.build.tsbuildinfo +1 -1
- package/node_modules/hono/dist/types/adapter/aws-lambda/handler.d.ts +1 -1
- package/node_modules/hono/dist/types/jsx/base.d.ts +1 -3
- package/node_modules/hono/dist/types/jsx/context.d.ts +39 -0
- package/node_modules/hono/dist/utils/ipaddr.js +6 -1
- package/node_modules/hono/package.json +19 -13
- package/node_modules/http-proxy-middleware/README.md +3 -10
- package/node_modules/http-proxy-middleware/dist/errors.d.ts +4 -0
- package/node_modules/http-proxy-middleware/dist/errors.js +16 -1
- package/node_modules/http-proxy-middleware/dist/factory.d.ts +2 -2
- package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body-utils/stringify-form-data.d.ts +7 -0
- package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body-utils/stringify-form-data.js +45 -0
- package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/handlers/fix-request-body.js +23 -23
- package/node_modules/http-proxy-middleware/dist/handlers/response-interceptor.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/handlers/response-interceptor.js +1 -1
- package/node_modules/http-proxy-middleware/dist/http-proxy-middleware.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/http-proxy-middleware.js +2 -2
- package/node_modules/http-proxy-middleware/dist/legacy/create-proxy-middleware.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/legacy/create-proxy-middleware.js +1 -1
- package/node_modules/http-proxy-middleware/dist/legacy/options-adapter.js +1 -1
- package/node_modules/http-proxy-middleware/dist/legacy/types.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/path-filter.d.ts +1 -1
- package/node_modules/http-proxy-middleware/dist/path-filter.js +1 -1
- package/node_modules/http-proxy-middleware/dist/path-rewriter.js +1 -1
- package/node_modules/http-proxy-middleware/dist/plugins/default/error-response-plugin.js +2 -1
- package/node_modules/http-proxy-middleware/dist/plugins/default/logger-plugin.js +3 -3
- package/node_modules/http-proxy-middleware/dist/router.js +26 -8
- package/node_modules/http-proxy-middleware/dist/status-code.js +15 -12
- package/node_modules/http-proxy-middleware/dist/types.d.ts +3 -3
- package/node_modules/http-proxy-middleware/dist/types.js +0 -4
- package/node_modules/http-proxy-middleware/dist/utils/sanitize.d.ts +1 -0
- package/node_modules/http-proxy-middleware/dist/utils/sanitize.js +6 -0
- package/node_modules/http-proxy-middleware/package.json +30 -28
- package/node_modules/qs/CHANGELOG.md +11 -0
- package/node_modules/qs/dist/qs.js +17 -17
- package/node_modules/qs/lib/parse.js +19 -8
- package/node_modules/qs/lib/utils.js +47 -8
- package/node_modules/qs/package.json +6 -5
- package/node_modules/qs/test/parse.js +230 -0
- package/node_modules/qs/test/utils.js +194 -0
- package/node_modules/side-channel/CHANGELOG.md +10 -0
- package/node_modules/side-channel/README.md +1 -1
- package/node_modules/side-channel/index.js +5 -2
- package/node_modules/side-channel/package.json +10 -10
- package/node_modules/side-channel/test/index.js +16 -0
- package/node_modules/undici-types/agent.d.ts +0 -2
- package/node_modules/undici-types/client.d.ts +25 -19
- package/node_modules/undici-types/dispatcher.d.ts +7 -33
- package/node_modules/undici-types/dispatcher1-wrapper.d.ts +7 -0
- package/node_modules/undici-types/fetch.d.ts +24 -4
- package/node_modules/undici-types/h2c-client.d.ts +6 -6
- package/node_modules/undici-types/handlers.d.ts +1 -2
- package/node_modules/undici-types/header.d.ts +5 -0
- package/node_modules/undici-types/index.d.ts +3 -1
- package/node_modules/undici-types/interceptors.d.ts +1 -1
- package/node_modules/undici-types/package.json +1 -1
- package/node_modules/undici-types/pool.d.ts +0 -2
- package/node_modules/undici-types/proxy-agent.d.ts +2 -2
- package/node_modules/undici-types/round-robin-pool.d.ts +0 -2
- package/node_modules/undici-types/snapshot-agent.d.ts +4 -0
- package/node_modules/undici-types/socks5-proxy-agent.d.ts +2 -2
- package/node_modules/undici-types/webidl.d.ts +0 -1
- package/package.json +3 -3
- package/dist/ui/assets/index-CS16uhqb.js +0 -102
- package/node_modules/@types/node/compatibility/iterators.d.ts +0 -21
|
@@ -50,8 +50,62 @@ function computeContentHash(binding, content) {
|
|
|
50
50
|
return `sha256:${sha256Hex(contentCanonicalBytes(binding.kind, content))}`;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
// src/
|
|
53
|
+
// src/intent-disclosure.ts
|
|
54
54
|
import { createHash as createHash2 } from "crypto";
|
|
55
|
+
function sha256Hex2(bytes) {
|
|
56
|
+
return createHash2("sha256").update(bytes, "utf8").digest("hex");
|
|
57
|
+
}
|
|
58
|
+
function computeIntentHash(intentText) {
|
|
59
|
+
return `sha256:${sha256Hex2(canonicalizeText(intentText))}`;
|
|
60
|
+
}
|
|
61
|
+
function intentDisclosureCanonicalBytes(intentCiphertext, approversFrozen) {
|
|
62
|
+
const sorted = [...approversFrozen].sort();
|
|
63
|
+
return `${intentCiphertext}
|
|
64
|
+
${canonicalize(sorted)}`;
|
|
65
|
+
}
|
|
66
|
+
function computeIntentDisclosureHash(intentCiphertext, approversFrozen) {
|
|
67
|
+
return `sha256:${sha256Hex2(intentDisclosureCanonicalBytes(intentCiphertext, approversFrozen))}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/identity.ts
|
|
71
|
+
function validateSubject(subject) {
|
|
72
|
+
const errors = [];
|
|
73
|
+
const { did, assurance, method, trust_root, verifier, disclose, owner_signature } = subject;
|
|
74
|
+
if (!did) errors.push("subject.did is required");
|
|
75
|
+
if (assurance === "low") {
|
|
76
|
+
if (method !== "self_declared") errors.push(`low assurance requires method "self_declared", got "${method}"`);
|
|
77
|
+
if (trust_root !== "self") errors.push(`low assurance requires trust_root "self", got "${trust_root}"`);
|
|
78
|
+
if (disclose) errors.push("low assurance MUST NOT disclose a name");
|
|
79
|
+
} else if (assurance === "high") {
|
|
80
|
+
if (method === "as_vouched") {
|
|
81
|
+
if (trust_root !== "as") errors.push('as_vouched requires trust_root "as"');
|
|
82
|
+
if (!verifier) errors.push("as_vouched requires a verifier");
|
|
83
|
+
} else if (method === "eudi") {
|
|
84
|
+
if (trust_root !== "external") errors.push('eudi requires trust_root "external"');
|
|
85
|
+
if (!verifier) errors.push("eudi requires a verifier");
|
|
86
|
+
if (!owner_signature) errors.push("eudi requires an owner_signature");
|
|
87
|
+
} else {
|
|
88
|
+
errors.push(`high assurance requires method "as_vouched" or "eudi", got "${method}"`);
|
|
89
|
+
}
|
|
90
|
+
if (disclose && typeof disclose.name !== "string") errors.push("disclose.name must be a string");
|
|
91
|
+
} else {
|
|
92
|
+
errors.push(`assurance must be "low" or "high", got "${assurance}"`);
|
|
93
|
+
}
|
|
94
|
+
return { valid: errors.length === 0, errors };
|
|
95
|
+
}
|
|
96
|
+
function deriveIdentityLine(subject, { operatorName }) {
|
|
97
|
+
const name = subject?.assurance === "high" ? subject.disclose?.name : void 0;
|
|
98
|
+
if (!name) {
|
|
99
|
+
return `Sent by an AI agent via ${operatorName}`;
|
|
100
|
+
}
|
|
101
|
+
if (subject.method === "eudi") {
|
|
102
|
+
return `Sent by an AI agent of ${name} \u2014 identity verified (EUDI)`;
|
|
103
|
+
}
|
|
104
|
+
return `Sent by an AI agent of ${name} \u2014 verified by ${operatorName}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/frame.ts
|
|
108
|
+
import { createHash as createHash3 } from "crypto";
|
|
55
109
|
function validateFrameParams(params, profile) {
|
|
56
110
|
const errors = [];
|
|
57
111
|
if (!profile.frameSchema) {
|
|
@@ -88,7 +142,7 @@ function canonicalFrame(params, profile) {
|
|
|
88
142
|
return lines.join("\n");
|
|
89
143
|
}
|
|
90
144
|
function frameHash(canonicalFrameString) {
|
|
91
|
-
const hash =
|
|
145
|
+
const hash = createHash3("sha256").update(canonicalFrameString, "utf8").digest("hex");
|
|
92
146
|
return `sha256:${hash}`;
|
|
93
147
|
}
|
|
94
148
|
function computeFrameHash(params, profile) {
|
|
@@ -172,17 +226,17 @@ function canonicalContext(params, profile) {
|
|
|
172
226
|
}
|
|
173
227
|
function computeBoundsHash(params, profile) {
|
|
174
228
|
const canonical = canonicalBounds(params, profile);
|
|
175
|
-
const hash =
|
|
229
|
+
const hash = createHash3("sha256").update(canonical, "utf8").digest("hex");
|
|
176
230
|
return `sha256:${hash}`;
|
|
177
231
|
}
|
|
178
232
|
function computeContextHash(params, profile) {
|
|
179
233
|
const canonical = canonicalContext(params, profile);
|
|
180
|
-
const hash =
|
|
234
|
+
const hash = createHash3("sha256").update(canonical, "utf8").digest("hex");
|
|
181
235
|
return `sha256:${hash}`;
|
|
182
236
|
}
|
|
183
237
|
|
|
184
238
|
// src/attestation.ts
|
|
185
|
-
import { createHash as
|
|
239
|
+
import { createHash as createHash4 } from "crypto";
|
|
186
240
|
import * as ed from "@noble/ed25519";
|
|
187
241
|
function decodeAttestationBlob(blob) {
|
|
188
242
|
try {
|
|
@@ -200,7 +254,7 @@ function encodeAttestationBlob(attestation) {
|
|
|
200
254
|
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
201
255
|
}
|
|
202
256
|
function attestationId(blob) {
|
|
203
|
-
const hash =
|
|
257
|
+
const hash = createHash4("sha256").update(blob, "utf8").digest("hex");
|
|
204
258
|
return `sha256:${hash}`;
|
|
205
259
|
}
|
|
206
260
|
async function verifyAttestationSignature(attestation, publicKeyHex) {
|
|
@@ -330,11 +384,11 @@ async function verifyV3(request, profile, publicKeyHex, now, executionLog, error
|
|
|
330
384
|
try {
|
|
331
385
|
checkAttestationExpiry(attestation.payload, now);
|
|
332
386
|
} catch {
|
|
333
|
-
const domainNames = attestation.payload.resolved_domains.map((d) => d.domain).join(", ");
|
|
387
|
+
const domainNames = (attestation.payload.resolved_domains ?? []).map((d) => d.domain).join(", ");
|
|
334
388
|
errors.push({ code: "TTL_EXPIRED", message: `Attestation for domain "${domainNames}" has expired` });
|
|
335
389
|
continue;
|
|
336
390
|
}
|
|
337
|
-
for (const rd of attestation.payload.resolved_domains) {
|
|
391
|
+
for (const rd of attestation.payload.resolved_domains ?? []) {
|
|
338
392
|
coveredDomains.add(rd.domain);
|
|
339
393
|
}
|
|
340
394
|
}
|
|
@@ -409,11 +463,11 @@ async function verifyV4(request, profile, publicKeyHex, now, executionLog) {
|
|
|
409
463
|
try {
|
|
410
464
|
checkAttestationExpiry(attestation.payload, now);
|
|
411
465
|
} catch {
|
|
412
|
-
const domainNames = attestation.payload.resolved_domains.map((d) => d.domain).join(", ");
|
|
466
|
+
const domainNames = (attestation.payload.resolved_domains ?? []).map((d) => d.domain).join(", ");
|
|
413
467
|
errors.push({ code: "TTL_EXPIRED", message: `Attestation for domain "${domainNames}" has expired` });
|
|
414
468
|
continue;
|
|
415
469
|
}
|
|
416
|
-
for (const rd of attestation.payload.resolved_domains) {
|
|
470
|
+
for (const rd of attestation.payload.resolved_domains ?? []) {
|
|
417
471
|
coveredDomains.add(rd.domain);
|
|
418
472
|
}
|
|
419
473
|
}
|
|
@@ -716,18 +770,23 @@ export {
|
|
|
716
770
|
computeContentHash,
|
|
717
771
|
computeContextHash,
|
|
718
772
|
computeFrameHash,
|
|
773
|
+
computeIntentDisclosureHash,
|
|
774
|
+
computeIntentHash,
|
|
719
775
|
contentCanonicalBytes,
|
|
720
776
|
decodeAttestationBlob,
|
|
777
|
+
deriveIdentityLine,
|
|
721
778
|
encodeAttestationBlob,
|
|
722
779
|
frameHash,
|
|
723
780
|
getAllProfiles,
|
|
724
781
|
getProfile,
|
|
782
|
+
intentDisclosureCanonicalBytes,
|
|
725
783
|
isV4Attestation,
|
|
726
784
|
listProfiles,
|
|
727
785
|
registerProfile,
|
|
728
786
|
validateBoundsParams,
|
|
729
787
|
validateContextParams,
|
|
730
788
|
validateFrameParams,
|
|
789
|
+
validateSubject,
|
|
731
790
|
verify,
|
|
732
791
|
verifyAttestation,
|
|
733
792
|
verifyAttestationSignature,
|
|
@@ -131,12 +131,12 @@ async function verifyV3(
|
|
|
131
131
|
try {
|
|
132
132
|
checkAttestationExpiry(attestation.payload, now);
|
|
133
133
|
} catch {
|
|
134
|
-
const domainNames = attestation.payload.resolved_domains.map(d => d.domain).join(', ');
|
|
134
|
+
const domainNames = (attestation.payload.resolved_domains ?? []).map(d => d.domain).join(', ');
|
|
135
135
|
errors.push({ code: 'TTL_EXPIRED', message: `Attestation for domain "${domainNames}" has expired` });
|
|
136
136
|
continue;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
for (const rd of attestation.payload.resolved_domains) {
|
|
139
|
+
for (const rd of attestation.payload.resolved_domains ?? []) {
|
|
140
140
|
coveredDomains.add(rd.domain);
|
|
141
141
|
}
|
|
142
142
|
}
|
|
@@ -244,12 +244,12 @@ async function verifyV4(
|
|
|
244
244
|
try {
|
|
245
245
|
checkAttestationExpiry(attestation.payload, now);
|
|
246
246
|
} catch {
|
|
247
|
-
const domainNames = attestation.payload.resolved_domains.map(d => d.domain).join(', ');
|
|
247
|
+
const domainNames = (attestation.payload.resolved_domains ?? []).map(d => d.domain).join(', ');
|
|
248
248
|
errors.push({ code: 'TTL_EXPIRED', message: `Attestation for domain "${domainNames}" has expired` });
|
|
249
249
|
continue;
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
for (const rd of attestation.payload.resolved_domains) {
|
|
252
|
+
for (const rd of attestation.payload.resolved_domains ?? []) {
|
|
253
253
|
coveredDomains.add(rd.domain);
|
|
254
254
|
}
|
|
255
255
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity Assurance (HAP v0.6) — validation + footer derivation for the signed
|
|
3
|
+
* {@link Subject} overlay. See review.md → "Identity Assurance".
|
|
4
|
+
*
|
|
5
|
+
* Two pure helpers, the single source of truth for both the gateway footer and
|
|
6
|
+
* the AS verify page:
|
|
7
|
+
* - {@link validateSubject} — enforces the level/method/trust-root invariants.
|
|
8
|
+
* - {@link deriveIdentityLine} — the human-readable footer line for a subject.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Subject } from './types';
|
|
12
|
+
|
|
13
|
+
export interface SubjectValidation {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
errors: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Enforce the Identity-Assurance invariants on a single subject:
|
|
20
|
+
* - `self_declared` ⇒ `low` / `self`, no disclosed name.
|
|
21
|
+
* - `as_vouched` ⇒ `high` / `as`, `verifier` required.
|
|
22
|
+
* - `eudi` ⇒ `high` / `external`, `verifier` + `owner_signature` required.
|
|
23
|
+
* - `disclose.name` only at `assurance:"high"`.
|
|
24
|
+
*/
|
|
25
|
+
export function validateSubject(subject: Subject): SubjectValidation {
|
|
26
|
+
const errors: string[] = [];
|
|
27
|
+
const { did, assurance, method, trust_root, verifier, disclose, owner_signature } = subject;
|
|
28
|
+
|
|
29
|
+
if (!did) errors.push('subject.did is required');
|
|
30
|
+
|
|
31
|
+
if (assurance === 'low') {
|
|
32
|
+
if (method !== 'self_declared') errors.push(`low assurance requires method "self_declared", got "${method}"`);
|
|
33
|
+
if (trust_root !== 'self') errors.push(`low assurance requires trust_root "self", got "${trust_root}"`);
|
|
34
|
+
if (disclose) errors.push('low assurance MUST NOT disclose a name');
|
|
35
|
+
} else if (assurance === 'high') {
|
|
36
|
+
if (method === 'as_vouched') {
|
|
37
|
+
if (trust_root !== 'as') errors.push('as_vouched requires trust_root "as"');
|
|
38
|
+
if (!verifier) errors.push('as_vouched requires a verifier');
|
|
39
|
+
} else if (method === 'eudi') {
|
|
40
|
+
if (trust_root !== 'external') errors.push('eudi requires trust_root "external"');
|
|
41
|
+
if (!verifier) errors.push('eudi requires a verifier');
|
|
42
|
+
if (!owner_signature) errors.push('eudi requires an owner_signature');
|
|
43
|
+
} else {
|
|
44
|
+
errors.push(`high assurance requires method "as_vouched" or "eudi", got "${method}"`);
|
|
45
|
+
}
|
|
46
|
+
if (disclose && typeof disclose.name !== 'string') errors.push('disclose.name must be a string');
|
|
47
|
+
} else {
|
|
48
|
+
errors.push(`assurance must be "low" or "high", got "${assurance as string}"`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { valid: errors.length === 0, errors };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The footer line for a subject — the ONE place this wording lives.
|
|
56
|
+
* The name appears only at `high` with a disclosed name; otherwise no name.
|
|
57
|
+
*
|
|
58
|
+
* - low / no disclosure → "Sent by an AI agent via «operator»"
|
|
59
|
+
* - high / as_vouched → "Sent by an AI agent of «name» — verified by «operator»"
|
|
60
|
+
* - high / eudi → "Sent by an AI agent of «name» — identity verified (EUDI)"
|
|
61
|
+
*
|
|
62
|
+
* `operatorName` is the rendered AS operator (e.g. "Suveren") — never hardcode it
|
|
63
|
+
* upstream; a different operator self-vouches under its own name.
|
|
64
|
+
*/
|
|
65
|
+
export function deriveIdentityLine(
|
|
66
|
+
subject: Subject | undefined | null,
|
|
67
|
+
{ operatorName }: { operatorName: string },
|
|
68
|
+
): string {
|
|
69
|
+
const name = subject?.assurance === 'high' ? subject.disclose?.name : undefined;
|
|
70
|
+
if (!name) {
|
|
71
|
+
return `Sent by an AI agent via ${operatorName}`;
|
|
72
|
+
}
|
|
73
|
+
if (subject!.method === 'eudi') {
|
|
74
|
+
return `Sent by an AI agent of ${name} — identity verified (EUDI)`;
|
|
75
|
+
}
|
|
76
|
+
return `Sent by an AI agent of ${name} — verified by ${operatorName}`;
|
|
77
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intent hashing + intent-disclosure binding (HAP v0.5).
|
|
3
|
+
*
|
|
4
|
+
* Two shared, environment-independent hashes that the attester (gateway), the
|
|
5
|
+
* Authority Server, and any approver MUST compute identically:
|
|
6
|
+
*
|
|
7
|
+
* 1. {@link computeIntentHash} — `gate_content_hashes.intent`. The intent text
|
|
8
|
+
* is canonicalized (Unicode NFC, LF line endings, trailing-whitespace strip
|
|
9
|
+
* — the same rule as content-binding's `canonicalizeText`) before hashing,
|
|
10
|
+
* so independent multi-owner approvers reproduce an identical hash from the
|
|
11
|
+
* same logical statement. See protocol.md → "Intent canonicalization".
|
|
12
|
+
*
|
|
13
|
+
* 2. {@link computeIntentDisclosureHash} — `intent_disclosure_hash`, the C2
|
|
14
|
+
* binding from companion spec `intent-disclosure@0.1`. It commits the
|
|
15
|
+
* encrypted disclosure object (`intent_ciphertext` + `approvers_frozen`)
|
|
16
|
+
* into the SIGNED attestation payload, so a compromised AS cannot swap the
|
|
17
|
+
* ciphertext/wrapped keys or widen/shrink the approver set without breaking
|
|
18
|
+
* the attestation signature.
|
|
19
|
+
*
|
|
20
|
+
* Both produce `sha256:<hex>` to match the frame/bounds/context hash format.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { createHash } from 'crypto';
|
|
24
|
+
import { canonicalize } from './canonicalize';
|
|
25
|
+
import { canonicalizeText } from './content-binding';
|
|
26
|
+
|
|
27
|
+
function sha256Hex(bytes: string): string {
|
|
28
|
+
return createHash('sha256').update(bytes, 'utf8').digest('hex');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* `gate_content_hashes.intent` for an intent statement.
|
|
33
|
+
* Canonicalizes the text (NFC / LF / trailing-whitespace strip) before hashing.
|
|
34
|
+
*/
|
|
35
|
+
export function computeIntentHash(intentText: string): string {
|
|
36
|
+
return `sha256:${sha256Hex(canonicalizeText(intentText))}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The exact bytes `intent_disclosure_hash` is taken over, WITHOUT hashing —
|
|
41
|
+
* exposed so an implementer debugging a mismatch can inspect the serialization
|
|
42
|
+
* both sides must agree on:
|
|
43
|
+
*
|
|
44
|
+
* intent_ciphertext ‖ "\n" ‖ JCS(sort(approvers_frozen))
|
|
45
|
+
*
|
|
46
|
+
* where `intent_ciphertext` is the exact base64url string of the disclosure
|
|
47
|
+
* object, `sort` orders the approver DIDs by Unicode code point, and `JCS` is
|
|
48
|
+
* RFC 8785 canonicalization of the sorted array (via {@link canonicalize}).
|
|
49
|
+
*/
|
|
50
|
+
export function intentDisclosureCanonicalBytes(
|
|
51
|
+
intentCiphertext: string,
|
|
52
|
+
approversFrozen: string[],
|
|
53
|
+
): string {
|
|
54
|
+
const sorted = [...approversFrozen].sort();
|
|
55
|
+
return `${intentCiphertext}\n${canonicalize(sorted)}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* `intent_disclosure_hash` — `sha256:<hex>` over
|
|
60
|
+
* {@link intentDisclosureCanonicalBytes}. Computed by the attester after
|
|
61
|
+
* encryption and signed into the attestation; recomputed by the AS (at
|
|
62
|
+
* issuance) and by each approver (on receipt) and compared, fail-closed.
|
|
63
|
+
*/
|
|
64
|
+
export function computeIntentDisclosureHash(
|
|
65
|
+
intentCiphertext: string,
|
|
66
|
+
approversFrozen: string[],
|
|
67
|
+
): string {
|
|
68
|
+
return `sha256:${sha256Hex(intentDisclosureCanonicalBytes(intentCiphertext, approversFrozen))}`;
|
|
69
|
+
}
|
|
@@ -19,7 +19,7 @@ export interface ResolvedDomain {
|
|
|
19
19
|
|
|
20
20
|
export interface AttestationPayload {
|
|
21
21
|
attestation_id: string;
|
|
22
|
-
version: '0.3' | '0.4';
|
|
22
|
+
version: '0.3' | '0.4' | '0.5';
|
|
23
23
|
profile_id: string;
|
|
24
24
|
/** v0.3 (deprecated) — hash of the authorization frame */
|
|
25
25
|
frame_hash?: string;
|
|
@@ -28,8 +28,37 @@ export interface AttestationPayload {
|
|
|
28
28
|
/** v0.4 — hash of the context parameters */
|
|
29
29
|
context_hash?: string;
|
|
30
30
|
execution_context_hash: string;
|
|
31
|
-
|
|
31
|
+
/**
|
|
32
|
+
* v0.4 (deprecated in v0.5) — domain-scoped owners. Optional so v0.5
|
|
33
|
+
* attestations that carry only `resolved_owners` remain valid. During the
|
|
34
|
+
* transition the AS emits BOTH `resolved_domains` (internal coverage) and
|
|
35
|
+
* `resolved_owners` (the v0.5 signed wire field).
|
|
36
|
+
*/
|
|
37
|
+
resolved_domains?: ResolvedDomain[];
|
|
38
|
+
/**
|
|
39
|
+
* v0.5 — the Decision Owner DIDs this attestation covers (person-centric
|
|
40
|
+
* model; replaces the abstract domain in `resolved_domains`). Carried in the
|
|
41
|
+
* signed payload so verifiers bind the action to the human(s) who authorized it.
|
|
42
|
+
*/
|
|
43
|
+
resolved_owners?: string[];
|
|
32
44
|
gate_content_hashes: Record<string, string>;
|
|
45
|
+
/**
|
|
46
|
+
* v0.5 (companion spec `intent-disclosure@0.1`) — present iff the attestation
|
|
47
|
+
* carries an encrypted-intent disclosure object. `sha256:`-prefixed hash that
|
|
48
|
+
* binds the disclosure's `intent_ciphertext` + `approvers_frozen` into the
|
|
49
|
+
* signed payload (see {@link computeIntentDisclosureHash}), so a compromised
|
|
50
|
+
* AS cannot swap the ciphertext/wrapped keys or alter the approver set
|
|
51
|
+
* without invalidating the attestation signature (companion invariant C2).
|
|
52
|
+
*/
|
|
53
|
+
intent_disclosure_hash?: string;
|
|
54
|
+
/**
|
|
55
|
+
* v0.6 (Identity Assurance) — optional signed overlay carrying the verified
|
|
56
|
+
* real-world identity of the Decision Owner(s), one entry per owner. Present
|
|
57
|
+
* only when identity is disclosed; an attestation with no `subjects` renders
|
|
58
|
+
* as `low` (pseudonymous, no name). See {@link Subject} and
|
|
59
|
+
* {@link deriveIdentityLine}.
|
|
60
|
+
*/
|
|
61
|
+
subjects?: Subject[];
|
|
33
62
|
/**
|
|
34
63
|
* v0.4 — commitment mode chosen by the decision owner at attestation time.
|
|
35
64
|
* - 'automatic': agent may invoke bounded tools without per-action review.
|
|
@@ -44,6 +73,37 @@ export interface AttestationPayload {
|
|
|
44
73
|
expires_at: number;
|
|
45
74
|
}
|
|
46
75
|
|
|
76
|
+
/**
|
|
77
|
+
* v0.6 Identity Assurance — a signed overlay binding a Decision Owner's verified
|
|
78
|
+
* real-world identity to the attestation, gated by HOW the identity was verified.
|
|
79
|
+
*
|
|
80
|
+
* Two display levels (`assurance`): `low` discloses no name; `high` MAY disclose a
|
|
81
|
+
* name. At `high`, two trust roots: `as` (the AS operator vouches — valid only
|
|
82
|
+
* within its own domain) and `external` (an external eID such as EUDI — carries the
|
|
83
|
+
* owner's own signature, AS-independent). See review.md → "Identity Assurance".
|
|
84
|
+
*/
|
|
85
|
+
export interface Subject {
|
|
86
|
+
/** The Decision Owner DID this subject describes (matches an entry in resolved_owners). */
|
|
87
|
+
did: string;
|
|
88
|
+
/** `low` → no name shown; `high` → the name MAY be shown. */
|
|
89
|
+
assurance: 'low' | 'high';
|
|
90
|
+
/** How identity was established. */
|
|
91
|
+
method: 'self_declared' | 'as_vouched' | 'eudi';
|
|
92
|
+
/** Who vouches: `self` (owner's claim), `as` (operator), `external` (eID scheme). */
|
|
93
|
+
trust_root: 'self' | 'as' | 'external';
|
|
94
|
+
/** Verifier id — the AS operator (as_vouched) or the eID scheme (eudi). Required at `high`. */
|
|
95
|
+
verifier?: string;
|
|
96
|
+
/** Disclosed attributes. `name` present ONLY at `assurance:"high"` and when disclosure is on. */
|
|
97
|
+
disclose?: { name: string };
|
|
98
|
+
/** When the underlying verification was performed (unix seconds). */
|
|
99
|
+
verified_at?: number;
|
|
100
|
+
/**
|
|
101
|
+
* eudi only (Phase 2) — the owner's per-event wallet signature, making the
|
|
102
|
+
* identity claim non-repudiable independent of the AS. Null/absent otherwise.
|
|
103
|
+
*/
|
|
104
|
+
owner_signature?: string | null;
|
|
105
|
+
}
|
|
106
|
+
|
|
47
107
|
export interface Attestation {
|
|
48
108
|
header: AttestationHeader;
|
|
49
109
|
payload: AttestationPayload;
|
|
@@ -51,7 +51,14 @@ $ npm install body-parser
|
|
|
51
51
|
## API
|
|
52
52
|
|
|
53
53
|
```js
|
|
54
|
+
// Import all parsers
|
|
54
55
|
const bodyParser = require('body-parser')
|
|
56
|
+
|
|
57
|
+
// Or import individual parsers directly
|
|
58
|
+
const json = require('body-parser/json')
|
|
59
|
+
const urlencoded = require('body-parser/urlencoded')
|
|
60
|
+
const raw = require('body-parser/raw')
|
|
61
|
+
const text = require('body-parser/text')
|
|
55
62
|
```
|
|
56
63
|
|
|
57
64
|
The `bodyParser` object exposes various factories to create middlewares. All
|
|
@@ -93,11 +100,13 @@ specifies the number of bytes; if it is a string, the value is passed to the
|
|
|
93
100
|
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
|
94
101
|
to `'100kb'`.
|
|
95
102
|
|
|
103
|
+
> It’s recommended not to configure a very high limit and to use the default value whenever possible. Allowing larger payloads increases memory usage because of the resources required for decoding and transformations, and it can also lead to longer response times as more data is processed. By ‘very high’, we mean values above the default, for example payloads of 5 MB or more can already start to introduce these risks. With the default limits, these issues do not occur.
|
|
104
|
+
|
|
96
105
|
##### reviver
|
|
97
106
|
|
|
98
107
|
The `reviver` option is passed directly to `JSON.parse` as the second
|
|
99
108
|
argument. You can find more information on this argument
|
|
100
|
-
[in the MDN documentation about JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#
|
|
109
|
+
[in the MDN documentation about JSON.parse](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#the_reviver_parameter).
|
|
101
110
|
|
|
102
111
|
##### strict
|
|
103
112
|
|
|
@@ -149,6 +158,8 @@ specifies the number of bytes; if it is a string, the value is passed to the
|
|
|
149
158
|
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
|
150
159
|
to `'100kb'`.
|
|
151
160
|
|
|
161
|
+
> It’s recommended not to configure a very high limit and to use the default value whenever possible. Allowing larger payloads increases memory usage because of the resources required for decoding and transformations, and it can also lead to longer response times as more data is processed. By ‘very high’, we mean values above the default, for example payloads of 5 MB or more can already start to introduce these risks. With the default limits, these issues do not occur.
|
|
162
|
+
|
|
152
163
|
##### type
|
|
153
164
|
|
|
154
165
|
The `type` option is used to determine what media type the middleware will
|
|
@@ -200,6 +211,8 @@ specifies the number of bytes; if it is a string, the value is passed to the
|
|
|
200
211
|
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
|
201
212
|
to `'100kb'`.
|
|
202
213
|
|
|
214
|
+
> It’s recommended not to configure a very high limit and to use the default value whenever possible. Allowing larger payloads increases memory usage because of the resources required for decoding and transformations, and it can also lead to longer response times as more data is processed. By ‘very high’, we mean values above the default, for example payloads of 5 MB or more can already start to introduce these risks. With the default limits, these issues do not occur.
|
|
215
|
+
|
|
203
216
|
##### type
|
|
204
217
|
|
|
205
218
|
The `type` option is used to determine what media type the middleware will
|
|
@@ -255,6 +268,8 @@ specifies the number of bytes; if it is a string, the value is passed to the
|
|
|
255
268
|
[bytes](https://www.npmjs.com/package/bytes) library for parsing. Defaults
|
|
256
269
|
to `'100kb'`.
|
|
257
270
|
|
|
271
|
+
> It’s recommended not to configure a very high limit and to use the default value whenever possible. Allowing larger payloads increases memory usage because of the resources required for decoding and transformations, and it can also lead to longer response times as more data is processed. By ‘very high’, we mean values above the default, for example payloads of 5 MB or more can already start to introduce these risks. With the default limits, these issues do not occur.
|
|
272
|
+
|
|
258
273
|
##### parameterLimit
|
|
259
274
|
|
|
260
275
|
The `parameterLimit` option controls the maximum number of parameters that
|
|
@@ -24,41 +24,25 @@ exports = module.exports = bodyParser
|
|
|
24
24
|
* JSON parser.
|
|
25
25
|
* @public
|
|
26
26
|
*/
|
|
27
|
-
|
|
28
|
-
configurable: true,
|
|
29
|
-
enumerable: true,
|
|
30
|
-
get: () => require('./lib/types/json')
|
|
31
|
-
})
|
|
27
|
+
exports.json = require('./lib/types/json')
|
|
32
28
|
|
|
33
29
|
/**
|
|
34
30
|
* Raw parser.
|
|
35
31
|
* @public
|
|
36
32
|
*/
|
|
37
|
-
|
|
38
|
-
configurable: true,
|
|
39
|
-
enumerable: true,
|
|
40
|
-
get: () => require('./lib/types/raw')
|
|
41
|
-
})
|
|
33
|
+
exports.raw = require('./lib/types/raw')
|
|
42
34
|
|
|
43
35
|
/**
|
|
44
36
|
* Text parser.
|
|
45
37
|
* @public
|
|
46
38
|
*/
|
|
47
|
-
|
|
48
|
-
configurable: true,
|
|
49
|
-
enumerable: true,
|
|
50
|
-
get: () => require('./lib/types/text')
|
|
51
|
-
})
|
|
39
|
+
exports.text = require('./lib/types/text')
|
|
52
40
|
|
|
53
41
|
/**
|
|
54
42
|
* URL-encoded parser.
|
|
55
43
|
* @public
|
|
56
44
|
*/
|
|
57
|
-
|
|
58
|
-
configurable: true,
|
|
59
|
-
enumerable: true,
|
|
60
|
-
get: () => require('./lib/types/urlencoded')
|
|
61
|
-
})
|
|
45
|
+
exports.urlencoded = require('./lib/types/urlencoded')
|
|
62
46
|
|
|
63
47
|
/**
|
|
64
48
|
* Create a middleware to parse json and urlencoded bodies.
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
* @private
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
const createError = require('http-errors')
|
|
15
|
+
const getBody = require('raw-body')
|
|
16
|
+
const iconv = require('iconv-lite')
|
|
17
|
+
const onFinished = require('on-finished')
|
|
18
|
+
const zlib = require('node:zlib')
|
|
19
|
+
const hasBody = require('type-is').hasBody
|
|
20
|
+
const { getCharset } = require('./utils')
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Module exports.
|
|
@@ -63,7 +63,7 @@ function read (req, res, next, parse, debug, options) {
|
|
|
63
63
|
return
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
let encoding = null
|
|
67
67
|
if (options?.skipCharset !== true) {
|
|
68
68
|
encoding = getCharset(req) || options.defaultCharset
|
|
69
69
|
|
|
@@ -78,12 +78,12 @@ function read (req, res, next, parse, debug, options) {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
let length
|
|
82
|
+
const opts = options
|
|
83
|
+
let stream
|
|
84
84
|
|
|
85
85
|
// read options
|
|
86
|
-
|
|
86
|
+
const verify = opts.verify
|
|
87
87
|
|
|
88
88
|
try {
|
|
89
89
|
// get the content stream
|
|
@@ -112,7 +112,7 @@ function read (req, res, next, parse, debug, options) {
|
|
|
112
112
|
debug('read body')
|
|
113
113
|
getBody(stream, opts, function (error, body) {
|
|
114
114
|
if (error) {
|
|
115
|
-
|
|
115
|
+
let _error
|
|
116
116
|
|
|
117
117
|
if (error.type === 'encoding.unsupported') {
|
|
118
118
|
// echo back charset
|
|
@@ -153,7 +153,7 @@ function read (req, res, next, parse, debug, options) {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
// parse
|
|
156
|
-
|
|
156
|
+
let str = body
|
|
157
157
|
try {
|
|
158
158
|
debug('parse body')
|
|
159
159
|
str = typeof body !== 'string' && encoding !== null
|
|
@@ -182,8 +182,8 @@ function read (req, res, next, parse, debug, options) {
|
|
|
182
182
|
* @private
|
|
183
183
|
*/
|
|
184
184
|
function contentstream (req, debug, inflate) {
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
const encoding = (req.headers['content-encoding'] || 'identity').toLowerCase()
|
|
186
|
+
const length = req.headers['content-length']
|
|
187
187
|
|
|
188
188
|
debug('content-encoding "%s"', encoding)
|
|
189
189
|
|
|
@@ -199,7 +199,7 @@ function contentstream (req, debug, inflate) {
|
|
|
199
199
|
return req
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
|
|
202
|
+
const stream = createDecompressionStream(encoding, debug)
|
|
203
203
|
req.pipe(stream)
|
|
204
204
|
return stream
|
|
205
205
|
}
|