@peac/middleware-core 0.10.9

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/index.js ADDED
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /**
3
+ * PEAC Middleware Core
4
+ *
5
+ * Framework-agnostic middleware primitives for PEAC receipt issuance.
6
+ *
7
+ * This package provides the core building blocks for integrating PEAC
8
+ * receipt issuance into any HTTP framework. For framework-specific
9
+ * implementations, see:
10
+ * - `@peac/middleware-express` for Express.js
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { createReceipt, validateConfig, wrapResponse } from '@peac/middleware-core';
15
+ *
16
+ * // Validate configuration at startup
17
+ * validateConfig(config);
18
+ *
19
+ * // In request handler
20
+ * const result = await createReceipt(config, requestCtx, responseCtx);
21
+ *
22
+ * // Add headers
23
+ * for (const [key, value] of Object.entries(result.headers)) {
24
+ * res.setHeader(key, value);
25
+ * }
26
+ *
27
+ * // Handle body transport
28
+ * if (result.bodyWrapper) {
29
+ * res.json(result.bodyWrapper);
30
+ * } else {
31
+ * res.json(originalBody);
32
+ * }
33
+ * ```
34
+ *
35
+ * @packageDocumentation
36
+ */
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ exports.MemoryRateLimitStore = exports.createReceiptWithClaims = exports.createReceipt = exports.buildReceiptResult = exports.buildResponseHeaders = exports.wrapResponse = exports.selectTransport = exports.applyDefaults = exports.MAX_PATH_LENGTH = exports.CONFIG_DEFAULTS = exports.ConfigError = exports.validateConfigAsync = exports.validateConfig = void 0;
39
+ // Configuration
40
+ var config_js_1 = require("./config.js");
41
+ Object.defineProperty(exports, "validateConfig", { enumerable: true, get: function () { return config_js_1.validateConfig; } });
42
+ Object.defineProperty(exports, "validateConfigAsync", { enumerable: true, get: function () { return config_js_1.validateConfigAsync; } });
43
+ Object.defineProperty(exports, "ConfigError", { enumerable: true, get: function () { return config_js_1.ConfigError; } });
44
+ Object.defineProperty(exports, "CONFIG_DEFAULTS", { enumerable: true, get: function () { return config_js_1.CONFIG_DEFAULTS; } });
45
+ Object.defineProperty(exports, "MAX_PATH_LENGTH", { enumerable: true, get: function () { return config_js_1.MAX_PATH_LENGTH; } });
46
+ Object.defineProperty(exports, "applyDefaults", { enumerable: true, get: function () { return config_js_1.applyDefaults; } });
47
+ // Transport
48
+ var transport_js_1 = require("./transport.js");
49
+ Object.defineProperty(exports, "selectTransport", { enumerable: true, get: function () { return transport_js_1.selectTransport; } });
50
+ Object.defineProperty(exports, "wrapResponse", { enumerable: true, get: function () { return transport_js_1.wrapResponse; } });
51
+ Object.defineProperty(exports, "buildResponseHeaders", { enumerable: true, get: function () { return transport_js_1.buildResponseHeaders; } });
52
+ Object.defineProperty(exports, "buildReceiptResult", { enumerable: true, get: function () { return transport_js_1.buildReceiptResult; } });
53
+ // Receipt generation
54
+ var receipt_js_1 = require("./receipt.js");
55
+ Object.defineProperty(exports, "createReceipt", { enumerable: true, get: function () { return receipt_js_1.createReceipt; } });
56
+ Object.defineProperty(exports, "createReceiptWithClaims", { enumerable: true, get: function () { return receipt_js_1.createReceiptWithClaims; } });
57
+ var rate_limit_js_1 = require("./rate-limit.js");
58
+ Object.defineProperty(exports, "MemoryRateLimitStore", { enumerable: true, get: function () { return rate_limit_js_1.MemoryRateLimitStore; } });
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;;;AAeH,gBAAgB;AAChB,yCAOqB;AANnB,2GAAA,cAAc,OAAA;AACd,gHAAA,mBAAmB,OAAA;AACnB,wGAAA,WAAW,OAAA;AACX,4GAAA,eAAe,OAAA;AACf,4GAAA,eAAe,OAAA;AACf,0GAAA,aAAa,OAAA;AAGf,YAAY;AACZ,+CAKwB;AAJtB,+GAAA,eAAe,OAAA;AACf,4GAAA,YAAY,OAAA;AACZ,oHAAA,oBAAoB,OAAA;AACpB,kHAAA,kBAAkB,OAAA;AAGpB,qBAAqB;AACrB,2CAAsE;AAA7D,2GAAA,aAAa,OAAA;AAAE,qHAAA,uBAAuB,OAAA;AAI/C,iDAAuD;AAA9C,qHAAA,oBAAoB,OAAA"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Rate-limit store interface and bounded in-memory implementation.
3
+ *
4
+ * Provides a pluggable rate-limit store abstraction. The default
5
+ * MemoryRateLimitStore uses LRU eviction to bound memory usage.
6
+ *
7
+ * For production multi-instance deployments, implement RateLimitStore
8
+ * backed by Redis or similar shared storage.
9
+ */
10
+ /**
11
+ * Pluggable rate-limit store interface.
12
+ *
13
+ * Increment returns the current count and window reset time.
14
+ * Implementations must handle window expiry and cleanup.
15
+ */
16
+ export interface RateLimitStore {
17
+ increment(key: string, windowMs: number): Promise<{
18
+ count: number;
19
+ resetAt: number;
20
+ }>;
21
+ reset(key: string): Promise<void>;
22
+ }
23
+ export interface MemoryRateLimitStoreOptions {
24
+ /** Maximum number of tracked keys before LRU eviction (default: 10000) */
25
+ maxKeys?: number;
26
+ }
27
+ /**
28
+ * Bounded in-memory rate-limit store with LRU eviction.
29
+ *
30
+ * - Expired windows are lazily cleaned on access
31
+ * - When maxKeys is exceeded, the least-recently-accessed entry is evicted
32
+ * - Suitable for single-instance deployments (state lost on restart)
33
+ */
34
+ export declare class MemoryRateLimitStore implements RateLimitStore {
35
+ private readonly store;
36
+ private readonly maxKeys;
37
+ constructor(options?: MemoryRateLimitStoreOptions);
38
+ increment(key: string, windowMs: number): Promise<{
39
+ count: number;
40
+ resetAt: number;
41
+ }>;
42
+ reset(key: string): Promise<void>;
43
+ /** Number of tracked keys */
44
+ get size(): number;
45
+ /** Remove all entries */
46
+ clear(): void;
47
+ private evictIfNeeded;
48
+ }
49
+ //# sourceMappingURL=rate-limit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtF,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AASD,MAAM,WAAW,2BAA2B;IAC1C,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,qBAAa,oBAAqB,YAAW,cAAc;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,CAAC,EAAE,2BAA2B;IAI3C,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAsBrF,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvC,6BAA6B;IAC7B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,yBAAyB;IACzB,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,aAAa;CAWtB"}
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ /**
3
+ * Rate-limit store interface and bounded in-memory implementation.
4
+ *
5
+ * Provides a pluggable rate-limit store abstraction. The default
6
+ * MemoryRateLimitStore uses LRU eviction to bound memory usage.
7
+ *
8
+ * For production multi-instance deployments, implement RateLimitStore
9
+ * backed by Redis or similar shared storage.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MemoryRateLimitStore = void 0;
13
+ /**
14
+ * Bounded in-memory rate-limit store with LRU eviction.
15
+ *
16
+ * - Expired windows are lazily cleaned on access
17
+ * - When maxKeys is exceeded, the least-recently-accessed entry is evicted
18
+ * - Suitable for single-instance deployments (state lost on restart)
19
+ */
20
+ class MemoryRateLimitStore {
21
+ store = new Map();
22
+ maxKeys;
23
+ constructor(options) {
24
+ this.maxKeys = options?.maxKeys ?? 10_000;
25
+ }
26
+ async increment(key, windowMs) {
27
+ const now = Date.now();
28
+ let entry = this.store.get(key);
29
+ if (!entry || now >= entry.resetAt) {
30
+ // Expired or new -- start fresh window
31
+ entry = { count: 0, resetAt: now + windowMs, lastAccess: now };
32
+ }
33
+ entry.count++;
34
+ entry.lastAccess = now;
35
+ // Re-set to maintain Map insertion order (most recent last)
36
+ this.store.delete(key);
37
+ this.store.set(key, entry);
38
+ // Evict oldest entries if over capacity
39
+ this.evictIfNeeded();
40
+ return { count: entry.count, resetAt: entry.resetAt };
41
+ }
42
+ async reset(key) {
43
+ this.store.delete(key);
44
+ }
45
+ /** Number of tracked keys */
46
+ get size() {
47
+ return this.store.size;
48
+ }
49
+ /** Remove all entries */
50
+ clear() {
51
+ this.store.clear();
52
+ }
53
+ evictIfNeeded() {
54
+ while (this.store.size > this.maxKeys) {
55
+ // Map iterates in insertion order -- first key is oldest
56
+ const oldestKey = this.store.keys().next().value;
57
+ if (oldestKey !== undefined) {
58
+ this.store.delete(oldestKey);
59
+ }
60
+ else {
61
+ break;
62
+ }
63
+ }
64
+ }
65
+ }
66
+ exports.MemoryRateLimitStore = MemoryRateLimitStore;
67
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAyBH;;;;;;GAMG;AACH,MAAa,oBAAoB;IACd,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAC;IACvC,OAAO,CAAS;IAEjC,YAAY,OAAqC;QAC/C,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,MAAM,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAW,EAAE,QAAgB;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAEhC,IAAI,CAAC,KAAK,IAAI,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACnC,uCAAuC;YACvC,KAAK,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC;QACjE,CAAC;QAED,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,KAAK,CAAC,UAAU,GAAG,GAAG,CAAC;QAEvB,4DAA4D;QAC5D,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAE3B,wCAAwC;QACxC,IAAI,CAAC,aAAa,EAAE,CAAC;QAErB,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAAW;QACrB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,6BAA6B;IAC7B,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,yBAAyB;IACzB,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAEO,aAAa;QACnB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YACtC,yDAAyD;YACzD,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YACjD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;gBAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YAC/B,CAAC;iBAAM,CAAC;gBACN,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAvDD,oDAuDC"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Receipt Generation
3
+ *
4
+ * Creates PEAC receipts from request/response context.
5
+ *
6
+ * This module produces **attestation receipts** - lightweight signed tokens
7
+ * that attest to API interactions. For full payment receipts with amt/cur/payment
8
+ * fields, use @peac/protocol directly.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ import type { MiddlewareConfig, RequestContext, ResponseContext, ReceiptResult, ReceiptClaimsInput } from './types.js';
13
+ /**
14
+ * Create a receipt for a request/response pair
15
+ *
16
+ * This is the main function for middleware receipt generation.
17
+ * It validates configuration, builds claims, signs the receipt,
18
+ * and determines the appropriate transport profile.
19
+ *
20
+ * By default, includes minimal interaction binding (method, path, status)
21
+ * in the `ext[MIDDLEWARE_INTERACTION_KEY]` field for evidentiary value.
22
+ *
23
+ * @param config - Middleware configuration
24
+ * @param request - Request context
25
+ * @param response - Response context
26
+ * @returns Receipt result with JWS, headers, and optional body wrapper
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const result = await createReceipt(config, requestCtx, responseCtx);
31
+ *
32
+ * // Add headers to response
33
+ * for (const [key, value] of Object.entries(result.headers)) {
34
+ * res.setHeader(key, value);
35
+ * }
36
+ *
37
+ * // If body transport, use wrapped body
38
+ * if (result.bodyWrapper) {
39
+ * res.json(result.bodyWrapper);
40
+ * }
41
+ * ```
42
+ */
43
+ export declare function createReceipt(config: MiddlewareConfig, request: RequestContext, response: ResponseContext): Promise<ReceiptResult>;
44
+ /**
45
+ * Create a receipt with explicit claims (bypasses context extraction)
46
+ *
47
+ * Use this when you have explicit claims and don't need context extraction.
48
+ * Does NOT include automatic interaction binding.
49
+ *
50
+ * @param config - Middleware configuration
51
+ * @param claims - Explicit claims to include
52
+ * @param responseBody - Optional response body for body transport
53
+ * @returns Receipt result
54
+ */
55
+ export declare function createReceiptWithClaims(config: MiddlewareConfig, claims: ReceiptClaimsInput & {
56
+ aud: string;
57
+ }, responseBody?: unknown): Promise<ReceiptResult>;
58
+ //# sourceMappingURL=receipt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipt.d.ts","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EACV,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAiJpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,gBAAgB,EACxB,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,eAAe,GACxB,OAAO,CAAC,aAAa,CAAC,CAsFxB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,kBAAkB,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EAC5C,YAAY,CAAC,EAAE,OAAO,GACrB,OAAO,CAAC,aAAa,CAAC,CAgDxB"}
@@ -0,0 +1,256 @@
1
+ "use strict";
2
+ /**
3
+ * Receipt Generation
4
+ *
5
+ * Creates PEAC receipts from request/response context.
6
+ *
7
+ * This module produces **attestation receipts** - lightweight signed tokens
8
+ * that attest to API interactions. For full payment receipts with amt/cur/payment
9
+ * fields, use @peac/protocol directly.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.createReceipt = createReceipt;
15
+ exports.createReceiptWithClaims = createReceiptWithClaims;
16
+ const uuidv7_1 = require("uuidv7");
17
+ const crypto_1 = require("@peac/crypto");
18
+ const schema_1 = require("@peac/schema");
19
+ const config_js_1 = require("./config.js");
20
+ const transport_js_1 = require("./transport.js");
21
+ /**
22
+ * Create a lowercase header lookup map for case-insensitive access
23
+ *
24
+ * HTTP headers are case-insensitive per RFC 7230, but different frameworks
25
+ * may provide them with different casing. This normalizes to lowercase.
26
+ */
27
+ function normalizeHeaders(headers) {
28
+ const normalized = {};
29
+ for (const [key, value] of Object.entries(headers)) {
30
+ normalized[key.toLowerCase()] = value;
31
+ }
32
+ return normalized;
33
+ }
34
+ /**
35
+ * Get a header value (case-insensitive)
36
+ */
37
+ function getHeader(headers, name) {
38
+ const value = headers[name.toLowerCase()];
39
+ if (value === undefined)
40
+ return undefined;
41
+ return Array.isArray(value) ? value[0] : value;
42
+ }
43
+ /**
44
+ * Normalize issuer URL (remove trailing slashes for consistency)
45
+ *
46
+ * Uses explicit loop instead of regex to avoid ReDoS with quantifiers.
47
+ */
48
+ function normalizeIssuer(issuer) {
49
+ let end = issuer.length;
50
+ while (end > 0 && issuer[end - 1] === '/') {
51
+ end--;
52
+ }
53
+ return end === issuer.length ? issuer : issuer.slice(0, end);
54
+ }
55
+ /**
56
+ * Process path for interaction binding based on mode
57
+ *
58
+ * - 'minimal': Strip query string, truncate to MAX_PATH_LENGTH
59
+ * - 'full': Keep full path with query string, truncate to MAX_PATH_LENGTH
60
+ *
61
+ * @param path - Original request path (may include query string)
62
+ * @param mode - Interaction binding mode
63
+ * @returns Processed path
64
+ */
65
+ function processPath(path, mode) {
66
+ let processedPath = path;
67
+ // Strip query string in minimal mode (privacy-safe default)
68
+ if (mode === 'minimal') {
69
+ const queryIndex = path.indexOf('?');
70
+ if (queryIndex !== -1) {
71
+ processedPath = path.substring(0, queryIndex);
72
+ }
73
+ }
74
+ // Truncate to maximum length (DoS protection)
75
+ if (processedPath.length > config_js_1.MAX_PATH_LENGTH) {
76
+ processedPath = processedPath.substring(0, config_js_1.MAX_PATH_LENGTH);
77
+ }
78
+ return processedPath;
79
+ }
80
+ /**
81
+ * Extract audience from request context
82
+ *
83
+ * Derives audience from the request host or origin header (case-insensitive).
84
+ */
85
+ function extractAudience(normalizedHeaders) {
86
+ // Try to get from Host header (case-insensitive)
87
+ const host = getHeader(normalizedHeaders, 'host');
88
+ if (host) {
89
+ // Assume HTTPS for audience
90
+ return `https://${host}`;
91
+ }
92
+ // Fallback to origin header
93
+ const origin = getHeader(normalizedHeaders, 'origin');
94
+ if (origin) {
95
+ return origin;
96
+ }
97
+ // Should not happen with proper request context
98
+ return 'https://localhost';
99
+ }
100
+ /**
101
+ * Convert JWK private key to raw bytes
102
+ */
103
+ function jwkToPrivateKeyBytes(jwk) {
104
+ return (0, crypto_1.base64urlDecode)(jwk.d);
105
+ }
106
+ /**
107
+ * Create a receipt for a request/response pair
108
+ *
109
+ * This is the main function for middleware receipt generation.
110
+ * It validates configuration, builds claims, signs the receipt,
111
+ * and determines the appropriate transport profile.
112
+ *
113
+ * By default, includes minimal interaction binding (method, path, status)
114
+ * in the `ext[MIDDLEWARE_INTERACTION_KEY]` field for evidentiary value.
115
+ *
116
+ * @param config - Middleware configuration
117
+ * @param request - Request context
118
+ * @param response - Response context
119
+ * @returns Receipt result with JWS, headers, and optional body wrapper
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * const result = await createReceipt(config, requestCtx, responseCtx);
124
+ *
125
+ * // Add headers to response
126
+ * for (const [key, value] of Object.entries(result.headers)) {
127
+ * res.setHeader(key, value);
128
+ * }
129
+ *
130
+ * // If body transport, use wrapped body
131
+ * if (result.bodyWrapper) {
132
+ * res.json(result.bodyWrapper);
133
+ * }
134
+ * ```
135
+ */
136
+ async function createReceipt(config, request, response) {
137
+ // Validate configuration
138
+ (0, config_js_1.validateConfig)(config);
139
+ // Apply defaults
140
+ const fullConfig = (0, config_js_1.applyDefaults)(config);
141
+ // Normalize headers for case-insensitive access
142
+ const normalizedHeaders = normalizeHeaders(request.headers);
143
+ // Generate receipt ID (UUIDv7 for time-ordering)
144
+ const rid = (0, uuidv7_1.uuidv7)();
145
+ // Get current timestamp
146
+ const iat = Math.floor(Date.now() / 1000);
147
+ const exp = iat + fullConfig.expiresIn;
148
+ // Normalize issuer and extract audience
149
+ const normalizedIssuer = normalizeIssuer(config.issuer);
150
+ const audience = extractAudience(normalizedHeaders);
151
+ // Build base claims
152
+ const claims = {
153
+ iss: normalizedIssuer,
154
+ aud: audience,
155
+ iat,
156
+ exp,
157
+ rid,
158
+ };
159
+ // Add interaction binding unless disabled
160
+ if (fullConfig.interactionBinding !== 'off') {
161
+ const processedPath = processPath(request.path, fullConfig.interactionBinding);
162
+ const interactionBinding = {
163
+ method: request.method.toUpperCase(),
164
+ path: processedPath,
165
+ status: response.statusCode,
166
+ };
167
+ claims.ext = {
168
+ [schema_1.MIDDLEWARE_INTERACTION_KEY]: interactionBinding,
169
+ };
170
+ }
171
+ // Apply custom claims if generator is provided
172
+ if (config.claimsGenerator) {
173
+ const customClaims = await config.claimsGenerator(request);
174
+ // Override audience if provided
175
+ if (customClaims.aud) {
176
+ claims.aud = customClaims.aud;
177
+ }
178
+ // Add subject if provided
179
+ if (customClaims.sub) {
180
+ claims.sub = customClaims.sub;
181
+ }
182
+ // Merge extensions (custom claims override defaults)
183
+ if (customClaims.ext) {
184
+ claims.ext = { ...claims.ext, ...customClaims.ext };
185
+ }
186
+ }
187
+ // Sign the receipt
188
+ const privateKeyBytes = jwkToPrivateKeyBytes(config.signingKey);
189
+ const receipt = await (0, crypto_1.sign)(claims, privateKeyBytes, config.keyId);
190
+ // Determine transport profile
191
+ const transport = (0, transport_js_1.selectTransport)(receipt, fullConfig);
192
+ // Generate pointer URL if needed
193
+ let pointerUrl;
194
+ if (transport === 'pointer' && config.pointerUrlGenerator) {
195
+ pointerUrl = await config.pointerUrlGenerator(receipt);
196
+ }
197
+ // Build complete result
198
+ return (0, transport_js_1.buildReceiptResult)({
199
+ receipt,
200
+ transport,
201
+ pointerUrl,
202
+ originalBody: response.body,
203
+ });
204
+ }
205
+ /**
206
+ * Create a receipt with explicit claims (bypasses context extraction)
207
+ *
208
+ * Use this when you have explicit claims and don't need context extraction.
209
+ * Does NOT include automatic interaction binding.
210
+ *
211
+ * @param config - Middleware configuration
212
+ * @param claims - Explicit claims to include
213
+ * @param responseBody - Optional response body for body transport
214
+ * @returns Receipt result
215
+ */
216
+ async function createReceiptWithClaims(config, claims, responseBody) {
217
+ // Validate configuration
218
+ (0, config_js_1.validateConfig)(config);
219
+ // Apply defaults
220
+ const fullConfig = (0, config_js_1.applyDefaults)(config);
221
+ // Generate receipt ID
222
+ const rid = (0, uuidv7_1.uuidv7)();
223
+ // Get current timestamp
224
+ const iat = Math.floor(Date.now() / 1000);
225
+ const exp = iat + fullConfig.expiresIn;
226
+ // Normalize issuer
227
+ const normalizedIssuer = normalizeIssuer(config.issuer);
228
+ // Build claims
229
+ const receiptClaims = {
230
+ iss: normalizedIssuer,
231
+ aud: claims.aud,
232
+ iat,
233
+ exp,
234
+ rid,
235
+ ...(claims.sub && { sub: claims.sub }),
236
+ ...(claims.ext && { ext: claims.ext }),
237
+ };
238
+ // Sign the receipt
239
+ const privateKeyBytes = jwkToPrivateKeyBytes(config.signingKey);
240
+ const receipt = await (0, crypto_1.sign)(receiptClaims, privateKeyBytes, config.keyId);
241
+ // Determine transport profile
242
+ const transport = (0, transport_js_1.selectTransport)(receipt, fullConfig);
243
+ // Generate pointer URL if needed
244
+ let pointerUrl;
245
+ if (transport === 'pointer' && config.pointerUrlGenerator) {
246
+ pointerUrl = await config.pointerUrlGenerator(receipt);
247
+ }
248
+ // Build complete result
249
+ return (0, transport_js_1.buildReceiptResult)({
250
+ receipt,
251
+ transport,
252
+ pointerUrl,
253
+ originalBody: responseBody,
254
+ });
255
+ }
256
+ //# sourceMappingURL=receipt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"receipt.js","sourceRoot":"","sources":["../src/receipt.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Transport Profile Selection and Response Wrapping
3
+ *
4
+ * Implements transport profile selection and body wrapping per TRANSPORT-PROFILES.md.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+ import type { MiddlewareConfig } from './types.js';
9
+ /**
10
+ * Calculate receipt size and determine appropriate transport
11
+ *
12
+ * Falls back from header to body if receipt exceeds maxHeaderSize.
13
+ * Pointer transport is only used if explicitly configured (never auto-selected).
14
+ *
15
+ * @param receipt - JWS compact serialization
16
+ * @param config - Transport configuration (optional fields)
17
+ * @returns Selected transport profile
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * const transport = selectTransport(receipt, { maxHeaderSize: 4096 });
22
+ * // Returns 'header' if receipt fits, 'body' otherwise
23
+ *
24
+ * // Also accepts empty config for defaults
25
+ * const transport = selectTransport(receipt, {});
26
+ * ```
27
+ */
28
+ export declare function selectTransport(receipt: string, config?: Partial<Pick<MiddlewareConfig, 'transport' | 'maxHeaderSize'>>): 'header' | 'body' | 'pointer';
29
+ /**
30
+ * Wrap a response body with a PEAC receipt (for body transport profile)
31
+ *
32
+ * Creates a wrapper object containing the original data and the receipt.
33
+ * Per TRANSPORT-PROFILES.md, the body format is:
34
+ * `{ "data": <original>, "peac_receipt": "<jws>" }`
35
+ *
36
+ * @param data - Original response body
37
+ * @param receipt - JWS compact serialization
38
+ * @returns Wrapped response body
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const original = { items: [1, 2, 3] };
43
+ * const wrapped = wrapResponse(original, receipt);
44
+ * // { data: { items: [1, 2, 3] }, peac_receipt: "eyJ..." }
45
+ * res.json(wrapped);
46
+ * ```
47
+ */
48
+ export declare function wrapResponse<T>(data: T, receipt: string): {
49
+ data: T;
50
+ peac_receipt: string;
51
+ };
52
+ /**
53
+ * Build response headers for a receipt based on transport profile
54
+ *
55
+ * For header profile: adds PEAC-Receipt header
56
+ * For pointer profile: adds PEAC-Receipt-Pointer header
57
+ * For body profile: returns empty headers (receipt is in body)
58
+ *
59
+ * @param receipt - JWS compact serialization
60
+ * @param transport - Transport profile to use
61
+ * @param pointerUrl - URL for pointer profile (required if transport is 'pointer')
62
+ * @returns Headers to add to response
63
+ */
64
+ export declare function buildResponseHeaders(receipt: string, transport: 'header' | 'body' | 'pointer', pointerUrl?: string): Promise<Record<string, string>>;
65
+ /**
66
+ * Input for building complete receipt result
67
+ */
68
+ export interface BuildReceiptResultInput {
69
+ receipt: string;
70
+ transport: 'header' | 'body' | 'pointer';
71
+ pointerUrl?: string;
72
+ originalBody?: unknown;
73
+ }
74
+ /**
75
+ * Build complete receipt result with headers and optional body wrapper
76
+ *
77
+ * @param input - Receipt and transport information
78
+ * @returns Complete receipt result ready for response
79
+ */
80
+ export declare function buildReceiptResult(input: BuildReceiptResultInput): Promise<{
81
+ receipt: string;
82
+ transport: 'header' | 'body' | 'pointer';
83
+ headers: Record<string, string>;
84
+ bodyWrapper?: {
85
+ data: unknown;
86
+ peac_receipt: string;
87
+ };
88
+ }>;
89
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA2CnD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,OAAO,CAAC,IAAI,CAAC,gBAAgB,EAAE,WAAW,GAAG,eAAe,CAAC,CAAM,GAC1E,QAAQ,GAAG,MAAM,GAAG,SAAS,CAsB/B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAK3F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,EACxC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CA4BjC;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;IACzC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;IACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;CACvD,CAAC,CAoBD"}