@peac/middleware-express 0.10.9 → 0.10.10

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/LICENSE CHANGED
@@ -175,7 +175,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
175
175
 
176
176
  END OF TERMS AND CONDITIONS
177
177
 
178
- Copyright 2025 PEAC Protocol Contributors
178
+ Copyright 2025-2026 PEAC Protocol Contributors
179
179
 
180
180
  Licensed under the Apache License, Version 2.0 (the "License");
181
181
  you may not use this file except in compliance with the License.
package/dist/index.cjs ADDED
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ var middlewareCore = require('@peac/middleware-core');
4
+
5
+ // src/middleware.ts
6
+ var PEAC_CONTEXT_KEY = Symbol("peac-context");
7
+ function hasPeacContext(req) {
8
+ return PEAC_CONTEXT_KEY in req;
9
+ }
10
+ function getReceiptFromResponse(res) {
11
+ const receipt = res.getHeader("PEAC-Receipt");
12
+ return typeof receipt === "string" ? receipt : void 0;
13
+ }
14
+ function buildRequestContext(req) {
15
+ return {
16
+ method: req.method,
17
+ path: req.path,
18
+ headers: req.headers,
19
+ body: req.body,
20
+ timestamp: Date.now()
21
+ };
22
+ }
23
+ function peacMiddleware(config) {
24
+ middlewareCore.validateConfig(config);
25
+ return async function peacReceiptMiddleware(req, res, next) {
26
+ if (config.skip?.(req)) {
27
+ next();
28
+ return;
29
+ }
30
+ const requestContext = buildRequestContext(req);
31
+ req[PEAC_CONTEXT_KEY] = requestContext;
32
+ const enhancedConfig = {
33
+ ...config,
34
+ claimsGenerator: async (ctx) => {
35
+ const baseClaims = config.claimsGenerator ? await config.claimsGenerator(ctx) : {};
36
+ return {
37
+ ...baseClaims,
38
+ // Override audience if custom extractor provided
39
+ ...config.audienceExtractor && { aud: config.audienceExtractor(req) },
40
+ // Add subject if extractor provided
41
+ ...config.subjectExtractor && { sub: config.subjectExtractor(req) }
42
+ };
43
+ }
44
+ };
45
+ const originalJson = res.json.bind(res);
46
+ res.json = function jsonWithReceipt(body) {
47
+ middlewareCore.createReceipt(enhancedConfig, requestContext, {
48
+ statusCode: res.statusCode,
49
+ headers: res.getHeaders(),
50
+ body
51
+ }).then((result) => {
52
+ for (const [key, value] of Object.entries(result.headers)) {
53
+ res.setHeader(key, value);
54
+ }
55
+ if (result.bodyWrapper) {
56
+ return originalJson(result.bodyWrapper);
57
+ }
58
+ return originalJson(body);
59
+ }).catch((error) => {
60
+ if (config.onError) {
61
+ config.onError(error, req, res);
62
+ } else {
63
+ console.error("[PEAC] Receipt generation failed:", error.message);
64
+ }
65
+ return originalJson(body);
66
+ });
67
+ return res;
68
+ };
69
+ next();
70
+ };
71
+ }
72
+ function peacMiddlewareSync(config) {
73
+ middlewareCore.validateConfig(config);
74
+ return async function peacReceiptMiddlewareSync(req, res, next) {
75
+ if (config.skip?.(req)) {
76
+ next();
77
+ return;
78
+ }
79
+ const requestContext = buildRequestContext(req);
80
+ req[PEAC_CONTEXT_KEY] = requestContext;
81
+ const enhancedConfig = {
82
+ ...config,
83
+ claimsGenerator: async (ctx) => {
84
+ const baseClaims = config.claimsGenerator ? await config.claimsGenerator(ctx) : {};
85
+ return {
86
+ ...baseClaims,
87
+ ...config.audienceExtractor && { aud: config.audienceExtractor(req) },
88
+ ...config.subjectExtractor && { sub: config.subjectExtractor(req) }
89
+ };
90
+ }
91
+ };
92
+ const originalJson = res.json.bind(res);
93
+ res.json = function jsonWithReceiptSync(body) {
94
+ const syncWrapper = async () => {
95
+ try {
96
+ const result = await middlewareCore.createReceipt(enhancedConfig, requestContext, {
97
+ statusCode: res.statusCode,
98
+ headers: res.getHeaders(),
99
+ body
100
+ });
101
+ for (const [key, value] of Object.entries(result.headers)) {
102
+ res.setHeader(key, value);
103
+ }
104
+ if (result.bodyWrapper) {
105
+ originalJson(result.bodyWrapper);
106
+ } else {
107
+ originalJson(body);
108
+ }
109
+ } catch (error) {
110
+ if (config.onError) {
111
+ config.onError(error, req, res);
112
+ } else {
113
+ console.error("[PEAC] Receipt generation failed:", error.message);
114
+ }
115
+ originalJson(body);
116
+ }
117
+ };
118
+ syncWrapper();
119
+ return res;
120
+ };
121
+ next();
122
+ };
123
+ }
124
+
125
+ exports.getReceiptFromResponse = getReceiptFromResponse;
126
+ exports.hasPeacContext = hasPeacContext;
127
+ exports.peacMiddleware = peacMiddleware;
128
+ exports.peacMiddlewareSync = peacMiddlewareSync;
129
+ //# sourceMappingURL=index.cjs.map
130
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.ts"],"names":["validateConfig","createReceipt"],"mappings":";;;;;AAoCA,IAAM,gBAAA,GAAmB,OAAO,cAAc,CAAA;AAYvC,SAAS,eAAe,GAAA,EAA6C;AAC1E,EAAA,OAAO,gBAAA,IAAoB,GAAA;AAC7B;AAQO,SAAS,uBAAuB,GAAA,EAAmC;AACxE,EAAA,MAAM,OAAA,GAAU,GAAA,CAAI,SAAA,CAAU,cAAc,CAAA;AAC5C,EAAA,OAAO,OAAO,OAAA,KAAY,QAAA,GAAW,OAAA,GAAU,MAAA;AACjD;AAKA,SAAS,oBAAoB,GAAA,EAA8B;AACzD,EAAA,OAAO;AAAA,IACL,QAAQ,GAAA,CAAI,MAAA;AAAA,IACZ,MAAM,GAAA,CAAI,IAAA;AAAA,IACV,SAAS,GAAA,CAAI,OAAA;AAAA,IACb,MAAM,GAAA,CAAI,IAAA;AAAA,IACV,SAAA,EAAW,KAAK,GAAA;AAAI,GACtB;AACF;AAiCO,SAAS,eAAe,MAAA,EAAiD;AAE9E,EAAAA,6BAAA,CAAe,MAAM,CAAA;AAErB,EAAA,OAAO,eAAe,qBAAA,CACpB,GAAA,EACA,GAAA,EACA,IAAA,EACe;AAEf,IAAA,IAAI,MAAA,CAAO,IAAA,GAAO,GAAG,CAAA,EAAG;AACtB,MAAA,IAAA,EAAK;AACL,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,cAAA,GAAiB,oBAAoB,GAAG,CAAA;AAC9C,IAAC,GAAA,CAA+B,gBAAgB,CAAA,GAAI,cAAA;AAGpD,IAAA,MAAM,cAAA,GAAmC;AAAA,MACvC,GAAG,MAAA;AAAA,MACH,eAAA,EAAiB,OAAO,GAAA,KAAQ;AAC9B,QAAA,MAAM,UAAA,GAAa,OAAO,eAAA,GAAkB,MAAM,OAAO,eAAA,CAAgB,GAAG,IAAI,EAAC;AAEjF,QAAA,OAAO;AAAA,UACL,GAAG,UAAA;AAAA;AAAA,UAEH,GAAI,OAAO,iBAAA,IAAqB,EAAE,KAAK,MAAA,CAAO,iBAAA,CAAkB,GAAG,CAAA,EAAE;AAAA;AAAA,UAErE,GAAI,OAAO,gBAAA,IAAoB,EAAE,KAAK,MAAA,CAAO,gBAAA,CAAiB,GAAG,CAAA;AAAE,SACrE;AAAA,MACF;AAAA,KACF;AAGA,IAAA,MAAM,YAAA,GAAe,GAAA,CAAI,IAAA,CAAK,IAAA,CAAK,GAAG,CAAA;AAGtC,IAAA,GAAA,CAAI,IAAA,GAAO,SAAS,eAAA,CAAgB,IAAA,EAAyB;AAE3D,MAAAC,4BAAA,CAAc,gBAAgB,cAAA,EAAgB;AAAA,QAC5C,YAAY,GAAA,CAAI,UAAA;AAAA,QAChB,OAAA,EAAS,IAAI,UAAA,EAAW;AAAA,QACxB;AAAA,OACD,CAAA,CACE,IAAA,CAAK,CAAC,MAAA,KAAW;AAEhB,QAAA,KAAA,MAAW,CAAC,KAAK,KAAK,CAAA,IAAK,OAAO,OAAA,CAAQ,MAAA,CAAO,OAAO,CAAA,EAAG;AACzD,UAAA,GAAA,CAAI,SAAA,CAAU,KAAK,KAAK,CAAA;AAAA,QAC1B;AAGA,QAAA,IAAI,OAAO,WAAA,EAAa;AACtB,UAAA,OAAO,YAAA,CAAa,OAAO,WAAW,CAAA;AAAA,QACxC;AAEA,QAAA,OAAO,aAAa,IAAI,CAAA;AAAA,MAC1B,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,KAAiB;AAEvB,QAAA,IAAI,OAAO,OAAA,EAAS;AAClB,UAAA,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,GAAA,EAAK,GAAG,CAAA;AAAA,QAChC,CAAA,MAAO;AAEL,UAAA,OAAA,CAAQ,KAAA,CAAM,mCAAA,EAAqC,KAAA,CAAM,OAAO,CAAA;AAAA,QAClE;AAGA,QAAA,OAAO,aAAa,IAAI,CAAA;AAAA,MAC1B,CAAC,CAAA;AAIH,MAAA,OAAO,GAAA;AAAA,IACT,CAAA;AAEA,IAAA,IAAA,EAAK;AAAA,EACP,CAAA;AACF;AAYO,SAAS,mBAAmB,MAAA,EAAiD;AAElF,EAAAD,6BAAA,CAAe,MAAM,CAAA;AAErB,EAAA,OAAO,eAAe,yBAAA,CACpB,GAAA,EACA,GAAA,EACA,IAAA,EACe;AAEf,IAAA,IAAI,MAAA,CAAO,IAAA,GAAO,GAAG,CAAA,EAAG;AACtB,MAAA,IAAA,EAAK;AACL,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,cAAA,GAAiB,oBAAoB,GAAG,CAAA;AAC9C,IAAC,GAAA,CAA+B,gBAAgB,CAAA,GAAI,cAAA;AAGpD,IAAA,MAAM,cAAA,GAAmC;AAAA,MACvC,GAAG,MAAA;AAAA,MACH,eAAA,EAAiB,OAAO,GAAA,KAAQ;AAC9B,QAAA,MAAM,UAAA,GAAa,OAAO,eAAA,GAAkB,MAAM,OAAO,eAAA,CAAgB,GAAG,IAAI,EAAC;AAEjF,QAAA,OAAO;AAAA,UACL,GAAG,UAAA;AAAA,UACH,GAAI,OAAO,iBAAA,IAAqB,EAAE,KAAK,MAAA,CAAO,iBAAA,CAAkB,GAAG,CAAA,EAAE;AAAA,UACrE,GAAI,OAAO,gBAAA,IAAoB,EAAE,KAAK,MAAA,CAAO,gBAAA,CAAiB,GAAG,CAAA;AAAE,SACrE;AAAA,MACF;AAAA,KACF;AAGA,IAAA,MAAM,YAAA,GAAe,GAAA,CAAI,IAAA,CAAK,IAAA,CAAK,GAAG,CAAA;AAGtC,IAAA,GAAA,CAAI,IAAA,GAAO,SAAS,mBAAA,CAAoB,IAAA,EAAyB;AAG/D,MAAA,MAAM,cAAc,YAA2B;AAC7C,QAAA,IAAI;AACF,UAAA,MAAM,MAAA,GAAS,MAAMC,4BAAA,CAAc,cAAA,EAAgB,cAAA,EAAgB;AAAA,YACjE,YAAY,GAAA,CAAI,UAAA;AAAA,YAChB,OAAA,EAAS,IAAI,UAAA,EAAW;AAAA,YACxB;AAAA,WACD,CAAA;AAGD,UAAA,KAAA,MAAW,CAAC,KAAK,KAAK,CAAA,IAAK,OAAO,OAAA,CAAQ,MAAA,CAAO,OAAO,CAAA,EAAG;AACzD,YAAA,GAAA,CAAI,SAAA,CAAU,KAAK,KAAK,CAAA;AAAA,UAC1B;AAGA,UAAA,IAAI,OAAO,WAAA,EAAa;AACtB,YAAA,YAAA,CAAa,OAAO,WAAW,CAAA;AAAA,UACjC,CAAA,MAAO;AACL,YAAA,YAAA,CAAa,IAAI,CAAA;AAAA,UACnB;AAAA,QACF,SAAS,KAAA,EAAO;AACd,UAAA,IAAI,OAAO,OAAA,EAAS;AAClB,YAAA,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAgB,GAAA,EAAK,GAAG,CAAA;AAAA,UACzC,CAAA,MAAO;AACL,YAAA,OAAA,CAAQ,KAAA,CAAM,mCAAA,EAAsC,KAAA,CAAgB,OAAO,CAAA;AAAA,UAC7E;AACA,UAAA,YAAA,CAAa,IAAI,CAAA;AAAA,QACnB;AAAA,MACF,CAAA;AAGA,MAAA,WAAA,EAAY;AACZ,MAAA,OAAO,GAAA;AAAA,IACT,CAAA;AAEA,IAAA,IAAA,EAAK;AAAA,EACP,CAAA;AACF","file":"index.cjs","sourcesContent":["/**\n * Express.js Middleware for PEAC Receipt Issuance\n *\n * Intercepts responses and adds PEAC receipts automatically.\n *\n * @packageDocumentation\n */\n\nimport type { Request, Response, NextFunction, RequestHandler } from 'express';\nimport {\n createReceipt,\n validateConfig,\n type MiddlewareConfig,\n type RequestContext,\n} from '@peac/middleware-core';\n\n/**\n * Express-specific middleware configuration\n */\nexport interface ExpressMiddlewareConfig extends MiddlewareConfig {\n /** Skip receipt generation for certain routes */\n skip?: (req: Request) => boolean;\n\n /** Custom audience extraction from request */\n audienceExtractor?: (req: Request) => string;\n\n /** Custom subject extraction from request */\n subjectExtractor?: (req: Request) => string | undefined;\n\n /** Error handler for receipt generation failures */\n onError?: (error: Error, req: Request, res: Response) => void;\n}\n\n/**\n * Symbol to store PEAC context on request\n */\nconst PEAC_CONTEXT_KEY = Symbol('peac-context');\n\n/**\n * Request with PEAC context attached\n */\nexport interface RequestWithPeacContext extends Request {\n [PEAC_CONTEXT_KEY]?: RequestContext;\n}\n\n/**\n * Check if a request has PEAC context\n */\nexport function hasPeacContext(req: Request): req is RequestWithPeacContext {\n return PEAC_CONTEXT_KEY in req;\n}\n\n/**\n * Get the PEAC receipt from a response (for testing/debugging)\n *\n * @param res - Express response object\n * @returns Receipt JWS if present, undefined otherwise\n */\nexport function getReceiptFromResponse(res: Response): string | undefined {\n const receipt = res.getHeader('PEAC-Receipt');\n return typeof receipt === 'string' ? receipt : undefined;\n}\n\n/**\n * Convert Express request to RequestContext\n */\nfunction buildRequestContext(req: Request): RequestContext {\n return {\n method: req.method,\n path: req.path,\n headers: req.headers as Record<string, string | string[] | undefined>,\n body: req.body,\n timestamp: Date.now(),\n };\n}\n\n/**\n * Express middleware that adds PEAC receipts to responses\n *\n * The middleware intercepts `res.json()` and `res.send()` to inject\n * PEAC receipts into responses. For header transport (default), the\n * receipt is added as a `PEAC-Receipt` header. For body transport,\n * the response is wrapped in a `{ data, peac_receipt }` structure.\n *\n * @param config - Middleware configuration\n * @returns Express request handler\n *\n * @example\n * ```typescript\n * import express from 'express';\n * import { peacMiddleware } from '@peac/middleware-express';\n *\n * const app = express();\n *\n * // Add PEAC middleware\n * app.use(peacMiddleware({\n * issuer: 'https://api.example.com',\n * signingKey: privateKey,\n * keyId: 'prod-2026-02',\n * }));\n *\n * app.get('/api/data', (req, res) => {\n * res.json({ items: [1, 2, 3] });\n * // PEAC-Receipt header automatically added\n * });\n * ```\n */\nexport function peacMiddleware(config: ExpressMiddlewareConfig): RequestHandler {\n // Validate configuration at initialization\n validateConfig(config);\n\n return async function peacReceiptMiddleware(\n req: Request,\n res: Response,\n next: NextFunction\n ): Promise<void> {\n // Check if we should skip this request\n if (config.skip?.(req)) {\n next();\n return;\n }\n\n // Build and store request context\n const requestContext = buildRequestContext(req);\n (req as RequestWithPeacContext)[PEAC_CONTEXT_KEY] = requestContext;\n\n // Enhance config with Express-specific extractors\n const enhancedConfig: MiddlewareConfig = {\n ...config,\n claimsGenerator: async (ctx) => {\n const baseClaims = config.claimsGenerator ? await config.claimsGenerator(ctx) : {};\n\n return {\n ...baseClaims,\n // Override audience if custom extractor provided\n ...(config.audienceExtractor && { aud: config.audienceExtractor(req) }),\n // Add subject if extractor provided\n ...(config.subjectExtractor && { sub: config.subjectExtractor(req) }),\n };\n },\n };\n\n // Store original json method\n const originalJson = res.json.bind(res);\n\n // Override res.json to inject receipt\n res.json = function jsonWithReceipt(body: unknown): Response {\n // Generate receipt asynchronously\n createReceipt(enhancedConfig, requestContext, {\n statusCode: res.statusCode,\n headers: res.getHeaders() as Record<string, string | string[] | undefined>,\n body,\n })\n .then((result) => {\n // Add headers from receipt result\n for (const [key, value] of Object.entries(result.headers)) {\n res.setHeader(key, value);\n }\n\n // If body transport, use wrapped body\n if (result.bodyWrapper) {\n return originalJson(result.bodyWrapper);\n }\n\n return originalJson(body);\n })\n .catch((error: Error) => {\n // Handle errors without breaking the response\n if (config.onError) {\n config.onError(error, req, res);\n } else {\n // Log but don't fail the response\n console.error('[PEAC] Receipt generation failed:', error.message);\n }\n\n // Send original body on error\n return originalJson(body);\n });\n\n // Return response for chaining (synchronous return)\n // The actual response will be sent by the promise chain\n return res;\n };\n\n next();\n };\n}\n\n/**\n * Create middleware with synchronous receipt generation (blocking)\n *\n * This variant waits for receipt generation before sending the response.\n * Use this when you need to guarantee the receipt is in the response,\n * but be aware it adds latency.\n *\n * @param config - Middleware configuration\n * @returns Express request handler\n */\nexport function peacMiddlewareSync(config: ExpressMiddlewareConfig): RequestHandler {\n // Validate configuration at initialization\n validateConfig(config);\n\n return async function peacReceiptMiddlewareSync(\n req: Request,\n res: Response,\n next: NextFunction\n ): Promise<void> {\n // Check if we should skip this request\n if (config.skip?.(req)) {\n next();\n return;\n }\n\n // Build and store request context\n const requestContext = buildRequestContext(req);\n (req as RequestWithPeacContext)[PEAC_CONTEXT_KEY] = requestContext;\n\n // Enhance config with Express-specific extractors\n const enhancedConfig: MiddlewareConfig = {\n ...config,\n claimsGenerator: async (ctx) => {\n const baseClaims = config.claimsGenerator ? await config.claimsGenerator(ctx) : {};\n\n return {\n ...baseClaims,\n ...(config.audienceExtractor && { aud: config.audienceExtractor(req) }),\n ...(config.subjectExtractor && { sub: config.subjectExtractor(req) }),\n };\n },\n };\n\n // Store original json method\n const originalJson = res.json.bind(res);\n\n // Override res.json to inject receipt synchronously\n res.json = function jsonWithReceiptSync(body: unknown): Response {\n // We can't make this truly sync due to crypto operations,\n // but we can make it blocking by using a sync wrapper\n const syncWrapper = async (): Promise<void> => {\n try {\n const result = await createReceipt(enhancedConfig, requestContext, {\n statusCode: res.statusCode,\n headers: res.getHeaders() as Record<string, string | string[] | undefined>,\n body,\n });\n\n // Add headers\n for (const [key, value] of Object.entries(result.headers)) {\n res.setHeader(key, value);\n }\n\n // Send response\n if (result.bodyWrapper) {\n originalJson(result.bodyWrapper);\n } else {\n originalJson(body);\n }\n } catch (error) {\n if (config.onError) {\n config.onError(error as Error, req, res);\n } else {\n console.error('[PEAC] Receipt generation failed:', (error as Error).message);\n }\n originalJson(body);\n }\n };\n\n // Execute synchronously by blocking\n syncWrapper();\n return res;\n };\n\n next();\n };\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,125 @@
1
+ import { validateConfig, createReceipt } from '@peac/middleware-core';
2
+
3
+ // src/middleware.ts
4
+ var PEAC_CONTEXT_KEY = Symbol("peac-context");
5
+ function hasPeacContext(req) {
6
+ return PEAC_CONTEXT_KEY in req;
7
+ }
8
+ function getReceiptFromResponse(res) {
9
+ const receipt = res.getHeader("PEAC-Receipt");
10
+ return typeof receipt === "string" ? receipt : void 0;
11
+ }
12
+ function buildRequestContext(req) {
13
+ return {
14
+ method: req.method,
15
+ path: req.path,
16
+ headers: req.headers,
17
+ body: req.body,
18
+ timestamp: Date.now()
19
+ };
20
+ }
21
+ function peacMiddleware(config) {
22
+ validateConfig(config);
23
+ return async function peacReceiptMiddleware(req, res, next) {
24
+ if (config.skip?.(req)) {
25
+ next();
26
+ return;
27
+ }
28
+ const requestContext = buildRequestContext(req);
29
+ req[PEAC_CONTEXT_KEY] = requestContext;
30
+ const enhancedConfig = {
31
+ ...config,
32
+ claimsGenerator: async (ctx) => {
33
+ const baseClaims = config.claimsGenerator ? await config.claimsGenerator(ctx) : {};
34
+ return {
35
+ ...baseClaims,
36
+ // Override audience if custom extractor provided
37
+ ...config.audienceExtractor && { aud: config.audienceExtractor(req) },
38
+ // Add subject if extractor provided
39
+ ...config.subjectExtractor && { sub: config.subjectExtractor(req) }
40
+ };
41
+ }
42
+ };
43
+ const originalJson = res.json.bind(res);
44
+ res.json = function jsonWithReceipt(body) {
45
+ createReceipt(enhancedConfig, requestContext, {
46
+ statusCode: res.statusCode,
47
+ headers: res.getHeaders(),
48
+ body
49
+ }).then((result) => {
50
+ for (const [key, value] of Object.entries(result.headers)) {
51
+ res.setHeader(key, value);
52
+ }
53
+ if (result.bodyWrapper) {
54
+ return originalJson(result.bodyWrapper);
55
+ }
56
+ return originalJson(body);
57
+ }).catch((error) => {
58
+ if (config.onError) {
59
+ config.onError(error, req, res);
60
+ } else {
61
+ console.error("[PEAC] Receipt generation failed:", error.message);
62
+ }
63
+ return originalJson(body);
64
+ });
65
+ return res;
66
+ };
67
+ next();
68
+ };
69
+ }
70
+ function peacMiddlewareSync(config) {
71
+ validateConfig(config);
72
+ return async function peacReceiptMiddlewareSync(req, res, next) {
73
+ if (config.skip?.(req)) {
74
+ next();
75
+ return;
76
+ }
77
+ const requestContext = buildRequestContext(req);
78
+ req[PEAC_CONTEXT_KEY] = requestContext;
79
+ const enhancedConfig = {
80
+ ...config,
81
+ claimsGenerator: async (ctx) => {
82
+ const baseClaims = config.claimsGenerator ? await config.claimsGenerator(ctx) : {};
83
+ return {
84
+ ...baseClaims,
85
+ ...config.audienceExtractor && { aud: config.audienceExtractor(req) },
86
+ ...config.subjectExtractor && { sub: config.subjectExtractor(req) }
87
+ };
88
+ }
89
+ };
90
+ const originalJson = res.json.bind(res);
91
+ res.json = function jsonWithReceiptSync(body) {
92
+ const syncWrapper = async () => {
93
+ try {
94
+ const result = await createReceipt(enhancedConfig, requestContext, {
95
+ statusCode: res.statusCode,
96
+ headers: res.getHeaders(),
97
+ body
98
+ });
99
+ for (const [key, value] of Object.entries(result.headers)) {
100
+ res.setHeader(key, value);
101
+ }
102
+ if (result.bodyWrapper) {
103
+ originalJson(result.bodyWrapper);
104
+ } else {
105
+ originalJson(body);
106
+ }
107
+ } catch (error) {
108
+ if (config.onError) {
109
+ config.onError(error, req, res);
110
+ } else {
111
+ console.error("[PEAC] Receipt generation failed:", error.message);
112
+ }
113
+ originalJson(body);
114
+ }
115
+ };
116
+ syncWrapper();
117
+ return res;
118
+ };
119
+ next();
120
+ };
121
+ }
122
+
123
+ export { getReceiptFromResponse, hasPeacContext, peacMiddleware, peacMiddlewareSync };
124
+ //# sourceMappingURL=index.mjs.map
125
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * Express.js Middleware for PEAC Receipt Issuance\n *\n * Intercepts responses and adds PEAC receipts automatically.\n *\n * @packageDocumentation\n */\n\nimport type { Request, Response, NextFunction, RequestHandler } from 'express';\nimport {\n createReceipt,\n validateConfig,\n type MiddlewareConfig,\n type RequestContext,\n} from '@peac/middleware-core';\n\n/**\n * Express-specific middleware configuration\n */\nexport interface ExpressMiddlewareConfig extends MiddlewareConfig {\n /** Skip receipt generation for certain routes */\n skip?: (req: Request) => boolean;\n\n /** Custom audience extraction from request */\n audienceExtractor?: (req: Request) => string;\n\n /** Custom subject extraction from request */\n subjectExtractor?: (req: Request) => string | undefined;\n\n /** Error handler for receipt generation failures */\n onError?: (error: Error, req: Request, res: Response) => void;\n}\n\n/**\n * Symbol to store PEAC context on request\n */\nconst PEAC_CONTEXT_KEY = Symbol('peac-context');\n\n/**\n * Request with PEAC context attached\n */\nexport interface RequestWithPeacContext extends Request {\n [PEAC_CONTEXT_KEY]?: RequestContext;\n}\n\n/**\n * Check if a request has PEAC context\n */\nexport function hasPeacContext(req: Request): req is RequestWithPeacContext {\n return PEAC_CONTEXT_KEY in req;\n}\n\n/**\n * Get the PEAC receipt from a response (for testing/debugging)\n *\n * @param res - Express response object\n * @returns Receipt JWS if present, undefined otherwise\n */\nexport function getReceiptFromResponse(res: Response): string | undefined {\n const receipt = res.getHeader('PEAC-Receipt');\n return typeof receipt === 'string' ? receipt : undefined;\n}\n\n/**\n * Convert Express request to RequestContext\n */\nfunction buildRequestContext(req: Request): RequestContext {\n return {\n method: req.method,\n path: req.path,\n headers: req.headers as Record<string, string | string[] | undefined>,\n body: req.body,\n timestamp: Date.now(),\n };\n}\n\n/**\n * Express middleware that adds PEAC receipts to responses\n *\n * The middleware intercepts `res.json()` and `res.send()` to inject\n * PEAC receipts into responses. For header transport (default), the\n * receipt is added as a `PEAC-Receipt` header. For body transport,\n * the response is wrapped in a `{ data, peac_receipt }` structure.\n *\n * @param config - Middleware configuration\n * @returns Express request handler\n *\n * @example\n * ```typescript\n * import express from 'express';\n * import { peacMiddleware } from '@peac/middleware-express';\n *\n * const app = express();\n *\n * // Add PEAC middleware\n * app.use(peacMiddleware({\n * issuer: 'https://api.example.com',\n * signingKey: privateKey,\n * keyId: 'prod-2026-02',\n * }));\n *\n * app.get('/api/data', (req, res) => {\n * res.json({ items: [1, 2, 3] });\n * // PEAC-Receipt header automatically added\n * });\n * ```\n */\nexport function peacMiddleware(config: ExpressMiddlewareConfig): RequestHandler {\n // Validate configuration at initialization\n validateConfig(config);\n\n return async function peacReceiptMiddleware(\n req: Request,\n res: Response,\n next: NextFunction\n ): Promise<void> {\n // Check if we should skip this request\n if (config.skip?.(req)) {\n next();\n return;\n }\n\n // Build and store request context\n const requestContext = buildRequestContext(req);\n (req as RequestWithPeacContext)[PEAC_CONTEXT_KEY] = requestContext;\n\n // Enhance config with Express-specific extractors\n const enhancedConfig: MiddlewareConfig = {\n ...config,\n claimsGenerator: async (ctx) => {\n const baseClaims = config.claimsGenerator ? await config.claimsGenerator(ctx) : {};\n\n return {\n ...baseClaims,\n // Override audience if custom extractor provided\n ...(config.audienceExtractor && { aud: config.audienceExtractor(req) }),\n // Add subject if extractor provided\n ...(config.subjectExtractor && { sub: config.subjectExtractor(req) }),\n };\n },\n };\n\n // Store original json method\n const originalJson = res.json.bind(res);\n\n // Override res.json to inject receipt\n res.json = function jsonWithReceipt(body: unknown): Response {\n // Generate receipt asynchronously\n createReceipt(enhancedConfig, requestContext, {\n statusCode: res.statusCode,\n headers: res.getHeaders() as Record<string, string | string[] | undefined>,\n body,\n })\n .then((result) => {\n // Add headers from receipt result\n for (const [key, value] of Object.entries(result.headers)) {\n res.setHeader(key, value);\n }\n\n // If body transport, use wrapped body\n if (result.bodyWrapper) {\n return originalJson(result.bodyWrapper);\n }\n\n return originalJson(body);\n })\n .catch((error: Error) => {\n // Handle errors without breaking the response\n if (config.onError) {\n config.onError(error, req, res);\n } else {\n // Log but don't fail the response\n console.error('[PEAC] Receipt generation failed:', error.message);\n }\n\n // Send original body on error\n return originalJson(body);\n });\n\n // Return response for chaining (synchronous return)\n // The actual response will be sent by the promise chain\n return res;\n };\n\n next();\n };\n}\n\n/**\n * Create middleware with synchronous receipt generation (blocking)\n *\n * This variant waits for receipt generation before sending the response.\n * Use this when you need to guarantee the receipt is in the response,\n * but be aware it adds latency.\n *\n * @param config - Middleware configuration\n * @returns Express request handler\n */\nexport function peacMiddlewareSync(config: ExpressMiddlewareConfig): RequestHandler {\n // Validate configuration at initialization\n validateConfig(config);\n\n return async function peacReceiptMiddlewareSync(\n req: Request,\n res: Response,\n next: NextFunction\n ): Promise<void> {\n // Check if we should skip this request\n if (config.skip?.(req)) {\n next();\n return;\n }\n\n // Build and store request context\n const requestContext = buildRequestContext(req);\n (req as RequestWithPeacContext)[PEAC_CONTEXT_KEY] = requestContext;\n\n // Enhance config with Express-specific extractors\n const enhancedConfig: MiddlewareConfig = {\n ...config,\n claimsGenerator: async (ctx) => {\n const baseClaims = config.claimsGenerator ? await config.claimsGenerator(ctx) : {};\n\n return {\n ...baseClaims,\n ...(config.audienceExtractor && { aud: config.audienceExtractor(req) }),\n ...(config.subjectExtractor && { sub: config.subjectExtractor(req) }),\n };\n },\n };\n\n // Store original json method\n const originalJson = res.json.bind(res);\n\n // Override res.json to inject receipt synchronously\n res.json = function jsonWithReceiptSync(body: unknown): Response {\n // We can't make this truly sync due to crypto operations,\n // but we can make it blocking by using a sync wrapper\n const syncWrapper = async (): Promise<void> => {\n try {\n const result = await createReceipt(enhancedConfig, requestContext, {\n statusCode: res.statusCode,\n headers: res.getHeaders() as Record<string, string | string[] | undefined>,\n body,\n });\n\n // Add headers\n for (const [key, value] of Object.entries(result.headers)) {\n res.setHeader(key, value);\n }\n\n // Send response\n if (result.bodyWrapper) {\n originalJson(result.bodyWrapper);\n } else {\n originalJson(body);\n }\n } catch (error) {\n if (config.onError) {\n config.onError(error as Error, req, res);\n } else {\n console.error('[PEAC] Receipt generation failed:', (error as Error).message);\n }\n originalJson(body);\n }\n };\n\n // Execute synchronously by blocking\n syncWrapper();\n return res;\n };\n\n next();\n };\n}\n"]}
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@peac/middleware-express",
3
- "version": "0.10.9",
3
+ "version": "0.10.10",
4
4
  "description": "Express.js middleware for automatic PEAC receipt issuance",
5
- "main": "dist/index.js",
5
+ "main": "dist/index.cjs",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
8
8
  ".": {
9
9
  "types": "./dist/index.d.ts",
10
- "import": "./dist/index.js",
11
- "require": "./dist/index.js",
12
- "default": "./dist/index.js"
13
- }
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.cjs",
12
+ "default": "./dist/index.mjs"
13
+ },
14
+ "./package.json": "./package.json"
14
15
  },
