@blamejs/core 0.10.14 → 0.11.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 +4 -0
- package/README.md +1 -0
- package/index.js +18 -0
- package/lib/auth/oauth.js +187 -0
- package/lib/auth/saml.js +1366 -13
- package/lib/cms-codec.js +141 -0
- package/lib/compliance.js +73 -0
- package/lib/csp.js +271 -0
- package/lib/dbsc.js +299 -0
- package/lib/fedcm.js +264 -0
- package/lib/hal.js +125 -0
- package/lib/importmap-integrity.js +90 -0
- package/lib/jsonapi.js +230 -0
- package/lib/lro.js +200 -0
- package/lib/mail-crypto-pgp.js +312 -2
- package/lib/mail-crypto-smime.js +530 -69
- package/lib/mail-deploy.js +632 -5
- package/lib/metrics.js +62 -12
- package/lib/middleware/security-headers.js +2 -1
- package/lib/standard-webhooks.js +183 -0
- package/lib/web-push-vapid.js +322 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -6,8 +6,12 @@ Pre-1.0 the surface is intentionally evolving — every release may
|
|
|
6
6
|
change something operators depend on. Read each entry before
|
|
7
7
|
upgrading across more than a few patches at a time.
|
|
8
8
|
|
|
9
|
+
## v0.11.x
|
|
10
|
+
|
|
11
|
+
- v0.11.0 (2026-05-19) — **Mail-crypto sign/verify + encrypt/decrypt, SAML Single Logout (Redirect / POST / SOAP) + EncryptedAssertion + Holder-of-Key, browser identity (FedCM / DBSC / VAPID), CSP3 builder, hypermedia + observability formats, OAuth DCR management + OIDC Native SSO, sectoral compliance posture growth.** **`b.cms.parseSignedData(buf)`** walks an inbound RFC 5652 SignedData ContentInfo and returns `{ digestAlgs, encapContent, certificates, signerInfos }` so consumers verify signatures without re-implementing the SignedData walker. **`b.mail.crypto.smime.sign(opts)` + `.verify(opts)`** are now live on the `b.cms` substrate: `sign` emits an RFC 8551 `multipart/signed; protocol="application/pkcs7-signature"; micalg=sha3-{256,512}` envelope; `verify` recomputes the message digest, compares it against the signed-attrs `messageDigest` attribute, refuses tamper with `mail-crypto/smime/message-digest-mismatch`, and PQC-verifies the signature against the operator-supplied signer public key. Supports ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f signers; SHA-2 family refused at `cms/bad-digest`. **`b.mail.crypto.pgp.experimental.encrypt(opts)` + `.decrypt(opts)` + `.wkd.computeUrl(email)`** ship under the `experimental` namespace because the relevant IANA codepoint registrations are still in draft: ML-KEM-1024 KEM + ChaCha20-Poly1305 AEAD multi-recipient envelope; WKD URL computer per [draft-koch-openpgp-webkey-service](https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/) (SHAKE256-hash localpart + zbase32 encoding; operators supply their own HTTPS fetcher). **`b.auth.saml.sp.buildLogoutRequest({...})` + `parseLogoutRequest(b64, opts)` + `buildLogoutResponse({...})`** implement SAML 2.0 Single Logout on the HTTP-Redirect binding per SAML Bindings §3.4.4.1 with PQC-signed canonical query (ML-DSA-65 / ML-DSA-87 / Ed25519); SP metadata now emits `<md:SingleLogoutService>` bindings when `singleLogoutServiceUrl` is set; tamper / wrong-key / missing-signature each refuse as typed errors. **`b.webPush.generateVapidKeypair()` + `.buildVapidAuthHeader(opts)`** sign the RFC 8292 VAPID JWT inline (ECDSA-P256 per the spec; the framework's PQC-default JWT signer refuses ES256 by design, so `b.webPush` owns the signing rather than relaxing the broader policy). **`b.fedcm`** ships the W3C FedCM 2024 IdP-side response builders: `wellKnown({ provider_urls })` / `config({ accounts_endpoint, ... })` / `accountsResponse({ accounts })` / `idAssertionResponse({ token })`. **`b.dbsc`** implements IETF Device-Bound Session Credentials: `challenge({ secretKey })` mints an HMAC-SHA3-512-signed token requiring no server-side storage; `verifyBindingAssertion(jwt, opts)` refuses HS256 / none as algorithm-confusion, validates ES256/RS256 against the embedded JWK, and returns the RFC 7638 JWK thumbprint so operators pin the binding key to a session. **`b.importmapIntegrity.build({ modules })`** emits a WICG Import Maps + SRI integrity map (SHA-384 default) so browsers refuse module bytes that don't match. **OpenMetrics 1.0 exposition** — `b.metrics` registry `exposition({ format: "openmetrics" })` emits the counter `_total` suffix, `# UNIT` lines, exemplar trace IDs on histograms, and `# EOF` terminator per the openmetrics.io 1.0 wire format. **`b.standardWebhooks.sign + verify`** implement the standardwebhooks.com consortium spec (Stripe / Svix / Okta wire format): HMAC-SHA256, multi-version signature header, 5-minute default skew tolerance. **`b.lro`** implements AIP-151 Long-Running Operations (`create({ store }) → { submit, status, list, cancel }`) with operator-supplied storage and AbortSignal-aware cancellation. **`b.jsonApi.dataResponse(data, opts) + .errorResponse(errors)`** wraps domain payloads in the JSON:API v1.1 top-level shape; refuses missing Resource Object `type`. **`b.hal.resource(payload, { links, embedded, templates })`** builds HAL responses (draft-kelly-json-hal) with an RFC 8288 link-object normaliser. **Lib-side codebase-patterns detector `number-coerce-or-zero-on-json-source`** refuses `Number(<var>['<kebab-cased-key>']) || 0` shapes — that coercion silently accepts `Infinity`, NaN, negative values, and arbitrary strings on operator-untrusted JSON-source input. **(a) `b.cms.parseSignedData(buf)`** — RFC 5652 §5.1 SignedData walker that surfaces `digestAlgs` / `encapContent` / `certificates` / `signerInfos` as structured arrays so downstream verifiers can check signatures without re-implementing the walker. **(b) `b.mail.crypto.smime.sign(opts)` + `b.mail.crypto.smime.verify(opts)` — LIVE on the b.cms substrate.** `sign` composes `b.cms.encodeSignedData` and wraps in an RFC 8551 `multipart/signed; protocol="application/pkcs7-signature"; micalg=sha3-{256,512}` envelope. `verify` parses the CMS SignedData payload, walks signed-attributes to extract the `messageDigest` attribute, recomputes the message digest, refuses tamper with `mail-crypto/smime/message-digest-mismatch`, and PQC-verifies the signature against the operator-supplied signer public key. Supports ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f signers; SHA-2 family refused at `cms/bad-digest`. **(c) `b.mail.crypto.pgp.experimental.encrypt(opts)` + `decrypt(opts)` + `wkd.computeUrl(email)`** — PQC PGP encrypt/decrypt under `experimental` namespace (RFC 9580bis PKESK ML-KEM codepoints haven't IANA-registered yet; framework-private envelope similar to v0.10.10 `b.jose.jwe.experimental`). ML-KEM-1024 KEM + ChaCha20-Poly1305 AEAD + per-recipient KEK derived via SHAKE256 bound to the literal label `pgp/experimental/chacha20-poly1305`. Multi-recipient envelopes; tamper / wrong-key refusal as typed errors. WKD URL computer per [draft-koch-openpgp-webkey-service](https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/) — SHAKE256-hash localpart + zbase32 encoding; returns `{ direct, advanced }` URLs (operators supply their own HTTPS fetcher). **(d) `b.auth.saml.sp.buildLogoutRequest({...})` + `parseLogoutRequest(b64, opts)` + `buildLogoutResponse({...})`** — SAML 2.0 Single Logout on the HTTP-Redirect binding per SAML Bindings §3.4.4.1 with PQC-signed canonical query string (ML-DSA-65 / ML-DSA-87 / Ed25519). `parseLogoutRequest` inflates the operator-supplied SAMLRequest, optionally verifies the redirect-binding signature against an IdP public key, and surfaces NameID / SessionIndex / Issuer. Tamper / wrong-key / missing-signature each refuse as typed errors. Closes the largest item from the v0.10.15 SAML SLO + identity-residual queued plan. **(e) Codex P2 detector `number-coerce-or-zero-on-json-source`** (lib-side) — flags `Number(<var>['<kebab-cased-key>']) || 0` shapes that silently accept Infinity / NaN / negative on operator-untrusted JSON-source numeric input. The v0.10.15 TLS-RPT fix was a one-off; this detector forces the discipline. **Additional residual closure:** `b.auth.saml.sp.buildLogoutRequestPost` / `parseLogoutRequestPost` / `buildLogoutRequestSoap` / `parseLogoutResponseSoap` add SAML SLO HTTP-POST + SOAP synchronous back-channel bindings with embedded XMLDSig-Enveloped signatures. **SignatureMethod surface** spans the W3C XMLDSig Core 1.1 + RFC 9231 vocabulary so the framework interops with deployed IdPs out of the box: `rsa-sha256` / `rsa-sha384` / `rsa-sha512` (W3C XMLDSig Core 1.1), `ecdsa-sha256` / `ecdsa-sha384` / `ecdsa-sha512` (W3C XMLDSig Core 1.1), `ed25519` (RFC 9231). Classical keys are PEM strings or `node:crypto` KeyObject instances; PQC keys remain `Uint8Array` from `b.pqcSoftware.ml_dsa_*.keygen()`. Post-quantum `ml-dsa-65` / `ml-dsa-87` are also accepted on the same surface under clearly framework-private URIs (`urn:blamejs:experimental:saml-sig-alg:ml-dsa-65` / `:ml-dsa-87`) — no IETF/W3C XMLDSig registration exists for ML-DSA yet (LAMPS WG drafts in flight). Verification refuses `'unsupported-c14n'` (only exclusive c14n; inclusive-c14n IdPs must upgrade), alg-confusion (SignatureMethod URI must match the operator-declared `idpVerifyAlg`), signature-wrapping (Reference URI must match root ID via timing-safe digest compare), and SHA-1 digest methods (CVE-2017-7525-class). **EncryptedAssertion (SAML 2.0 §2.5)** decrypts AES-128-GCM / AES-256-GCM (W3C XMLEnc 1.1 §5.2.4) content + RSA-OAEP-MGF1P / xmlenc11 RSA-OAEP key transport (SHA-256/384/512 only — SHA-1 OAEP refused as CVE-2023-49141-class). AES-CBC content encryption is **refused** under both `xmlenc#aes128-cbc` and `xmlenc#aes256-cbc` — CVE-2011-1473 padding-oracle research makes CBC mode under XMLEnc unsuitable without a per-content MAC; operators integrating with IdPs that default to CBC (older ADFS / Azure AD / Okta / Keycloak / OneLogin) switch the IdP's content-encryption setting to AES-128-GCM or AES-256-GCM. Framework-experimental URIs `urn:blamejs:experimental:xmlenc:ml-kem-1024` (key transport) and `urn:blamejs:experimental:xmlenc:xchacha20-poly1305` (content) are accepted alongside the W3C URIs. `b.mail.crypto.smime.verify({ trustAnchorCertsPem })` now walks the SignerInfo cert chain leaf-to-root via `node:crypto.X509Certificate` with notBefore/notAfter checks and refuses `mail-crypto/smime/untrusted-chain` / `cert-expired` / `cert-not-yet-valid`; revocation is operator-wired via `b.network.tls.ocsp` when freshness is required. `b.auth.saml.sp.verifyResponse({ holderOfKey: { presentedCertPem } })` honors `urn:oasis:names:tc:SAML:2.0:cm:holder-of-key` SubjectConfirmation: SHA3-512 fingerprint of the embedded KeyInfo/X509Data certificate is compared against the operator-supplied presented mTLS / possession-proof cert via timingSafeEqual; HoK and Bearer confirmations coexist. `b.auth.oauth.readClient(uri, token)` / `updateClient(uri, token, metadata)` / `deleteClient(uri, token)` implement RFC 7592 Dynamic Client Registration Management Protocol (GET/PUT/DELETE bound to the registration_access_token returned by `registerClient`); updateClient enforces the same redirect_uris-array refusal as registerClient. `b.auth.oauth.nativeSsoExchange({ deviceSecret, idToken, audience })` convenience-wraps `exchangeToken` for OpenID Connect Native SSO 1.0 §6 with `urn:openid:params:token-type:device-secret` added to the RFC 8693 §3 token-type allowlist. **`b.csp.build(directives, opts?)` + `b.csp.nonce(byteLen?)` + `b.csp.hash(scriptBody, alg?)`** ship the CSP Level 3 builder surface: refuses `'unsafe-*'` / catch-all `*` / `https:` / `data:` in non-image directives without an explicit acknowledgement opt; auto-appends `require-trusted-types-for 'script'` plus the operator-supplied `trusted-types` policy list when any script-* directive is set; emits ≥128-bit nonces by default; computes sha256/sha384/sha512 hash sources for inline scripts. `b.middleware.securityHeaders` `coep` opt now documents the W3C CR 2024-12 `credentialless` value alongside `require-corp`. **`b.compliance` posture catalog gains 17 sectoral / cybersecurity / AI-governance regimes:** `42-cfr-part-2`, `hti-1`, `uscdi-v4`, `irs-1075`, `nist-800-172-r3`, `tlp-2.0`, `soci-au`, `nis2`, `cra`, `ffiec-cat-2`, `cri-profile-v2.0`, `m-22-09`, `m-22-18`, `nist-800-53-r5-privacy`, `nist-ai-600-1-genai`, `nist-csf-2.0`, `sb-53`, `nyc-ll144-2024`. Each posture cascade pins the regime's normative floor (backupEncryptionRequired / auditChainSignedRequired / tlsMinVersion / requireVacuumAfterErase). **References:** [RFC 5652 CMS](https://www.rfc-editor.org/rfc/rfc5652.html) · [RFC 8551 S/MIME 4.0](https://www.rfc-editor.org/rfc/rfc8551.html) · [RFC 9580 OpenPGP](https://www.rfc-editor.org/rfc/rfc9580.html) · [draft-koch-openpgp-webkey-service](https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/) · [SAML Bindings §3.4 HTTP-Redirect + §3.5 HTTP-POST + §3.2 SOAP](https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf) · [SAML 2.0 §2.5 EncryptedAssertion](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf) · [W3C XML-Encryption 1.1](https://www.w3.org/TR/xmlenc-core1/) · [RFC 7592 Dynamic Client Management](https://www.rfc-editor.org/rfc/rfc7592.html) · [OpenID Connect Native SSO 1.0](https://openid.net/specs/openid-connect-native-sso-1_0.html) · [W3C CSP Level 3](https://www.w3.org/TR/CSP3/) · [W3C Trusted Types](https://www.w3.org/TR/trusted-types/).
|
|
9
12
|
## v0.10.x
|
|
10
13
|
|
|
14
|
+
- v0.10.15 (2026-05-18) — **TLS-RPT receiver — RFC 8460 aggregate-report ingest.** New primitive surface under `b.mail.deploy` that closes the receive-side of TLS-RPT (the publish-side shipped v0.7.29 + v0.9.56). **(a) `b.mail.deploy.parseTlsRptReport(bytes, opts?)`** — pure parser + RFC 8460 §4.4 schema validator. Accepts `application/tlsrpt+json` (raw) and `application/tlsrpt+gzip` (auto-detected via the RFC 1952 gzip magic bytes `0x1f 0x8b` or routed when `opts.contentType` names a gzip media-type). Caps compressed payload at 4 MiB (RFC 8460 §5.2 community ceiling), decompressed at 32 MiB (operator-overridable), and refuses decompression amplification > 50:1 — defends [CVE-2025-0725](https://nvd.nist.gov/vuln/detail/CVE-2025-0725) (libcurl + zlib decompression amplification) and the broader CVE-2024-zlib bomb class. Routes through `b.guardJson.parse` for proto-pollution / depth / key-count defenses before walking the §4.4 schema. Refuses on missing required fields (`organization-name` / `contact-info` / `report-id` / `date-range.{start,end}-datetime` / `policies`) and enforces the §4.4 erratum that `policies` MUST be a non-empty array even for single-policy reports. Returns the normalized report shape plus `sessionTotals: { success, failure }` and a `wasCompressed` flag. **(b) `b.mail.deploy.tlsRptIngestHttp({...})`** — factory returning an `(req, res)` HTTPS POST handler mounted at the operator's `rua=https://<host>/<path>` endpoint per RFC 8460 §5.4. Negotiates the two IANA-registered media types ([RFC 8460 §6.4-6.5](https://www.rfc-editor.org/rfc/rfc8460.html#section-6.4)), returns 405 on non-POST, 415 on bad media-type (with `Accept:` header), 413 on size / bomb / ratio refusal, 400 on parse failure (with `Error-Type:` header naming the typed error code), 201 on accept. Optional `trustedReporters` array refuses non-trusted reporting domains (RFC 8460 §5.3-class defense extended to the HTTPS path). Body collection routes through `b.safeBuffer.boundedChunkCollector` — cap enforced at every `push()`, not after — so a hostile reporter sending a 10-GB body rejects on the chunk that overflows. Emits the `mail.tlsrpt.ingest_http` audit event with `policyDomains` set + session totals on every accept / refuse. **(c) `b.mail.deploy.tlsRptReportSchema()`** — schema descriptor (required fields, policy types, result types) for operator dashboards. Pure function. **(d) Codebase-patterns detector `gunzip-without-output-size-cap`** (lib-side) — every `zlib.gunzipSync` / `zlib.createGunzip` / `zlib.brotliDecompressSync` MUST sit in a file that also names `maxOutputLength` (Node-native cap) per the CVE-2025-0725 defense class. Companion-check `requires` field added to the lib-side runner. **Deferred from v1:** mailto: ingest (no operator demand surfaced — HTTPS POST is the de-facto deployment shape for TLS-RPT; operators wanting mailto: today compose `b.mail.server.mx` + `parseTlsRptReport`) and brotli decompression (no fielded reporter uses `Content-Encoding: br` for TLS-RPT; the RFC 8460 §6.4-6.5 IANA registry only names `+json` and `+gzip`). Each reopens with a documented condition. References: [RFC 8460 SMTP TLS Reporting](https://www.rfc-editor.org/rfc/rfc8460.html) · [RFC 8461 MTA-STS](https://www.rfc-editor.org/rfc/rfc8461.html) · [RFC 1952 gzip](https://www.rfc-editor.org/rfc/rfc1952.html) · [CVE-2025-0725](https://nvd.nist.gov/vuln/detail/CVE-2025-0725).
|
|
11
15
|
- v0.10.14 (2026-05-18) — **codebase-patterns hardening — test-side catalog gains 3 new detectors + 1 migration; lib-side catalog gains 1 new detector with comment-skip preprocessing.** Closes the same class of bug that caused the v0.10.13 macOS hang (test-discipline-without-enforcement). **(a) `test-codebase-patterns.test.js` — test-side antipattern runner** now supports `matchOn: "basename"` mode and `requires` companion-content checks. **(b) M1 migration —** `testNoReleaseNamedTestFiles` moves from the lib-side catalog to the test-side catalog (the rule scans test-file basenames, not lib-source content). **(c) N1 — `Promise + setTimeout` direct sleep in tests refused.** Tests calling `await new Promise(r => setTimeout(r, N))` for synchronization MUST use `helpers.waitUntil` per CLAUDE.md rule §11b. 49 pre-existing files are allowlisted as a documented v0.10.14 migration backlog; the gate prevents new occurrences. **(d) N2 — hardcoded server bind ports refused.** Tests calling `.listen(N)` with a literal non-zero port MUST use `.listen(0)` + `server.address().port` to avoid `SMOKE_PARALLEL=64` bind races. Detector scoped to the bind path (`.listen(...)`); read-only protocol-constant references (`port: 993` / `port: 587` in autoconfig XML) don't trip. **(e) N3 — tests creating `b.db` handles without an isolation primitive refused.** Any test calling `b.db.create(` MUST also wire one of `helpers.setupTestDb` / `helpers.setupVaultOnly` / `node:fs.mkdtempSync`. Leaked per-test SQLite state corrupts subsequent tests under `SMOKE_PARALLEL=64`. **(f) N4 — raw `audit.emit(...)` outside drop-silent wrap refused (lib).** Per CLAUDE.md rule §5 hot-path audit sinks must be drop-silent. Detector found and fixed an existing violation in `lib/subject.js:_writeAudit` whose comment promised swallowing but actually let the throw escape. The lib-side runner gains a `skipCommentLines` per-entry opt so docstring `@example` lines don't trip detectors that match comment-friendly tokens. **(g) N5 deferred —** `Date.now()` vs `process.hrtime()` for elapsed-time math needs semantic distinction (elapsed-math vs row-age); regex alone is too noisy. v0.10.13's stream-throttle elapsed-clamp shipped the highest-value fix already; remaining call sites get per-file review in a later patch.
|
|
12
16
|
- v0.10.13 (2026-05-18) — **`b.cms` CMS codec + `b.streamThrottle` bandwidth limiter + histogram-aware snapshot writer + Windows-safe daemonize.** **`b.cms` — RFC 5652 Cryptographic Message Syntax codec.** New crypto primitive: in-tree CMS encoder + decoder built on the existing `b.asn1Der` walker and the vendored noble-post-quantum primitives. **(a) `b.cms.encodeSignedData({ encapContent, digestAlg, signers })`** — emits a DER-encoded `ContentInfo` carrying `SignedData` per RFC 5652 §5 with PQC signers: ML-DSA-65 + ML-DSA-87 ([RFC 9909](https://www.rfc-editor.org/rfc/rfc9909.html)) and SLH-DSA-SHAKE-256f ([RFC 9881](https://www.rfc-editor.org/rfc/rfc9881.html)). Digest algorithms are SHA3-256 or SHA3-512 (PQC-first; SHA-2 family refused with `cms/bad-digest`). Signed-attributes carry `contentType` + `messageDigest` + `signingTime` in DER-canonical SET-OF ordering; the signature input re-tags the IMPLICIT `[0]` to the universal SET (`0x31`) per §5.4 paragraph 3 so signatures round-trip with any conforming verifier. Signer identifiers carry the full `issuerAndSerialNumber` extracted from the operator-supplied cert DER ([RFC 5652 §10.2.4](https://www.rfc-editor.org/rfc/rfc5652#section-10.2.4)). **(b) `b.cms.encodeEnvelopedData({ plaintext, recipients })`** — emits a DER-encoded `ContentInfo` carrying `EnvelopedData` with `KEMRecipientInfo` recipients per [RFC 9629](https://www.rfc-editor.org/rfc/rfc9629.html) and ML-KEM-1024 per [RFC 9936](https://www.rfc-editor.org/rfc/rfc9936.html). Each recipient encapsulates against the operator-supplied ML-KEM-1024 public key; the framework's SHAKE256 KDF derives a 32-byte content-encryption KEK from the KEM shared-secret bound to the literal label `cms/kemri/chacha20-poly1305` (so a key derived for this composition cannot be confused with one derived for any other framework path). Content encryption is ChaCha20-Poly1305 ([RFC 8103](https://www.rfc-editor.org/rfc/rfc8103.html) OID); the AEAD tag makes Efail-class CBC-malleability impossible by construction ([CVE-2017-17688](https://nvd.nist.gov/vuln/detail/CVE-2017-17688) / [CVE-2017-17689](https://nvd.nist.gov/vuln/detail/CVE-2017-17689)). **(c) `b.cms.decode(buf, { maxBytes? })`** — returns `{ contentType, content }` where `contentType` is the dotted-OID string and `content` is the inner `asn1-der` node. Refuses input past `maxBytes` (default 64 MiB), non-SEQUENCE top-level, missing `[0] EXPLICIT` content, and malformed OID encodings (closes the [CVE-2022-47629](https://nvd.nist.gov/vuln/detail/CVE-2022-47629) libksba class via the existing `b.asn1Der` strict-decode posture). **(d) Refusal posture documented in `lib/cms-codec.js` @intro**: only PQC signature algorithms (`cms/bad-sig-alg`), only ML-KEM-1024 recipients (`cms/bad-recipient-type`), non-empty signers / recipients required at encode (`cms/no-signers` / `cms/no-recipients`). **Operator impact:** no breaking changes; new primitive at `b.cms`. **Deferred to v0.10.14:** the on-the-wire S/MIME 4.0 layer ([RFC 8551](https://www.rfc-editor.org/rfc/rfc8551.html) `multipart/signed` framing, base64 DER body, `micalg` mapping) and OpenPGP encrypt + decrypt + WKD discovery ([RFC 9580](https://www.rfc-editor.org/rfc/rfc9580.html) §5.1 / §5.13 packets plus [draft-koch-openpgp-webkey-service](https://datatracker.ietf.org/doc/draft-koch-openpgp-webkey-service/)) land together so the mail-crypto surface lights up coherently rather than half-on-each-side across two patches. AuthEnvelopedData ([RFC 5083](https://www.rfc-editor.org/rfc/rfc5083.html)) as a distinct `ContentInfo` shape is deferred — EnvelopedData with ChaCha20-Poly1305 is already AEAD by construction; the §5083 OID rewrap lights up alongside S/MIME for peers that refuse the EnvelopedData form. References: [RFC 5652 CMS](https://www.rfc-editor.org/rfc/rfc5652.html) · [RFC 9629 KEMRecipientInfo](https://www.rfc-editor.org/rfc/rfc9629.html) · [RFC 9909 ML-DSA in X.509+CMS](https://www.rfc-editor.org/rfc/rfc9909.html) · [RFC 9881 SLH-DSA in X.509+CMS](https://www.rfc-editor.org/rfc/rfc9881.html) · [RFC 9936 ML-KEM in CMS](https://www.rfc-editor.org/rfc/rfc9936.html) · [RFC 8103 ChaCha20-Poly1305 in CMS](https://www.rfc-editor.org/rfc/rfc8103.html). **(e) `b.streamThrottle` — token-bucket bandwidth limiter.** New primitive: `b.streamThrottle.create({ bytesPerSec, burstBytes? })` returns a shared token bucket whose `.transform()` instances each consume from the same budget. The missing primitive between per-request rate-limit and per-process worker pools: N parallel transfers share the operator-configured byte budget rather than each getting their own. Composes with `node:stream.pipeline` as a regular `stream.Transform`; chunks larger than `burstBytes` refuse with `stream-throttle/oversize-chunk` unless `transform({ allowOversize: true })`. Algorithm is the [RFC 2697 srTCM](https://www.rfc-editor.org/rfc/rfc2697.html) single-rate token-bucket shape, with lazy refill so there is no per-throttle background timer. **(f) Histogram-aware metrics snapshot writer.** `b.metrics.snapshot.startWriter` gains an opt-in `registry` field. When supplied, the JSON snapshot grows a `metrics` field carrying every registered counter / gauge / histogram in structured form — histograms include `buckets` + `observations: [{ labels, counts, sum, count }]`, so sidecar readers compose `histogram_quantile()` against the snapshot file without running a separate `/metrics` HTTP endpoint. `fileMode` default unchanged (0o640). **(g) Windows-safe daemonize.** `b.daemon.start` detached-fork mode now branches by platform. POSIX continues inheriting the parent-opened log FD via `stdio: ["ignore", logFd, logFd]` (unchanged). Windows now uses `stdio: "ignore"` + `windowsHide: true` so the child has no inherited handles that the OS invalidates on parent exit — the previously-broken Windows daemonize path now produces a survivable detached process. The child is responsible for opening its own log file (operators pass `--log` in `opts.args`). `daemon.started` audit gains `stdioMode` so operators can grep for the chosen strategy. **Closes: [#94](https://github.com/blamejs/blamejs/issues/94), [#100](https://github.com/blamejs/blamejs/issues/100), [#101](https://github.com/blamejs/blamejs/issues/101); also closes [#92](https://github.com/blamejs/blamejs/issues/92) and [#93](https://github.com/blamejs/blamejs/issues/93) (already shipped in v0.10.9 as `b.promisePool` / `b.sdNotify` — left open until now).**
|
|
13
17
|
- v0.10.12 (2026-05-18) — **`b.agent.tenant` adoption across the mail-server listeners.** The v0.10.11 shared `b.mail.serverRegistry` primitive gains optional `opts.tenantScope` (a `b.agent.tenant.create()` instance) + `opts.agentTenantId` (the tenant this listener serves). When supplied, every method dispatch first gates on `tenantScope.check(state.actor, agentTenantId)` BEFORE guard validation or audit emission; cross-tenant access surfaces as the v0.9.25-typed `agent-tenant/cross-tenant-access-refused` which the listener's catch-path converts to the protocol's `BAD` / `NO` refusal reply. **(a) `b.mail.server.imap.create({ tenantScope, agentTenantId })`** — IMAP dispatch is gated for every command after AUTH. **(b) `b.mail.server.jmap.create({ tenantScope, agentTenantId })`** — JMAP per-method dispatch routes through the tenant scope alongside its existing per-`accountId` isolation. **(c) `b.mail.server.managesieve.create({ tenantScope, agentTenantId })`** — ManageSieve same pattern. **(d) `b.mail.server.submission.create({ tenantScope, agentTenantId })`** — submission listener gates at the AUTH-success boundary (before `state.actor` is committed) so cross-tenant authentication surfaces as `535 5.7.0 Authentication rejected (cross-tenant)` and the SMTP envelope never begins under the wrong tenant. **(e) `b.mail.server.pop3.create({ tenantScope, agentTenantId })`** — same AUTH-success gate; cross-tenant refusal returns `-ERR Authentication rejected (cross-tenant)`. New audit events: `mail.server.submission.cross_tenant_refused` and `mail.server.pop3.cross_tenant_refused`. **Operator impact:** no breaking changes — `tenantScope` / `agentTenantId` are optional; operators not running multi-tenant see identical behavior. Operators with multi-tenant deployments wire `b.agent.tenant.create({...})` once and pass the same scope to every per-tenant listener instance — cross-tenant isolation becomes structural rather than per-handler opt-in. **Deferred to v0.10.12.1:** per-tenant `b.mailStore` seal-key derivation via `tenantScope.derivedKey(tenantId, "seal")` and per-tenant audit namespaces via `tenantScope.auditFor(tenantId)`. Today every mail listener seals through the framework primary vault key — adequate for single-tenant and multi-tenant-trusted deployments; the v0.10.12.1 follow-up adds per-tenant key separation for compromise-isolation use cases. References: [RFC 9051 IMAP4rev2 §3 state machine](https://www.rfc-editor.org/rfc/rfc9051#section-3) · [RFC 8620 JMAP Core §1.6.2 accountId](https://www.rfc-editor.org/rfc/rfc8620#section-1.6.2) · [RFC 6409 Submission §6.1 actor-to-MAIL-FROM identity binding](https://www.rfc-editor.org/rfc/rfc6409#section-6.1) · [RFC 1939 POP3 §6 transaction state](https://www.rfc-editor.org/rfc/rfc1939#section-6) · v0.9.25 `b.agent.tenant` contract.
|
package/README.md
CHANGED
|
@@ -97,6 +97,7 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
|
|
|
97
97
|
- **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`)
|
|
98
98
|
- **CMS codec** — RFC 5652 Cryptographic Message Syntax encoder + decoder with PQC signers (ML-DSA-65 / ML-DSA-87 / SLH-DSA-SHAKE-256f; RFC 9909 + 9881) and KEMRecipientInfo recipients (ML-KEM-1024; RFC 9629 + 9936); ChaCha20-Poly1305 content encryption (RFC 8103) so Efail-class malleability cannot apply (`b.cms`)
|
|
99
99
|
- **Stream throttle** — shared token-bucket bandwidth limiter (RFC 2697 srTCM shape); N concurrent `node:stream` pipelines draw from one operator-configured `bytesPerSec` budget (`b.streamThrottle`)
|
|
100
|
+
- **TLS-RPT receiver** — RFC 8460 inbound aggregate-report ingest; HTTPS POST handler + §4.4 schema parser with gzip-bomb / ratio-bomb / depth-bomb defenses (`b.mail.deploy.parseTlsRptReport` / `b.mail.deploy.tlsRptIngestHttp`)
|
|
100
101
|
- **TLS / channel binding** — RFC 9266 TLS-Exporter token-to-session pinning (`b.tlsExporter`); RFC 9162 CT v2 inclusion-proof verification (`b.network.tls.ct.verifyInclusion`); RFC 8555 ACME + RFC 9773 ARI for 47-day certs with `{ jitter: true }` fleet-scheduling (`b.acme.renewIfDue`); draft-aaron-acme-profiles (`acme.listProfiles()` + `newOrder({ profile })`); draft-ietf-acme-dns-account-label (`acme.dnsAccount01ChallengeRecord(token, { identifier })`); RFC 8470 0-RTT inbound posture refuse / replay-cache (`b.router.create({tls0Rtt})`); RFC 9794 SecP256r1MLKEM768 in preferred-group order (`b.network.tls.preferredGroups`)
|
|
101
102
|
- **mTLS CA** — pure-JS, issues clientAuth / serverAuth / dual-EKU certs with SAN; auto-detects highest-PQC signature alg (today ECDSA-P384-SHA384; self-upgrades to SLH-DSA / ML-DSA when X.509 ecosystem catches up); PQC TLS gates inbound + outbound (`b.mtlsCa`, `b.pqcGate`, `b.pqcAgent`)
|
|
102
103
|
### HTTP
|
package/index.js
CHANGED
|
@@ -377,11 +377,29 @@ var daemon = require("./lib/daemon");
|
|
|
377
377
|
var selfUpdate = require("./lib/self-update");
|
|
378
378
|
var cmsCodec = require("./lib/cms-codec");
|
|
379
379
|
var streamThrottle = require("./lib/stream-throttle");
|
|
380
|
+
var webPush = require("./lib/web-push-vapid");
|
|
381
|
+
var fedcm = require("./lib/fedcm");
|
|
382
|
+
var dbsc = require("./lib/dbsc");
|
|
383
|
+
var importmapIntegrity = require("./lib/importmap-integrity");
|
|
384
|
+
var standardWebhooks = require("./lib/standard-webhooks");
|
|
385
|
+
var lro = require("./lib/lro");
|
|
386
|
+
var jsonApi = require("./lib/jsonapi");
|
|
387
|
+
var hal = require("./lib/hal");
|
|
388
|
+
var csp = require("./lib/csp");
|
|
380
389
|
|
|
381
390
|
module.exports = {
|
|
382
391
|
crypto: crypto,
|
|
383
392
|
cms: cmsCodec,
|
|
393
|
+
csp: csp,
|
|
384
394
|
streamThrottle: streamThrottle,
|
|
395
|
+
webPush: webPush,
|
|
396
|
+
fedcm: fedcm,
|
|
397
|
+
dbsc: dbsc,
|
|
398
|
+
importmapIntegrity: importmapIntegrity,
|
|
399
|
+
standardWebhooks: standardWebhooks,
|
|
400
|
+
lro: lro,
|
|
401
|
+
jsonApi: jsonApi,
|
|
402
|
+
hal: hal,
|
|
385
403
|
router: router,
|
|
386
404
|
constants: constants,
|
|
387
405
|
vault: vault,
|
package/lib/auth/oauth.js
CHANGED
|
@@ -237,6 +237,10 @@ var RFC_8693_TOKEN_TYPES = Object.freeze([
|
|
|
237
237
|
"urn:ietf:params:oauth:token-type:saml1",
|
|
238
238
|
"urn:ietf:params:oauth:token-type:saml2",
|
|
239
239
|
"urn:ietf:params:oauth:token-type:jwt",
|
|
240
|
+
// openid-native-sso-1_0 §6 — device_secret is the token type
|
|
241
|
+
// carrying the per-device long-lived secret returned alongside
|
|
242
|
+
// id_token during native-sso-aware authentication.
|
|
243
|
+
"urn:openid:params:token-type:device-secret",
|
|
240
244
|
]);
|
|
241
245
|
|
|
242
246
|
// ---- helpers ----
|
|
@@ -1691,6 +1695,131 @@ function create(opts) {
|
|
|
1691
1695
|
return parsed;
|
|
1692
1696
|
}
|
|
1693
1697
|
|
|
1698
|
+
/**
|
|
1699
|
+
* @primitive b.auth.oauth.readClient
|
|
1700
|
+
* @signature b.auth.oauth.readClient(registrationClientUri, registrationAccessToken)
|
|
1701
|
+
* @since 0.10.16
|
|
1702
|
+
* @status stable
|
|
1703
|
+
* @related b.auth.oauth.registerClient, b.auth.oauth.updateClient, b.auth.oauth.deleteClient
|
|
1704
|
+
*
|
|
1705
|
+
* RFC 7592 §2.1 OAuth 2.0 Dynamic Client Registration Management
|
|
1706
|
+
* Protocol — read the current client configuration via GET against
|
|
1707
|
+
* the operator-supplied `registration_client_uri` carrying the
|
|
1708
|
+
* `registration_access_token`. Returns the AS's full client metadata.
|
|
1709
|
+
*
|
|
1710
|
+
* @example
|
|
1711
|
+
* var meta = await oauth.readClient(rv.registration_client_uri,
|
|
1712
|
+
* rv.registration_access_token);
|
|
1713
|
+
*/
|
|
1714
|
+
async function readClient(registrationClientUri, registrationAccessToken) {
|
|
1715
|
+
return _dcrManagementCall("GET", registrationClientUri, registrationAccessToken, null);
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
/**
|
|
1719
|
+
* @primitive b.auth.oauth.updateClient
|
|
1720
|
+
* @signature b.auth.oauth.updateClient(registrationClientUri, registrationAccessToken, metadata)
|
|
1721
|
+
* @since 0.10.16
|
|
1722
|
+
* @status stable
|
|
1723
|
+
*
|
|
1724
|
+
* RFC 7592 §2.2 update the dynamically-registered client's metadata
|
|
1725
|
+
* via PUT. The AS may rotate `registration_access_token` / regenerate
|
|
1726
|
+
* `client_secret` in the response — operators MUST persist the new
|
|
1727
|
+
* values atomically with the update.
|
|
1728
|
+
*
|
|
1729
|
+
* @example
|
|
1730
|
+
* var updated = await oauth.updateClient(
|
|
1731
|
+
* rv.registration_client_uri,
|
|
1732
|
+
* rv.registration_access_token,
|
|
1733
|
+
* { redirect_uris: ["https://rp.example/cb-new"],
|
|
1734
|
+
* grant_types: ["authorization_code", "refresh_token"] });
|
|
1735
|
+
*/
|
|
1736
|
+
async function updateClient(registrationClientUri, registrationAccessToken, metadata) {
|
|
1737
|
+
if (!metadata || typeof metadata !== "object") {
|
|
1738
|
+
throw new OAuthError("auth-oauth/bad-update",
|
|
1739
|
+
"updateClient: metadata must be an object");
|
|
1740
|
+
}
|
|
1741
|
+
if (!Array.isArray(metadata.redirect_uris) || metadata.redirect_uris.length === 0) {
|
|
1742
|
+
throw new OAuthError("auth-oauth/update-no-redirect-uris",
|
|
1743
|
+
"updateClient: metadata.redirect_uris must be a non-empty array " +
|
|
1744
|
+
"(same posture as registerClient — RFC 7591/7592 makes it optional, " +
|
|
1745
|
+
"operating without explicit URIs creates an open-redirect surface)");
|
|
1746
|
+
}
|
|
1747
|
+
for (var ri = 0; ri < metadata.redirect_uris.length; ri++) {
|
|
1748
|
+
_validateUrl(metadata.redirect_uris[ri], allowHttp,
|
|
1749
|
+
"metadata.redirect_uris[" + ri + "]");
|
|
1750
|
+
}
|
|
1751
|
+
return _dcrManagementCall("PUT", registrationClientUri, registrationAccessToken, metadata);
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
/**
|
|
1755
|
+
* @primitive b.auth.oauth.deleteClient
|
|
1756
|
+
* @signature b.auth.oauth.deleteClient(registrationClientUri, registrationAccessToken)
|
|
1757
|
+
* @since 0.10.16
|
|
1758
|
+
* @status stable
|
|
1759
|
+
*
|
|
1760
|
+
* RFC 7592 §2.3 deregister the dynamically-registered client via
|
|
1761
|
+
* DELETE. The AS responds 204 No Content on success; this primitive
|
|
1762
|
+
* returns true / throws on failure (404 = client already gone is
|
|
1763
|
+
* surfaced as a specific error so the caller can swallow it).
|
|
1764
|
+
*
|
|
1765
|
+
* @example
|
|
1766
|
+
* await oauth.deleteClient(rv.registration_client_uri,
|
|
1767
|
+
* rv.registration_access_token);
|
|
1768
|
+
*/
|
|
1769
|
+
async function deleteClient(registrationClientUri, registrationAccessToken) {
|
|
1770
|
+
await _dcrManagementCall("DELETE", registrationClientUri, registrationAccessToken, null);
|
|
1771
|
+
return true;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
async function _dcrManagementCall(method, registrationClientUri, registrationAccessToken, body) {
|
|
1775
|
+
if (typeof registrationClientUri !== "string" || registrationClientUri.length === 0) {
|
|
1776
|
+
throw new OAuthError("auth-oauth/bad-registration-client-uri",
|
|
1777
|
+
method.toLowerCase() + "Client: registrationClientUri must be a non-empty string");
|
|
1778
|
+
}
|
|
1779
|
+
if (typeof registrationAccessToken !== "string" || registrationAccessToken.length === 0) {
|
|
1780
|
+
throw new OAuthError("auth-oauth/bad-registration-access-token",
|
|
1781
|
+
method.toLowerCase() + "Client: registrationAccessToken must be a non-empty string");
|
|
1782
|
+
}
|
|
1783
|
+
_validateUrl(registrationClientUri, allowHttp, "registrationClientUri");
|
|
1784
|
+
var headers = {
|
|
1785
|
+
"Authorization": "Bearer " + registrationAccessToken,
|
|
1786
|
+
"Accept": "application/json",
|
|
1787
|
+
};
|
|
1788
|
+
var req = {
|
|
1789
|
+
url: registrationClientUri,
|
|
1790
|
+
method: method,
|
|
1791
|
+
headers: headers,
|
|
1792
|
+
};
|
|
1793
|
+
if (body !== null) {
|
|
1794
|
+
headers["Content-Type"] = "application/json";
|
|
1795
|
+
req.body = Buffer.from(safeJson.stringify(body), "utf8");
|
|
1796
|
+
}
|
|
1797
|
+
if (allowHttp) req.allowedProtocols = safeUrl.ALLOW_HTTP_ALL;
|
|
1798
|
+
if (allowInternal !== null) req.allowInternal = allowInternal;
|
|
1799
|
+
Object.assign(req, httpClientOpts);
|
|
1800
|
+
var res = await httpClient.request(req);
|
|
1801
|
+
if (method === "DELETE") {
|
|
1802
|
+
if (res.statusCode === 204 || res.statusCode === 200) return null;
|
|
1803
|
+
if (res.statusCode === 404) {
|
|
1804
|
+
throw new OAuthError("auth-oauth/dcr-not-found",
|
|
1805
|
+
"deleteClient: 404 — registrationClientUri does not resolve to a client");
|
|
1806
|
+
}
|
|
1807
|
+
throw new OAuthError("auth-oauth/dcr-delete-failed-" + res.statusCode,
|
|
1808
|
+
"deleteClient: " + res.statusCode);
|
|
1809
|
+
}
|
|
1810
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
1811
|
+
var errText = res.body ? res.body.toString("utf8").slice(0, 500) : "";
|
|
1812
|
+
throw new OAuthError("auth-oauth/dcr-" + method.toLowerCase() + "-failed-" + res.statusCode,
|
|
1813
|
+
method.toLowerCase() + "Client: " + res.statusCode + ": " + errText);
|
|
1814
|
+
}
|
|
1815
|
+
var text = res.body ? res.body.toString("utf8") : "";
|
|
1816
|
+
try { return safeJson.parse(text, { maxBytes: OAUTH_MAX_RESPONSE_BYTES }); }
|
|
1817
|
+
catch (e) {
|
|
1818
|
+
throw new OAuthError("auth-oauth/dcr-bad-response",
|
|
1819
|
+
method.toLowerCase() + "Client: response not JSON: " + ((e && e.message) || String(e)));
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1694
1823
|
/**
|
|
1695
1824
|
* @primitive b.auth.oauth.deviceAuthorization
|
|
1696
1825
|
* @signature b.auth.oauth.deviceAuthorization(opts?)
|
|
@@ -1911,6 +2040,60 @@ function create(opts) {
|
|
|
1911
2040
|
return await _normalizeTokens(parsed, xopts);
|
|
1912
2041
|
}
|
|
1913
2042
|
|
|
2043
|
+
/**
|
|
2044
|
+
* @primitive b.auth.oauth.nativeSsoExchange
|
|
2045
|
+
* @signature b.auth.oauth.nativeSsoExchange(opts)
|
|
2046
|
+
* @since 0.10.16
|
|
2047
|
+
* @status stable
|
|
2048
|
+
* @related b.auth.oauth.exchangeToken
|
|
2049
|
+
*
|
|
2050
|
+
* OpenID Connect Native SSO 1.0 §6 — exchange a `device_secret` +
|
|
2051
|
+
* `id_token` pair for a fresh access token for a different client
|
|
2052
|
+
* on the same device (the "second app SSO" pattern). Composes
|
|
2053
|
+
* exchangeToken with the Native-SSO requested-token-type +
|
|
2054
|
+
* device-secret URNs.
|
|
2055
|
+
*
|
|
2056
|
+
* The device_secret comes from the AS in the same response body as
|
|
2057
|
+
* id_token on the initial authentication when the AS supports Native
|
|
2058
|
+
* SSO; sibling apps on the same device get it via a platform IPC
|
|
2059
|
+
* channel.
|
|
2060
|
+
*
|
|
2061
|
+
* @opts
|
|
2062
|
+
* {
|
|
2063
|
+
* deviceSecret: string, // required — opaque device_secret from initial auth
|
|
2064
|
+
* idToken: string, // required — last-seen id_token bound to the device_secret
|
|
2065
|
+
* audience?: string, // optional — second app's client_id / resource indicator
|
|
2066
|
+
* scope?: string[],
|
|
2067
|
+
* }
|
|
2068
|
+
*
|
|
2069
|
+
* @example
|
|
2070
|
+
* var tokens = await oauth.nativeSsoExchange({
|
|
2071
|
+
* deviceSecret: secondAppRequest.deviceSecret,
|
|
2072
|
+
* idToken: secondAppRequest.idToken,
|
|
2073
|
+
* audience: "second-app-client-id",
|
|
2074
|
+
* });
|
|
2075
|
+
*/
|
|
2076
|
+
async function nativeSsoExchange(nopts) {
|
|
2077
|
+
nopts = nopts || {};
|
|
2078
|
+
if (typeof nopts.deviceSecret !== "string" || nopts.deviceSecret.length === 0) {
|
|
2079
|
+
throw new OAuthError("auth-oauth/bad-native-sso",
|
|
2080
|
+
"nativeSsoExchange: opts.deviceSecret required");
|
|
2081
|
+
}
|
|
2082
|
+
if (typeof nopts.idToken !== "string" || nopts.idToken.length === 0) {
|
|
2083
|
+
throw new OAuthError("auth-oauth/bad-native-sso",
|
|
2084
|
+
"nativeSsoExchange: opts.idToken required");
|
|
2085
|
+
}
|
|
2086
|
+
return await exchangeToken({
|
|
2087
|
+
subjectToken: nopts.idToken,
|
|
2088
|
+
subjectTokenType: "urn:ietf:params:oauth:token-type:id_token",
|
|
2089
|
+
actorToken: nopts.deviceSecret,
|
|
2090
|
+
actorTokenType: "urn:openid:params:token-type:device-secret",
|
|
2091
|
+
audience: nopts.audience,
|
|
2092
|
+
scope: nopts.scope,
|
|
2093
|
+
requestedTokenType: "urn:ietf:params:oauth:token-type:access_token",
|
|
2094
|
+
});
|
|
2095
|
+
}
|
|
2096
|
+
|
|
1914
2097
|
return {
|
|
1915
2098
|
authorizationUrl: authorizationUrl,
|
|
1916
2099
|
exchangeCode: exchangeCode,
|
|
@@ -1928,9 +2111,13 @@ function create(opts) {
|
|
|
1928
2111
|
parseJarmResponse: parseJarmResponse,
|
|
1929
2112
|
introspectToken: introspectToken,
|
|
1930
2113
|
registerClient: registerClient,
|
|
2114
|
+
readClient: readClient,
|
|
2115
|
+
updateClient: updateClient,
|
|
2116
|
+
deleteClient: deleteClient,
|
|
1931
2117
|
deviceAuthorization: deviceAuthorization,
|
|
1932
2118
|
pollDeviceCode: pollDeviceCode,
|
|
1933
2119
|
exchangeToken: exchangeToken,
|
|
2120
|
+
nativeSsoExchange: nativeSsoExchange,
|
|
1934
2121
|
// Diagnostic / power-user surface
|
|
1935
2122
|
issuer: issuer,
|
|
1936
2123
|
clientId: clientId,
|