@usebetterdev/audit-express 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -46,11 +46,8 @@ function toWebRequest(req) {
|
|
|
46
46
|
headers
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
|
-
var defaultExtractor = {
|
|
50
|
-
actor: (0, import_audit_core.fromBearerToken)("sub")
|
|
51
|
-
};
|
|
52
49
|
function createExpressMiddleware(options = {}) {
|
|
53
|
-
const extractor = options.extractor ?? defaultExtractor;
|
|
50
|
+
const extractor = options.extractor ?? import_audit_core.defaultExtractor;
|
|
54
51
|
const handlerOptions = {};
|
|
55
52
|
if (options.onError) {
|
|
56
53
|
handlerOptions.onError = options.onError;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n AuditContext,\n ContextExtractor,\n MiddlewareHandlerOptions,\n} from \"@usebetterdev/audit-core\";\nimport {\n
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n AuditContext,\n ContextExtractor,\n MiddlewareHandlerOptions,\n} from \"@usebetterdev/audit-core\";\nimport {\n defaultExtractor,\n handleMiddleware,\n} from \"@usebetterdev/audit-core\";\n\n// ---------------------------------------------------------------------------\n// Interface types\n// ---------------------------------------------------------------------------\n\nexport interface ExpressRequestLike {\n headers: Record<string, string | string[] | number | undefined>;\n url: string;\n originalUrl?: string;\n hostname?: string;\n method?: string;\n}\n\nexport interface ExpressResponseLike {\n /** Used to detect when the response is finished so the ALS scope stays open. Real Express responses always have this (inherited from http.ServerResponse). */\n on?: (event: string, listener: (...args: unknown[]) => void) => unknown;\n}\n\nexport type ExpressNextFunction = (error?: unknown) => void;\n\nexport type ExpressMiddleware = (\n request: ExpressRequestLike,\n response: ExpressResponseLike,\n next: ExpressNextFunction,\n) => Promise<void>;\n\nexport interface CreateExpressMiddlewareOptions {\n /** Context extractor for pulling actor identity from the request. */\n extractor?: ContextExtractor;\n /** Called when an extractor throws. Defaults to silent fail-open. */\n onError?: (error: unknown) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Request bridging\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge an Express `IncomingMessage`-shaped request to a Web `Request`.\n *\n * Node.js headers can be `string | string[] | number | undefined`.\n * This normalises them to the `string` values `Headers` expects:\n * - `string[]` → joined with `\", \"`\n * - `number` → converted with `String()`\n * - `undefined`→ skipped\n *\n * **Security note:** `req.hostname` comes from the `Host` header and is\n * unvalidated user input. The reconstructed URL is used only to satisfy the\n * Web `Request` constructor — built-in extractors read headers, not the URL\n * authority. Custom extractors must not trust `new URL(request.url).hostname`.\n */\nfunction toWebRequest(req: ExpressRequestLike): Request {\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined) {\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(key, value.join(\", \"));\n } else if (typeof value === \"number\") {\n headers.set(key, String(value));\n } else {\n headers.set(key, value);\n }\n }\n\n const rawPath = req.originalUrl ?? req.url;\n const url = rawPath.startsWith(\"/\")\n ? `http://${req.hostname ?? \"localhost\"}${rawPath}`\n : rawPath;\n\n return new Request(url, {\n method: req.method ?? \"GET\",\n headers,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Middleware\n// ---------------------------------------------------------------------------\n\n/**\n * Creates an Express-compatible middleware that populates audit context\n * via AsyncLocalStorage on every request.\n *\n * Bridges Express's `IncomingMessage` to a Web `Request`, then delegates\n * to the shared `handleMiddleware()` from audit-core.\n *\n * **ALS scope lifetime:** the scope stays open until the response finishes\n * (via `response.on('finish'/'close')`), so audit context is available\n * even inside `await`-ed operations in async route handlers.\n *\n * The middleware is non-blocking: if context extraction fails, the request\n * proceeds without audit context (fail open).\n */\nexport function createExpressMiddleware(\n options: CreateExpressMiddlewareOptions = {},\n): ExpressMiddleware {\n const extractor = options.extractor ?? defaultExtractor;\n const handlerOptions: MiddlewareHandlerOptions = {};\n if (options.onError) {\n handlerOptions.onError = options.onError;\n }\n\n return async (request, response, next) => {\n try {\n const webRequest = toWebRequest(request);\n\n const nextWrapper = (): Promise<void> =>\n new Promise<void>((resolve) => {\n const done = () => resolve();\n if (response.on) {\n response.on(\"finish\", done);\n response.on(\"close\", done);\n }\n next();\n if (!response.on) {\n done();\n }\n });\n\n await handleMiddleware(extractor, webRequest, nextWrapper, handlerOptions);\n } catch (error) {\n next(error);\n }\n };\n}\n\n/**\n * Convenience wrapper: `app.use(betterAuditExpress())`.\n *\n * Uses the default JWT extractor (reads `sub` from `Authorization: Bearer <jwt>`).\n * Pass options to customise extraction.\n */\nexport function betterAuditExpress(\n options: CreateExpressMiddlewareOptions = {},\n): ExpressMiddleware {\n return createExpressMiddleware(options);\n}\n\nexport type { AuditContext, ContextExtractor };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,wBAGO;AAoDP,SAAS,aAAa,KAAkC;AACtD,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACtD,QAAI,UAAU,QAAW;AACvB;AAAA,IACF;AACA,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,cAAQ,IAAI,KAAK,MAAM,KAAK,IAAI,CAAC;AAAA,IACnC,WAAW,OAAO,UAAU,UAAU;AACpC,cAAQ,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,IAChC,OAAO;AACL,cAAQ,IAAI,KAAK,KAAK;AAAA,IACxB;AAAA,EACF;AAEA,QAAM,UAAU,IAAI,eAAe,IAAI;AACvC,QAAM,MAAM,QAAQ,WAAW,GAAG,IAC9B,UAAU,IAAI,YAAY,WAAW,GAAG,OAAO,KAC/C;AAEJ,SAAO,IAAI,QAAQ,KAAK;AAAA,IACtB,QAAQ,IAAI,UAAU;AAAA,IACtB;AAAA,EACF,CAAC;AACH;AAoBO,SAAS,wBACd,UAA0C,CAAC,GACxB;AACnB,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,iBAA2C,CAAC;AAClD,MAAI,QAAQ,SAAS;AACnB,mBAAe,UAAU,QAAQ;AAAA,EACnC;AAEA,SAAO,OAAO,SAAS,UAAU,SAAS;AACxC,QAAI;AACF,YAAM,aAAa,aAAa,OAAO;AAEvC,YAAM,cAAc,MAClB,IAAI,QAAc,CAAC,YAAY;AAC7B,cAAM,OAAO,MAAM,QAAQ;AAC3B,YAAI,SAAS,IAAI;AACf,mBAAS,GAAG,UAAU,IAAI;AAC1B,mBAAS,GAAG,SAAS,IAAI;AAAA,QAC3B;AACA,aAAK;AACL,YAAI,CAAC,SAAS,IAAI;AAChB,eAAK;AAAA,QACP;AAAA,MACF,CAAC;AAEH,gBAAM,oCAAiB,WAAW,YAAY,aAAa,cAAc;AAAA,IAC3E,SAAS,OAAO;AACd,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AACF;AAQO,SAAS,mBACd,UAA0C,CAAC,GACxB;AACnB,SAAO,wBAAwB,OAAO;AACxC;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
defaultExtractor,
|
|
4
|
+
handleMiddleware
|
|
5
5
|
} from "@usebetterdev/audit-core";
|
|
6
6
|
function toWebRequest(req) {
|
|
7
7
|
const headers = new Headers();
|
|
@@ -24,9 +24,6 @@ function toWebRequest(req) {
|
|
|
24
24
|
headers
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
|
-
var defaultExtractor = {
|
|
28
|
-
actor: fromBearerToken("sub")
|
|
29
|
-
};
|
|
30
27
|
function createExpressMiddleware(options = {}) {
|
|
31
28
|
const extractor = options.extractor ?? defaultExtractor;
|
|
32
29
|
const handlerOptions = {};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n AuditContext,\n ContextExtractor,\n MiddlewareHandlerOptions,\n} from \"@usebetterdev/audit-core\";\nimport {\n
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type {\n AuditContext,\n ContextExtractor,\n MiddlewareHandlerOptions,\n} from \"@usebetterdev/audit-core\";\nimport {\n defaultExtractor,\n handleMiddleware,\n} from \"@usebetterdev/audit-core\";\n\n// ---------------------------------------------------------------------------\n// Interface types\n// ---------------------------------------------------------------------------\n\nexport interface ExpressRequestLike {\n headers: Record<string, string | string[] | number | undefined>;\n url: string;\n originalUrl?: string;\n hostname?: string;\n method?: string;\n}\n\nexport interface ExpressResponseLike {\n /** Used to detect when the response is finished so the ALS scope stays open. Real Express responses always have this (inherited from http.ServerResponse). */\n on?: (event: string, listener: (...args: unknown[]) => void) => unknown;\n}\n\nexport type ExpressNextFunction = (error?: unknown) => void;\n\nexport type ExpressMiddleware = (\n request: ExpressRequestLike,\n response: ExpressResponseLike,\n next: ExpressNextFunction,\n) => Promise<void>;\n\nexport interface CreateExpressMiddlewareOptions {\n /** Context extractor for pulling actor identity from the request. */\n extractor?: ContextExtractor;\n /** Called when an extractor throws. Defaults to silent fail-open. */\n onError?: (error: unknown) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Request bridging\n// ---------------------------------------------------------------------------\n\n/**\n * Bridge an Express `IncomingMessage`-shaped request to a Web `Request`.\n *\n * Node.js headers can be `string | string[] | number | undefined`.\n * This normalises them to the `string` values `Headers` expects:\n * - `string[]` → joined with `\", \"`\n * - `number` → converted with `String()`\n * - `undefined`→ skipped\n *\n * **Security note:** `req.hostname` comes from the `Host` header and is\n * unvalidated user input. The reconstructed URL is used only to satisfy the\n * Web `Request` constructor — built-in extractors read headers, not the URL\n * authority. Custom extractors must not trust `new URL(request.url).hostname`.\n */\nfunction toWebRequest(req: ExpressRequestLike): Request {\n const headers = new Headers();\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined) {\n continue;\n }\n if (Array.isArray(value)) {\n headers.set(key, value.join(\", \"));\n } else if (typeof value === \"number\") {\n headers.set(key, String(value));\n } else {\n headers.set(key, value);\n }\n }\n\n const rawPath = req.originalUrl ?? req.url;\n const url = rawPath.startsWith(\"/\")\n ? `http://${req.hostname ?? \"localhost\"}${rawPath}`\n : rawPath;\n\n return new Request(url, {\n method: req.method ?? \"GET\",\n headers,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Middleware\n// ---------------------------------------------------------------------------\n\n/**\n * Creates an Express-compatible middleware that populates audit context\n * via AsyncLocalStorage on every request.\n *\n * Bridges Express's `IncomingMessage` to a Web `Request`, then delegates\n * to the shared `handleMiddleware()` from audit-core.\n *\n * **ALS scope lifetime:** the scope stays open until the response finishes\n * (via `response.on('finish'/'close')`), so audit context is available\n * even inside `await`-ed operations in async route handlers.\n *\n * The middleware is non-blocking: if context extraction fails, the request\n * proceeds without audit context (fail open).\n */\nexport function createExpressMiddleware(\n options: CreateExpressMiddlewareOptions = {},\n): ExpressMiddleware {\n const extractor = options.extractor ?? defaultExtractor;\n const handlerOptions: MiddlewareHandlerOptions = {};\n if (options.onError) {\n handlerOptions.onError = options.onError;\n }\n\n return async (request, response, next) => {\n try {\n const webRequest = toWebRequest(request);\n\n const nextWrapper = (): Promise<void> =>\n new Promise<void>((resolve) => {\n const done = () => resolve();\n if (response.on) {\n response.on(\"finish\", done);\n response.on(\"close\", done);\n }\n next();\n if (!response.on) {\n done();\n }\n });\n\n await handleMiddleware(extractor, webRequest, nextWrapper, handlerOptions);\n } catch (error) {\n next(error);\n }\n };\n}\n\n/**\n * Convenience wrapper: `app.use(betterAuditExpress())`.\n *\n * Uses the default JWT extractor (reads `sub` from `Authorization: Bearer <jwt>`).\n * Pass options to customise extraction.\n */\nexport function betterAuditExpress(\n options: CreateExpressMiddlewareOptions = {},\n): ExpressMiddleware {\n return createExpressMiddleware(options);\n}\n\nexport type { AuditContext, ContextExtractor };\n"],"mappings":";AAKA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAoDP,SAAS,aAAa,KAAkC;AACtD,QAAM,UAAU,IAAI,QAAQ;AAC5B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACtD,QAAI,UAAU,QAAW;AACvB;AAAA,IACF;AACA,QAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,cAAQ,IAAI,KAAK,MAAM,KAAK,IAAI,CAAC;AAAA,IACnC,WAAW,OAAO,UAAU,UAAU;AACpC,cAAQ,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,IAChC,OAAO;AACL,cAAQ,IAAI,KAAK,KAAK;AAAA,IACxB;AAAA,EACF;AAEA,QAAM,UAAU,IAAI,eAAe,IAAI;AACvC,QAAM,MAAM,QAAQ,WAAW,GAAG,IAC9B,UAAU,IAAI,YAAY,WAAW,GAAG,OAAO,KAC/C;AAEJ,SAAO,IAAI,QAAQ,KAAK;AAAA,IACtB,QAAQ,IAAI,UAAU;AAAA,IACtB;AAAA,EACF,CAAC;AACH;AAoBO,SAAS,wBACd,UAA0C,CAAC,GACxB;AACnB,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,iBAA2C,CAAC;AAClD,MAAI,QAAQ,SAAS;AACnB,mBAAe,UAAU,QAAQ;AAAA,EACnC;AAEA,SAAO,OAAO,SAAS,UAAU,SAAS;AACxC,QAAI;AACF,YAAM,aAAa,aAAa,OAAO;AAEvC,YAAM,cAAc,MAClB,IAAI,QAAc,CAAC,YAAY;AAC7B,cAAM,OAAO,MAAM,QAAQ;AAC3B,YAAI,SAAS,IAAI;AACf,mBAAS,GAAG,UAAU,IAAI;AAC1B,mBAAS,GAAG,SAAS,IAAI;AAAA,QAC3B;AACA,aAAK;AACL,YAAI,CAAC,SAAS,IAAI;AAChB,eAAK;AAAA,QACP;AAAA,MACF,CAAC;AAEH,YAAM,iBAAiB,WAAW,YAAY,aAAa,cAAc;AAAA,IAC3E,SAAS,OAAO;AACd,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AACF;AAQO,SAAS,mBACd,UAA0C,CAAC,GACxB;AACnB,SAAO,wBAAwB,OAAO;AACxC;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usebetterdev/audit-express",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"repository": "github:usebetter-dev/usebetter",
|
|
5
5
|
"bugs": "https://github.com/usebetter-dev/usebetter/issues",
|
|
6
6
|
"homepage": "https://github.com/usebetter-dev/usebetter#readme",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"README.md"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@usebetterdev/audit-core": "0.
|
|
27
|
+
"@usebetterdev/audit-core": "0.7.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"express": ">=4"
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"supertest": "^7.0.0",
|
|
38
38
|
"tsup": "^8.3.5",
|
|
39
39
|
"typescript": "~5.7.2",
|
|
40
|
-
"vitest": "^2.1.6"
|
|
40
|
+
"vitest": "^2.1.6",
|
|
41
|
+
"@usebetterdev/test-utils": "^0.5.2"
|
|
41
42
|
},
|
|
42
43
|
"engines": {
|
|
43
44
|
"node": ">=22"
|