15
16
  "repository": {
16
17
  "type": "git",
@@ -32,7 +33,7 @@
32
33
  "provenance": true
33
34
  },
34
35
  "dependencies": {
35
- "@peac/middleware-core": "0.10.9"
36
+ "@peac/middleware-core": "0.10.10"
36
37
  },
37
38
  "peerDependencies": {
38
39
  "express": "^4.18.0 || ^5.0.0"
@@ -44,14 +45,17 @@
44
45
  "supertest": "^6.3.3",
45
46
  "@types/supertest": "^6.0.2",
46
47
  "typescript": "^5.3.3",
47
- "vitest": "^1.1.0",
48
- "@peac/crypto": "0.10.9"
48
+ "vitest": "^4.0.0",
49
+ "tsup": "^8.0.0",
50
+ "@peac/crypto": "0.10.10"
49
51
  },
50
52
  "scripts": {
51
53
  "prebuild": "rm -rf dist tsconfig.tsbuildinfo",
52
- "build": "tsc",
54
+ "build": "pnpm run build:js && pnpm run build:types",
53
55
  "test": "vitest run",
54
56
  "test:watch": "vitest",
55
- "clean": "rm -rf dist tsconfig.tsbuildinfo"
57
+ "clean": "rm -rf dist tsconfig.tsbuildinfo",
58
+ "build:js": "tsup",
59
+ "build:types": "rm -f dist/.tsbuildinfo && tsc && rm -f dist/.tsbuildinfo"
56
60
  }
