@peac/middleware-express 0.10.9 → 0.10.11
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 +1 -1
- package/dist/index.cjs +130 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +125 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +15 -11
- package/dist/index.js +0 -41
- package/dist/index.js.map +0 -1
- package/dist/middleware.js +0 -220
- package/dist/middleware.js.map +0 -1
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":"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","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.
|
|
3
|
+
"version": "0.10.11",
|
|
4
4
|
"description": "Express.js middleware for automatic PEAC receipt issuance",
|
|
5
|
-
"main": "dist/index.
|
|
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.
|
|
11
|
-
"require": "./dist/index.
|
|
12
|
-
"default": "./dist/index.
|
|
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.
|
|
36
|
+
"@peac/middleware-core": "0.10.11"
|
|
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": "^
|
|
48
|
-
"
|
|
48
|
+
"vitest": "^4.0.0",
|
|
49
|
+
"tsup": "^8.0.0",
|
|
50
|
+
"@peac/crypto": "0.10.11"
|
|
49
51
|
},
|
|
50
52
|
"scripts": {
|
|
51
53
|
"prebuild": "rm -rf dist tsconfig.tsbuildinfo",
|
|
52
|
-
"build": "
|
|
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"}
|
package/dist/middleware.js
DELETED
|
@@ -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
|
package/dist/middleware.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|