@blamejs/blamejs-shop 0.1.29 → 0.1.31
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 +4 -0
- package/README.md +4 -1
- package/SECURITY.md +9 -0
- package/lib/admin.js +317 -1
- package/lib/asset-manifest.json +1 -1
- package/lib/product-qa.js +88 -0
- package/lib/storefront.js +640 -1
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +10 -0
- package/lib/vendor/blamejs/README.md +4 -0
- package/lib/vendor/blamejs/api-snapshot.json +116 -2
- package/lib/vendor/blamejs/index.js +8 -0
- package/lib/vendor/blamejs/lib/acme.js +2 -2
- package/lib/vendor/blamejs/lib/auth/dpop.js +14 -44
- package/lib/vendor/blamejs/lib/base32.js +154 -0
- package/lib/vendor/blamejs/lib/dbsc.js +5 -18
- package/lib/vendor/blamejs/lib/json-schema.js +740 -0
- package/lib/vendor/blamejs/lib/jwk.js +127 -0
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +43 -6
- package/lib/vendor/blamejs/lib/totp.js +10 -31
- package/lib/vendor/blamejs/lib/uri-template.js +286 -0
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.12.64.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.65.json +27 -0
- package/lib/vendor/blamejs/release-notes/v0.12.66.json +18 -0
- package/lib/vendor/blamejs/release-notes/v0.12.68.json +27 -0
- package/lib/vendor/blamejs/release-notes/v0.12.69.json +27 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/base32.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bot-guard.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +4 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/json-schema.test.js +134 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwk.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/uri-template.test.js +99 -0
- package/package.json +1 -1
package/lib/vendor/MANIFEST.json
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"_about": "blamejs.shop vendors a single framework — blamejs — which itself bundles every server-side crypto/identity dependency. The transitive packages blamejs ships are surfaced in its own MANIFEST.json at lib/vendor/blamejs/lib/vendor/MANIFEST.json — Trivy / Grype rely on that nested data for CVE attribution.",
|
|
4
4
|
"packages": {
|
|
5
5
|
"blamejs": {
|
|
6
|
-
"version": "0.12.
|
|
7
|
-
"tag": "v0.12.
|
|
6
|
+
"version": "0.12.69",
|
|
7
|
+
"tag": "v0.12.69",
|
|
8
8
|
"license": "Apache-2.0",
|
|
9
9
|
"author": "blamejs contributors",
|
|
10
10
|
"source": "https://github.com/blamejs/blamejs",
|
|
@@ -8,6 +8,16 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.12.x
|
|
10
10
|
|
|
11
|
+
- v0.12.69 (2026-05-26) — **`b.middleware.botGuard` no longer blocks browsers that omit Sec-Fetch-Mode.** b.middleware.botGuard treated a missing Sec-Fetch-Mode header as a bot signal and returned 403 Forbidden, which refused legitimate browsers on any origin where the browser does not emit Fetch Metadata: every plain-HTTP non-localhost origin (Umbrel apps, LAN and *.local reverse-proxy deployments) and Safari before 16.4 even over HTTPS. Browsers only send Sec-Fetch-* in a secure context, so its absence is normal there — not a bot. Sec-Fetch-Mode is now advisory only: it never blocks, and it sets req.suspectedBot in mode:"tag" only on a secure-context HTML GET where a modern browser would have sent it. Drive-by bots are still blocked by the missing-Accept-Language and User-Agent heuristics. No configuration change is needed; if you had widened skipPaths or disabled bot-guard to work around this, you can revert that. **Fixed:** *`b.middleware.botGuard` no longer 403s browsers over plain HTTP or older Safari* — A missing `Sec-Fetch-Mode` was a blocking heuristic, but browsers omit Fetch Metadata outside a secure context (every plain-HTTP non-localhost origin — Umbrel, LAN, `*.local` proxies) and Safari < 16.4 omits it even over HTTPS. Those legitimate browsers were refused with `403 Forbidden`. `Sec-Fetch-Mode` is now advisory: it never blocks, and only sets `req.suspectedBot` in `mode: "tag"` on a secure-context HTML GET. The `Accept-Language` and User-Agent heuristics (which catch the same bots) are unchanged. **Detectors:** *reserved-hostname trailing-dot detector recognizes regex strips* — The codebase-patterns gate that requires stripping the RFC 1034 trailing root-zone dot before a reserved-hostname comparison now also recognizes end-anchored regex strips (`.replace(/\.$/, …)`), not only the `charAt` / `while`-loop forms.
|
|
12
|
+
|
|
13
|
+
- v0.12.68 (2026-05-26) — **`b.jwk` — RFC 7638 JWK thumbprint.** Compute the RFC 7638 thumbprint of a JSON Web Key — the canonical base64url(SHA-256(canonical-JSON)) identifier used to name a key (DPoP jkt bindings, ACME account-key thumbprints, DBSC session pins, kid derivation). b.jwk.thumbprint(jwk) returns the digest; b.jwk.canonicalize(jwk) returns the exact JSON that is hashed — only the key-type's required members, member names in lexicographic order, no whitespace, so the same key always yields the same thumbprint regardless of how its JWK was serialized. The standard key types are supported (EC, RSA, oct, OKP per RFC 8037) plus AKP, the IANA key type Node uses for ML-DSA / SLH-DSA post-quantum public keys; SHA-256 is the default, with hash: "sha384" | "sha512" for RFC 9278 thumbprint-with-hash. Verified against the RFC 7638 §3.1 worked example. b.auth.dpop, b.acme, and b.dbsc now compute their thumbprints through this primitive. **Added:** *`b.jwk.thumbprint` / `b.jwk.canonicalize`* — RFC 7638 JWK thumbprint. `thumbprint(jwk, opts)` returns `base64url(hash(canonical-JSON))` — only the key-type's required members feed the hash, so optional fields (`kid`, `use`, `alg`, …) never change the result. `canonicalize(jwk)` returns the canonical JSON string itself. Supports EC / RSA / oct / OKP and the AKP post-quantum key type; SHA-256 default, `hash` selects SHA-384 / SHA-512. Throws `JwkError` on an invalid key or unknown hash. **Changed:** *DPoP, ACME, and DBSC compose `b.jwk`* — `b.auth.dpop` (the `jkt` proof-key thumbprint), `b.acme` (the RFC 8555 account-key authorization), and `b.dbsc` (the session-pin thumbprint) now compute RFC 7638 thumbprints through `b.jwk` instead of carrying their own implementations. Behavior is unchanged — DPoP still refuses symmetric key types, and each surface keeps its own error codes.
|
|
14
|
+
|
|
15
|
+
- v0.12.66 (2026-05-26) — **`b.uriTemplate` — RFC 6570 URI Template expansion.** Expand RFC 6570 URI Templates — the {var} syntax that OpenAPI links, HAL _links, and hypermedia API clients use to turn a template plus a set of variables into a concrete URI. The full Level 4 grammar is supported: every operator ({+var} reserved, {#var} fragment, {.var} label, {/var} path, {;var} path-style parameters, {?var} query, {&var} query continuation), the {var:3} prefix modifier, and the {var*} explode modifier for lists and associative arrays. b.uriTemplate.expand(template, vars) returns the expanded string; b.uriTemplate.compile(template) parses once for templates applied to many variable sets. A malformed template (unclosed expression, reserved operator, non-numeric prefix, unmatched brace) throws UriTemplateError. Verified against the official uritemplate-test conformance suite (all 135 spec, extended, and negative cases). **Added:** *`b.uriTemplate.expand` / `b.uriTemplate.compile`* — RFC 6570 URI Template expansion, full Level 4. `expand(template, vars)` substitutes variables into a template and returns the URI; `compile(template)` returns a reusable `{ expand }` for repeated use. Variable values may be strings, numbers, booleans, arrays (lists), or plain objects (associative arrays); undefined, null, and empty list/map variables are omitted. All eight operators, the `:N` prefix modifier, and the `*` explode modifier follow §3.2, including reserved-set encoding for `{+var}` / `{#var}`. Composes naturally with `b.hal`, `b.linkHeader`, and `b.openapi` link objects. A malformed template throws `UriTemplateError`.
|
|
16
|
+
|
|
17
|
+
- v0.12.65 (2026-05-26) — **`b.base32` — RFC 4648 Base32 encode / decode.** Encode and decode RFC 4648 Base32 — the case-insensitive alphabet behind TOTP / 2FA secrets, DNSSEC NSEC3 hashes, and human-transcribable identifiers. Both RFC 4648 variants are supported: the standard alphabet (the default) and the extended-hex alphabet. b.base32.encode pads to an 8-character boundary by default (pass padding: false for the bare form TOTP key URIs use); b.base32.decode is strict by default but accepts the real-world shapes humans produce — lower-case, embedded spaces and dashes, missing padding — under loose: true. Verified against the RFC 4648 §10 test vectors for both alphabets. The TOTP primitive now composes this codec instead of carrying its own Base32 implementation. **Added:** *`b.base32.encode` / `b.base32.decode`* — RFC 4648 Base32 codec. `encode(buf, opts)` takes a Buffer or Uint8Array and returns a Base32 string, padded to an 8-character boundary unless `padding: false`; `decode(str, opts)` returns a Buffer. The `variant` option selects the standard (`"rfc4648"`, default) or extended-hex (`"rfc4648-hex"`) alphabet. Decoding is strict by default — any character outside the alphabet throws `Base32Error` — and `loose: true` up-cases the input and ignores embedded spaces, dashes, and missing padding, which is how copied TOTP keys and hand-typed codes arrive. **Changed:** *TOTP composes `b.base32`* — `b.auth.totp` now encodes and decodes its secrets through `b.base32` rather than a private Base32 implementation. Behavior is unchanged — secrets are still emitted unpadded and parsed leniently (case-insensitive, ignoring spaces and dashes).
|
|
18
|
+
|
|
19
|
+
- v0.12.64 (2026-05-25) — **`b.jsonSchema` — JSON Schema 2020-12 validation.** Validate JSON against a JSON Schema 2020-12 document — the dialect OpenAPI 3.1 adopted and the most widely implemented schema language. b.jsonSchema.compile(schema) returns a reusable validator; b.jsonSchema.validate(schema, instance) compiles and runs in one call, returning { valid, errors } where each error names the failing instance location, keyword, and schema path. The full 2020-12 vocabulary is supported: every applicator (allOf / anyOf / oneOf / not / if-then-else, properties / patternProperties / additionalProperties / prefixItems / items / contains), the annotation-aware unevaluatedProperties / unevaluatedItems, every assertion keyword, and reference resolution ($ref / $anchor / $dynamicRef / $dynamicAnchor / $defs / $id base URIs). format is an annotation by default (opt in to assertion with assertFormat). External references resolve through an operator-supplied schema map — never a network fetch. Verified against the official JSON-Schema-Test-Suite (1292 of 1295 draft2020-12 cases; the remainder need the bundled dialect metaschema or $vocabulary selection, both opt-in). This is the standards-track counterpart to the fluent b.safeSchema builder and the portable b.jtd. **Added:** *`b.jsonSchema` — JSON Schema 2020-12* — `compile(schema, opts)` returns `{ validate, isValid }`; `validate(schema, instance, opts)` and `isValid(schema, instance, opts)` compile and run in one call. `validate` returns `{ valid, errors }`, each error a `{ instancePath, keyword, schemaPath, message }`. The full 2020-12 vocabulary is implemented — applicators, annotation-aware `unevaluatedProperties` / `unevaluatedItems`, every assertion keyword, and `$ref` / `$anchor` / `$dynamicRef` / `$dynamicAnchor` / `$defs` / `$id` resolution. `format` is an annotation by default (`assertFormat: true` to assert). External references resolve through `opts.schemas` (a URI→schema map), never a network fetch. Reach for it when the schema is an existing JSON Schema document (an API contract, OpenAPI component, or config schema); `b.safeSchema` remains the fluent in-process builder and `b.jtd` the portable codegen-friendly option. Validating a schema document against the dialect metaschema requires supplying that metaschema via `opts.schemas`, and `$vocabulary`-based keyword selection is not honored (every standard keyword always asserts).
|
|
20
|
+
|
|
11
21
|
- v0.12.63 (2026-05-25) — **`b.cloudEvents` gains the JSON event format, batch, and the HTTP binding.** b.cloudEvents grows beyond wrap / parse into a full CloudEvents 1.0.2 surface. b.cloudEvents.validate / isValid check an envelope against the spec without throwing (the non-throwing companion to parse). toJSON / fromJSON serialize and parse the JSON event format, and toJSONBatch / fromJSONBatch handle the JSON batch format; untrusted bodies parse through the framework's bounded, prototype-pollution-safe JSON reader. The new http.* binding speaks both content modes the spec defines — binary mode spreads context attributes across percent-encoded ce-* headers with the data in the body, structured mode carries the whole event as application/cloudevents+json — plus the batch mode, and http.decode auto-detects the incoming mode from Content-Type exactly as a conformant receiver does. Verified against the spec's normative example events. **Added:** *`b.cloudEvents` JSON event format, batch, and HTTP binding* — `validate` / `isValid` report spec violations without throwing (the non-throwing companion to `parse`). `toJSON` / `fromJSON` and `toJSONBatch` / `fromJSONBatch` serialize and parse the JSON event and batch formats over the existing envelope shape. `http.encodeBinary` / `http.encodeStructured` / `http.encodeBatch` render the three HTTP content modes — binary spreads attributes across percent-encoded `ce-*` headers, structured and batched carry the event(s) as `application/cloudevents+json` / `application/cloudevents-batch+json` — and `http.decode` parses a request back into an envelope (or array) by auto-detecting the mode from `Content-Type`. `b.jtd` or `b.safeSchema` still validate the event's `data` payload. **Fixed:** *`b.csp.build` accepts `fenced-frame-src` and `webrtc`* — The CSP3 `fenced-frame-src` directive — which the default security-headers policy emits to block `<fencedframe>` embeds — was missing from the builder's recognized-directive set, so the default policy could not round-trip through `b.csp.build` (it threw `csp/unknown-directive`). Both `fenced-frame-src` and the CSP3 `webrtc` directive are now recognized.
|
|
12
22
|
|
|
13
23
|
- v0.12.62 (2026-05-26) — **`b.jtd` — JSON Type Definition validation (RFC 8927).** Validate JSON against a JSON Type Definition schema (RFC 8927) — a small, portable, cross-implementation schema language, the interop-friendly companion to the framework's fluent b.safeSchema builder. b.jtd.validate(schema, instance) returns an array of { instancePath, schemaPath } errors (empty = valid); b.jtd.isValid is the boolean form. All eight schema forms are supported — empty, type, enum, elements, properties (with optional / additional properties and nullable), values, discriminator (with mapping), and ref (with definitions) — including the integer-range and RFC 3339 timestamp types. A malformed schema is rejected at compile time with jtd/bad-schema rather than silently mis-validating. Verified against the official json-typedef-spec suites: all 316 validation cases and all 49 invalid-schema cases. **Added:** *`b.jtd.validate(schema, instance)` / `b.jtd.isValid(schema, instance)`* — `validate` returns the RFC 8927 error list — each `{ instancePath, schemaPath }` naming the offending value and the broken schema rule — and `isValid` is the boolean convenience form. Supports every JTD form and type: the numeric types enforce their exact ranges (int8 … uint32, float32 / float64), `timestamp` requires an RFC 3339 date-time, `properties` honours `optionalProperties` / `additionalProperties` / `nullable`, and `discriminator` selects a `mapping` schema by a tag property. The schema is checked for well-formedness before validation, so unknown keywords, multiple forms, bad refs, or a discriminator over a non-properties mapping all throw `jtd/bad-schema`. Use JTD for schemas you share across implementations or generate code from; use `b.safeSchema` for in-process fluent validation.
|
|
@@ -71,6 +71,7 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
|
|
|
71
71
|
|
|
72
72
|
- **Passwords** — Argon2id + policy primitive (`b.auth.password`); NIST 800-63B / PCI-DSS 4.0 / HIPAA-AAL2 profiles; HaveIBeenPwned k-anonymity breach check; length / context / dictionary / complexity rules; rotation + history
|
|
73
73
|
- **Multi-factor + WebAuthn** — passkeys (WebAuthn), TOTP, JWT (PQ-default)
|
|
74
|
+
- **JWK thumbprint** — RFC 7638 `base64url(SHA-256(canonical-JSON))` key identifier (`b.jwk.thumbprint` / `canonicalize`): EC / RSA / oct / OKP + the AKP post-quantum key type, SHA-256/384/512; the canonical key name behind DPoP `jkt`, ACME account keys, and DBSC session pins
|
|
74
75
|
- **OAuth / OIDC RP** — `b.auth.oauth`
|
|
75
76
|
- RP-Initiated / Front-Channel / Back-Channel Logout 1.0 (`parseFrontchannelLogoutRequest` + `verifyBackchannelLogoutToken` with jti-replay defense)
|
|
76
77
|
- RFC 9207 AS Issuer Identifier validation on callbacks (`parseCallback` — refuses iss mismatch + OP `error=` redirect)
|
|
@@ -99,7 +100,10 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
|
|
|
99
100
|
- **Signed webhooks + API encryption** — SLH-DSA-SHAKE-256f default; ML-DSA-65 opt-in; ECIES API encryption (`b.webhook`, `b.crypto`)
|
|
100
101
|
- **HPKE / HTTP signatures** — RFC 9180 HPKE with ML-KEM-1024 + HKDF-SHA3-512 + ChaCha20-Poly1305 (`b.crypto.hpke`); RFC 9421 HTTP Message Signatures with derived components and ed25519 / ML-DSA-65 (`b.crypto.httpSig`); RFC 9530 Content-Digest / Repr-Digest body-integrity fields (SHA-256 / SHA-512, legacy algorithms refused — `b.contentDigest`) to sign the digest rather than the whole body
|
|
101
102
|
- **Link header** — RFC 8288 Web Linking codec (`b.linkHeader.parse` / `serialize`): parse and build `Link: <uri>; rel="next"` relations, the standard REST pagination mechanism; quote-aware (a comma inside a quoted parameter never splits the list)
|
|
103
|
+
- **URI Templates** — RFC 6570 expansion (`b.uriTemplate.expand` / `compile`): full Level 4 — every operator, the `:N` prefix and `*` explode modifiers — turning `{/path}{?q*}` plus variables into a concrete URI; validated against the official uritemplate-test suite. The `{var}` syntax behind OpenAPI links and HAL `_links`
|
|
102
104
|
- **JSON Type Definition** — RFC 8927 validation (`b.jtd.validate` / `isValid`): portable, cross-implementation schema validation (all eight forms — type / enum / elements / properties / values / discriminator / ref / empty), returning instancePath / schemaPath errors; validated against the official 316-case suite. Interop companion to the fluent `b.safeSchema` builder
|
|
105
|
+
- **JSON Schema 2020-12** — the OpenAPI 3.1 dialect (`b.jsonSchema.compile` / `validate` / `isValid`): full vocabulary including every applicator, annotation-aware `unevaluatedProperties` / `unevaluatedItems`, and `$ref` / `$dynamicRef` / `$anchor` / `$id` resolution (external refs via an operator-supplied schema map, never a network fetch); `format` is an annotation unless `assertFormat` is set; returns located `{ valid, errors }`. Validated against the official JSON-Schema-Test-Suite. Standards-track counterpart to `b.safeSchema` and `b.jtd`
|
|
106
|
+
- **Base32** — RFC 4648 codec (`b.base32.encode` / `decode`): standard + extended-hex alphabets, padded or bare, strict or lenient decode (case-insensitive, ignoring spaces / dashes for copied TOTP keys); validated against the RFC 4648 §10 vectors. The codec behind `b.auth.totp` secrets
|
|
103
107
|
- **JSONPath** — full RFC 9535 query evaluator (`b.jsonPath.query` / `paths`): name / wildcard / index / slice / descendant selectors, `?filter` expressions, and the five standard functions, with compile-time well-typedness checks (validated against the official 703-case compliance suite); complements the JSONPath guards
|
|
104
108
|
- **JSON Pointer / Patch** — RFC 6901 `b.jsonPointer.get` (reference a value by `/foo/0/bar`) + RFC 6902 `b.jsonPatch.apply` (atomic add / remove / replace / move / copy / test for HTTP PATCH; the input document is never mutated, structural `test` comparison) + RFC 7396 `b.jsonMergePatch.merge` (the `merge-patch+json` partial-document format; both PATCH formats are prototype-pollution-safe)
|
|
105
109
|
- **Canonical JSON** — RFC 8785 JSON Canonicalization Scheme (`b.canonicalJson.stringifyJcs`): the deterministic, sorted-key byte form to hash or sign (custom credentials, receipts, deterministic request signing); UTF-16 key ordering + ECMAScript number formatting, with a lenient `stringify` variant for Buffers / Dates / BigInts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"frameworkVersion": "0.12.
|
|
4
|
-
"createdAt": "2026-05-
|
|
3
|
+
"frameworkVersion": "0.12.69",
|
|
4
|
+
"createdAt": "2026-05-26T14:58:13.295Z",
|
|
5
5
|
"exports": {
|
|
6
6
|
"a2a": {
|
|
7
7
|
"type": "object",
|
|
@@ -4781,6 +4781,36 @@
|
|
|
4781
4781
|
}
|
|
4782
4782
|
}
|
|
4783
4783
|
},
|
|
4784
|
+
"base32": {
|
|
4785
|
+
"type": "object",
|
|
4786
|
+
"members": {
|
|
4787
|
+
"ALPHABETS": {
|
|
4788
|
+
"type": "object",
|
|
4789
|
+
"members": {
|
|
4790
|
+
"rfc4648": {
|
|
4791
|
+
"type": "primitive",
|
|
4792
|
+
"valueType": "string"
|
|
4793
|
+
},
|
|
4794
|
+
"rfc4648-hex": {
|
|
4795
|
+
"type": "primitive",
|
|
4796
|
+
"valueType": "string"
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4799
|
+
},
|
|
4800
|
+
"Base32Error": {
|
|
4801
|
+
"type": "function",
|
|
4802
|
+
"arity": 4
|
|
4803
|
+
},
|
|
4804
|
+
"decode": {
|
|
4805
|
+
"type": "function",
|
|
4806
|
+
"arity": 2
|
|
4807
|
+
},
|
|
4808
|
+
"encode": {
|
|
4809
|
+
"type": "function",
|
|
4810
|
+
"arity": 2
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
},
|
|
4784
4814
|
"bootGates": {
|
|
4785
4815
|
"type": "object",
|
|
4786
4816
|
"members": {
|
|
@@ -37108,6 +37138,31 @@
|
|
|
37108
37138
|
}
|
|
37109
37139
|
}
|
|
37110
37140
|
},
|
|
37141
|
+
"jsonSchema": {
|
|
37142
|
+
"type": "object",
|
|
37143
|
+
"members": {
|
|
37144
|
+
"DIALECT": {
|
|
37145
|
+
"type": "primitive",
|
|
37146
|
+
"valueType": "string"
|
|
37147
|
+
},
|
|
37148
|
+
"JsonSchemaError": {
|
|
37149
|
+
"type": "function",
|
|
37150
|
+
"arity": 4
|
|
37151
|
+
},
|
|
37152
|
+
"compile": {
|
|
37153
|
+
"type": "function",
|
|
37154
|
+
"arity": 2
|
|
37155
|
+
},
|
|
37156
|
+
"isValid": {
|
|
37157
|
+
"type": "function",
|
|
37158
|
+
"arity": 3
|
|
37159
|
+
},
|
|
37160
|
+
"validate": {
|
|
37161
|
+
"type": "function",
|
|
37162
|
+
"arity": 3
|
|
37163
|
+
}
|
|
37164
|
+
}
|
|
37165
|
+
},
|
|
37111
37166
|
"jtd": {
|
|
37112
37167
|
"type": "object",
|
|
37113
37168
|
"members": {
|
|
@@ -37125,6 +37180,48 @@
|
|
|
37125
37180
|
}
|
|
37126
37181
|
}
|
|
37127
37182
|
},
|
|
37183
|
+
"jwk": {
|
|
37184
|
+
"type": "object",
|
|
37185
|
+
"members": {
|
|
37186
|
+
"JwkError": {
|
|
37187
|
+
"type": "function",
|
|
37188
|
+
"arity": 4
|
|
37189
|
+
},
|
|
37190
|
+
"REQUIRED": {
|
|
37191
|
+
"type": "object",
|
|
37192
|
+
"members": {
|
|
37193
|
+
"AKP": {
|
|
37194
|
+
"type": "instance",
|
|
37195
|
+
"ctorName": "Array"
|
|
37196
|
+
},
|
|
37197
|
+
"EC": {
|
|
37198
|
+
"type": "instance",
|
|
37199
|
+
"ctorName": "Array"
|
|
37200
|
+
},
|
|
37201
|
+
"OKP": {
|
|
37202
|
+
"type": "instance",
|
|
37203
|
+
"ctorName": "Array"
|
|
37204
|
+
},
|
|
37205
|
+
"RSA": {
|
|
37206
|
+
"type": "instance",
|
|
37207
|
+
"ctorName": "Array"
|
|
37208
|
+
},
|
|
37209
|
+
"oct": {
|
|
37210
|
+
"type": "instance",
|
|
37211
|
+
"ctorName": "Array"
|
|
37212
|
+
}
|
|
37213
|
+
}
|
|
37214
|
+
},
|
|
37215
|
+
"canonicalize": {
|
|
37216
|
+
"type": "function",
|
|
37217
|
+
"arity": 1
|
|
37218
|
+
},
|
|
37219
|
+
"thumbprint": {
|
|
37220
|
+
"type": "function",
|
|
37221
|
+
"arity": 2
|
|
37222
|
+
}
|
|
37223
|
+
}
|
|
37224
|
+
},
|
|
37128
37225
|
"keychain": {
|
|
37129
37226
|
"type": "object",
|
|
37130
37227
|
"members": {
|
|
@@ -49338,6 +49435,23 @@
|
|
|
49338
49435
|
}
|
|
49339
49436
|
}
|
|
49340
49437
|
},
|
|
49438
|
+
"uriTemplate": {
|
|
49439
|
+
"type": "object",
|
|
49440
|
+
"members": {
|
|
49441
|
+
"UriTemplateError": {
|
|
49442
|
+
"type": "function",
|
|
49443
|
+
"arity": 4
|
|
49444
|
+
},
|
|
49445
|
+
"compile": {
|
|
49446
|
+
"type": "function",
|
|
49447
|
+
"arity": 1
|
|
49448
|
+
},
|
|
49449
|
+
"expand": {
|
|
49450
|
+
"type": "function",
|
|
49451
|
+
"arity": 2
|
|
49452
|
+
}
|
|
49453
|
+
}
|
|
49454
|
+
},
|
|
49341
49455
|
"uuid": {
|
|
49342
49456
|
"type": "object",
|
|
49343
49457
|
"members": {
|
|
@@ -403,6 +403,10 @@ var jsonPatch = require("./lib/json-patch");
|
|
|
403
403
|
var jsonMergePatch = require("./lib/json-merge-patch");
|
|
404
404
|
var jsonPath = require("./lib/json-path");
|
|
405
405
|
var jtd = require("./lib/jtd");
|
|
406
|
+
var jsonSchema = require("./lib/json-schema");
|
|
407
|
+
var base32 = require("./lib/base32");
|
|
408
|
+
var uriTemplate = require("./lib/uri-template");
|
|
409
|
+
var jwk = require("./lib/jwk");
|
|
406
410
|
var standardWebhooks = require("./lib/standard-webhooks");
|
|
407
411
|
var lro = require("./lib/lro");
|
|
408
412
|
var jsonApi = require("./lib/jsonapi");
|
|
@@ -427,6 +431,10 @@ module.exports = {
|
|
|
427
431
|
jsonMergePatch: jsonMergePatch,
|
|
428
432
|
jsonPath: jsonPath,
|
|
429
433
|
jtd: jtd,
|
|
434
|
+
jsonSchema: jsonSchema,
|
|
435
|
+
base32: base32,
|
|
436
|
+
uriTemplate: uriTemplate,
|
|
437
|
+
jwk: jwk,
|
|
430
438
|
standardWebhooks: standardWebhooks,
|
|
431
439
|
lro: lro,
|
|
432
440
|
jsonApi: jsonApi,
|
|
@@ -45,6 +45,7 @@ var nodeCrypto = require("node:crypto");
|
|
|
45
45
|
|
|
46
46
|
var C = require("./constants");
|
|
47
47
|
var asn1 = require("./asn1-der");
|
|
48
|
+
var jwk = require("./jwk");
|
|
48
49
|
var safeUrl = require("./safe-url");
|
|
49
50
|
var safeJson = require("./safe-json");
|
|
50
51
|
var validateOpts = require("./validate-opts");
|
|
@@ -114,8 +115,7 @@ function _publicJwkFromKeyObject(keyObject) {
|
|
|
114
115
|
|
|
115
116
|
function _jwkThumbprint(publicJwk) {
|
|
116
117
|
// RFC 7638 §3 — base64url(SHA-256(canonical JSON of required members)).
|
|
117
|
-
|
|
118
|
-
return _b64u(nodeCrypto.createHash("sha256").update(canon).digest());
|
|
118
|
+
return jwk.thumbprint(publicJwk);
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
function _signJws(privateKey, protectedHeader, payload) {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
var nodeCrypto = require("node:crypto");
|
|
30
30
|
var bCrypto = require("../crypto");
|
|
31
|
+
var jwk = require("../jwk");
|
|
31
32
|
var safeJson = require("../safe-json");
|
|
32
33
|
var safeUrl = require("../safe-url");
|
|
33
34
|
var validateOpts = require("../validate-opts");
|
|
@@ -84,52 +85,21 @@ function _b64urlDecode(s) {
|
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (typeof
|
|
94
|
-
throw new AuthError("auth-dpop/bad-jwk", "jwk
|
|
95
|
-
}
|
|
96
|
-
if (jwk.kty === "EC") {
|
|
97
|
-
if (typeof jwk.crv !== "string" || typeof jwk.x !== "string" || typeof jwk.y !== "string") {
|
|
98
|
-
throw new AuthError("auth-dpop/bad-jwk", "EC jwk requires crv, x, y");
|
|
99
|
-
}
|
|
100
|
-
return JSON.stringify({ crv: jwk.crv, kty: "EC", x: jwk.x, y: jwk.y });
|
|
88
|
+
// Asymmetric key types DPoP accepts (its proof model relies on a
|
|
89
|
+
// signature, so symmetric "oct" keys are refused). AKP is the IANA key
|
|
90
|
+
// type for ML-DSA / SLH-DSA PQC public keys.
|
|
91
|
+
var DPOP_KTY = { EC: 1, OKP: 1, RSA: 1, AKP: 1 };
|
|
92
|
+
|
|
93
|
+
function thumbprint(key) {
|
|
94
|
+
if (!key || typeof key !== "object" || typeof key.kty !== "string" || key.kty.length === 0) {
|
|
95
|
+
throw new AuthError("auth-dpop/bad-jwk", "jwk must be an object with a kty");
|
|
101
96
|
}
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
throw new AuthError("auth-dpop/bad-jwk", "OKP jwk requires crv, x");
|
|
105
|
-
}
|
|
106
|
-
return JSON.stringify({ crv: jwk.crv, kty: "OKP", x: jwk.x });
|
|
97
|
+
if (!DPOP_KTY[key.kty]) {
|
|
98
|
+
throw new AuthError("auth-dpop/refused-kty", "jwk.kty='" + key.kty + "' is not allowed (DPoP requires asymmetric kty)");
|
|
107
99
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
return JSON.stringify({ e: jwk.e, kty: "RSA", n: jwk.n });
|
|
113
|
-
}
|
|
114
|
-
if (jwk.kty === "AKP") {
|
|
115
|
-
// PQC asymmetric key package (draft-ietf-cose-cnsa-pqc / IANA AKP
|
|
116
|
-
// registry). Node:crypto exports ML-DSA / SLH-DSA public keys with
|
|
117
|
-
// kty=AKP, alg=<algId>, pub=<base64url public bytes>.
|
|
118
|
-
if (typeof jwk.alg !== "string" || typeof jwk.pub !== "string") {
|
|
119
|
-
throw new AuthError("auth-dpop/bad-jwk", "AKP jwk requires alg, pub");
|
|
120
|
-
}
|
|
121
|
-
return JSON.stringify({ alg: jwk.alg, kty: "AKP", pub: jwk.pub });
|
|
122
|
-
}
|
|
123
|
-
// Symmetric keys (oct) and any other kty are refused outright — DPoP's
|
|
124
|
-
// proof model requires asymmetric.
|
|
125
|
-
throw new AuthError("auth-dpop/refused-kty",
|
|
126
|
-
"jwk.kty='" + jwk.kty + "' is not allowed (DPoP requires asymmetric kty)");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function thumbprint(jwk) {
|
|
130
|
-
var canonical = _canonicalJwk(jwk);
|
|
131
|
-
var hash = nodeCrypto.createHash("sha256").update(canonical, "utf8").digest();
|
|
132
|
-
return _b64urlEncode(hash);
|
|
100
|
+
// The RFC 7638 thumbprint itself is computed by b.jwk.
|
|
101
|
+
try { return jwk.thumbprint(key); }
|
|
102
|
+
catch (e) { throw new AuthError("auth-dpop/bad-jwk", (e && e.message) || "invalid jwk"); }
|
|
133
103
|
}
|
|
134
104
|
|
|
135
105
|
function _sha256B64Url(input) {
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.base32
|
|
4
|
+
* @nav Data
|
|
5
|
+
* @title Base32
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* Encode and decode <a href="https://www.rfc-editor.org/rfc/rfc4648">RFC
|
|
9
|
+
* 4648</a> Base32 — the case-insensitive, digit-light alphabet used for
|
|
10
|
+
* TOTP / 2FA secrets, DNSSEC NSEC3 hashes, and human-transcribable
|
|
11
|
+
* identifiers. Both RFC 4648 variants are supported: the standard
|
|
12
|
+
* alphabet (<code>variant: "rfc4648"</code>, default) and the
|
|
13
|
+
* extended-hex alphabet (<code>variant: "rfc4648-hex"</code>, which sorts
|
|
14
|
+
* in the same order as the underlying bytes).
|
|
15
|
+
*
|
|
16
|
+
* <code>encode</code> pads to an 8-character boundary with
|
|
17
|
+
* <code>=</code> by default (pass <code>padding: false</code> for the
|
|
18
|
+
* bare form TOTP key URIs use). <code>decode</code> is strict by default
|
|
19
|
+
* — it rejects any character outside the alphabet — but
|
|
20
|
+
* <code>loose: true</code> accepts the real-world shapes humans produce:
|
|
21
|
+
* lower-case input, embedded spaces and dashes, and missing padding.
|
|
22
|
+
*
|
|
23
|
+
* @card
|
|
24
|
+
* RFC 4648 Base32 encode / decode (standard + extended-hex alphabets,
|
|
25
|
+
* padded or bare, strict or lenient) — the codec behind TOTP secrets and
|
|
26
|
+
* transcribable identifiers.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
var { defineClass } = require("./framework-error");
|
|
30
|
+
|
|
31
|
+
var Base32Error = defineClass("Base32Error", { alwaysPermanent: true });
|
|
32
|
+
|
|
33
|
+
var ALPHABETS = {
|
|
34
|
+
"rfc4648": "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
|
|
35
|
+
"rfc4648-hex": "0123456789ABCDEFGHIJKLMNOPQRSTUV",
|
|
36
|
+
};
|
|
37
|
+
// Reverse lookups per variant: char-code → 5-bit value.
|
|
38
|
+
var LOOKUPS = {};
|
|
39
|
+
Object.keys(ALPHABETS).forEach(function (v) {
|
|
40
|
+
var map = {};
|
|
41
|
+
for (var i = 0; i < ALPHABETS[v].length; i++) map[ALPHABETS[v].charAt(i)] = i;
|
|
42
|
+
LOOKUPS[v] = map;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
var GROUP = 8; // allow:raw-byte-literal — Base32 emits 8 chars per 5 input bytes (RFC 4648 §6)
|
|
46
|
+
var BITS = 5; // 5 bits per Base32 symbol
|
|
47
|
+
|
|
48
|
+
function _alphabet(variant) {
|
|
49
|
+
var a = ALPHABETS[variant || "rfc4648"];
|
|
50
|
+
if (!a) throw new Base32Error("base32/bad-variant", "base32: variant must be 'rfc4648' or 'rfc4648-hex'");
|
|
51
|
+
return a;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @primitive b.base32.encode
|
|
56
|
+
* @signature b.base32.encode(input, opts?)
|
|
57
|
+
* @since 0.12.65
|
|
58
|
+
* @status stable
|
|
59
|
+
* @related b.base32.decode
|
|
60
|
+
*
|
|
61
|
+
* Encode a Buffer (or Uint8Array) to an RFC 4648 Base32 string. Output is
|
|
62
|
+
* padded to an 8-character boundary with <code>=</code> unless
|
|
63
|
+
* <code>padding: false</code>. The empty input encodes to the empty string.
|
|
64
|
+
*
|
|
65
|
+
* @opts
|
|
66
|
+
* variant: "rfc4648" | "rfc4648-hex", // default: "rfc4648"
|
|
67
|
+
* padding: boolean, // default: true
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* b.base32.encode(Buffer.from("foobar"));
|
|
71
|
+
* // → "MFRGGZDFMZTWQ===="
|
|
72
|
+
*/
|
|
73
|
+
function encode(input, opts) {
|
|
74
|
+
opts = opts || {};
|
|
75
|
+
var buf;
|
|
76
|
+
if (Buffer.isBuffer(input)) buf = input;
|
|
77
|
+
else if (input instanceof Uint8Array) buf = Buffer.from(input);
|
|
78
|
+
else throw new Base32Error("base32/bad-input", "base32.encode: input must be a Buffer or Uint8Array");
|
|
79
|
+
var alphabet = _alphabet(opts.variant);
|
|
80
|
+
var pad = opts.padding !== false;
|
|
81
|
+
|
|
82
|
+
var out = "";
|
|
83
|
+
var value = 0, bits = 0;
|
|
84
|
+
for (var i = 0; i < buf.length; i++) {
|
|
85
|
+
value = (value << 8) | buf[i]; // allow:raw-byte-literal — shift in one input byte
|
|
86
|
+
bits += 8; // allow:raw-byte-literal — eight bits per input byte
|
|
87
|
+
while (bits >= BITS) {
|
|
88
|
+
out += alphabet.charAt((value >>> (bits - BITS)) & 31); // allow:raw-byte-literal — low 5 bits mask (2^5 - 1)
|
|
89
|
+
bits -= BITS;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (bits > 0) out += alphabet.charAt((value << (BITS - bits)) & 31); // allow:raw-byte-literal — final partial group, low 5 bits
|
|
93
|
+
if (pad) while (out.length % GROUP !== 0) out += "=";
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @primitive b.base32.decode
|
|
99
|
+
* @signature b.base32.decode(str, opts?)
|
|
100
|
+
* @since 0.12.65
|
|
101
|
+
* @status stable
|
|
102
|
+
* @related b.base32.encode
|
|
103
|
+
*
|
|
104
|
+
* Decode an RFC 4648 Base32 string to a Buffer. Strict by default: any
|
|
105
|
+
* character outside the variant's alphabet (other than trailing
|
|
106
|
+
* <code>=</code> padding) throws <code>Base32Error</code>. With
|
|
107
|
+
* <code>loose: true</code> the decoder up-cases the input and ignores
|
|
108
|
+
* embedded spaces and dashes (and missing padding) — the shapes TOTP keys
|
|
109
|
+
* and hand-typed codes take.
|
|
110
|
+
*
|
|
111
|
+
* @opts
|
|
112
|
+
* variant: "rfc4648" | "rfc4648-hex", // default: "rfc4648"
|
|
113
|
+
* loose: boolean, // default: false
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* b.base32.decode("MFRGGZDFMZTWQ====").toString();
|
|
117
|
+
* // → "foobar"
|
|
118
|
+
*/
|
|
119
|
+
function decode(str, opts) {
|
|
120
|
+
opts = opts || {};
|
|
121
|
+
if (typeof str !== "string") throw new Base32Error("base32/bad-input", "base32.decode: input must be a string");
|
|
122
|
+
_alphabet(opts.variant);
|
|
123
|
+
var lookup = LOOKUPS[opts.variant || "rfc4648"];
|
|
124
|
+
var loose = opts.loose === true;
|
|
125
|
+
|
|
126
|
+
var bytes = [];
|
|
127
|
+
var value = 0, bits = 0;
|
|
128
|
+
var inPad = false; // once "=" padding starts, only more "=" may follow
|
|
129
|
+
for (var i = 0; i < str.length; i++) {
|
|
130
|
+
var ch = str.charAt(i);
|
|
131
|
+
if (ch === "=") { inPad = true; continue; } // trailing padding
|
|
132
|
+
if (loose && (ch === " " || ch === "-")) continue; // ignore separators
|
|
133
|
+
// A data character after padding is malformed in either mode — the "="
|
|
134
|
+
// run must be trailing (rejects "M=Y======" / "MZXW=6YTB").
|
|
135
|
+
if (inPad) throw new Base32Error("base32/bad-char", "base32.decode: data character '" + ch + "' after padding at index " + i);
|
|
136
|
+
if (loose) ch = ch.toUpperCase();
|
|
137
|
+
var idx = lookup[ch];
|
|
138
|
+
if (idx === undefined) throw new Base32Error("base32/bad-char", "base32.decode: invalid Base32 character '" + str.charAt(i) + "' at index " + i);
|
|
139
|
+
value = (value << BITS) | idx;
|
|
140
|
+
bits += BITS;
|
|
141
|
+
if (bits >= 8) { // allow:raw-byte-literal — emit a full output byte
|
|
142
|
+
bytes.push((value >>> (bits - 8)) & 0xff); // allow:raw-byte-literal — eight-bit output byte mask
|
|
143
|
+
bits -= 8; // allow:raw-byte-literal — consumed eight bits
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return Buffer.from(bytes);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
module.exports = {
|
|
150
|
+
encode: encode,
|
|
151
|
+
decode: decode,
|
|
152
|
+
ALPHABETS: ALPHABETS,
|
|
153
|
+
Base32Error: Base32Error,
|
|
154
|
+
};
|
|
@@ -36,7 +36,7 @@ var nodeCrypto = require("node:crypto");
|
|
|
36
36
|
var validateOpts = require("./validate-opts");
|
|
37
37
|
var safeJson = require("./safe-json");
|
|
38
38
|
var bCrypto = require("./crypto");
|
|
39
|
-
var
|
|
39
|
+
var jwk = require("./jwk");
|
|
40
40
|
var jwtExternal = require("./auth/jwt-external");
|
|
41
41
|
var C = require("./constants");
|
|
42
42
|
var { defineClass } = require("./framework-error");
|
|
@@ -272,23 +272,10 @@ function _trimLeadingZeros(buf) {
|
|
|
272
272
|
return buf.slice(i);
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
function _jwkThumbprint(
|
|
276
|
-
// RFC 7638
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
var members;
|
|
280
|
-
if (jwk.kty === "EC") {
|
|
281
|
-
members = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };
|
|
282
|
-
} else if (jwk.kty === "RSA") {
|
|
283
|
-
members = { e: jwk.e, kty: jwk.kty, n: jwk.n };
|
|
284
|
-
} else {
|
|
285
|
-
throw new DbscError("dbsc/bad-jwk-kty",
|
|
286
|
-
"jwkThumbprint: unsupported kty " + jwk.kty);
|
|
287
|
-
}
|
|
288
|
-
var canonical = canonicalJson.stringify(members);
|
|
289
|
-
return bCrypto.toBase64Url(
|
|
290
|
-
nodeCrypto.createHash("sha256").update(Buffer.from(canonical, "utf8")).digest()
|
|
291
|
-
);
|
|
275
|
+
function _jwkThumbprint(key) {
|
|
276
|
+
// RFC 7638 thumbprint (base64url(SHA-256(canonical JWK))) via b.jwk.
|
|
277
|
+
try { return jwk.thumbprint(key); }
|
|
278
|
+
catch (e) { throw new DbscError("dbsc/bad-jwk-kty", "jwkThumbprint: " + ((e && e.message) || "invalid jwk")); }
|
|
292
279
|
}
|
|
293
280
|
|
|
294
281
|
module.exports = {
|