@dnsid-ai/sdk 0.32.0 → 0.33.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/dist/node.cjs CHANGED
@@ -38,6 +38,7 @@ __export(node_exports, {
38
38
  createNodeIdentityManagerFromFile: () => createNodeIdentityManagerFromFile,
39
39
  createNodeIdentityVerifier: () => createNodeIdentityVerifier,
40
40
  createRegistryClientFromEnvironment: () => createRegistryClientFromEnvironment,
41
+ createResponseSigningMiddleware: () => createResponseSigningMiddleware,
41
42
  loadCliDirectory: () => loadCliDirectory,
42
43
  loadEnvironment: () => loadEnvironment,
43
44
  loadFile: () => loadFile,
@@ -321,6 +322,124 @@ async function importPrivateKey(key) {
321
322
  );
322
323
  }
323
324
 
325
+ // src/node-response-signing.ts
326
+ var MAX_BUFFERED_BYTES = 8 * 1024 * 1024;
327
+ var SIGNED_FIELDS = ["content-digest", "cache-control", "signature-input", "signature"];
328
+ function createResponseSigningMiddleware(profile, options = {}) {
329
+ return (req, res, next) => {
330
+ if (req.headers["accept-signature"] === void 0) return next();
331
+ if (res.headersSent) return next(new Error("response headers already sent; register response signing first"));
332
+ const writeHead = res.writeHead.bind(res);
333
+ const end = res.end.bind(res);
334
+ const chunks = [];
335
+ let size = 0;
336
+ let overflow = false;
337
+ let ended = false;
338
+ let done = false;
339
+ let releasing = false;
340
+ const collect = (chunk, encoding) => {
341
+ if (chunk === void 0 || chunk === null || typeof chunk === "function") return true;
342
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk, typeof encoding === "string" ? encoding : "utf8") : Buffer.from(chunk);
343
+ size += bytes.length;
344
+ if (size > MAX_BUFFERED_BYTES) {
345
+ overflow = true;
346
+ chunks.length = 0;
347
+ return false;
348
+ }
349
+ chunks.push(bytes);
350
+ return true;
351
+ };
352
+ const send = (body) => {
353
+ done = true;
354
+ releasing = true;
355
+ end(body);
356
+ };
357
+ const fail = () => {
358
+ if (done) return;
359
+ for (const name of res.getHeaderNames()) res.removeHeader(name);
360
+ res.statusCode = 500;
361
+ send();
362
+ };
363
+ res.writeHead = ((status, ...args) => {
364
+ if (releasing) return writeHead(status);
365
+ res.statusCode = status;
366
+ if (typeof args[0] === "string") res.statusMessage = args.shift();
367
+ setHeaders(res, args[0]);
368
+ return res;
369
+ });
370
+ res.flushHeaders = () => fail();
371
+ res.write = ((chunk, encoding, callback) => {
372
+ const cb = typeof encoding === "function" ? encoding : callback;
373
+ if (ended || done || !collect(chunk, encoding)) {
374
+ if (typeof cb === "function") process.nextTick(cb, new Error("response not writable"));
375
+ return false;
376
+ }
377
+ if (typeof cb === "function") process.nextTick(cb);
378
+ return true;
379
+ });
380
+ res.end = ((chunk, encoding, callback) => {
381
+ if (ended || done) return res;
382
+ ended = true;
383
+ const cb = [chunk, encoding, callback].find((arg) => typeof arg === "function");
384
+ if (typeof cb === "function") res.once("finish", cb);
385
+ collect(chunk, encoding);
386
+ void release();
387
+ return res;
388
+ });
389
+ async function release() {
390
+ if (done) return;
391
+ if (overflow) return fail();
392
+ try {
393
+ const noContent = req.method === "HEAD" || [204, 205, 304].includes(res.statusCode);
394
+ const body = Buffer.concat(chunks);
395
+ const signed = await profile.signRequestedHttpResponse(
396
+ toRequest(req, options.origin),
397
+ new Response(noContent ? null : body, { status: res.statusCode, headers: toHeaders(res.getHeaders()) })
398
+ );
399
+ if (done) return;
400
+ for (const name of SIGNED_FIELDS) {
401
+ const value = signed.headers.get(name);
402
+ if (value !== null) res.setHeader(name, value);
403
+ }
404
+ send(noContent ? void 0 : body);
405
+ } catch {
406
+ fail();
407
+ }
408
+ }
409
+ next();
410
+ };
411
+ }
412
+ function toRequest(req, origin) {
413
+ const path3 = req.originalUrl ?? req.url ?? "";
414
+ if (!path3.startsWith("/")) throw new Error("only origin-form request targets can be signed");
415
+ const scheme = req.socket.encrypted ? "https" : "http";
416
+ const base = origin ?? `${scheme}://${req.headers.host ?? ""}`;
417
+ return new Request(`${new URL(base).origin}${path3}`, { method: req.method, headers: toHeaders(req.headers) });
418
+ }
419
+ function toHeaders(fields) {
420
+ const headers = new Headers();
421
+ for (const [name, value] of Object.entries(fields)) {
422
+ for (const item of Array.isArray(value) ? value : [value]) {
423
+ if (item !== void 0) headers.append(name, String(item));
424
+ }
425
+ }
426
+ return headers;
427
+ }
428
+ function setHeaders(res, headers) {
429
+ if (Array.isArray(headers)) {
430
+ const grouped = /* @__PURE__ */ new Map();
431
+ for (let i = 0; i + 1 < headers.length; i += 2) {
432
+ const name = String(headers[i]).toLowerCase();
433
+ grouped.set(name, [...grouped.get(name) ?? [], String(headers[i + 1])]);
434
+ }
435
+ for (const [name, values] of grouped) res.setHeader(name, values);
436
+ } else if (typeof headers === "object" && headers !== null) {
437
+ for (const [name, value] of Object.entries(headers)) {
438
+ if (value !== void 0) res.setHeader(name, value);
439
+ }
440
+ }
441
+ }
442
+
324
443
  // src/node-identity-manager.ts
