@blamejs/core 0.7.106 → 0.8.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/NOTICE +17 -1
  3. package/README.md +4 -3
  4. package/index.js +16 -0
  5. package/lib/asyncapi-bindings.js +160 -0
  6. package/lib/asyncapi-traits.js +143 -0
  7. package/lib/asyncapi.js +531 -0
  8. package/lib/audit.js +6 -0
  9. package/lib/auth/acr-vocabulary.js +265 -0
  10. package/lib/auth/auth-time-tracker.js +111 -0
  11. package/lib/auth/elevation-grant.js +306 -0
  12. package/lib/auth/sd-jwt-vc-disclosure.js +95 -0
  13. package/lib/auth/sd-jwt-vc-holder.js +203 -0
  14. package/lib/auth/sd-jwt-vc-issuer.js +197 -0
  15. package/lib/auth/sd-jwt-vc.js +526 -0
  16. package/lib/auth/step-up-policy.js +335 -0
  17. package/lib/auth/step-up.js +445 -0
  18. package/lib/compliance-ai-act-logging.js +186 -0
  19. package/lib/compliance-ai-act-prohibited.js +205 -0
  20. package/lib/compliance-ai-act-risk.js +189 -0
  21. package/lib/compliance-ai-act-transparency.js +200 -0
  22. package/lib/compliance-ai-act.js +558 -0
  23. package/lib/compliance.js +2 -0
  24. package/lib/crypto.js +32 -0
  25. package/lib/flag-cache.js +136 -0
  26. package/lib/flag-evaluation-context.js +135 -0
  27. package/lib/flag-providers.js +279 -0
  28. package/lib/flag-targeting.js +210 -0
  29. package/lib/flag.js +284 -0
  30. package/lib/inbox.js +367 -0
  31. package/lib/mail-arc-sign.js +372 -0
  32. package/lib/mail-auth.js +2 -0
  33. package/lib/middleware/ai-act-disclosure.js +166 -0
  34. package/lib/middleware/asyncapi-serve.js +136 -0
  35. package/lib/middleware/flag-context.js +76 -0
  36. package/lib/middleware/index.js +15 -0
  37. package/lib/middleware/openapi-serve.js +143 -0
  38. package/lib/middleware/require-step-up.js +186 -0
  39. package/lib/openapi-paths-builder.js +248 -0
  40. package/lib/openapi-schema-walk.js +192 -0
  41. package/lib/openapi-security.js +169 -0
  42. package/lib/openapi-yaml.js +154 -0
  43. package/lib/openapi.js +443 -0
  44. package/lib/pqc-software.js +195 -0
  45. package/lib/vault/index.js +3 -0
  46. package/lib/vault-aad.js +259 -0
  47. package/lib/vendor/MANIFEST.json +29 -0
  48. package/lib/vendor/noble-post-quantum.cjs +18 -0
  49. package/lib/ws-client.js +829 -0
  50. package/package.json +1 -1
  51. package/sbom.cyclonedx.json +6 -6
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ /**
3
+ * flag-context middleware — extracts an OpenFeature evaluation context
4
+ * onto the request so downstream handlers and multiple flag clients
5
+ * can read a consistent context without re-deriving it per call.
6
+ *
7
+ * var attachCtx = b.middleware.flagContext({
8
+ * userKey: "x-user-id", // header to pull targetingKey from
9
+ * extractAttributes: function (req) { // operator-supplied augmentation
10
+ * return { tenantId: req.tenantId, environment: process.env.NODE_ENV };
11
+ * },
12
+ * });
13
+ * router.use(attachCtx);
14
+ *
15
+ * // Downstream:
16
+ * var ctx = req.flagCtx; // readonly Frozen object
17
+ * var enabled = b.flagClient.getBoolean("foo", ctx);
18
+ *
19
+ * The middleware does NOT evaluate flags — it only constructs the
20
+ * context. Pair with `flag.middleware()` for the request-attached
21
+ * convenience accessor; or pass `req.flagCtx` directly to a flag
22
+ * client method for a more decoupled shape (e.g. when several flag
23
+ * clients with different providers share the same context).
24
+ */
25
+
26
+ var validateOpts = require("../validate-opts");
27
+ var lazyRequire = require("../lazy-require");
28
+ var { defineClass } = require("../framework-error");
29
+ var FlagError = defineClass("FlagError", { alwaysPermanent: true });
30
+
31
+ var contextMod = lazyRequire(function () { return require("../flag-evaluation-context"); });
32
+
33
+ function create(opts) {
34
+ opts = opts || {};
35
+ validateOpts(opts, [
36
+ "userKey", "userKeyHeader", "extractAttributes", "tenantKeyHeader",
37
+ ], "middleware.flagContext");
38
+ if (opts.extractAttributes != null && typeof opts.extractAttributes !== "function") {
39
+ throw new FlagError("flag/bad-opt",
40
+ "flagContext: extractAttributes must be a function");
41
+ }
42
+ var userKeyHeader = (typeof opts.userKeyHeader === "string" && opts.userKeyHeader.length > 0)
43
+ ? opts.userKeyHeader.toLowerCase()
44
+ : null;
45
+ var tenantKeyHeader = (typeof opts.tenantKeyHeader === "string" && opts.tenantKeyHeader.length > 0)
46
+ ? opts.tenantKeyHeader.toLowerCase()
47
+ : null;
48
+ var explicitUserKey = (typeof opts.userKey === "string" && opts.userKey.length > 0)
49
+ ? opts.userKey
50
+ : null;
51
+
52
+ return function flagContextMiddleware(req, res, next) {
53
+ var headers = req.headers || {};
54
+ var headerKey = userKeyHeader && typeof headers[userKeyHeader] === "string"
55
+ ? headers[userKeyHeader]
56
+ : null;
57
+ var fromReqOpts = {};
58
+ if (explicitUserKey) fromReqOpts.userKey = explicitUserKey;
59
+ else if (headerKey) fromReqOpts.userKey = headerKey;
60
+ var augment = {};
61
+ if (typeof opts.extractAttributes === "function") {
62
+ try {
63
+ var extra = opts.extractAttributes(req);
64
+ if (extra && typeof extra === "object") augment = extra;
65
+ } catch (_e) { /* drop-silent on extraction error */ }
66
+ }
67
+ if (tenantKeyHeader && typeof headers[tenantKeyHeader] === "string") {
68
+ augment.tenantId = headers[tenantKeyHeader];
69
+ }
70
+ fromReqOpts.extra = augment;
71
+ req.flagCtx = contextMod().fromRequest(req, fromReqOpts);
72
+ return next();
73
+ };
74
+ }
75
+
76
+ module.exports = { create: create };
@@ -16,7 +16,11 @@
16
16
  * 6. (your auth + business middleware + routes)
