@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.
- package/CHANGELOG.md +19 -1
- package/NOTICE +17 -1
- package/README.md +4 -3
- package/index.js +16 -0
- package/lib/asyncapi-bindings.js +160 -0
- package/lib/asyncapi-traits.js +143 -0
- package/lib/asyncapi.js +531 -0
- package/lib/audit.js +6 -0
- package/lib/auth/acr-vocabulary.js +265 -0
- package/lib/auth/auth-time-tracker.js +111 -0
- package/lib/auth/elevation-grant.js +306 -0
- package/lib/auth/sd-jwt-vc-disclosure.js +95 -0
- package/lib/auth/sd-jwt-vc-holder.js +203 -0
- package/lib/auth/sd-jwt-vc-issuer.js +197 -0
- package/lib/auth/sd-jwt-vc.js +526 -0
- package/lib/auth/step-up-policy.js +335 -0
- package/lib/auth/step-up.js +445 -0
- package/lib/compliance-ai-act-logging.js +186 -0
- package/lib/compliance-ai-act-prohibited.js +205 -0
- package/lib/compliance-ai-act-risk.js +189 -0
- package/lib/compliance-ai-act-transparency.js +200 -0
- package/lib/compliance-ai-act.js +558 -0
- package/lib/compliance.js +2 -0
- package/lib/crypto.js +32 -0
- package/lib/flag-cache.js +136 -0
- package/lib/flag-evaluation-context.js +135 -0
- package/lib/flag-providers.js +279 -0
- package/lib/flag-targeting.js +210 -0
- package/lib/flag.js +284 -0
- package/lib/inbox.js +367 -0
- package/lib/mail-arc-sign.js +372 -0
- package/lib/mail-auth.js +2 -0
- package/lib/middleware/ai-act-disclosure.js +166 -0
- package/lib/middleware/asyncapi-serve.js +136 -0
- package/lib/middleware/flag-context.js +76 -0
- package/lib/middleware/index.js +15 -0
- package/lib/middleware/openapi-serve.js +143 -0
- package/lib/middleware/require-step-up.js +186 -0
- package/lib/openapi-paths-builder.js +248 -0
- package/lib/openapi-schema-walk.js +192 -0
- package/lib/openapi-security.js +169 -0
- package/lib/openapi-yaml.js +154 -0
- package/lib/openapi.js +443 -0
- package/lib/pqc-software.js +195 -0
- package/lib/vault/index.js +3 -0
- package/lib/vault-aad.js +259 -0
- package/lib/vendor/MANIFEST.json +29 -0
- package/lib/vendor/noble-post-quantum.cjs +18 -0
- package/lib/ws-client.js +829 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/lib/asyncapi.js
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.asyncapi — AsyncAPI 3.0 schema-document builder.
|
|
4
|
+
*
|
|
5
|
+
* AsyncAPI is the event-driven sibling to OpenAPI. Operators describe
|
|
6
|
+
* pubsub / websocket / kafka / mqtt surfaces as a single document the
|
|
7
|
+
* framework can serve at /asyncapi.json (or /asyncapi.yaml) for
|
|
8
|
+
* downstream tooling.
|
|
9
|
+
*
|
|
10
|
+
* var aapi = b.asyncapi.create({
|
|
11
|
+
* info: { title: "Acme Events API", version: "1.0.0" },
|
|
12
|
+
* servers: {
|
|
13
|
+
* production: { host: "broker.acme.example.com:9092",
|
|
14
|
+
* protocol: "kafka",
|
|
15
|
+
* description: "Kafka broker" },
|
|
16
|
+
* },
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* aapi.channel("orders.created", {
|
|
20
|
+
* address: "orders.created",
|
|
21
|
+
* messages: {
|
|
22
|
+
* OrderCreated: {
|
|
23
|
+
* payload: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
|
|
24
|
+
* contentType: "application/json",
|
|
25
|
+
* },
|
|
26
|
+
* },
|
|
27
|
+
* bindings: { kafka: b.asyncapi.bindings.kafka({ topic: "orders.created", partitions: 4 }) },
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* aapi.operation("publishOrderCreated", {
|
|
31
|
+
* action: "send",
|
|
32
|
+
* channel: "orders.created",
|
|
33
|
+
* summary: "Publish an order-created event",
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* var doc = aapi.toJson(); // AsyncAPI 3.0 JSON document
|
|
37
|
+
* var yaml = aapi.toYaml(); // YAML serialisation
|
|
38
|
+
*
|
|
39
|
+
* The builder is FRAMEWORK-FACING: it produces a valid AsyncAPI 3.0
|
|
40
|
+
* document, but the operator's hand-written contract is the source of
|
|
41
|
+
* truth — it does NOT auto-walk b.pubsub topics or b.websocketChannels
|
|
42
|
+
* subscriptions (operators frequently want a smaller / different
|
|
43
|
+
* surface published than what is in-process).
|
|
44
|
+
*
|
|
45
|
+
* Public surface (b.asyncapi.*):
|
|
46
|
+
*
|
|
47
|
+
* .create({ info, servers, defaultContentType, security, externalDocs })
|
|
48
|
+
* -> builder
|
|
49
|
+
*
|
|
50
|
+
* builder.channel(channelId, opts) // register channel
|
|
51
|
+
* builder.operation(operationId, opts) // register operation
|
|
52
|
+
* builder.schema(name, schemaSpec) // reusable schema
|
|
53
|
+
* builder.message(name, messageSpec) // reusable message
|
|
54
|
+
* builder.security.add(name, scheme)
|
|
55
|
+
* builder.tag({ name, description })
|
|
56
|
+
* builder.toJson() / toJsonString() / toYaml()
|
|
57
|
+
*
|
|
58
|
+
* b.asyncapi.bindings.{websockets, kafka, amqp, mqtt, http}
|
|
59
|
+
* -> typed binding builders.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
var validateOpts = require("./validate-opts");
|
|
63
|
+
var lazyRequire = require("./lazy-require");
|
|
64
|
+
var schemaWalk = require("./openapi-schema-walk");
|
|
65
|
+
var openapiSecurity = require("./openapi-security");
|
|
66
|
+
var openapiYaml = require("./openapi-yaml");
|
|
67
|
+
var bindingsMod = require("./asyncapi-bindings");
|
|
68
|
+
var traitsMod = require("./asyncapi-traits");
|
|
69
|
+
var { defineClass } = require("./framework-error");
|
|
70
|
+
var AsyncApiError = defineClass("AsyncApiError", { alwaysPermanent: true });
|
|
71
|
+
|
|
72
|
+
var audit = lazyRequire(function () { return require("./audit"); });
|
|
73
|
+
|
|
74
|
+
var ASYNCAPI_VERSION = "3.0.0";
|
|
75
|
+
|
|
76
|
+
var VALID_OPERATION_ACTIONS = ["send", "receive"];
|
|
77
|
+
|
|
78
|
+
function create(opts) {
|
|
79
|
+
opts = opts || {};
|
|
80
|
+
validateOpts(opts, [
|
|
81
|
+
"info", "servers", "defaultContentType", "security",
|
|
82
|
+
"externalDocs", "tags", "id",
|
|
83
|
+
], "asyncapi.create");
|
|
84
|
+
if (!opts.info || typeof opts.info !== "object") {
|
|
85
|
+
throw new AsyncApiError("asyncapi/bad-info",
|
|
86
|
+
"create: info object is required (title + version)");
|
|
87
|
+
}
|
|
88
|
+
validateOpts.requireNonEmptyString(opts.info.title,
|
|
89
|
+
"create: info.title", AsyncApiError, "asyncapi/bad-info");
|
|
90
|
+
validateOpts.requireNonEmptyString(opts.info.version,
|
|
91
|
+
"create: info.version", AsyncApiError, "asyncapi/bad-info");
|
|
92
|
+
|
|
93
|
+
var info = Object.assign({}, opts.info);
|
|
94
|
+
var servers = _validateServers(opts.servers);
|
|
95
|
+
var channels = {};
|
|
96
|
+
var operations = {};
|
|
97
|
+
var components = {
|
|
98
|
+
schemas: {},
|
|
99
|
+
messages: {},
|
|
100
|
+
parameters: {},
|
|
101
|
+
securitySchemes: {},
|
|
102
|
+
serverVariables: {},
|
|
103
|
+
correlationIds: {},
|
|
104
|
+
operationTraits: {},
|
|
105
|
+
messageTraits: {},
|
|
106
|
+
replies: {},
|
|
107
|
+
replyAddresses: {},
|
|
108
|
+
};
|
|
109
|
+
var docTags = Array.isArray(opts.tags) ? opts.tags.slice() : [];
|
|
110
|
+
var docSecurity = Array.isArray(opts.security) ? opts.security.slice() : [];
|
|
111
|
+
var defaultContentType = (typeof opts.defaultContentType === "string" && opts.defaultContentType.length > 0)
|
|
112
|
+
? opts.defaultContentType : "application/json";
|
|
113
|
+
var externalDocs = opts.externalDocs || null;
|
|
114
|
+
var docId = opts.id || null;
|
|
115
|
+
|
|
116
|
+
function _addChannel(channelId, channelOpts) {
|
|
117
|
+
validateOpts.requireNonEmptyString(channelId, "channel: channelId",
|
|
118
|
+
AsyncApiError, "asyncapi/bad-channel");
|
|
119
|
+
if (Object.prototype.hasOwnProperty.call(channels, channelId)) {
|
|
120
|
+
throw new AsyncApiError("asyncapi/duplicate-channel",
|
|
121
|
+
"channel: " + channelId + " already registered");
|
|
122
|
+
}
|
|
123
|
+
channelOpts = channelOpts || {};
|
|
124
|
+
validateOpts(channelOpts, [
|
|
125
|
+
"address", "messages", "title", "summary", "description",
|
|
126
|
+
"servers", "parameters", "tags", "bindings", "externalDocs",
|
|
127
|
+
], "channel");
|
|
128
|
+
var ch = {};
|
|
129
|
+
if (channelOpts.address) ch.address = channelOpts.address;
|
|
130
|
+
if (channelOpts.title) ch.title = channelOpts.title;
|
|
131
|
+
if (channelOpts.summary) ch.summary = channelOpts.summary;
|
|
132
|
+
if (channelOpts.description) ch.description = channelOpts.description;
|
|
133
|
+
if (Array.isArray(channelOpts.servers)) ch.servers = channelOpts.servers.slice();
|
|
134
|
+
if (channelOpts.bindings) ch.bindings = channelOpts.bindings;
|
|
135
|
+
if (channelOpts.externalDocs) ch.externalDocs = channelOpts.externalDocs;
|
|
136
|
+
if (Array.isArray(channelOpts.tags)) ch.tags = channelOpts.tags.slice();
|
|
137
|
+
if (channelOpts.parameters && typeof channelOpts.parameters === "object") {
|
|
138
|
+
ch.parameters = {};
|
|
139
|
+
for (var pn in channelOpts.parameters) {
|
|
140
|
+
if (!Object.prototype.hasOwnProperty.call(channelOpts.parameters, pn)) continue;
|
|
141
|
+
ch.parameters[pn] = channelOpts.parameters[pn];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (channelOpts.messages && typeof channelOpts.messages === "object") {
|
|
145
|
+
ch.messages = {};
|
|
146
|
+
for (var mn in channelOpts.messages) {
|
|
147
|
+
if (!Object.prototype.hasOwnProperty.call(channelOpts.messages, mn)) continue;
|
|
148
|
+
ch.messages[mn] = _normaliseMessage(channelOpts.messages[mn],
|
|
149
|
+
"channel " + channelId + ".messages." + mn);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
channels[channelId] = ch;
|
|
153
|
+
return ch;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function _addOperation(operationId, opOpts) {
|
|
157
|
+
validateOpts.requireNonEmptyString(operationId, "operation: operationId",
|
|
158
|
+
AsyncApiError, "asyncapi/bad-operation");
|
|
159
|
+
if (Object.prototype.hasOwnProperty.call(operations, operationId)) {
|
|
160
|
+
throw new AsyncApiError("asyncapi/duplicate-operation",
|
|
161
|
+
"operation: " + operationId + " already registered");
|
|
162
|
+
}
|
|
163
|
+
opOpts = opOpts || {};
|
|
164
|
+
validateOpts(opOpts, [
|
|
165
|
+
"action", "channel", "messages", "summary", "description",
|
|
166
|
+
"tags", "bindings", "security", "externalDocs", "reply",
|
|
167
|
+
], "operation");
|
|
168
|
+
if (VALID_OPERATION_ACTIONS.indexOf(opOpts.action) === -1) {
|
|
169
|
+
throw new AsyncApiError("asyncapi/bad-operation",
|
|
170
|
+
"operation: action must be 'send' or 'receive' - got " +
|
|
171
|
+
JSON.stringify(opOpts.action));
|
|
172
|
+
}
|
|
173
|
+
validateOpts.requireNonEmptyString(opOpts.channel, "operation: channel",
|
|
174
|
+
AsyncApiError, "asyncapi/bad-operation");
|
|
175
|
+
if (!Object.prototype.hasOwnProperty.call(channels, opOpts.channel)) {
|
|
176
|
+
throw new AsyncApiError("asyncapi/dangling-channel",
|
|
177
|
+
"operation " + operationId + ": channel " + JSON.stringify(opOpts.channel) +
|
|
178
|
+
" is not registered (declare it via builder.channel() first)");
|
|
179
|
+
}
|
|
180
|
+
var op = {
|
|
181
|
+
action: opOpts.action,
|
|
182
|
+
channel: { "$ref": "#/channels/" + opOpts.channel },
|
|
183
|
+
};
|
|
184
|
+
if (opOpts.summary) op.summary = opOpts.summary;
|
|
185
|
+
if (opOpts.description) op.description = opOpts.description;
|
|
186
|
+
if (Array.isArray(opOpts.tags)) op.tags = opOpts.tags.slice();
|
|
187
|
+
if (opOpts.bindings) op.bindings = opOpts.bindings;
|
|
188
|
+
if (Array.isArray(opOpts.security)) op.security = opOpts.security.slice();
|
|
189
|
+
if (opOpts.externalDocs) op.externalDocs = opOpts.externalDocs;
|
|
190
|
+
if (opOpts.reply) op.reply = opOpts.reply;
|
|
191
|
+
if (Array.isArray(opOpts.messages) && opOpts.messages.length > 0) {
|
|
192
|
+
op.messages = opOpts.messages.map(function (m, idx) {
|
|
193
|
+
if (typeof m === "string") {
|
|
194
|
+
return { "$ref": "#/channels/" + opOpts.channel + "/messages/" + m };
|
|
195
|
+
}
|
|
196
|
+
if (m && typeof m === "object" && typeof m["$ref"] === "string") {
|
|
197
|
+
return { "$ref": m["$ref"] };
|
|
198
|
+
}
|
|
199
|
+
throw new AsyncApiError("asyncapi/bad-operation",
|
|
200
|
+
"operation " + operationId + ".messages[" + idx +
|
|
201
|
+
"]: must be a message name string or an object with $ref");
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
operations[operationId] = op;
|
|
205
|
+
return op;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function _normaliseMessage(input, label) {
|
|
209
|
+
if (!input || typeof input !== "object") {
|
|
210
|
+
throw new AsyncApiError("asyncapi/bad-message",
|
|
211
|
+
label + ": message must be an object");
|
|
212
|
+
}
|
|
213
|
+
var m = {};
|
|
214
|
+
if (input.name) m.name = input.name;
|
|
215
|
+
if (input.title) m.title = input.title;
|
|
216
|
+
if (input.summary) m.summary = input.summary;
|
|
217
|
+
if (input.description) m.description = input.description;
|
|
218
|
+
if (input.contentType) m.contentType = input.contentType;
|
|
219
|
+
if (input.headers != null) m.headers = schemaWalk.walk(input.headers);
|
|
220
|
+
if (input.payload != null) m.payload = schemaWalk.walk(input.payload);
|
|
221
|
+
if (input.correlationId) m.correlationId = input.correlationId;
|
|
222
|
+
if (input.bindings) m.bindings = input.bindings;
|
|
223
|
+
if (input.tags) m.tags = input.tags;
|
|
224
|
+
if (Array.isArray(input.examples)) m.examples = input.examples.slice();
|
|
225
|
+
if (input.traits) m.traits = input.traits;
|
|
226
|
+
if (input.externalDocs) m.externalDocs = input.externalDocs;
|
|
227
|
+
return m;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
info: info,
|
|
232
|
+
asyncapi: ASYNCAPI_VERSION,
|
|
233
|
+
|
|
234
|
+
channel: function (id, channelOpts) { _addChannel(id, channelOpts); return this; },
|
|
235
|
+
operation: function (id, opOpts) { _addOperation(id, opOpts); return this; },
|
|
236
|
+
|
|
237
|
+
schema: function (name, schemaSpec) {
|
|
238
|
+
validateOpts.requireNonEmptyString(name, "schema: name",
|
|
239
|
+
AsyncApiError, "asyncapi/bad-component");
|
|
240
|
+
if (Object.prototype.hasOwnProperty.call(components.schemas, name)) {
|
|
241
|
+
throw new AsyncApiError("asyncapi/duplicate-component",
|
|
242
|
+
"schema: components.schemas." + name + " already registered");
|
|
243
|
+
}
|
|
244
|
+
components.schemas[name] = schemaWalk.walk(schemaSpec);
|
|
245
|
+
return this;
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
message: function (name, messageSpec) {
|
|
249
|
+
validateOpts.requireNonEmptyString(name, "message: name",
|
|
250
|
+
AsyncApiError, "asyncapi/bad-component");
|
|
251
|
+
components.messages[name] = _normaliseMessage(messageSpec, "message: " + name);
|
|
252
|
+
return this;
|
|
253
|
+
},
|
|
254
|
+
|
|
255
|
+
parameter: function (name, paramSpec) {
|
|
256
|
+
validateOpts.requireNonEmptyString(name, "parameter: name",
|
|
257
|
+
AsyncApiError, "asyncapi/bad-component");
|
|
258
|
+
components.parameters[name] = paramSpec;
|
|
259
|
+
return this;
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
correlationId: function (name, idSpec) {
|
|
263
|
+
validateOpts.requireNonEmptyString(name, "correlationId: name",
|
|
264
|
+
AsyncApiError, "asyncapi/bad-component");
|
|
265
|
+
if (!idSpec || typeof idSpec !== "object" || typeof idSpec.location !== "string") {
|
|
266
|
+
throw new AsyncApiError("asyncapi/bad-correlation-id",
|
|
267
|
+
"correlationId: spec must be { location: 'runtime expression', description? }");
|
|
268
|
+
}
|
|
269
|
+
components.correlationIds[name] = idSpec;
|
|
270
|
+
return this;
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
security: {
|
|
274
|
+
add: function (name, scheme) {
|
|
275
|
+
validateOpts.requireNonEmptyString(name, "security.add: name",
|
|
276
|
+
AsyncApiError, "asyncapi/bad-security");
|
|
277
|
+
if (!scheme || typeof scheme !== "object" || typeof scheme.type !== "string") {
|
|
278
|
+
throw new AsyncApiError("asyncapi/bad-security",
|
|
279
|
+
"security.add: scheme must be a securityScheme object with a type");
|
|
280
|
+
}
|
|
281
|
+
components.securitySchemes[name] = scheme;
|
|
282
|
+
return this;
|
|
283
|
+
},
|
|
284
|
+
require: function (requirement) {
|
|
285
|
+
if (!requirement || typeof requirement !== "object") {
|
|
286
|
+
throw new AsyncApiError("asyncapi/bad-security",
|
|
287
|
+
"security.require: requirement must be an object like { schemeName: ['scope'] }");
|
|
288
|
+
}
|
|
289
|
+
docSecurity.push(requirement);
|
|
290
|
+
return this;
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
tag: function (tagSpec) {
|
|
295
|
+
if (!tagSpec || typeof tagSpec !== "object" ||
|
|
296
|
+
typeof tagSpec.name !== "string" || tagSpec.name.length === 0) {
|
|
297
|
+
throw new AsyncApiError("asyncapi/bad-tag",
|
|
298
|
+
"tag: tagSpec.name is required");
|
|
299
|
+
}
|
|
300
|
+
docTags.push(tagSpec);
|
|
301
|
+
return this;
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
server: function (serverId, serverSpec) {
|
|
305
|
+
validateOpts.requireNonEmptyString(serverId, "server: serverId",
|
|
306
|
+
AsyncApiError, "asyncapi/bad-server");
|
|
307
|
+
_validateServerEntry(serverSpec, "server " + serverId);
|
|
308
|
+
servers[serverId] = Object.assign({}, serverSpec);
|
|
309
|
+
return this;
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
toJson: function () {
|
|
313
|
+
var doc = {
|
|
314
|
+
asyncapi: ASYNCAPI_VERSION,
|
|
315
|
+
info: info,
|
|
316
|
+
};
|
|
317
|
+
if (docId) doc.id = docId;
|
|
318
|
+
if (defaultContentType) doc.defaultContentType = defaultContentType;
|
|
319
|
+
if (Object.keys(servers).length > 0) doc.servers = servers;
|
|
320
|
+
|
|
321
|
+
doc.channels = channels;
|
|
322
|
+
doc.operations = operations;
|
|
323
|
+
|
|
324
|
+
var anyComponent = false;
|
|
325
|
+
var componentsOut = {};
|
|
326
|
+
var keys = ["schemas", "messages", "parameters", "securitySchemes",
|
|
327
|
+
"serverVariables", "correlationIds", "operationTraits",
|
|
328
|
+
"messageTraits", "replies", "replyAddresses"];
|
|
329
|
+
for (var k = 0; k < keys.length; k += 1) {
|
|
330
|
+
var key = keys[k];
|
|
331
|
+
if (Object.keys(components[key]).length > 0) {
|
|
332
|
+
componentsOut[key] = components[key];
|
|
333
|
+
anyComponent = true;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (anyComponent) doc.components = componentsOut;
|
|
337
|
+
|
|
338
|
+
// Validate doc-level security references.
|
|
339
|
+
for (var r = 0; r < docSecurity.length; r += 1) {
|
|
340
|
+
for (var schemeName in docSecurity[r]) {
|
|
341
|
+
if (!Object.prototype.hasOwnProperty.call(docSecurity[r], schemeName)) continue;
|
|
342
|
+
if (!components.securitySchemes[schemeName]) {
|
|
343
|
+
throw new AsyncApiError("asyncapi/dangling-security",
|
|
344
|
+
"toJson: doc-level security references undefined scheme " +
|
|
345
|
+
JSON.stringify(schemeName));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// Operation-level security
|
|
350
|
+
for (var opId in operations) {
|
|
351
|
+
if (!Object.prototype.hasOwnProperty.call(operations, opId)) continue;
|
|
352
|
+
var opSec = operations[opId].security;
|
|
353
|
+
if (Array.isArray(opSec)) {
|
|
354
|
+
for (var os = 0; os < opSec.length; os += 1) {
|
|
355
|
+
for (var sn in opSec[os]) {
|
|
356
|
+
if (!Object.prototype.hasOwnProperty.call(opSec[os], sn)) continue;
|
|
357
|
+
if (!components.securitySchemes[sn]) {
|
|
358
|
+
throw new AsyncApiError("asyncapi/dangling-security",
|
|
359
|
+
"toJson: operation " + opId + " references undefined security scheme " +
|
|
360
|
+
JSON.stringify(sn));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (docSecurity.length > 0) doc.security = docSecurity.slice();
|
|
368
|
+
if (docTags.length > 0) doc.tags = docTags.slice();
|
|
369
|
+
if (externalDocs) doc.externalDocs = externalDocs;
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
audit().safeEmit({
|
|
373
|
+
action: "asyncapi.document.built",
|
|
374
|
+
outcome: "success",
|
|
375
|
+
actor: null,
|
|
376
|
+
metadata: {
|
|
377
|
+
title: info.title,
|
|
378
|
+
version: info.version,
|
|
379
|
+
channelCount: Object.keys(channels).length,
|
|
380
|
+
operationCount: Object.keys(operations).length,
|
|
381
|
+
schemaCount: Object.keys(components.schemas).length,
|
|
382
|
+
messageCount: Object.keys(components.messages).length,
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
} catch (_e) { /* drop-silent */ }
|
|
386
|
+
|
|
387
|
+
return doc;
|
|
388
|
+
},
|
|
389
|
+
|
|
390
|
+
toJsonString: function (indent) {
|
|
391
|
+
return JSON.stringify(this.toJson(), null, indent || 2);
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
toYaml: function () {
|
|
395
|
+
return openapiYaml.toYaml(this.toJson());
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function _validateServers(input) {
|
|
401
|
+
if (input == null) return {};
|
|
402
|
+
if (typeof input !== "object" || Array.isArray(input)) {
|
|
403
|
+
throw new AsyncApiError("asyncapi/bad-server",
|
|
404
|
+
"create: servers must be a map { serverId: spec, ... }");
|
|
405
|
+
}
|
|
406
|
+
var out = {};
|
|
407
|
+
for (var key in input) {
|
|
408
|
+
if (!Object.prototype.hasOwnProperty.call(input, key)) continue;
|
|
409
|
+
_validateServerEntry(input[key], "create: servers." + key);
|
|
410
|
+
out[key] = Object.assign({}, input[key]);
|
|
411
|
+
}
|
|
412
|
+
return out;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function _validateServerEntry(entry, label) {
|
|
416
|
+
if (!entry || typeof entry !== "object") {
|
|
417
|
+
throw new AsyncApiError("asyncapi/bad-server",
|
|
418
|
+
label + ": server must be an object");
|
|
419
|
+
}
|
|
420
|
+
validateOpts.requireNonEmptyString(entry.host,
|
|
421
|
+
label + ": host", AsyncApiError, "asyncapi/bad-server");
|
|
422
|
+
validateOpts.requireNonEmptyString(entry.protocol,
|
|
423
|
+
label + ": protocol", AsyncApiError, "asyncapi/bad-server");
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Parse + validate an external AsyncAPI 3.0 document. Like openapi.parse:
|
|
427
|
+
// returns `{ doc, errors[], valid }`. Throws on invalid JSON.
|
|
428
|
+
function parse(jsonStringOrObject) {
|
|
429
|
+
var doc;
|
|
430
|
+
if (typeof jsonStringOrObject === "string") {
|
|
431
|
+
try { doc = JSON.parse(jsonStringOrObject); } // allow:bare-json-parse — operator-supplied AsyncAPI doc; size-bounded by caller
|
|
432
|
+
catch (e) {
|
|
433
|
+
throw new AsyncApiError("asyncapi/bad-json",
|
|
434
|
+
"asyncapi.parse: invalid JSON — " + e.message);
|
|
435
|
+
}
|
|
436
|
+
} else if (jsonStringOrObject != null && typeof jsonStringOrObject === "object") {
|
|
437
|
+
doc = jsonStringOrObject;
|
|
438
|
+
} else {
|
|
439
|
+
throw new AsyncApiError("asyncapi/bad-input",
|
|
440
|
+
"asyncapi.parse: input must be a JSON string or a plain object");
|
|
441
|
+
}
|
|
442
|
+
var errors = [];
|
|
443
|
+
if (typeof doc.asyncapi !== "string") {
|
|
444
|
+
errors.push("missing or non-string `asyncapi` version field (must be 3.0.x)");
|
|
445
|
+
} else if (doc.asyncapi.indexOf("3.0") !== 0) {
|
|
446
|
+
errors.push("`asyncapi` version must be 3.0.x — got " + JSON.stringify(doc.asyncapi));
|
|
447
|
+
}
|
|
448
|
+
if (!doc.info || typeof doc.info !== "object") {
|
|
449
|
+
errors.push("missing or non-object `info`");
|
|
450
|
+
} else {
|
|
451
|
+
if (typeof doc.info.title !== "string" || doc.info.title.length === 0) {
|
|
452
|
+
errors.push("info.title must be a non-empty string");
|
|
453
|
+
}
|
|
454
|
+
if (typeof doc.info.version !== "string" || doc.info.version.length === 0) {
|
|
455
|
+
errors.push("info.version must be a non-empty string");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
// Channels must be an object when present.
|
|
459
|
+
if (doc.channels != null && typeof doc.channels !== "object") {
|
|
460
|
+
errors.push("`channels` must be an object when present");
|
|
461
|
+
}
|
|
462
|
+
// Operations must reference declared channels.
|
|
463
|
+
var channels = doc.channels || {};
|
|
464
|
+
if (doc.operations && typeof doc.operations === "object") {
|
|
465
|
+
for (var opId in doc.operations) {
|
|
466
|
+
if (!Object.prototype.hasOwnProperty.call(doc.operations, opId)) continue;
|
|
467
|
+
var op = doc.operations[opId];
|
|
468
|
+
if (!op || typeof op !== "object") {
|
|
469
|
+
errors.push("operations." + opId + ": must be an object");
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (op.action !== "send" && op.action !== "receive") {
|
|
473
|
+
errors.push("operations." + opId + ".action must be 'send' or 'receive' — got " +
|
|
474
|
+
JSON.stringify(op.action));
|
|
475
|
+
}
|
|
476
|
+
var channelRef = op.channel;
|
|
477
|
+
if (channelRef && typeof channelRef === "object" && typeof channelRef["$ref"] === "string") {
|
|
478
|
+
var refMatch = channelRef["$ref"].match(/^#\/channels\/(.+)$/);
|
|
479
|
+
if (refMatch && !Object.prototype.hasOwnProperty.call(channels, refMatch[1])) {
|
|
480
|
+
errors.push("operations." + opId + ".channel: $ref " +
|
|
481
|
+
JSON.stringify(channelRef["$ref"]) +
|
|
482
|
+
" does not resolve to a declared channel");
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
errors.push("operations." + opId + ".channel: must be a $ref to #/channels/<id>");
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// Server entries must carry host + protocol when present.
|
|
490
|
+
if (doc.servers && typeof doc.servers === "object") {
|
|
491
|
+
for (var sid in doc.servers) {
|
|
492
|
+
if (!Object.prototype.hasOwnProperty.call(doc.servers, sid)) continue;
|
|
493
|
+
var entry = doc.servers[sid];
|
|
494
|
+
if (!entry || typeof entry !== "object") {
|
|
495
|
+
errors.push("servers." + sid + ": must be an object");
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
if (typeof entry.host !== "string" || entry.host.length === 0) {
|
|
499
|
+
errors.push("servers." + sid + ".host must be a non-empty string");
|
|
500
|
+
}
|
|
501
|
+
if (typeof entry.protocol !== "string" || entry.protocol.length === 0) {
|
|
502
|
+
errors.push("servers." + sid + ".protocol must be a non-empty string");
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Dangling doc-level security references.
|
|
507
|
+
var schemes = (doc.components && doc.components.securitySchemes) || {};
|
|
508
|
+
if (Array.isArray(doc.security)) {
|
|
509
|
+
for (var s = 0; s < doc.security.length; s += 1) {
|
|
510
|
+
for (var sn in doc.security[s]) {
|
|
511
|
+
if (!Object.prototype.hasOwnProperty.call(doc.security[s], sn)) continue;
|
|
512
|
+
if (!schemes[sn]) {
|
|
513
|
+
errors.push("doc-level security references undefined scheme " + JSON.stringify(sn));
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return { doc: doc, errors: errors, valid: errors.length === 0 };
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
module.exports = {
|
|
522
|
+
create: create,
|
|
523
|
+
parse: parse,
|
|
524
|
+
bindings: bindingsMod,
|
|
525
|
+
traits: traitsMod,
|
|
526
|
+
schemaWalk: schemaWalk.walk,
|
|
527
|
+
security: openapiSecurity,
|
|
528
|
+
toYaml: openapiYaml.toYaml,
|
|
529
|
+
VERSION: ASYNCAPI_VERSION,
|
|
530
|
+
AsyncApiError: AsyncApiError,
|
|
531
|
+
};
|
package/lib/audit.js
CHANGED
|
@@ -216,6 +216,12 @@ var FRAMEWORK_NAMESPACES = [
|
|
|
216
216
|
"network", // b.middleware.networkAllowlist (network.gate.denied)
|
|
217
217
|
"notify", // b.notify
|
|
218
218
|
"objectstore", // b.objectStore.bucketOps (objectstore.bucket.* / objectstore.object.*)
|
|
219
|
+
"openapi", // b.openapi (openapi.document.built / openapi.document.served)
|
|
220
|
+
"asyncapi", // b.asyncapi (asyncapi.document.built)
|
|
221
|
+
"vault", // b.vault.aad (vault.aad.sealed / vault.aad.unseal_failed)
|
|
222
|
+
"wsclient", // b.wsClient (wsclient.connected / closed / error)
|
|
223
|
+
"inbox", // b.inbox (inbox.received / handled / handle_failed / swept)
|
|
224
|
+
"flag", // b.flag (flag.evaluated / flag.evaluation.error / flag.cache.bust)
|
|
219
225
|
"permissions", // b.permissions
|
|
220
226
|
"restore", // b.restore
|
|
221
227
|
"retention", // b.retention (retention.rule.declared / sweep.started / row.processed / sweep.completed / sweep.failed)
|