325
444
  var import_protocol2 = require("@dnsid-ai/protocol");
326
445
  async function createNodeIdentityManager(config, deps = {}) {
@@ -736,6 +855,7 @@ function optional(raw, key, type, source) {
736
855
  createNodeIdentityManagerFromFile,
737
856
  createNodeIdentityVerifier,
738
857
  createRegistryClientFromEnvironment,
858
+ createResponseSigningMiddleware,
739
859
  loadCliDirectory,
740
860
  loadEnvironment,
741
861
  loadFile,
package/dist/node.d.cts CHANGED
@@ -1,9 +1,10 @@
1
1
  export { CreateIdentityManagerDependencies } from './index.cjs';
2
2
  import * as _dnsid_ai_protocol from '@dnsid-ai/protocol';
3
3
  import { KeyProvider, DnsIdJWK, DnsidConfig, IdentityManagerDependencies, IdentityManager, IdentityConfig } from '@dnsid-ai/protocol';
4
+ import { IncomingMessage, ServerResponse } from 'node:http';
5
+ import { HttpSignaturesProfile } from '@dnsid-ai/http-signatures';
4
6
  import { RegistryClientOptions, RegistryClient } from '@dnsid-ai/registry';
5
7
  import '@dnsid-ai/jose';
6
- import '@dnsid-ai/http-signatures';
7
8
  import '@dnsid-ai/web-bot-auth';
8
9
 
9
10
  type LocalKeyAlgorithm = 'EdDSA' | 'ES256';
@@ -45,6 +46,29 @@ declare class LocalKeyProvider implements KeyProvider {
45
46
  private mutate;
46
47
  }
47
48
 
49
+ /** Options for {@link createResponseSigningMiddleware}. */
50
+ interface ResponseSigningMiddlewareOptions {
51
+ /**
52
+ * Public origin the client used, such as `https://bob.test`. Set it behind a
53
+ * TLS-terminating proxy. Default: the socket's scheme and the Host header.
54
+ */
55
+ origin?: string;
56
+ }
57
+ /** Connect-style middleware, usable with Express, Connect, or a plain `node:http` handler. */
58
+ type NodeMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
59
+ /**
60
+ * Signs responses to requests that carry Accept-Signature, for `node:http` servers.
61
+ * Requests without negotiation pass through unbuffered. Negotiated responses are
62
+ * buffered up to 8 MiB and signed with `signRequestedHttpResponse` before any header
63
+ * is sent. On a signing failure, an exceeded bound, or a `flushHeaders()` call
64
+ * (streaming, such as SSE) it sends an empty 500; it never sends the unsigned
65
+ * response. Register it before request verification, compression, and handlers
66
+ * so that it sees their final status, headers, and content coding.
67
+ * Informational responses written with `writeContinue` or `writeEarlyHints` are
68
+ * sent directly and are not signed.
69
+ */
70
+ declare function createResponseSigningMiddleware(profile: HttpSignaturesProfile, options?: ResponseSigningMiddlewareOptions): NodeMiddleware;
71
+
48
72
  /**
49
73
  * Creates an IdentityManager with Node.js DNS and HTTPS defaults.
50
74
  *
@@ -150,4 +174,4 @@ declare function createNodeIdentityManagerFromFile(filePath: string, overlay?: L
150
174
  /** `RegistryClient` from `DNSID_REGISTRY_URL` and `DNSID_API_KEY`; explicit options win, including transport overrides. The constructor defaults to the local registry. */
151
175
  declare function createRegistryClientFromEnvironment(env?: EnvironmentSource, options?: RegistryClientOptions): Promise<RegistryClient>;
152
176
 
153
- export { type EnvironmentSource, type KeySource, type LoadedConfig, type LoadedDnsidConfig, type LoadedRegistryConfig, type LocalKeyAlgorithm, LocalKeyProvider, type LogTrust, type ManagedRegistrationConfig, constructIdentityManager, createNodeIdentityManager, createNodeIdentityManagerFromDnsid, createNodeIdentityManagerFromEnvironment, createNodeIdentityManagerFromFile, createNodeIdentityVerifier, createRegistryClientFromEnvironment, loadCliDirectory, loadEnvironment, loadFile, mergeLoadedConfig };
177
+ export { type EnvironmentSource, type KeySource, type LoadedConfig, type LoadedDnsidConfig, type LoadedRegistryConfig, type LocalKeyAlgorithm, LocalKeyProvider, type LogTrust, type ManagedRegistrationConfig, type NodeMiddleware, type ResponseSigningMiddlewareOptions, constructIdentityManager, createNodeIdentityManager, createNodeIdentityManagerFromDnsid, createNodeIdentityManagerFromEnvironment, createNodeIdentityManagerFromFile, createNodeIdentityVerifier, createRegistryClientFromEnvironment, createResponseSigningMiddleware, loadCliDirectory, loadEnvironment, loadFile, mergeLoadedConfig };
package/dist/node.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  export { CreateIdentityManagerDependencies } from './index.js';
2
2
  import * as _dnsid_ai_protocol from '@dnsid-ai/protocol';
3
3
  import { KeyProvider, DnsIdJWK, DnsidConfig, IdentityManagerDependencies, IdentityManager, IdentityConfig } from '@dnsid-ai/protocol';
4
+ import { IncomingMessage, ServerResponse } from 'node:http';
5
+ import { HttpSignaturesProfile } from '@dnsid-ai/http-signatures';
4
6
  import { RegistryClientOptions, RegistryClient } from '@dnsid-ai/registry';
5
7
  import '@dnsid-ai/jose';
6
- import '@dnsid-ai/http-signatures';
7
8
  import '@dnsid-ai/web-bot-auth';
8
9
 
9
10
  type LocalKeyAlgorithm = 'EdDSA' | 'ES256';
@@ -45,6 +46,29 @@ declare class LocalKeyProvider implements KeyProvider {
45
46
  private mutate;
46
47
  }
47
48
 
49
+ /** Options for {@link createResponseSigningMiddleware}. */
50
+ interface ResponseSigningMiddlewareOptions {
51
+ /**
52
+ * Public origin the client used, such as `https://bob.test`. Set it behind a
53
+ * TLS-terminating proxy. Default: the socket's scheme and the Host header.
54
+ */
55
+ origin?: string;
56
+ }
57
+ /** Connect-style middleware, usable with Express, Connect, or a plain `node:http` handler. */
58
+ type NodeMiddleware = (req: IncomingMessage, res: ServerResponse, next: (err?: unknown) => void) => void;
59
+ /**
60
+ * Signs responses to requests that carry Accept-Signature, for `node:http` servers.
61
+ * Requests without negotiation pass through unbuffered. Negotiated responses are
62
+ * buffered up to 8 MiB and signed with `signRequestedHttpResponse` before any header
63
+ * is sent. On a signing failure, an exceeded bound, or a `flushHeaders()` call
64
+ * (streaming, such as SSE) it sends an empty 500; it never sends the unsigned
65
+ * response. Register it before request verification, compression, and handlers
66
+ * so that it sees their final status, headers, and content coding.
67
+ * Informational responses written with `writeContinue` or `writeEarlyHints` are
68
+ * sent directly and are not signed.
69
+ */
70
+ declare function createResponseSigningMiddleware(profile: HttpSignaturesProfile, options?: ResponseSigningMiddlewareOptions): NodeMiddleware;
71
+
48
72
  /**
49
73
  * Creates an IdentityManager with Node.js DNS and HTTPS defaults.
50
74
  *
@@ -150,4 +174,4 @@ declare function createNodeIdentityManagerFromFile(filePath: string, overlay?: L
150
174
  /** `RegistryClient` from `DNSID_REGISTRY_URL` and `DNSID_API_KEY`; explicit options win, including transport overrides. The constructor defaults to the local registry. */
151
175
  declare function createRegistryClientFromEnvironment(env?: EnvironmentSource, options?: RegistryClientOptions): Promise<RegistryClient>;
152
176
 
153
- export { type EnvironmentSource, type KeySource, type LoadedConfig, type LoadedDnsidConfig, type LoadedRegistryConfig, type LocalKeyAlgorithm, LocalKeyProvider, type LogTrust, type ManagedRegistrationConfig, constructIdentityManager, createNodeIdentityManager, createNodeIdentityManagerFromDnsid, createNodeIdentityManagerFromEnvironment, createNodeIdentityManagerFromFile, createNodeIdentityVerifier, createRegistryClientFromEnvironment, loadCliDirectory, loadEnvironment, loadFile, mergeLoadedConfig };
177
+ export { type EnvironmentSource, type KeySource, type LoadedConfig, type LoadedDnsidConfig, type LoadedRegistryConfig, type LocalKeyAlgorithm, LocalKeyProvider, type LogTrust, type ManagedRegistrationConfig, type NodeMiddleware, type ResponseSigningMiddlewareOptions, constructIdentityManager, createNodeIdentityManager, createNodeIdentityManagerFromDnsid, createNodeIdentityManagerFromEnvironment, createNodeIdentityManagerFromFile, createNodeIdentityVerifier, createRegistryClientFromEnvironment, createResponseSigningMiddleware, loadCliDirectory, loadEnvironment, loadFile, mergeLoadedConfig };
package/dist/node.js CHANGED
@@ -274,6 +274,124 @@ async function importPrivateKey(key) {
274
274
  );
275
275
  }
276
276
 
277
+ // src/node-response-signing.ts
278
+ var MAX_BUFFERED_BYTES = 8 * 1024 * 1024;
279
+ var SIGNED_FIELDS = ["content-digest", "cache-control", "signature-input", "signature"];
280
+ function createResponseSigningMiddleware(profile, options = {}) {
281
+ return (req, res, next) => {
282
+ if (req.headers["accept-signature"] === void 0) return next();
283
+ if (res.headersSent) return next(new Error("response headers already sent; register response signing first"));
284
+ const writeHead = res.writeHead.bind(res);
285
+ const end = res.end.bind(res);
286
+ const chunks = [];
287
+ let size = 0;
288
+ let overflow = false;
289
+ let ended = false;
290
+ let done = false;
291
+ let releasing = false;
292
+ const collect = (chunk, encoding) => {
293
+ if (chunk === void 0 || chunk === null || typeof chunk === "function") return true;
294
+ const bytes = typeof chunk === "string" ? Buffer.from(chunk, typeof encoding === "string" ? encoding : "utf8") : Buffer.from(chunk);
295
+ size += bytes.length;
296
+ if (size > MAX_BUFFERED_BYTES) {
297
+ overflow = true;
298
+ chunks.length = 0;
299
+ return false;
300
+ }
301
+ chunks.push(bytes);
302
+ return true;
303
+ };
304
+ const send = (body) => {
305
+ done = true;
306
+ releasing = true;
307
+ end(body);
308
+ };
309
+ const fail = () => {
310
+ if (done) return;
311
+ for (const name of res.getHeaderNames()) res.removeHeader(name);
312
+ res.statusCode = 500;
313
+ send();
314
+ };
315
+ res.writeHead = ((status, ...args) => {
316
+ if (releasing) return writeHead(status);
317
+ res.statusCode = status;
318
+ if (typeof args[0] === "string") res.statusMessage = args.shift();
319
+ setHeaders(res, args[0]);
320
+ return res;
321
+ });
322
+ res.flushHeaders = () => fail();
323
+ res.write = ((chunk, encoding, callback) => {
324
+ const cb = typeof encoding === "function" ? encoding : callback;
325
+ if (ended || done || !collect(chunk, encoding)) {
326
+ if (typeof cb === "function") process.nextTick(cb, new Error("response not writable"));
327
+ return false;
328
+ }
329
+ if (typeof cb === "function") process.nextTick(cb);
330
+ return true;
331
+ });
332
+ res.end = ((chunk, encoding, callback) => {
333
+ if (ended || done) return res;
334
+ ended = true;
335
+ const cb = [chunk, encoding, callback].find((arg) => typeof arg === "function");
336
+ if (typeof cb === "function") res.once("finish", cb);
337
+ collect(chunk, encoding);
338
+ void release();
339
+ return res;
340
+ });
341
+ async function release() {
342
+ if (done) return;
343
+ if (overflow) return fail();
344
+ try {
345
+ const noContent = req.method === "HEAD" || [204, 205, 304].includes(res.statusCode);
346
+ const body = Buffer.concat(chunks);
347
+ const signed = await profile.signRequestedHttpResponse(
348
+ toRequest(req, options.origin),
349
+ new Response(noContent ? null : body, { status: res.statusCode, headers: toHeaders(res.getHeaders()) })
350
+ );
351
+ if (done) return;
352
+ for (const name of SIGNED_FIELDS) {
353
+ const value = signed.headers.get(name);
354
+ if (value !== null) res.setHeader(name, value);
355
+ }
356
+ send(noContent ? void 0 : body);
357
+ } catch {
358
+ fail();
359
+ }
360
+ }
361
+ next();
362
+ };
363
+ }
364
+ function toRequest(req, origin) {
365
+ const path3 = req.originalUrl ?? req.url ?? "";
366
+ if (!path3.startsWith("/")) throw new Error("only origin-form request targets can be signed");
367
+ const scheme = req.socket.encrypted ? "https" : "http";
368
+ const base = origin ?? `${scheme}://${req.headers.host ?? ""}`;
369
+ return new Request(`${new URL(base).origin}${path3}`, { method: req.method, headers: toHeaders(req.headers) });
370
+ }
371
+ function toHeaders(fields) {
372
+ const headers = new Headers();
373
+ for (const [name, value] of Object.entries(fields)) {
374
+ for (const item of Array.isArray(value) ? value : [value]) {
375
+ if (item !== void 0) headers.append(name, String(item));
376
+ }
377
+ }
378
+ return headers;
379
+ }
380
+ function setHeaders(res, headers) {
381
+ if (Array.isArray(headers)) {
382
+ const grouped = /* @__PURE__ */ new Map();
383
+ for (let i = 0; i + 1 < headers.length; i += 2) {
384
+ const name = String(headers[i]).toLowerCase();
385
+ grouped.set(name, [...grouped.get(name) ?? [], String(headers[i + 1])]);
386
+ }
387
+ for (const [name, values] of grouped) res.setHeader(name, values);
388
+ } else if (typeof headers === "object" && headers !== null) {
389
+ for (const [name, value] of Object.entries(headers)) {
390
+ if (value !== void 0) res.setHeader(name, value);
391
+ }
392
+ }
393
+ }
394
+
277
395
  // src/node-identity-manager.ts
278
396
  import { IdentityManager, ArgumentError as ArgumentError2, validateDnsidConfig } from "@dnsid-ai/protocol";
279
397
  async function createNodeIdentityManager(config, deps = {}) {
@@ -699,6 +817,7 @@ export {
699
817
  createNodeIdentityManagerFromFile,
700
818
  createNodeIdentityVerifier,
701
819
  createRegistryClientFromEnvironment,
820
+ createResponseSigningMiddleware,
702
821
  loadCliDirectory,
703
822
  loadEnvironment,
704
823
  loadFile,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dnsid-ai/sdk",
3
3
  "author": "Known Systems AI, Inc.",
4
- "version": "0.32.0",
4
+ "version": "0.33.0",
5
5
  "description": "Runtime-neutral DNSid SDK entrypoint with optional Node.js convenience helpers.",
6
6
  "type": "module",
7
7
  "types": "./dist/index.d.ts",
@@ -42,16 +42,16 @@
42
42
  "typecheck": "tsc --noEmit"
43
43
  },
44
44
  "dependencies": {
45
- "@dnsid-ai/protocol": "^0.32.0",
46
- "@dnsid-ai/http-signatures": "^0.32.0",
47
- "@dnsid-ai/jose": "^0.32.0",
48
- "@dnsid-ai/log-c2sp-tlog": "^0.32.0",
49
- "@dnsid-ai/registry": "^0.32.0",
50
- "@dnsid-ai/web-bot-auth": "^0.32.0"
45
+ "@dnsid-ai/protocol": "^0.33.0",
46
+ "@dnsid-ai/http-signatures": "^0.33.0",
47
+ "@dnsid-ai/jose": "^0.33.0",
48
+ "@dnsid-ai/log-c2sp-tlog": "^0.33.0",
49
+ "@dnsid-ai/registry": "^0.33.0",
50
+ "@dnsid-ai/web-bot-auth": "^0.33.0"
51
51
  },
52
52
  "peerDependencies": {
53
- "@dnsid-ai/key-aws": "^0.32.0",
54
- "@dnsid-ai/transport": "^0.32.0"
53
+ "@dnsid-ai/key-aws": "^0.33.0",
54
+ "@dnsid-ai/transport": "^0.33.0"
55
55
  },
56
56
  "peerDependenciesMeta": {
57
57
  "@dnsid-ai/key-aws": {