57
61
  }
package/dist/index.js DELETED
@@ -1,41 +0,0 @@
1
- "use strict";
2
- /**
3
- * PEAC Middleware for Express.js
4
- *
5
- * Express.js middleware for automatic PEAC receipt issuance.
6
- *
7
- * @example
8
- * ```typescript
9
- * import express from 'express';
10
- * import { peacMiddleware } from '@peac/middleware-express';
11
- *
12
- * const app = express();
13
- *
14
- * app.use(peacMiddleware({
15
- * issuer: 'https://api.example.com',
16
- * signingKey: {
17
- * kty: 'OKP',
18
- * crv: 'Ed25519',
19
- * x: '<base64url public key>',
20
- * d: '<base64url private key>',
21
- * },
22
- * keyId: 'prod-2026-02',
23
- * }));
24
- *
25
- * app.get('/api/data', (req, res) => {
26
- * res.json({ message: 'Hello World' });
27
- * // PEAC-Receipt header automatically added
28
- * });
29
- * ```
30
- *
31
- * @packageDocumentation
32
- */
33
- Object.defineProperty(exports, "__esModule", { value: true });
34
- exports.hasPeacContext = exports.getReceiptFromResponse = exports.peacMiddlewareSync = exports.peacMiddleware = void 0;
35
- // Middleware
36
- var middleware_js_1 = require("./middleware.js");
37
- Object.defineProperty(exports, "peacMiddleware", { enumerable: true, get: function () { return middleware_js_1.peacMiddleware; } });
38
- Object.defineProperty(exports, "peacMiddlewareSync", { enumerable: true, get: function () { return middleware_js_1.peacMiddlewareSync; } });
39
- Object.defineProperty(exports, "getReceiptFromResponse", { enumerable: true, get: function () { return middleware_js_1.getReceiptFromResponse; } });
40
- Object.defineProperty(exports, "hasPeacContext", { enumerable: true, get: function () { return middleware_js_1.hasPeacContext; } });
41
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;;AAEH,aAAa;AACb,iDAKyB;AAJvB,+GAAA,cAAc,OAAA;AACd,mHAAA,kBAAkB,OAAA;AAClB,uHAAA,sBAAsB,OAAA;AACtB,+GAAA,cAAc,OAAA"}
@@ -1,220 +0,0 @@
1
- "use strict";
2
- /**
3
- * Express.js Middleware for PEAC Receipt Issuance
4
- *
5
- * Intercepts responses and adds PEAC receipts automatically.
6
- *
7
- * @packageDocumentation
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.hasPeacContext = hasPeacContext;
11
- exports.getReceiptFromResponse = getReceiptFromResponse;
12
- exports.peacMiddleware = peacMiddleware;
13
- exports.peacMiddlewareSync = peacMiddlewareSync;
14
- const middleware_core_1 = require("@peac/middleware-core");
15
- /**
16
- * Symbol to store PEAC context on request
17
- */
18
- const PEAC_CONTEXT_KEY = Symbol('peac-context');
19
- /**
20
- * Check if a request has PEAC context
21
- */
22
- function hasPeacContext(req) {
23
- return PEAC_CONTEXT_KEY in req;
24
- }
25
- /**
26
- * Get the PEAC receipt from a response (for testing/debugging)
27
- *
28
- * @param res - Express response object
29
- * @returns Receipt JWS if present, undefined otherwise
30
- */
31
- function getReceiptFromResponse(res) {
32
- const receipt = res.getHeader('PEAC-Receipt');
33
- return typeof receipt === 'string' ? receipt : undefined;
34
- }
35
- /**
36
- * Convert Express request to RequestContext
37
- */
38
- function buildRequestContext(req) {
39
- return {
40
- method: req.method,
41
- path: req.path,
42
- headers: req.headers,
43
- body: req.body,
44
- timestamp: Date.now(),
45
- };
46
- }
47
- /**
48
- * Express middleware that adds PEAC receipts to responses
49
- *
50
- * The middleware intercepts `res.json()` and `res.send()` to inject
51
- * PEAC receipts into responses. For header transport (default), the
52
- * receipt is added as a `PEAC-Receipt` header. For body transport,
53
- * the response is wrapped in a `{ data, peac_receipt }` structure.
54
- *
55
- * @param config - Middleware configuration
56
- * @returns Express request handler
57
- *
58
- * @example
59
- * ```typescript
60
- * import express from 'express';
61
- * import { peacMiddleware } from '@peac/middleware-express';
62
- *
63
- * const app = express();
64
- *
65
- * // Add PEAC middleware
66
- * app.use(peacMiddleware({
67
- * issuer: 'https://api.example.com',
68
- * signingKey: privateKey,
69
- * keyId: 'prod-2026-02',
70
- * }));
71
- *
72
- * app.get('/api/data', (req, res) => {
73
- * res.json({ items: [1, 2, 3] });
74
- * // PEAC-Receipt header automatically added
75
- * });
76
- * ```
77
- */
78
- function peacMiddleware(config) {
79
- // Validate configuration at initialization
80
- (0, middleware_core_1.validateConfig)(config);
81
- return async function peacReceiptMiddleware(req, res, next) {
82
- // Check if we should skip this request
83
- if (config.skip?.(req)) {
84
- next();
85
- return;
86
- }
87
- // Build and store request context
88
- const requestContext = buildRequestContext(req);
89
- req[PEAC_CONTEXT_KEY] = requestContext;
90
- // Enhance config with Express-specific extractors
91
- const enhancedConfig = {
92
- ...config,
93
- claimsGenerator: async (ctx) => {
94
- const baseClaims = config.claimsGenerator ? await config.claimsGenerator(ctx) : {};
95
- return {
96
- ...baseClaims,
97
- // Override audience if custom extractor provided
98
- ...(config.audienceExtractor && { aud: config.audienceExtractor(req) }),
99
- // Add subject if extractor provided
100
- ...(config.subjectExtractor && { sub: config.subjectExtractor(req) }),
101
- };
102
- },
103
- };
104
- // Store original json method
105
- const originalJson = res.json.bind(res);
106
- // Override res.json to inject receipt
107
- res.json = function jsonWithReceipt(body) {
108
- // Generate receipt asynchronously
109
- (0, middleware_core_1.createReceipt)(enhancedConfig, requestContext, {
110
- statusCode: res.statusCode,
111
- headers: res.getHeaders(),
112
- body,
113
- })
114
- .then((result) => {
115
- // Add headers from receipt result
116
- for (const [key, value] of Object.entries(result.headers)) {
117
- res.setHeader(key, value);
118
- }
119
- // If body transport, use wrapped body
120
- if (result.bodyWrapper) {
121
- return originalJson(result.bodyWrapper);
122
- }
123
- return originalJson(body);
124
- })
125
- .catch((error) => {
126
- // Handle errors without breaking the response
127
- if (config.onError) {
128
- config.onError(error, req, res);
129
- }
130
- else {
131
- // Log but don't fail the response
132
- console.error('[PEAC] Receipt generation failed:', error.message);
133
- }
134
- // Send original body on error
135
- return originalJson(body);
136
- });
137
- // Return response for chaining (synchronous return)
138
- // The actual response will be sent by the promise chain
139
- return res;
140
- };
141
- next();
142
- };
143
- }
144
- /**
145
- * Create middleware with synchronous receipt generation (blocking)
146
- *
147
- * This variant waits for receipt generation before sending the response.
148
- * Use this when you need to guarantee the receipt is in the response,
149
- * but be aware it adds latency.
150
- *
151
- * @param config - Middleware configuration
152
- * @returns Express request handler
153
- */
154
- function peacMiddlewareSync(config) {
155
- // Validate configuration at initialization
156
- (0, middleware_core_1.validateConfig)(config);
157
- return async function peacReceiptMiddlewareSync(req, res, next) {
158
- // Check if we should skip this request
159
- if (config.skip?.(req)) {
160
- next();
161
- return;
162
- }
163
- // Build and store request context
164
- const requestContext = buildRequestContext(req);
165
- req[PEAC_CONTEXT_KEY] = requestContext;
166
- // Enhance config with Express-specific extractors
167
- const enhancedConfig = {
168
- ...config,
169
- claimsGenerator: async (ctx) => {
170
- const baseClaims = config.claimsGenerator ? await config.claimsGenerator(ctx) : {};
171
- return {
172
- ...baseClaims,
173
- ...(config.audienceExtractor && { aud: config.audienceExtractor(req) }),
174
- ...(config.subjectExtractor && { sub: config.subjectExtractor(req) }),
175
- };
176
- },
177
- };
178
- // Store original json method
179
- const originalJson = res.json.bind(res);
180
- // Override res.json to inject receipt synchronously
181
- res.json = function jsonWithReceiptSync(body) {
182
- // We can't make this truly sync due to crypto operations,
183
- // but we can make it blocking by using a sync wrapper
184
- const syncWrapper = async () => {
185
- try {
186
- const result = await (0, middleware_core_1.createReceipt)(enhancedConfig, requestContext, {
187
- statusCode: res.statusCode,
188
- headers: res.getHeaders(),
189
- body,
190
- });
191
- // Add headers
192
- for (const [key, value] of Object.entries(result.headers)) {
193
- res.setHeader(key, value);
194
- }
195
- // Send response
196
- if (result.bodyWrapper) {
197
- originalJson(result.bodyWrapper);
198
- }
199
- else {
200
- originalJson(body);
201
- }
202
- }
203
- catch (error) {
204
- if (config.onError) {
205
- config.onError(error, req, res);
206
- }
207
- else {
208
- console.error('[PEAC] Receipt generation failed:', error.message);
209
- }
210
- originalJson(body);
211
- }
212
- };
213
- // Execute synchronously by blocking
214
- syncWrapper();
215
- return res;
216
- };
217
- next();
218
- };
219
- }
220
- //# sourceMappingURL=middleware.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}