@blamejs/core 0.7.107 → 0.8.4
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 +41 -1
- package/NOTICE +17 -1
- package/README.md +4 -3
- package/index.js +15 -0
- package/lib/asyncapi-bindings.js +160 -0
- package/lib/asyncapi-traits.js +143 -0
- package/lib/asyncapi.js +531 -0
- package/lib/audit-sign.js +1 -1
- package/lib/audit.js +68 -2
- 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/jwt.js +13 -0
- package/lib/auth/lockout.js +16 -3
- package/lib/auth/oauth.js +15 -1
- package/lib/auth/password.js +22 -2
- package/lib/auth/sd-jwt-vc-issuer.js +2 -2
- package/lib/auth/sd-jwt-vc.js +7 -2
- package/lib/auth/step-up-policy.js +335 -0
- package/lib/auth/step-up.js +445 -0
- package/lib/break-glass.js +53 -14
- package/lib/cache-redis.js +1 -1
- package/lib/cache.js +6 -1
- package/lib/cli.js +3 -3
- package/lib/cluster.js +24 -1
- package/lib/compliance-ai-act-logging.js +190 -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 +12 -2
- package/lib/config-drift.js +2 -2
- package/lib/crypto-field.js +21 -1
- package/lib/crypto.js +114 -1
- package/lib/db.js +35 -4
- package/lib/dev.js +30 -3
- package/lib/dual-control.js +19 -1
- package/lib/external-db.js +10 -0
- package/lib/file-upload.js +30 -3
- 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/guard-all.js +33 -16
- package/lib/guard-csv.js +16 -2
- package/lib/guard-html.js +35 -0
- package/lib/guard-svg.js +20 -0
- package/lib/http-client.js +57 -11
- package/lib/inbox.js +391 -0
- package/lib/log-stream-syslog.js +8 -0
- package/lib/log-stream.js +1 -1
- package/lib/mail-arc-sign.js +372 -0
- package/lib/mail-auth.js +2 -0
- package/lib/mail.js +40 -0
- package/lib/middleware/ai-act-disclosure.js +166 -0
- package/lib/middleware/asyncapi-serve.js +136 -0
- package/lib/middleware/attach-user.js +25 -2
- package/lib/middleware/bearer-auth.js +71 -6
- package/lib/middleware/body-parser.js +13 -0
- package/lib/middleware/cors.js +10 -0
- package/lib/middleware/csrf-protect.js +34 -3
- package/lib/middleware/dpop.js +3 -3
- package/lib/middleware/flag-context.js +76 -0
- package/lib/middleware/host-allowlist.js +1 -1
- package/lib/middleware/index.js +15 -0
- package/lib/middleware/openapi-serve.js +143 -0
- package/lib/middleware/require-aal.js +2 -2
- package/lib/middleware/require-step-up.js +186 -0
- package/lib/middleware/trace-propagate.js +1 -1
- package/lib/mtls-ca.js +23 -29
- package/lib/mtls-engine-default.js +21 -1
- package/lib/network-tls.js +21 -6
- package/lib/object-store/sigv4-bucket-ops.js +41 -0
- package/lib/observability-otlp-exporter.js +35 -2
- 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/outbox.js +3 -3
- package/lib/permissions.js +10 -1
- package/lib/pqc-agent.js +22 -1
- package/lib/pqc-software.js +195 -0
- package/lib/pubsub.js +8 -4
- package/lib/redact.js +26 -1
- package/lib/retention.js +26 -0
- package/lib/router.js +1 -0
- package/lib/scheduler.js +57 -1
- package/lib/session.js +3 -3
- package/lib/ssrf-guard.js +19 -4
- package/lib/static.js +12 -0
- package/lib/totp.js +16 -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 +978 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAPI 3.1 — paths / operations builder.
|
|
4
|
+
*
|
|
5
|
+
* Internal to lib/openapi.js. Holds the per-path operation table
|
|
6
|
+
* (method to operationObject) and produces the final `paths` map used
|
|
7
|
+
* by the document builder.
|
|
8
|
+
*
|
|
9
|
+
* Path keys MUST start with `/` per OpenAPI 3.1 §4.8. Path templates
|
|
10
|
+
* use `{name}` placeholders that bind to declared `parameters` of
|
|
11
|
+
* `in: path`. The builder validates that every `{name}` placeholder
|
|
12
|
+
* has a matching declared parameter at build-time.
|
|
13
|
+
*
|
|
14
|
+
* Operation methods accepted: get / put / post / delete / options /
|
|
15
|
+
* head / patch / trace (RFC 9110 + OpenAPI 3.1 §4.8.5).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
var validateOpts = require("./validate-opts");
|
|
19
|
+
var schemaWalk = require("./openapi-schema-walk");
|
|
20
|
+
var { defineClass } = require("./framework-error");
|
|
21
|
+
var OpenApiError = defineClass("OpenApiError", { alwaysPermanent: true });
|
|
22
|
+
|
|
23
|
+
var VALID_METHODS = ["get", "put", "post", "delete",
|
|
24
|
+
"options", "head", "patch", "trace"];
|
|
25
|
+
|
|
26
|
+
// Path-template parameter extraction — each `{name}` is a path param.
|
|
27
|
+
function _extractPathParams(pathTemplate) {
|
|
28
|
+
var out = [];
|
|
29
|
+
var pattern = /\{([a-zA-Z_][a-zA-Z0-9_-]*)\}/g;
|
|
30
|
+
var matched = pattern.exec(pathTemplate);
|
|
31
|
+
while (matched !== null) {
|
|
32
|
+
out.push(matched[1]);
|
|
33
|
+
matched = pattern.exec(pathTemplate);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function PathsBuilder() {
|
|
39
|
+
this._paths = {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
PathsBuilder.prototype.add = function (method, urlPattern, opts) {
|
|
43
|
+
opts = opts || {};
|
|
44
|
+
if (typeof method !== "string" || VALID_METHODS.indexOf(method.toLowerCase()) === -1) {
|
|
45
|
+
throw new OpenApiError("openapi/bad-method",
|
|
46
|
+
"paths.add: method must be one of " + VALID_METHODS.join(", ") +
|
|
47
|
+
" - got " + JSON.stringify(method));
|
|
48
|
+
}
|
|
49
|
+
validateOpts.requireNonEmptyString(urlPattern, "paths.add: urlPattern",
|
|
50
|
+
OpenApiError, "openapi/bad-path");
|
|
51
|
+
if (urlPattern.charAt(0) !== "/") {
|
|
52
|
+
throw new OpenApiError("openapi/bad-path",
|
|
53
|
+
"paths.add: urlPattern must start with '/' - got " +
|
|
54
|
+
JSON.stringify(urlPattern));
|
|
55
|
+
}
|
|
56
|
+
validateOpts(opts, [
|
|
57
|
+
"summary", "description", "operationId", "tags",
|
|
58
|
+
"parameters", "requestBody", "responses",
|
|
59
|
+
"security", "deprecated", "servers", "externalDocs",
|
|
60
|
+
], "paths.add");
|
|
61
|
+
|
|
62
|
+
var op = {};
|
|
63
|
+
if (typeof opts.summary === "string") op.summary = opts.summary;
|
|
64
|
+
if (typeof opts.description === "string") op.description = opts.description;
|
|
65
|
+
if (typeof opts.operationId === "string") op.operationId = opts.operationId;
|
|
66
|
+
if (Array.isArray(opts.tags) && opts.tags.length > 0) {
|
|
67
|
+
op.tags = opts.tags.map(function (t) {
|
|
68
|
+
if (typeof t !== "string" || t.length === 0) {
|
|
69
|
+
throw new OpenApiError("openapi/bad-tag",
|
|
70
|
+
"paths.add: tags must be non-empty strings");
|
|
71
|
+
}
|
|
72
|
+
return t;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Parameters
|
|
77
|
+
var declaredPathParams = Object.create(null);
|
|
78
|
+
if (Array.isArray(opts.parameters)) {
|
|
79
|
+
op.parameters = [];
|
|
80
|
+
for (var i = 0; i < opts.parameters.length; i += 1) {
|
|
81
|
+
var p = _normaliseParameter(opts.parameters[i], "paths.add: parameters[" + i + "]");
|
|
82
|
+
op.parameters.push(p);
|
|
83
|
+
if (p.in === "path") declaredPathParams[p.name] = true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Verify every {placeholder} in path is declared.
|
|
87
|
+
var placeholders = _extractPathParams(urlPattern);
|
|
88
|
+
for (var j = 0; j < placeholders.length; j += 1) {
|
|
89
|
+
if (!declaredPathParams[placeholders[j]]) {
|
|
90
|
+
throw new OpenApiError("openapi/missing-path-param",
|
|
91
|
+
"paths.add: path template " + JSON.stringify(urlPattern) +
|
|
92
|
+
" references {" + placeholders[j] +
|
|
93
|
+
"} but no parameter with in=path name=" + JSON.stringify(placeholders[j]) +
|
|
94
|
+
" was declared");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Request body
|
|
99
|
+
if (opts.requestBody) {
|
|
100
|
+
op.requestBody = _normaliseRequestBody(opts.requestBody, "paths.add: requestBody");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Responses (required)
|
|
104
|
+
if (!opts.responses || typeof opts.responses !== "object") {
|
|
105
|
+
throw new OpenApiError("openapi/missing-responses",
|
|
106
|
+
"paths.add: responses object is required (per OpenAPI 3.1 §4.8.5)");
|
|
107
|
+
}
|
|
108
|
+
op.responses = _normaliseResponses(opts.responses, "paths.add: responses");
|
|
109
|
+
|
|
110
|
+
if (Array.isArray(opts.security)) op.security = opts.security.slice();
|
|
111
|
+
if (opts.deprecated === true) op.deprecated = true;
|
|
112
|
+
if (Array.isArray(opts.servers)) op.servers = opts.servers.slice();
|
|
113
|
+
if (opts.externalDocs) op.externalDocs = opts.externalDocs;
|
|
114
|
+
|
|
115
|
+
if (!this._paths[urlPattern]) this._paths[urlPattern] = {};
|
|
116
|
+
if (this._paths[urlPattern][method.toLowerCase()]) {
|
|
117
|
+
throw new OpenApiError("openapi/duplicate-operation",
|
|
118
|
+
"paths.add: duplicate operation " + method.toUpperCase() + " " + urlPattern);
|
|
119
|
+
}
|
|
120
|
+
this._paths[urlPattern][method.toLowerCase()] = op;
|
|
121
|
+
return op;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
function _normaliseParameter(input, label) {
|
|
125
|
+
if (!input || typeof input !== "object") {
|
|
126
|
+
throw new OpenApiError("openapi/bad-parameter",
|
|
127
|
+
label + ": parameter must be an object");
|
|
128
|
+
}
|
|
129
|
+
validateOpts.requireNonEmptyString(input.name, label + ": name",
|
|
130
|
+
OpenApiError, "openapi/bad-parameter");
|
|
131
|
+
var validIn = ["path", "query", "header", "cookie"];
|
|
132
|
+
if (validIn.indexOf(input.in) === -1) {
|
|
133
|
+
throw new OpenApiError("openapi/bad-parameter",
|
|
134
|
+
label + ": in must be one of " + validIn.join(", ") +
|
|
135
|
+
" - got " + JSON.stringify(input.in));
|
|
136
|
+
}
|
|
137
|
+
if (input.in === "path" && input.required !== true) {
|
|
138
|
+
throw new OpenApiError("openapi/bad-parameter",
|
|
139
|
+
label + ": path parameter " + JSON.stringify(input.name) +
|
|
140
|
+
" must have required=true (per OpenAPI 3.1 §4.8.10)");
|
|
141
|
+
}
|
|
142
|
+
var p = {
|
|
143
|
+
name: input.name,
|
|
144
|
+
in: input.in,
|
|
145
|
+
};
|
|
146
|
+
if (typeof input.description === "string") p.description = input.description;
|
|
147
|
+
if (input.required === true) p.required = true;
|
|
148
|
+
if (input.deprecated === true) p.deprecated = true;
|
|
149
|
+
if (input.allowEmptyValue === true) p.allowEmptyValue = true;
|
|
150
|
+
if (input.schema != null) {
|
|
151
|
+
p.schema = schemaWalk.walk(input.schema);
|
|
152
|
+
}
|
|
153
|
+
if (input.example != null) p.example = input.example;
|
|
154
|
+
return p;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function _normaliseRequestBody(input, label) {
|
|
158
|
+
if (!input || typeof input !== "object") {
|
|
159
|
+
throw new OpenApiError("openapi/bad-request-body",
|
|
160
|
+
label + ": requestBody must be an object");
|
|
161
|
+
}
|
|
162
|
+
if (!input.content || typeof input.content !== "object") {
|
|
163
|
+
throw new OpenApiError("openapi/bad-request-body",
|
|
164
|
+
label + ": content map required (e.g. { 'application/json': { schema: ... } })");
|
|
165
|
+
}
|
|
166
|
+
var out = { content: {} };
|
|
167
|
+
if (typeof input.description === "string") out.description = input.description;
|
|
168
|
+
if (input.required === true) out.required = true;
|
|
169
|
+
for (var ct in input.content) {
|
|
170
|
+
if (!Object.prototype.hasOwnProperty.call(input.content, ct)) continue;
|
|
171
|
+
var entry = input.content[ct];
|
|
172
|
+
if (!entry || typeof entry !== "object") {
|
|
173
|
+
throw new OpenApiError("openapi/bad-request-body",
|
|
174
|
+
label + ": content[" + JSON.stringify(ct) + "] must be an object");
|
|
175
|
+
}
|
|
176
|
+
var ce = {};
|
|
177
|
+
if (entry.schema != null) ce.schema = schemaWalk.walk(entry.schema);
|
|
178
|
+
if (entry.example != null) ce.example = entry.example;
|
|
179
|
+
if (entry.examples != null) ce.examples = entry.examples;
|
|
180
|
+
if (entry.encoding != null) ce.encoding = entry.encoding;
|
|
181
|
+
out.content[ct] = ce;
|
|
182
|
+
}
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function _normaliseResponses(input, label) {
|
|
187
|
+
var out = {};
|
|
188
|
+
var statusKeys = Object.keys(input);
|
|
189
|
+
if (statusKeys.length === 0) {
|
|
190
|
+
throw new OpenApiError("openapi/missing-responses",
|
|
191
|
+
label + ": at least one response required");
|
|
192
|
+
}
|
|
193
|
+
for (var i = 0; i < statusKeys.length; i += 1) {
|
|
194
|
+
var status = statusKeys[i];
|
|
195
|
+
var resp = input[status];
|
|
196
|
+
if (!resp || typeof resp !== "object") {
|
|
197
|
+
throw new OpenApiError("openapi/bad-response",
|
|
198
|
+
label + "[" + status + "]: response must be an object");
|
|
199
|
+
}
|
|
200
|
+
var r = {};
|
|
201
|
+
if (typeof resp.description === "string") {
|
|
202
|
+
r.description = resp.description;
|
|
203
|
+
} else {
|
|
204
|
+
throw new OpenApiError("openapi/missing-response-description",
|
|
205
|
+
label + "[" + status + "]: description is required (per OpenAPI 3.1 §4.8.16)");
|
|
206
|
+
}
|
|
207
|
+
if (resp.headers != null) r.headers = resp.headers;
|
|
208
|
+
if (resp.content != null) {
|
|
209
|
+
r.content = {};
|
|
210
|
+
for (var ct in resp.content) {
|
|
211
|
+
if (!Object.prototype.hasOwnProperty.call(resp.content, ct)) continue;
|
|
212
|
+
var entry = resp.content[ct];
|
|
213
|
+
if (!entry || typeof entry !== "object") continue;
|
|
214
|
+
var ce = {};
|
|
215
|
+
if (entry.schema != null) ce.schema = schemaWalk.walk(entry.schema);
|
|
216
|
+
if (entry.example != null) ce.example = entry.example;
|
|
217
|
+
if (entry.examples != null) ce.examples = entry.examples;
|
|
218
|
+
r.content[ct] = ce;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (resp.links != null) r.links = resp.links;
|
|
222
|
+
out[status] = r;
|
|
223
|
+
}
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
PathsBuilder.prototype.toMap = function () {
|
|
228
|
+
var sorted = Object.keys(this._paths).sort();
|
|
229
|
+
var out = {};
|
|
230
|
+
for (var i = 0; i < sorted.length; i += 1) {
|
|
231
|
+
var pathKey = sorted[i];
|
|
232
|
+
var pathItem = this._paths[pathKey];
|
|
233
|
+
var ordered = {};
|
|
234
|
+
for (var j = 0; j < VALID_METHODS.length; j += 1) {
|
|
235
|
+
var method = VALID_METHODS[j];
|
|
236
|
+
if (pathItem[method]) ordered[method] = pathItem[method];
|
|
237
|
+
}
|
|
238
|
+
out[pathKey] = ordered;
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
module.exports = {
|
|
244
|
+
PathsBuilder: PathsBuilder,
|
|
245
|
+
VALID_METHODS: VALID_METHODS,
|
|
246
|
+
_extractPathParams: _extractPathParams,
|
|
247
|
+
OpenApiError: OpenApiError,
|
|
248
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAPI 3.1 — converts a b.safeSchema description into the JSON
|
|
4
|
+
* Schema dialect that OpenAPI 3.1 uses (which IS JSON Schema 2020-12
|
|
5
|
+
* proper, not the OpenAPI 3.0 fork). Operators pass either:
|
|
6
|
+
*
|
|
7
|
+
* - A b.safeSchema object (with the framework's `.parse / .optional /
|
|
8
|
+
* ...` interface) — we walk the spec to produce JSON Schema.
|
|
9
|
+
*
|
|
10
|
+
* - A plain JSON Schema object — passes through unchanged (just
|
|
11
|
+
* validated for shape so a typo in the operator's hand-written
|
|
12
|
+
* schema fails at build-time, not at consumer-time).
|
|
13
|
+
*
|
|
14
|
+
* - A primitive type name like "string" / "integer" / "boolean" —
|
|
15
|
+
* translated to the corresponding JSON Schema scalar.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
var validateOpts = require("./validate-opts");
|
|
19
|
+
var { defineClass } = require("./framework-error");
|
|
20
|
+
var OpenApiError = defineClass("OpenApiError", { alwaysPermanent: true });
|
|
21
|
+
|
|
22
|
+
var TYPE_KEYWORDS = ["type", "properties", "items", "enum", "const",
|
|
23
|
+
"format", "pattern", "anyOf", "oneOf", "allOf",
|
|
24
|
+
"not", "$ref", "$id"];
|
|
25
|
+
|
|
26
|
+
function _isPlainObject(v) {
|
|
27
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function _isJsonSchemaShape(v) {
|
|
31
|
+
if (!_isPlainObject(v)) return false;
|
|
32
|
+
for (var i = 0; i < TYPE_KEYWORDS.length; i += 1) {
|
|
33
|
+
if (Object.prototype.hasOwnProperty.call(v, TYPE_KEYWORDS[i])) return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function _isSafeSchema(v) {
|
|
39
|
+
return !!v && typeof v === "object" &&
|
|
40
|
+
typeof v.parse === "function" && typeof v._kind === "string";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function _safeSchemaToJsonSchema(schema) {
|
|
44
|
+
var out = {};
|
|
45
|
+
switch (schema._kind) {
|
|
46
|
+
case "string":
|
|
47
|
+
out.type = "string";
|
|
48
|
+
if (typeof schema._minLength === "number") out.minLength = schema._minLength;
|
|
49
|
+
if (typeof schema._maxLength === "number") out.maxLength = schema._maxLength;
|
|
50
|
+
if (schema._format) out.format = schema._format;
|
|
51
|
+
if (schema._regex && schema._regex.source) out.pattern = schema._regex.source;
|
|
52
|
+
break;
|
|
53
|
+
case "number":
|
|
54
|
+
case "integer":
|
|
55
|
+
out.type = (schema._kind === "integer") ? "integer" : "number";
|
|
56
|
+
if (typeof schema._min === "number") out.minimum = schema._min;
|
|
57
|
+
if (typeof schema._max === "number") out.maximum = schema._max;
|
|
58
|
+
if (schema._isInt === true) out.type = "integer";
|
|
59
|
+
break;
|
|
60
|
+
case "boolean":
|
|
61
|
+
out.type = "boolean";
|
|
62
|
+
break;
|
|
63
|
+
case "literal":
|
|
64
|
+
out.const = schema._value;
|
|
65
|
+
break;
|
|
66
|
+
case "enum":
|
|
67
|
+
out.enum = (schema._values || []).slice();
|
|
68
|
+
break;
|
|
69
|
+
case "null":
|
|
70
|
+
out.type = "null";
|
|
71
|
+
break;
|
|
72
|
+
case "array":
|
|
73
|
+
out.type = "array";
|
|
74
|
+
if (schema._element != null) out.items = walk(schema._element);
|
|
75
|
+
if (typeof schema._minItems === "number") out.minItems = schema._minItems;
|
|
76
|
+
if (typeof schema._maxItems === "number") out.maxItems = schema._maxItems;
|
|
77
|
+
break;
|
|
78
|
+
case "object":
|
|
79
|
+
out.type = "object";
|
|
80
|
+
out.properties = {};
|
|
81
|
+
var requiredList = [];
|
|
82
|
+
var shape = schema.shape || schema._shape || {};
|
|
83
|
+
for (var key in shape) {
|
|
84
|
+
if (Object.prototype.hasOwnProperty.call(shape, key)) {
|
|
85
|
+
var childSchema = shape[key];
|
|
86
|
+
out.properties[key] = walk(childSchema);
|
|
87
|
+
if (!childSchema._isOptional) {
|
|
88
|
+
requiredList.push(key);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (requiredList.length > 0) out.required = requiredList;
|
|
93
|
+
out.additionalProperties = (schema._mode === "passthrough") ? true : false;
|
|
94
|
+
break;
|
|
95
|
+
case "record":
|
|
96
|
+
out.type = "object";
|
|
97
|
+
if (schema._valueSchema) out.additionalProperties = walk(schema._valueSchema);
|
|
98
|
+
break;
|
|
99
|
+
case "union":
|
|
100
|
+
out.oneOf = (schema._options || []).map(walk);
|
|
101
|
+
break;
|
|
102
|
+
case "any":
|
|
103
|
+
case "unknown":
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
if (schema._description) out.description = schema._description;
|
|
109
|
+
if (schema._example != null) out.example = schema._example;
|
|
110
|
+
if (schema._isNullable === true && typeof out.type === "string") {
|
|
111
|
+
out.type = [out.type, "null"];
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function walk(input) {
|
|
117
|
+
if (input == null) return {};
|
|
118
|
+
if (typeof input === "string") {
|
|
119
|
+
return { type: input };
|
|
120
|
+
}
|
|
121
|
+
if (_isSafeSchema(input)) {
|
|
122
|
+
return _safeSchemaToJsonSchema(input);
|
|
123
|
+
}
|
|
124
|
+
if (_isJsonSchemaShape(input)) {
|
|
125
|
+
return _cloneJsonSchema(input);
|
|
126
|
+
}
|
|
127
|
+
if (_isPlainObject(input)) {
|
|
128
|
+
// Hand-shaped object — operator passed a JSON-Schema-like object
|
|
129
|
+
// without a recognised keyword; clone as-is and let the spec
|
|
130
|
+
// validation in build() flag it.
|
|
131
|
+
return _cloneJsonSchema(input);
|
|
132
|
+
}
|
|
133
|
+
throw new OpenApiError("openapi/bad-schema",
|
|
134
|
+
"schema-walk: unsupported schema input — got " + typeof input);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function _cloneJsonSchema(obj) {
|
|
138
|
+
if (obj == null || typeof obj !== "object") return obj;
|
|
139
|
+
if (Array.isArray(obj)) {
|
|
140
|
+
var arrOut = [];
|
|
141
|
+
for (var i = 0; i < obj.length; i += 1) {
|
|
142
|
+
arrOut.push(_cloneJsonSchema(obj[i]));
|
|
143
|
+
}
|
|
144
|
+
return arrOut;
|
|
145
|
+
}
|
|
146
|
+
var out = {};
|
|
147
|
+
for (var key in obj) {
|
|
148
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
149
|
+
out[key] = _cloneJsonSchema(obj[key]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Validates a JSON Schema document for the most common authoring
|
|
156
|
+
// errors. Used at build-time so a typo doesn't reach the spec
|
|
157
|
+
// consumer. Not a full JSON-Schema-vocabulary validator — that's
|
|
158
|
+
// outside scope; operators with strict needs run a downstream
|
|
159
|
+
// validator like ajv against the emitted document.
|
|
160
|
+
function validateJsonSchema(schema, label) {
|
|
161
|
+
validateOpts.requireNonEmptyString(label || "schema", "validateJsonSchema: label",
|
|
162
|
+
OpenApiError, "openapi/bad-schema");
|
|
163
|
+
if (!_isPlainObject(schema)) {
|
|
164
|
+
throw new OpenApiError("openapi/bad-schema",
|
|
165
|
+
label + ": schema must be a plain object");
|
|
166
|
+
}
|
|
167
|
+
if (typeof schema.type === "string") {
|
|
168
|
+
var validTypes = ["string", "number", "integer", "boolean",
|
|
169
|
+
"object", "array", "null"];
|
|
170
|
+
if (validTypes.indexOf(schema.type) === -1) {
|
|
171
|
+
throw new OpenApiError("openapi/bad-schema",
|
|
172
|
+
label + ": invalid type " + JSON.stringify(schema.type));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (Array.isArray(schema.type)) {
|
|
176
|
+
for (var i = 0; i < schema.type.length; i += 1) {
|
|
177
|
+
if (typeof schema.type[i] !== "string") {
|
|
178
|
+
throw new OpenApiError("openapi/bad-schema",
|
|
179
|
+
label + ": type[" + i + "] must be a string");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = {
|
|
187
|
+
walk: walk,
|
|
188
|
+
validateJsonSchema: validateJsonSchema,
|
|
189
|
+
_isSafeSchema: _isSafeSchema,
|
|
190
|
+
_isJsonSchemaShape: _isJsonSchemaShape,
|
|
191
|
+
OpenApiError: OpenApiError,
|
|
192
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAPI 3.1 — security-scheme builders.
|
|
4
|
+
*
|
|
5
|
+
* Each helper returns a JSON-Schema-compatible securityScheme object
|
|
6
|
+
* the OpenAPI builder slots under `components.securitySchemes`.
|
|
7
|
+
* Operators reference them by name in `security` requirements:
|
|
8
|
+
*
|
|
9
|
+
* var openapi = b.openapi.create({ ... });
|
|
10
|
+
* openapi.security.add("bearerJwt", b.openapi.security.bearer({ jwtBearer: true }));
|
|
11
|
+
* openapi.path("get", "/me", { security: [{ bearerJwt: [] }] });
|
|
12
|
+
*
|
|
13
|
+
* Helpers cover every IANA-registered scheme operators reach for:
|
|
14
|
+
*
|
|
15
|
+
* .bearer({ jwtBearer? }) → Bearer token
|
|
16
|
+
* .basic() → HTTP Basic auth
|
|
17
|
+
* .apiKey({ name, in }) → API key in header / query / cookie
|
|
18
|
+
* .oauth2({ flows }) → OAuth2 with AuthCode / ClientCreds /
|
|
19
|
+
* Implicit / Password flow specs
|
|
20
|
+
* .openIdConnect({ url }) → OIDC discovery URL
|
|
21
|
+
* .mtls() → mutual TLS (RFC 8705)
|
|
22
|
+
* .dpop() → DPoP-bound bearer (RFC 9449)
|
|
23
|
+
*
|
|
24
|
+
* The helpers throw at config-time on bad opts (config-time-throw discipline) so
|
|
25
|
+
* an operator's typo in the OpenAPI document fails at the build step,
|
|
26
|
+
* not at consumer-validation time.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
var validateOpts = require("./validate-opts");
|
|
30
|
+
var { defineClass } = require("./framework-error");
|
|
31
|
+
var OpenApiError = defineClass("OpenApiError", { alwaysPermanent: true });
|
|
32
|
+
|
|
33
|
+
function bearer(opts) {
|
|
34
|
+
opts = opts || {};
|
|
35
|
+
validateOpts(opts, ["jwtBearer", "description"], "openapi.security.bearer");
|
|
36
|
+
var out = {
|
|
37
|
+
type: "http",
|
|
38
|
+
scheme: "bearer",
|
|
39
|
+
};
|
|
40
|
+
if (opts.jwtBearer === true) out.bearerFormat = "JWT";
|
|
41
|
+
if (typeof opts.description === "string" && opts.description.length > 0) {
|
|
42
|
+
out.description = opts.description;
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function basic(opts) {
|
|
48
|
+
opts = opts || {};
|
|
49
|
+
validateOpts(opts, ["description"], "openapi.security.basic");
|
|
50
|
+
var out = { type: "http", scheme: "basic" };
|
|
51
|
+
if (typeof opts.description === "string" && opts.description.length > 0) {
|
|
52
|
+
out.description = opts.description;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function apiKey(opts) {
|
|
58
|
+
opts = opts || {};
|
|
59
|
+
validateOpts(opts, ["name", "in", "description"], "openapi.security.apiKey");
|
|
60
|
+
validateOpts.requireNonEmptyString(opts.name, "apiKey: name",
|
|
61
|
+
OpenApiError, "openapi/bad-security");
|
|
62
|
+
var validIn = ["header", "query", "cookie"];
|
|
63
|
+
if (validIn.indexOf(opts.in) === -1) {
|
|
64
|
+
throw new OpenApiError("openapi/bad-security",
|
|
65
|
+
"apiKey: in must be one of " + validIn.join(", ") +
|
|
66
|
+
" — got " + JSON.stringify(opts.in));
|
|
67
|
+
}
|
|
68
|
+
var out = { type: "apiKey", name: opts.name, in: opts.in };
|
|
69
|
+
if (typeof opts.description === "string" && opts.description.length > 0) {
|
|
70
|
+
out.description = opts.description;
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function _validateFlow(name, flow) {
|
|
76
|
+
if (!flow || typeof flow !== "object") {
|
|
77
|
+
throw new OpenApiError("openapi/bad-security",
|
|
78
|
+
"oauth2." + name + ": flow must be an object");
|
|
79
|
+
}
|
|
80
|
+
if (typeof flow.scopes !== "object" || flow.scopes == null) {
|
|
81
|
+
throw new OpenApiError("openapi/bad-security",
|
|
82
|
+
"oauth2." + name + ": scopes must be an object (scopeName -> description)");
|
|
83
|
+
}
|
|
84
|
+
if (name === "authorizationCode" || name === "implicit") {
|
|
85
|
+
validateOpts.requireNonEmptyString(flow.authorizationUrl,
|
|
86
|
+
"oauth2." + name + ": authorizationUrl",
|
|
87
|
+
OpenApiError, "openapi/bad-security");
|
|
88
|
+
}
|
|
89
|
+
if (name === "authorizationCode" || name === "password" || name === "clientCredentials") {
|
|
90
|
+
validateOpts.requireNonEmptyString(flow.tokenUrl,
|
|
91
|
+
"oauth2." + name + ": tokenUrl",
|
|
92
|
+
OpenApiError, "openapi/bad-security");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function oauth2(opts) {
|
|
97
|
+
opts = opts || {};
|
|
98
|
+
validateOpts(opts, ["flows", "description"], "openapi.security.oauth2");
|
|
99
|
+
if (!opts.flows || typeof opts.flows !== "object") {
|
|
100
|
+
throw new OpenApiError("openapi/bad-security",
|
|
101
|
+
"oauth2: flows must be an object — at least one of authorizationCode / clientCredentials / implicit / password");
|
|
102
|
+
}
|
|
103
|
+
var validFlowNames = ["authorizationCode", "clientCredentials", "implicit", "password"];
|
|
104
|
+
for (var k in opts.flows) {
|
|
105
|
+
if (!Object.prototype.hasOwnProperty.call(opts.flows, k)) continue;
|
|
106
|
+
if (validFlowNames.indexOf(k) === -1) {
|
|
107
|
+
throw new OpenApiError("openapi/bad-security",
|
|
108
|
+
"oauth2: unknown flow " + JSON.stringify(k) +
|
|
109
|
+
" — valid: " + validFlowNames.join(", "));
|
|
110
|
+
}
|
|
111
|
+
_validateFlow(k, opts.flows[k]);
|
|
112
|
+
}
|
|
113
|
+
var out = { type: "oauth2", flows: opts.flows };
|
|
114
|
+
if (typeof opts.description === "string" && opts.description.length > 0) {
|
|
115
|
+
out.description = opts.description;
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function openIdConnect(opts) {
|
|
121
|
+
opts = opts || {};
|
|
122
|
+
validateOpts(opts, ["url", "description"], "openapi.security.openIdConnect");
|
|
123
|
+
validateOpts.requireNonEmptyString(opts.url,
|
|
124
|
+
"openIdConnect: url", OpenApiError, "openapi/bad-security");
|
|
125
|
+
var out = { type: "openIdConnect", openIdConnectUrl: opts.url };
|
|
126
|
+
if (typeof opts.description === "string" && opts.description.length > 0) {
|
|
127
|
+
out.description = opts.description;
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// mTLS (RFC 8705) — modeled in OpenAPI 3.1 as `mutualTLS` security
|
|
133
|
+
// scheme type added in the 3.1 spec.
|
|
134
|
+
function mtls(opts) {
|
|
135
|
+
opts = opts || {};
|
|
136
|
+
validateOpts(opts, ["description"], "openapi.security.mtls");
|
|
137
|
+
var out = { type: "mutualTLS" };
|
|
138
|
+
if (typeof opts.description === "string" && opts.description.length > 0) {
|
|
139
|
+
out.description = opts.description;
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// DPoP-bound bearer — emitted as a Bearer scheme with a description
|
|
145
|
+
// noting the DPoP requirement; OpenAPI doesn't define a first-class
|
|
146
|
+
// DPoP scheme yet.
|
|
147
|
+
function dpop(opts) {
|
|
148
|
+
opts = opts || {};
|
|
149
|
+
validateOpts(opts, ["description"], "openapi.security.dpop");
|
|
150
|
+
var desc = (typeof opts.description === "string" && opts.description.length > 0)
|
|
151
|
+
? opts.description
|
|
152
|
+
: "Bearer token bound to a DPoP proof per RFC 9449. Client MUST send the access token in `Authorization: DPoP <token>` and the DPoP proof in the `DPoP` header.";
|
|
153
|
+
return {
|
|
154
|
+
type: "http",
|
|
155
|
+
scheme: "dpop",
|
|
156
|
+
description: desc,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
bearer: bearer,
|
|
162
|
+
basic: basic,
|
|
163
|
+
apiKey: apiKey,
|
|
164
|
+
oauth2: oauth2,
|
|
165
|
+
openIdConnect: openIdConnect,
|
|
166
|
+
mtls: mtls,
|
|
167
|
+
dpop: dpop,
|
|
168
|
+
OpenApiError: OpenApiError,
|
|
169
|
+
};
|