17
17
  * 7. errorHandler — must be LAST so it catches everything that throws
18
18
  */
19
+ var aiActDisclosure = require("./ai-act-disclosure");
19
20
  var apiEncrypt = require("./api-encrypt");
21
+ var openapiServe = require("./openapi-serve");
22
+ var asyncapiServe = require("./asyncapi-serve");
23
+ var flagContext = require("./flag-context");
20
24
  var assetlinks = require("./assetlinks");
21
25
  var attachUser = require("./attach-user");
22
26
  var bearerAuth = require("./bearer-auth");
@@ -43,6 +47,7 @@ var requireAal = require("./require-aal");
43
47
  var requireAuth = require("./require-auth");
44
48
  var requireContentType = require("./require-content-type");
45
49
  var requireMethods = require("./require-methods");
50
+ var requireStepUp = require("./require-step-up");
46
51
  var securityHeaders = require("./security-headers");
47
52
  var securityTxt = require("./security-txt");
48
53
  var spanHttpServer = require("./span-http-server");
@@ -65,6 +70,7 @@ module.exports = {
65
70
  requireAuth: requireAuth.create,
66
71
  requireContentType: requireContentType.create,
67
72
  requireMethods: requireMethods.create,
73
+ requireStepUp: requireStepUp.create,
68
74
  csrfProtect: csrfProtect.create,
69
75
  fetchMetadata: fetchMetadata.create,
70
76
  gpc: gpc.create,
@@ -78,6 +84,10 @@ module.exports = {
78
84
  sse: sse.create,
79
85
  requestLog: requestLog.create,
80
86
  apiEncrypt: apiEncrypt,
87
+ aiActDisclosure: aiActDisclosure.create,
88
+ openapiServe: openapiServe.create,
89
+ asyncapiServe: asyncapiServe.create,
90
+ flagContext: flagContext.create,
81
91
  assetlinks: assetlinks.create,
82
92
  dbRoleFor: dbRoleFor.create,
83
93
  dpop: dpop.create,
@@ -103,6 +113,7 @@ module.exports = {
103
113
  requireAuth: requireAuth,
104
114
  requireContentType: requireContentType,
105
115
  requireMethods: requireMethods,
116
+ requireStepUp: requireStepUp,
106
117
  csrfProtect: csrfProtect,
107
118
  fetchMetadata: fetchMetadata,
108
119
  bodyParser: bodyParser,
@@ -113,6 +124,10 @@ module.exports = {
113
124
  sse: sse,
114
125
  requestLog: requestLog,
115
126
  apiEncrypt: apiEncrypt,
127
+ aiActDisclosure: aiActDisclosure,
128
+ openapiServe: openapiServe,
129
+ asyncapiServe: asyncapiServe,
130
+ flagContext: flagContext,
116
131
  assetlinks: assetlinks,
117
132
  dbRoleFor: dbRoleFor,
118
133
  dpop: dpop,
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ /**
3
+ * openapi-serve middleware — expose an OpenAPI 3.1 document as a
4
+ * request-time JSON / YAML resource at a single mount point.
5
+ *
6
+ * var openapi = b.openapi.create({ ... });
7
+ * ...add paths / schemas / security...
8
+ *
9
+ * var serve = b.middleware.openapiServe({
10
+ * document: openapi,
11
+ * pathJson: "/openapi.json",
12
+ * pathYaml: "/openapi.yaml",
13
+ * pretty: true,
14
+ * cacheControl: "public, max-age=300",
15
+ * });
16
+ * router.use(serve);
17
+ *
18
+ * The middleware ONLY responds to GET requests for the configured
19
+ * paths; everything else passes to next() unchanged. ETag is computed
20
+ * from the JSON-string SHA3-512 to allow conditional GET.
21
+ *
22
+ * If `accessControl: "public"` (the default), the middleware emits
23
+ * `Access-Control-Allow-Origin: *` so external doc tooling can fetch.
24
+ * For internal-only docs operators set `accessControl: "same-origin"`
25
+ * which omits the CORS header.
26
+ */
27
+
28
+ var nodeCrypto = require("crypto");
29
+ var validateOpts = require("../validate-opts");
30
+ var lazyRequire = require("../lazy-require");
31
+ var { defineClass } = require("../framework-error");
32
+ var OpenApiError = defineClass("OpenApiError", { alwaysPermanent: true });
33
+
34
+ var openapiYaml = lazyRequire(function () { return require("../openapi-yaml"); });
35
+ var audit = lazyRequire(function () { return require("../audit"); });
36
+
37
+ function create(opts) {
38
+ opts = opts || {};
39
+ validateOpts(opts, [
40
+ "document", "pathJson", "pathYaml", "pretty",
41
+ "cacheControl", "accessControl", "audit",
42
+ ], "middleware.openapiServe");
43
+ if (!opts.document || typeof opts.document.toJson !== "function") {
44
+ throw new OpenApiError("openapi/bad-document",
45
+ "openapiServe: document must be a builder created via b.openapi.create()");
46
+ }
47
+ var pathJson = opts.pathJson || "/openapi.json";
48
+ var pathYaml = opts.pathYaml || "/openapi.yaml";
49
+ var pretty = opts.pretty === true ? 2 : 0;
50
+ var cacheControl = (typeof opts.cacheControl === "string" && opts.cacheControl.length > 0)
51
+ ? opts.cacheControl : "public, max-age=300";
52
+ var accessControl = opts.accessControl || "public";
53
+ var auditOn = opts.audit !== false;
54
+
55
+ if (typeof pathJson !== "string" || pathJson.charAt(0) !== "/") {
56
+ throw new OpenApiError("openapi/bad-path",
57
+ "openapiServe: pathJson must start with '/' - got " + JSON.stringify(pathJson));
58
+ }
59
+ if (typeof pathYaml !== "string" || pathYaml.charAt(0) !== "/") {
60
+ throw new OpenApiError("openapi/bad-path",
61
+ "openapiServe: pathYaml must start with '/' - got " + JSON.stringify(pathYaml));
62
+ }
63
+
64
+ var cachedDoc = null;
65
+ var cachedJsonStr = null;
66
+ var cachedYamlStr = null;
67
+ var cachedJsonEtag = null;
68
+ var cachedYamlEtag = null;
69
+
70
+ function _rebuild() {
71
+ cachedDoc = opts.document.toJson();
72
+ cachedJsonStr = JSON.stringify(cachedDoc, null, pretty);
73
+ cachedYamlStr = openapiYaml().toYaml(cachedDoc);
74
+ cachedJsonEtag = '"' + nodeCrypto.createHash("sha3-512").update(cachedJsonStr).digest("base64url").slice(0, 24) + '"';
75
+ cachedYamlEtag = '"' + nodeCrypto.createHash("sha3-512").update(cachedYamlStr).digest("base64url").slice(0, 24) + '"';
76
+ }
77
+ _rebuild();
78
+
79
+ function _writeBody(req, res, body, etag, contentType) {
80
+ var requestEtag = (req.headers && req.headers["if-none-match"]) || null;
81
+ if (requestEtag && requestEtag === etag) {
82
+ res.writeHead(304, { "ETag": etag, "Cache-Control": cacheControl }); // allow:raw-byte-literal — HTTP 304
83
+ res.end();
84
+ return;
85
+ }
86
+ var headers = {
87
+ "Content-Type": contentType,
88
+ "Content-Length": Buffer.byteLength(body),
89
+ "Cache-Control": cacheControl,
90
+ "ETag": etag,
91
+ };
92
+ if (accessControl === "public") {
93
+ headers["Access-Control-Allow-Origin"] = "*";
94
+ }
95
+ res.writeHead(200, headers); // allow:raw-byte-literal — HTTP 200
96
+ res.end(body);
97
+ }
98
+
99
+ var mw = function (req, res, next) {
100
+ if (typeof res.writeHead !== "function") return next();
101
+ var method = (req.method || "GET").toUpperCase();
102
+ if (method !== "GET" && method !== "HEAD") return next();
103
+ var pathname = req.pathname;
104
+ if (typeof pathname !== "string") {
105
+ var url = req.url || "";
106
+ var qIdx = url.indexOf("?");
107
+ pathname = qIdx === -1 ? url : url.slice(0, qIdx);
108
+ }
109
+ if (pathname === pathJson) {
110
+ _writeBody(req, res, cachedJsonStr, cachedJsonEtag, "application/json; charset=utf-8");
111
+ if (auditOn) {
112
+ try {
113
+ audit().safeEmit({
114
+ action: "openapi.document.served",
115
+ outcome: "success",
116
+ actor: null,
117
+ metadata: { format: "json", path: pathname, bytes: cachedJsonStr.length },
118
+ });
119
+ } catch (_e) { /* drop-silent */ }
120
+ }
121
+ return;
122
+ }
123
+ if (pathname === pathYaml) {
124
+ _writeBody(req, res, cachedYamlStr, cachedYamlEtag, "application/yaml; charset=utf-8");
125
+ if (auditOn) {
126
+ try {
127
+ audit().safeEmit({
128
+ action: "openapi.document.served",
129
+ outcome: "success",
130
+ actor: null,
131
+ metadata: { format: "yaml", path: pathname, bytes: cachedYamlStr.length },
132
+ });
133
+ } catch (_e) { /* drop-silent */ }
134
+ }
135
+ return;
136
+ }
137
+ return next();
138
+ };
139
+ mw.forceRebuild = _rebuild;
140
+ return mw;
141
+ }
142
+
143
+ module.exports = { create: create };
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ /**
3
+ * require-step-up middleware — gate routes per RFC 9470 OAuth 2.0
4
+ * Step-Up Authentication Challenge.
5
+ *
6
+ * Mounted AFTER attachUser / bearerAuth so the request carries the
7
+ * already-verified token claims.
8
+ *
9
+ * var sensitiveStepUp = b.middleware.requireStepUp({
10
+ * requirement: { acr: "high", maxAge: 300 },
11
+ * realm: "billing-api",
12
+ * });
13
+ * router.use("/billing/transfer", sensitiveStepUp);
14
+ *
15
+ * Failure shape (per RFC 9470 §3):
16
+ * 401 Unauthorized
17
+ * WWW-Authenticate: Bearer error="insufficient_user_authentication",
18
+ * error_description="...", acr_values="high", max_age="300"
19
+ * Content-Type: application/json
20
+ * { "error": "insufficient_user_authentication", "error_description": "..." }
21
+ *
22
+ * Operators with their own elevation grants pass `acceptGrant: true`
23
+ * and `grantHeader: "X-Step-Up-Grant"` (default) — the middleware
24
+ * checks for a valid b.auth.stepUp.grant token before evaluating the
25
+ * normal claims-based requirement, so a multi-step flow doesn't get
26
+ * step-up-prompted on every action.
27
+ *
28
+ * Options:
29
+ * {
30
+ * requirement: { acr / acrValues / maxAge / requiredAmr / phishingResistant },
31
+ * getClaims: function(req) { return req.user.claims; },
32
+ * realm: "api",
33
+ * audit: true,
34
+ * acceptGrant: true, // default
35
+ * grantHeader: "X-Step-Up-Grant", // default
36
+ * grantScope: null, // narrow grant by scope
37
+ * }
38
+ *
39
+ * NEVER weaken the security default to fix a broken caller. Operators
40
+ * configure their IdP to emit `acr` / `auth_time` / `amr` correctly;
41
+ * the middleware does not silently default these to "good enough" on a
42
+ * missing claim.
43
+ */
44
+
45
+ var lazyRequire = require("../lazy-require");
46
+ var validateOpts = require("../validate-opts");
47
+ var requestHelpers = require("../request-helpers");
48
+ var { AuthError } = require("../framework-error");
49
+
50
+ var stepUp = lazyRequire(function () { return require("../auth/step-up"); });
51
+ var elevation = lazyRequire(function () { return require("../auth/elevation-grant"); });
52
+ var audit = lazyRequire(function () { return require("../audit"); });
53
+
54
+ var DEFAULT_GRANT_HEADER = "x-step-up-grant";
55
+
56
+ function _defaultGetClaims(req) {
57
+ if (!req || typeof req !== "object") return null;
58
+ if (req.user && req.user.claims && typeof req.user.claims === "object") {
59
+ return req.user.claims;
60
+ }
61
+ if (req.user && typeof req.user === "object") {
62
+ return req.user;
63
+ }
64
+ return null;
65
+ }
66
+
67
+ function _writeChallenge(res, challenge, body, statusCode) {
68
+ if (res.headersSent) return;
69
+ var json = JSON.stringify(body);
70
+ res.writeHead(statusCode, { // allow:raw-byte-literal — HTTP status passthrough
71
+ "Content-Type": "application/json; charset=utf-8",
72
+ "Content-Length": Buffer.byteLength(json),
73
+ "WWW-Authenticate": challenge,
74
+ });
75
+ res.end(json);
76
+ }
77
+
78
+ function create(opts) {
79
+ opts = opts || {};
80
+ validateOpts(opts, [
81
+ "requirement", "getClaims", "realm", "audit",
82
+ "acceptGrant", "grantHeader", "grantScope", "errorDescription",
83
+ ], "middleware.requireStepUp");
84
+
85
+ if (!opts.requirement || typeof opts.requirement !== "object") {
86
+ throw new AuthError("auth-stepUp/bad-requirement",
87
+ "middleware.requireStepUp: opts.requirement must be an object");
88
+ }
89
+ validateOpts.optionalFunction(opts.getClaims,
90
+ "middleware.requireStepUp: getClaims", AuthError, "auth-stepUp/bad-opt");
91
+
92
+ var realm = (typeof opts.realm === "string" && opts.realm.length > 0)
93
+ ? opts.realm : "api";
94
+ var auditOn = opts.audit !== false;
95
+ var getClaims = (typeof opts.getClaims === "function")
96
+ ? opts.getClaims : _defaultGetClaims;
97
+ var acceptGrant = opts.acceptGrant !== false;
98
+ var grantHeader = (typeof opts.grantHeader === "string" && opts.grantHeader.length > 0)
99
+ ? opts.grantHeader.toLowerCase() : DEFAULT_GRANT_HEADER;
100
+ var grantScope = (typeof opts.grantScope === "string" && opts.grantScope.length > 0)
101
+ ? opts.grantScope : null;
102
+ var errorDesc = (typeof opts.errorDescription === "string" && opts.errorDescription.length > 0)
103
+ ? opts.errorDescription : null;
104
+
105
+ // Pre-validate the requirement so operator typos surface at boot, not
106
+ // on the first hot-path request.
107
+ var probe = stepUp().evaluate({ claims: { acr: "0" }, requirement: opts.requirement });
108
+ if (probe.error === "bad_requirement" || probe.error === "unknown_acr") {
109
+ throw new AuthError("auth-stepUp/bad-requirement",
110
+ "middleware.requireStepUp: " + (probe.reason || probe.error));
111
+ }
112
+
113
+ return function requireStepUpMiddleware(req, res, next) {
114
+ var headers = req.headers || {};
115
+
116
+ // Path 1: operator-issued elevation grant — short-circuit success.
117
+ if (acceptGrant) {
118
+ var grantToken = headers[grantHeader] || null;
119
+ if (typeof grantToken === "string" && grantToken.length > 0) {
120
+ var verifyOpts = {};
121
+ if (grantScope) verifyOpts.scope = grantScope;
122
+ var grantResult = elevation().verify(grantToken, verifyOpts);
123
+ if (grantResult.ok) {
124
+ if (auditOn) {
125
+ try {
126
+ audit().safeEmit({
127
+ action: "auth.stepup.satisfied",
128
+ outcome: "success",
129
+ actor: { userId: grantResult.payload.sub,
130
+ clientIp: requestHelpers.clientIp(req) },
131
+ metadata: {
132
+ reason: "grant",
133
+ jti: grantResult.payload.jti || null,
134
+ scope: grantResult.payload.scope,
135
+ route: req.url || null,
136
+ },
137
+ });
138
+ } catch (_e) { /* drop-silent */ }
139
+ }
140
+ if (req.user) req.user.stepUp = { byGrant: true, payload: grantResult.payload };
141
+ return next();
142
+ }
143
+ // Invalid grant — fall through to claims-based path; emit signal.
144
+ if (auditOn) {
145
+ try {
146
+ audit().safeEmit({
147
+ action: "auth.stepup.grant.rejected",
148
+ outcome: "denied",
149
+ actor: { clientIp: requestHelpers.clientIp(req) },
150
+ metadata: { error: grantResult.error, reason: grantResult.reason },
151
+ });
152
+ } catch (_e) { /* drop-silent */ }
153
+ }
154
+ }
155
+ }
156
+
157
+ // Path 2: claims-based evaluation.
158
+ var claims = getClaims(req);
159
+ var result = stepUp().evaluate({ claims: claims, requirement: opts.requirement });
160
+
161
+ if (result.ok) {
162
+ if (auditOn) stepUp().emitAuditSatisfied("requireStepUp", opts.requirement, result.presented, req);
163
+ if (req.user) req.user.stepUp = { byClaims: true, presented: result.presented };
164
+ return next();
165
+ }
166
+
167
+ if (auditOn) stepUp().emitAuditRequired("requireStepUp", opts.requirement, result.presented, req);
168
+
169
+ var challenge = stepUp().buildChallenge({
170
+ requirement: opts.requirement,
171
+ realm: realm,
172
+ error: stepUp().INSUFFICIENT_USER_AUTHENTICATION,
173
+ errorDescription: errorDesc || undefined,
174
+ });
175
+ _writeChallenge(res,
176
+ challenge,
177
+ {
178
+ error: stepUp().INSUFFICIENT_USER_AUTHENTICATION,
179
+ error_description: errorDesc || "A higher level of authentication is required",
180
+ },
181
+ 401 // allow:raw-byte-literal — HTTP 401
182
+ );
183
+ };
184
+ }
185
+
186
+ module.exports = { create: create };