@blamejs/blamejs-shop 0.0.44
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 +87 -0
- package/LICENSE +17 -0
- package/README.md +117 -0
- package/SECURITY.md +139 -0
- package/lib/admin.js +952 -0
- package/lib/analytics.js +267 -0
- package/lib/cart.js +279 -0
- package/lib/catalog-import.js +344 -0
- package/lib/catalog.js +769 -0
- package/lib/checkout.js +320 -0
- package/lib/config.js +151 -0
- package/lib/customers.js +322 -0
- package/lib/email.js +242 -0
- package/lib/externaldb-d1.js +283 -0
- package/lib/index.js +57 -0
- package/lib/inventory-alerts.js +198 -0
- package/lib/newsletter.js +142 -0
- package/lib/order.js +380 -0
- package/lib/payment.js +318 -0
- package/lib/pricing.js +185 -0
- package/lib/r2-bridge.js +169 -0
- package/lib/shipping.js +185 -0
- package/lib/storefront.js +2160 -0
- package/lib/subscriptions.js +410 -0
- package/lib/tax.js +161 -0
- package/lib/theme.js +194 -0
- package/lib/vendor/MANIFEST.json +19 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +23 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +34 -0
- package/lib/vendor/blamejs/.clusterfuzzlite/project.yaml +16 -0
- package/lib/vendor/blamejs/.dockerignore +45 -0
- package/lib/vendor/blamejs/.gitattributes +42 -0
- package/lib/vendor/blamejs/.github/CODEOWNERS +4 -0
- package/lib/vendor/blamejs/.github/FUNDING.yml +2 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/bug_report.md +58 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/lib/vendor/blamejs/.github/ISSUE_TEMPLATE/feature_request.md +99 -0
- package/lib/vendor/blamejs/.github/PULL_REQUEST_TEMPLATE.md +77 -0
- package/lib/vendor/blamejs/.github/dependabot.yml +37 -0
- package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +148 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +107 -0
- package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +122 -0
- package/lib/vendor/blamejs/.github/workflows/ci.yml +511 -0
- package/lib/vendor/blamejs/.github/workflows/codeql.yml +50 -0
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +655 -0
- package/lib/vendor/blamejs/.github/workflows/release-container.yml +406 -0
- package/lib/vendor/blamejs/.github/workflows/scorecard.yml +101 -0
- package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +134 -0
- package/lib/vendor/blamejs/.gitignore +102 -0
- package/lib/vendor/blamejs/.gitleaks.toml +166 -0
- package/lib/vendor/blamejs/.hadolint.yaml +18 -0
- package/lib/vendor/blamejs/.npmrc +5 -0
- package/lib/vendor/blamejs/.pinact.yaml +17 -0
- package/lib/vendor/blamejs/ARCHITECTURE.md +158 -0
- package/lib/vendor/blamejs/CHANGELOG.md +1351 -0
- package/lib/vendor/blamejs/CODE_OF_CONDUCT.md +86 -0
- package/lib/vendor/blamejs/CONTRIBUTING.md +156 -0
- package/lib/vendor/blamejs/GOVERNANCE.md +201 -0
- package/lib/vendor/blamejs/LICENSE +201 -0
- package/lib/vendor/blamejs/LTS-CALENDAR.md +29 -0
- package/lib/vendor/blamejs/MIGRATING.md +29 -0
- package/lib/vendor/blamejs/NOTICE +81 -0
- package/lib/vendor/blamejs/README.md +304 -0
- package/lib/vendor/blamejs/SECURITY.md +432 -0
- package/lib/vendor/blamejs/api-snapshot.json +48709 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.png +0 -0
- package/lib/vendor/blamejs/assets/BlameJS_Logo.svg +129 -0
- package/lib/vendor/blamejs/bench/README.md +77 -0
- package/lib/vendor/blamejs/bench/_helpers.js +70 -0
- package/lib/vendor/blamejs/bench/baseline.json +183 -0
- package/lib/vendor/blamejs/bench/crypto-hash.bench.js +19 -0
- package/lib/vendor/blamejs/bench/crypto-symmetric.bench.js +28 -0
- package/lib/vendor/blamejs/bench/run.js +140 -0
- package/lib/vendor/blamejs/bench/safe-json.bench.js +31 -0
- package/lib/vendor/blamejs/bin/blamejs.js +13 -0
- package/lib/vendor/blamejs/docker/caddy/Caddyfile +46 -0
- package/lib/vendor/blamejs/docker/coredns/Corefile +37 -0
- package/lib/vendor/blamejs/docker/haproxy/haproxy.cfg +52 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +118 -0
- package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +87 -0
- package/lib/vendor/blamejs/docker/mitmproxy/config.yaml +16 -0
- package/lib/vendor/blamejs/docker/mongo/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/mysql/my.cnf +12 -0
- package/lib/vendor/blamejs/docker/nats/nats.conf +33 -0
- package/lib/vendor/blamejs/docker/postgres/init-tls.sh +17 -0
- package/lib/vendor/blamejs/docker/postgres/postgresql.conf +18 -0
- package/lib/vendor/blamejs/docker/rabbitmq/rabbitmq.conf +18 -0
- package/lib/vendor/blamejs/docker/redis/redis.conf +15 -0
- package/lib/vendor/blamejs/docker/squid/squid.conf +24 -0
- package/lib/vendor/blamejs/docker/syslog/syslog-ng.conf +34 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +545 -0
- package/lib/vendor/blamejs/docs/cis-postgres-crosswalk.md +102 -0
- package/lib/vendor/blamejs/docs/cis-sqlite-equivalent.md +92 -0
- package/lib/vendor/blamejs/eslint.config.mjs +204 -0
- package/lib/vendor/blamejs/examples/wiki/Caddyfile +40 -0
- package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +218 -0
- package/lib/vendor/blamejs/examples/wiki/Dockerfile +120 -0
- package/lib/vendor/blamejs/examples/wiki/README.md +157 -0
- package/lib/vendor/blamejs/examples/wiki/cli-snapshot.json +250 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.prod.yml +231 -0
- package/lib/vendor/blamejs/examples/wiki/docker-compose.yml +166 -0
- package/lib/vendor/blamejs/examples/wiki/env-snapshot.json +217 -0
- package/lib/vendor/blamejs/examples/wiki/lib/auto-site-entries.js +139 -0
- package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +555 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-cli.js +507 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-env-vars.js +435 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-errors.js +282 -0
- package/lib/vendor/blamejs/examples/wiki/lib/harvest-vendored-deps.js +321 -0
- package/lib/vendor/blamejs/examples/wiki/lib/nav.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/lib/opts-resolver.js +75 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +508 -0
- package/lib/vendor/blamejs/examples/wiki/lib/section.js +276 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +587 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +318 -0
- package/lib/vendor/blamejs/examples/wiki/lib/symbol-index.js +122 -0
- package/lib/vendor/blamejs/examples/wiki/migrations/0001-pages-schema.js +74 -0
- package/lib/vendor/blamejs/examples/wiki/package.json +18 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.png +0 -0
- package/lib/vendor/blamejs/examples/wiki/public/img/blamejs-logo.svg +129 -0
- package/lib/vendor/blamejs/examples/wiki/public/robots.txt +5 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/MANIFEST.json +30 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.css +1 -0
- package/lib/vendor/blamejs/examples/wiki/public/vendor/prism.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/public/wiki.css +1250 -0
- package/lib/vendor/blamejs/examples/wiki/routes/admin.js +366 -0
- package/lib/vendor/blamejs/examples/wiki/routes/integration.js +230 -0
- package/lib/vendor/blamejs/examples/wiki/routes/pages.js +266 -0
- package/lib/vendor/blamejs/examples/wiki/scripts/backfill-module-metadata.js +214 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/0001-default-pages.js +35 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/_index.js +34 -0
- package/lib/vendor/blamejs/examples/wiki/seeders/prod/pages/api.js +76 -0
- package/lib/vendor/blamejs/examples/wiki/server.js +129 -0
- package/lib/vendor/blamejs/examples/wiki/site.config.js +197 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/README.md +38 -0
- package/lib/vendor/blamejs/examples/wiki/snippets/auth/password-hash.example.js +15 -0
- package/lib/vendor/blamejs/examples/wiki/src/editor.js +103 -0
- package/lib/vendor/blamejs/examples/wiki/src/wiki.js +349 -0
- package/lib/vendor/blamejs/examples/wiki/test/AUDIT.md +155 -0
- package/lib/vendor/blamejs/examples/wiki/test/codebase-patterns.test.js +594 -0
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +741 -0
- package/lib/vendor/blamejs/examples/wiki/test/find-missing-pages.js +254 -0
- package/lib/vendor/blamejs/examples/wiki/test/integration.js +391 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-cli-snapshot.js +379 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-env-snapshot.js +346 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-nav-coverage.js +212 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-site-coverage.js +252 -0
- package/lib/vendor/blamejs/examples/wiki/test/validate-source-comment-blocks.js +107 -0
- package/lib/vendor/blamejs/examples/wiki/views/_layout.html +115 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/api-keys.html +51 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/dashboard.html +22 -0
- package/lib/vendor/blamejs/examples/wiki/views/admin/edit.html +17 -0
- package/lib/vendor/blamejs/examples/wiki/views/home.html +85 -0
- package/lib/vendor/blamejs/examples/wiki/views/login.html +18 -0
- package/lib/vendor/blamejs/examples/wiki/views/page.html +5 -0
- package/lib/vendor/blamejs/examples/wiki/views/partials/nav.html +13 -0
- package/lib/vendor/blamejs/examples/wiki/views/search.html +19 -0
- package/lib/vendor/blamejs/examples/wiki/wiki.config.js +15 -0
- package/lib/vendor/blamejs/fuzz/README.md +137 -0
- package/lib/vendor/blamejs/fuzz/_expected.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-agent-registry.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-csv.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/01-basic.csv +3 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/02-formula.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-csv_seed_corpus/03-hyperlink.csv +1 -0
- package/lib/vendor/blamejs/fuzz/guard-dsn.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-email.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-email_seed_corpus/01-basic.eml +5 -0
- package/lib/vendor/blamejs/fuzz/guard-envelope.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-payload.fuzz.js +24 -0
- package/lib/vendor/blamejs/fuzz/guard-event-bus-topic.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-html.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/01-basic.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/02-script.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/03-event.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-html_seed_corpus/04-jsurl.html +1 -0
- package/lib/vendor/blamejs/fuzz/guard-idempotency-key.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-imap-command.fuzz.js +35 -0
- package/lib/vendor/blamejs/fuzz/guard-jmap.fuzz.js +41 -0
- package/lib/vendor/blamejs/fuzz/guard-json.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/01-basic.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/02-proto.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/03-dupkey.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/04-nan.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-json_seed_corpus/05-bom.json +1 -0
- package/lib/vendor/blamejs/fuzz/guard-list-id.fuzz.js +21 -0
- package/lib/vendor/blamejs/fuzz/guard-list-unsubscribe.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-compose.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-move.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-query.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-reply.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-mail-sieve.fuzz.js +36 -0
- package/lib/vendor/blamejs/fuzz/guard-managesieve-command.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/01-basic.md +2 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/02-jsurl.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-markdown_seed_corpus/03-jsimg.md +1 -0
- package/lib/vendor/blamejs/fuzz/guard-message-id.fuzz.js +26 -0
- package/lib/vendor/blamejs/fuzz/guard-pop3-command.fuzz.js +23 -0
- package/lib/vendor/blamejs/fuzz/guard-posture-chain.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-saga-config.fuzz.js +32 -0
- package/lib/vendor/blamejs/fuzz/guard-smtp-command.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/guard-snapshot-envelope.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-stream-args.fuzz.js +22 -0
- package/lib/vendor/blamejs/fuzz/guard-svg.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/01-basic.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-svg_seed_corpus/02-script.svg +1 -0
- package/lib/vendor/blamejs/fuzz/guard-tenant-id.fuzz.js +20 -0
- package/lib/vendor/blamejs/fuzz/guard-trace-context.fuzz.js +30 -0
- package/lib/vendor/blamejs/fuzz/guard-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-xml_seed_corpus/02-xxe.xml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/01-basic.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/02-anchor.yaml +2 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/03-norway.yaml +1 -0
- package/lib/vendor/blamejs/fuzz/guard-yaml_seed_corpus/04-multidoc.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-ini_seed_corpus/01-basic.ini +2 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-toml_seed_corpus/01-basic.toml +4 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-xml_seed_corpus/01-basic.xml +1 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/parsers__safe-yaml_seed_corpus/01-basic.yaml +4 -0
- package/lib/vendor/blamejs/fuzz/safe-decompress.fuzz.js +49 -0
- package/lib/vendor/blamejs/fuzz/safe-dns.fuzz.js +29 -0
- package/lib/vendor/blamejs/fuzz/safe-ical.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-icap.fuzz.js +42 -0
- package/lib/vendor/blamejs/fuzz/safe-json.fuzz.js +25 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/01-object.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/02-array.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/03-string.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/04-proto.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-json_seed_corpus/05-deep.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/02-filter.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/03-deepscan.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-jsonpath_seed_corpus/04-slice.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-mime.fuzz.js +27 -0
- package/lib/vendor/blamejs/fuzz/safe-mount-info.fuzz.js +33 -0
- package/lib/vendor/blamejs/fuzz/safe-sieve.fuzz.js +28 -0
- package/lib/vendor/blamejs/fuzz/safe-smtp.fuzz.js +64 -0
- package/lib/vendor/blamejs/fuzz/safe-url.fuzz.js +16 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/01-basic.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/02-userinfo.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/03-dangerous.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/04-data.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/05-ipv6.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-url_seed_corpus/06-idn.txt +1 -0
- package/lib/vendor/blamejs/fuzz/safe-vcard.fuzz.js +16 -0
- package/lib/vendor/blamejs/index.js +678 -0
- package/lib/vendor/blamejs/keys/release-pqc-pub.json +7 -0
- package/lib/vendor/blamejs/lib/_test/crypto-fixtures.js +67 -0
- package/lib/vendor/blamejs/lib/a2a-tasks.js +598 -0
- package/lib/vendor/blamejs/lib/a2a.js +407 -0
- package/lib/vendor/blamejs/lib/acme.js +1448 -0
- package/lib/vendor/blamejs/lib/agent-audit.js +45 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +382 -0
- package/lib/vendor/blamejs/lib/agent-idempotency.js +497 -0
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +717 -0
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +366 -0
- package/lib/vendor/blamejs/lib/agent-saga.js +321 -0
- package/lib/vendor/blamejs/lib/agent-snapshot.js +676 -0
- package/lib/vendor/blamejs/lib/agent-stream.js +269 -0
- package/lib/vendor/blamejs/lib/agent-tenant.js +632 -0
- package/lib/vendor/blamejs/lib/agent-trace.js +281 -0
- package/lib/vendor/blamejs/lib/ai-adverse-decision.js +184 -0
- package/lib/vendor/blamejs/lib/ai-content-detect.js +268 -0
- package/lib/vendor/blamejs/lib/ai-input.js +201 -0
- package/lib/vendor/blamejs/lib/ai-model-manifest.js +363 -0
- package/lib/vendor/blamejs/lib/ai-pref.js +340 -0
- package/lib/vendor/blamejs/lib/api-key.js +721 -0
- package/lib/vendor/blamejs/lib/api-snapshot.js +458 -0
- package/lib/vendor/blamejs/lib/app-shutdown.js +557 -0
- package/lib/vendor/blamejs/lib/app.js +365 -0
- package/lib/vendor/blamejs/lib/archive.js +547 -0
- package/lib/vendor/blamejs/lib/arg-parser.js +697 -0
- package/lib/vendor/blamejs/lib/argon2-builtin.js +173 -0
- package/lib/vendor/blamejs/lib/asn1-der.js +424 -0
- package/lib/vendor/blamejs/lib/asyncapi-bindings.js +160 -0
- package/lib/vendor/blamejs/lib/asyncapi-traits.js +143 -0
- package/lib/vendor/blamejs/lib/asyncapi.js +575 -0
- package/lib/vendor/blamejs/lib/atomic-file.js +1023 -0
- package/lib/vendor/blamejs/lib/audit-chain.js +266 -0
- package/lib/vendor/blamejs/lib/audit-daily-review.js +389 -0
- package/lib/vendor/blamejs/lib/audit-sign.js +751 -0
- package/lib/vendor/blamejs/lib/audit-tools.js +1113 -0
- package/lib/vendor/blamejs/lib/audit.js +1671 -0
- package/lib/vendor/blamejs/lib/auth/aal.js +169 -0
- package/lib/vendor/blamejs/lib/auth/access-lock.js +220 -0
- package/lib/vendor/blamejs/lib/auth/acr-vocabulary.js +265 -0
- package/lib/vendor/blamejs/lib/auth/ato-kill-switch.js +112 -0
- package/lib/vendor/blamejs/lib/auth/auth-time-tracker.js +111 -0
- package/lib/vendor/blamejs/lib/auth/bot-challenge.js +573 -0
- package/lib/vendor/blamejs/lib/auth/ciba.js +637 -0
- package/lib/vendor/blamejs/lib/auth/dpop.js +516 -0
- package/lib/vendor/blamejs/lib/auth/elevation-grant.js +306 -0
- package/lib/vendor/blamejs/lib/auth/fal.js +229 -0
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +681 -0
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +519 -0
- package/lib/vendor/blamejs/lib/auth/jwt.js +430 -0
- package/lib/vendor/blamejs/lib/auth/lockout.js +449 -0
- package/lib/vendor/blamejs/lib/auth/oauth.js +2141 -0
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +657 -0
- package/lib/vendor/blamejs/lib/auth/oid4vp.js +531 -0
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +600 -0
- package/lib/vendor/blamejs/lib/auth/passkey.js +676 -0
- package/lib/vendor/blamejs/lib/auth/password.js +693 -0
- package/lib/vendor/blamejs/lib/auth/saml.js +2109 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-disclosure.js +95 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-holder.js +225 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc-issuer.js +197 -0
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +728 -0
- package/lib/vendor/blamejs/lib/auth/status-list.js +272 -0
- package/lib/vendor/blamejs/lib/auth/step-up-policy.js +335 -0
- package/lib/vendor/blamejs/lib/auth/step-up.js +454 -0
- package/lib/vendor/blamejs/lib/auth-bot-challenge.js +505 -0
- package/lib/vendor/blamejs/lib/auth-header.js +148 -0
- package/lib/vendor/blamejs/lib/backup/bundle.js +265 -0
- package/lib/vendor/blamejs/lib/backup/crypto.js +176 -0
- package/lib/vendor/blamejs/lib/backup/index.js +1001 -0
- package/lib/vendor/blamejs/lib/backup/manifest.js +443 -0
- package/lib/vendor/blamejs/lib/boot-gates.js +174 -0
- package/lib/vendor/blamejs/lib/breach-deadline.js +272 -0
- package/lib/vendor/blamejs/lib/break-glass.js +1753 -0
- package/lib/vendor/blamejs/lib/budr.js +205 -0
- package/lib/vendor/blamejs/lib/bundler.js +461 -0
- package/lib/vendor/blamejs/lib/cache-redis.js +256 -0
- package/lib/vendor/blamejs/lib/cache-status.js +288 -0
- package/lib/vendor/blamejs/lib/cache.js +1331 -0
- package/lib/vendor/blamejs/lib/calendar.js +1240 -0
- package/lib/vendor/blamejs/lib/canonical-json.js +143 -0
- package/lib/vendor/blamejs/lib/cdn-cache-control.js +473 -0
- package/lib/vendor/blamejs/lib/cert.js +763 -0
- package/lib/vendor/blamejs/lib/chain-writer.js +259 -0
- package/lib/vendor/blamejs/lib/circuit-breaker.js +101 -0
- package/lib/vendor/blamejs/lib/cli-helpers.js +237 -0
- package/lib/vendor/blamejs/lib/cli.js +2328 -0
- package/lib/vendor/blamejs/lib/client-hints.js +318 -0
- package/lib/vendor/blamejs/lib/cloud-events.js +277 -0
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +317 -0
- package/lib/vendor/blamejs/lib/cluster-storage.js +351 -0
- package/lib/vendor/blamejs/lib/cluster.js +1017 -0
- package/lib/vendor/blamejs/lib/cms-codec.js +826 -0
- package/lib/vendor/blamejs/lib/codepoint-class.js +262 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-logging.js +190 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +205 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-risk.js +189 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +200 -0
- package/lib/vendor/blamejs/lib/compliance-ai-act.js +821 -0
- package/lib/vendor/blamejs/lib/compliance-eaa.js +204 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-aliases.js +167 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fetcher.js +206 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions-fuzzy.js +297 -0
- package/lib/vendor/blamejs/lib/compliance-sanctions.js +569 -0
- package/lib/vendor/blamejs/lib/compliance.js +1558 -0
- package/lib/vendor/blamejs/lib/config-drift.js +426 -0
- package/lib/vendor/blamejs/lib/config.js +446 -0
- package/lib/vendor/blamejs/lib/consent.js +369 -0
- package/lib/vendor/blamejs/lib/constants.js +209 -0
- package/lib/vendor/blamejs/lib/content-credentials.js +704 -0
- package/lib/vendor/blamejs/lib/cookies.js +560 -0
- package/lib/vendor/blamejs/lib/cra-report.js +299 -0
- package/lib/vendor/blamejs/lib/credential-hash.js +394 -0
- package/lib/vendor/blamejs/lib/crypto-field.js +1017 -0
- package/lib/vendor/blamejs/lib/crypto-hpke-pq.js +187 -0
- package/lib/vendor/blamejs/lib/crypto-hpke.js +256 -0
- package/lib/vendor/blamejs/lib/crypto.js +1908 -0
- package/lib/vendor/blamejs/lib/csp.js +271 -0
- package/lib/vendor/blamejs/lib/csv.js +418 -0
- package/lib/vendor/blamejs/lib/daemon.js +481 -0
- package/lib/vendor/blamejs/lib/dark-patterns.js +488 -0
- package/lib/vendor/blamejs/lib/data-act.js +328 -0
- package/lib/vendor/blamejs/lib/db-collection.js +587 -0
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +267 -0
- package/lib/vendor/blamejs/lib/db-declare-view.js +420 -0
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +333 -0
- package/lib/vendor/blamejs/lib/db-query.js +802 -0
- package/lib/vendor/blamejs/lib/db-role-context.js +50 -0
- package/lib/vendor/blamejs/lib/db-schema.js +322 -0
- package/lib/vendor/blamejs/lib/db.js +3111 -0
- package/lib/vendor/blamejs/lib/dbsc.js +299 -0
- package/lib/vendor/blamejs/lib/ddl-change-control.js +523 -0
- package/lib/vendor/blamejs/lib/deprecate.js +377 -0
- package/lib/vendor/blamejs/lib/dev.js +405 -0
- package/lib/vendor/blamejs/lib/dora.js +402 -0
- package/lib/vendor/blamejs/lib/dr-runbook.js +368 -0
- package/lib/vendor/blamejs/lib/dsr.js +1188 -0
- package/lib/vendor/blamejs/lib/dual-control.js +526 -0
- package/lib/vendor/blamejs/lib/early-hints.js +212 -0
- package/lib/vendor/blamejs/lib/error-page.js +420 -0
- package/lib/vendor/blamejs/lib/events.js +214 -0
- package/lib/vendor/blamejs/lib/external-db-migrate.js +659 -0
- package/lib/vendor/blamejs/lib/external-db.js +1877 -0
- package/lib/vendor/blamejs/lib/fapi2.js +394 -0
- package/lib/vendor/blamejs/lib/fda-21cfr11.js +395 -0
- package/lib/vendor/blamejs/lib/fdx.js +370 -0
- package/lib/vendor/blamejs/lib/fedcm.js +264 -0
- package/lib/vendor/blamejs/lib/file-type.js +360 -0
- package/lib/vendor/blamejs/lib/file-upload.js +1256 -0
- package/lib/vendor/blamejs/lib/flag-cache.js +136 -0
- package/lib/vendor/blamejs/lib/flag-evaluation-context.js +135 -0
- package/lib/vendor/blamejs/lib/flag-providers.js +279 -0
- package/lib/vendor/blamejs/lib/flag-targeting.js +210 -0
- package/lib/vendor/blamejs/lib/flag.js +346 -0
- package/lib/vendor/blamejs/lib/forms.js +525 -0
- package/lib/vendor/blamejs/lib/framework-error.js +724 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +845 -0
- package/lib/vendor/blamejs/lib/framework-sha1-hibp.js +34 -0
- package/lib/vendor/blamejs/lib/fsm.js +469 -0
- package/lib/vendor/blamejs/lib/gate-contract.js +1661 -0
- package/lib/vendor/blamejs/lib/gdpr-ropa.js +261 -0
- package/lib/vendor/blamejs/lib/graphql-federation.js +234 -0
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +179 -0
- package/lib/vendor/blamejs/lib/guard-all.js +555 -0
- package/lib/vendor/blamejs/lib/guard-archive.js +901 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +451 -0
- package/lib/vendor/blamejs/lib/guard-cidr.js +676 -0
- package/lib/vendor/blamejs/lib/guard-csv.js +1176 -0
- package/lib/vendor/blamejs/lib/guard-domain.js +814 -0
- package/lib/vendor/blamejs/lib/guard-dsn.js +382 -0
- package/lib/vendor/blamejs/lib/guard-email.js +951 -0
- package/lib/vendor/blamejs/lib/guard-envelope.js +294 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +217 -0
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +150 -0
- package/lib/vendor/blamejs/lib/guard-filename.js +956 -0
- package/lib/vendor/blamejs/lib/guard-graphql.js +731 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-aria.js +164 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-forms.js +144 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tables.js +154 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag-tagwalk.js +44 -0
- package/lib/vendor/blamejs/lib/guard-html-wcag.js +470 -0
- package/lib/vendor/blamejs/lib/guard-html.js +1209 -0
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +151 -0
- package/lib/vendor/blamejs/lib/guard-image.js +584 -0
- package/lib/vendor/blamejs/lib/guard-imap-command.js +337 -0
- package/lib/vendor/blamejs/lib/guard-jmap.js +321 -0
- package/lib/vendor/blamejs/lib/guard-json.js +935 -0
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +512 -0
- package/lib/vendor/blamejs/lib/guard-jwt.js +772 -0
- package/lib/vendor/blamejs/lib/guard-list-id.js +318 -0
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +412 -0
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +282 -0
- package/lib/vendor/blamejs/lib/guard-mail-move.js +202 -0
- package/lib/vendor/blamejs/lib/guard-mail-query.js +310 -0
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +172 -0
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +207 -0
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +566 -0
- package/lib/vendor/blamejs/lib/guard-markdown.js +768 -0
- package/lib/vendor/blamejs/lib/guard-message-id.js +267 -0
- package/lib/vendor/blamejs/lib/guard-mime.js +609 -0
- package/lib/vendor/blamejs/lib/guard-oauth.js +650 -0
- package/lib/vendor/blamejs/lib/guard-pdf.js +569 -0
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +317 -0
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +201 -0
- package/lib/vendor/blamejs/lib/guard-regex.js +632 -0
- package/lib/vendor/blamejs/lib/guard-saga-config.js +157 -0
- package/lib/vendor/blamejs/lib/guard-shell.js +522 -0
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +594 -0
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +168 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +166 -0
- package/lib/vendor/blamejs/lib/guard-svg.js +1163 -0
- package/lib/vendor/blamejs/lib/guard-template.js +490 -0
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +138 -0
- package/lib/vendor/blamejs/lib/guard-time.js +586 -0
- package/lib/vendor/blamejs/lib/guard-trace-context.js +172 -0
- package/lib/vendor/blamejs/lib/guard-uuid.js +548 -0
- package/lib/vendor/blamejs/lib/guard-xml.js +666 -0
- package/lib/vendor/blamejs/lib/guard-yaml.js +726 -0
- package/lib/vendor/blamejs/lib/hal.js +125 -0
- package/lib/vendor/blamejs/lib/handlers.js +350 -0
- package/lib/vendor/blamejs/lib/honeytoken.js +168 -0
- package/lib/vendor/blamejs/lib/html-balance.js +347 -0
- package/lib/vendor/blamejs/lib/http-client-cache.js +923 -0
- package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +519 -0
- package/lib/vendor/blamejs/lib/http-client.js +2152 -0
- package/lib/vendor/blamejs/lib/http-message-signature.js +589 -0
- package/lib/vendor/blamejs/lib/http2-teardown.js +34 -0
- package/lib/vendor/blamejs/lib/i18n-messageformat.js +398 -0
- package/lib/vendor/blamejs/lib/i18n.js +931 -0
- package/lib/vendor/blamejs/lib/iab-mspa.js +257 -0
- package/lib/vendor/blamejs/lib/iab-tcf.js +461 -0
- package/lib/vendor/blamejs/lib/importmap-integrity.js +90 -0
- package/lib/vendor/blamejs/lib/inbox.js +435 -0
- package/lib/vendor/blamejs/lib/incident-report.js +314 -0
- package/lib/vendor/blamejs/lib/ip-utils.js +102 -0
- package/lib/vendor/blamejs/lib/jobs.js +185 -0
- package/lib/vendor/blamejs/lib/jose-jwe-experimental.js +228 -0
- package/lib/vendor/blamejs/lib/jsonapi.js +230 -0
- package/lib/vendor/blamejs/lib/keychain.js +865 -0
- package/lib/vendor/blamejs/lib/lazy-require.js +48 -0
- package/lib/vendor/blamejs/lib/legal-hold.js +374 -0
- package/lib/vendor/blamejs/lib/local-db-thin.js +321 -0
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +369 -0
- package/lib/vendor/blamejs/lib/log-stream-local.js +146 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +410 -0
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +286 -0
- package/lib/vendor/blamejs/lib/log-stream-syslog.js +310 -0
- package/lib/vendor/blamejs/lib/log-stream-webhook.js +199 -0
- package/lib/vendor/blamejs/lib/log-stream.js +584 -0
- package/lib/vendor/blamejs/lib/log.js +625 -0
- package/lib/vendor/blamejs/lib/lro.js +200 -0
- package/lib/vendor/blamejs/lib/mail-agent.js +786 -0
- package/lib/vendor/blamejs/lib/mail-arc-sign.js +417 -0
- package/lib/vendor/blamejs/lib/mail-arf.js +343 -0
- package/lib/vendor/blamejs/lib/mail-auth.js +2144 -0
- package/lib/vendor/blamejs/lib/mail-bimi.js +1047 -0
- package/lib/vendor/blamejs/lib/mail-bounce.js +955 -0
- package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +1286 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +789 -0
- package/lib/vendor/blamejs/lib/mail-crypto.js +108 -0
- package/lib/vendor/blamejs/lib/mail-dav.js +1224 -0
- package/lib/vendor/blamejs/lib/mail-deploy.js +1119 -0
- package/lib/vendor/blamejs/lib/mail-dkim.js +1250 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +448 -0
- package/lib/vendor/blamejs/lib/mail-helo.js +473 -0
- package/lib/vendor/blamejs/lib/mail-journal.js +435 -0
- package/lib/vendor/blamejs/lib/mail-mdn.js +424 -0
- package/lib/vendor/blamejs/lib/mail-rbl.js +392 -0
- package/lib/vendor/blamejs/lib/mail-require-tls.js +198 -0
- package/lib/vendor/blamejs/lib/mail-scan.js +502 -0
- package/lib/vendor/blamejs/lib/mail-send-deliver.js +629 -0
- package/lib/vendor/blamejs/lib/mail-server-imap.js +1858 -0
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +1565 -0
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +908 -0
- package/lib/vendor/blamejs/lib/mail-server-mx.js +969 -0
- package/lib/vendor/blamejs/lib/mail-server-pop3.js +915 -0
- package/lib/vendor/blamejs/lib/mail-server-rate-limit.js +315 -0
- package/lib/vendor/blamejs/lib/mail-server-registry.js +378 -0
- package/lib/vendor/blamejs/lib/mail-server-submission.js +1396 -0
- package/lib/vendor/blamejs/lib/mail-server-tls.js +445 -0
- package/lib/vendor/blamejs/lib/mail-sieve.js +557 -0
- package/lib/vendor/blamejs/lib/mail-spam-score.js +284 -0
- package/lib/vendor/blamejs/lib/mail-srs.js +248 -0
- package/lib/vendor/blamejs/lib/mail-store-fts.js +394 -0
- package/lib/vendor/blamejs/lib/mail-store.js +929 -0
- package/lib/vendor/blamejs/lib/mail-unsubscribe.js +400 -0
- package/lib/vendor/blamejs/lib/mail.js +1971 -0
- package/lib/vendor/blamejs/lib/mcp-tool-registry.js +473 -0
- package/lib/vendor/blamejs/lib/mcp.js +950 -0
- package/lib/vendor/blamejs/lib/metrics.js +1503 -0
- package/lib/vendor/blamejs/lib/middleware/age-gate.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/ai-act-disclosure.js +203 -0
- package/lib/vendor/blamejs/lib/middleware/api-encrypt.js +981 -0
- package/lib/vendor/blamejs/lib/middleware/assetlinks.js +137 -0
- package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +171 -0
- package/lib/vendor/blamejs/lib/middleware/attach-user.js +220 -0
- package/lib/vendor/blamejs/lib/middleware/bearer-auth.js +293 -0
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +1519 -0
- package/lib/vendor/blamejs/lib/middleware/bot-disclose.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/bot-guard.js +217 -0
- package/lib/vendor/blamejs/lib/middleware/clear-site-data.js +122 -0
- package/lib/vendor/blamejs/lib/middleware/compose-pipeline.js +355 -0
- package/lib/vendor/blamejs/lib/middleware/compression.js +489 -0
- package/lib/vendor/blamejs/lib/middleware/cookies.js +130 -0
- package/lib/vendor/blamejs/lib/middleware/cors.js +386 -0
- package/lib/vendor/blamejs/lib/middleware/csp-nonce.js +388 -0
- package/lib/vendor/blamejs/lib/middleware/csp-report.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +499 -0
- package/lib/vendor/blamejs/lib/middleware/daily-byte-quota.js +243 -0
- package/lib/vendor/blamejs/lib/middleware/db-role-for.js +304 -0
- package/lib/vendor/blamejs/lib/middleware/dpop.js +402 -0
- package/lib/vendor/blamejs/lib/middleware/error-handler.js +69 -0
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +168 -0
- package/lib/vendor/blamejs/lib/middleware/flag-context.js +110 -0
- package/lib/vendor/blamejs/lib/middleware/gpc.js +153 -0
- package/lib/vendor/blamejs/lib/middleware/headers.js +242 -0
- package/lib/vendor/blamejs/lib/middleware/health.js +438 -0
- package/lib/vendor/blamejs/lib/middleware/host-allowlist.js +189 -0
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +964 -0
- package/lib/vendor/blamejs/lib/middleware/index.js +183 -0
- package/lib/vendor/blamejs/lib/middleware/nel.js +214 -0
- package/lib/vendor/blamejs/lib/middleware/network-allowlist.js +237 -0
- package/lib/vendor/blamejs/lib/middleware/no-cache.js +106 -0
- package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +177 -0
- package/lib/vendor/blamejs/lib/middleware/protected-resource-metadata.js +277 -0
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +556 -0
- package/lib/vendor/blamejs/lib/middleware/request-id.js +79 -0
- package/lib/vendor/blamejs/lib/middleware/request-log.js +205 -0
- package/lib/vendor/blamejs/lib/middleware/require-aal.js +138 -0
- package/lib/vendor/blamejs/lib/middleware/require-auth.js +144 -0
- package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +290 -0
- package/lib/vendor/blamejs/lib/middleware/require-content-type.js +113 -0
- package/lib/vendor/blamejs/lib/middleware/require-methods.js +97 -0
- package/lib/vendor/blamejs/lib/middleware/require-mtls.js +212 -0
- package/lib/vendor/blamejs/lib/middleware/require-step-up.js +226 -0
- package/lib/vendor/blamejs/lib/middleware/scim-server.js +375 -0
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +285 -0
- package/lib/vendor/blamejs/lib/middleware/security-txt.js +170 -0
- package/lib/vendor/blamejs/lib/middleware/span-http-server.js +280 -0
- package/lib/vendor/blamejs/lib/middleware/speculation-rules.js +323 -0
- package/lib/vendor/blamejs/lib/middleware/sse.js +200 -0
- package/lib/vendor/blamejs/lib/middleware/trace-log-correlation.js +167 -0
- package/lib/vendor/blamejs/lib/middleware/trace-propagate.js +148 -0
- package/lib/vendor/blamejs/lib/middleware/tus-upload.js +749 -0
- package/lib/vendor/blamejs/lib/middleware/web-app-manifest.js +164 -0
- package/lib/vendor/blamejs/lib/migration-files.js +37 -0
- package/lib/vendor/blamejs/lib/migrations.js +385 -0
- package/lib/vendor/blamejs/lib/mime-parse.js +198 -0
- package/lib/vendor/blamejs/lib/money.js +699 -0
- package/lib/vendor/blamejs/lib/mtls-ca.js +572 -0
- package/lib/vendor/blamejs/lib/mtls-engine-default.js +501 -0
- package/lib/vendor/blamejs/lib/network-byte-quota.js +308 -0
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +533 -0
- package/lib/vendor/blamejs/lib/network-dns.js +1930 -0
- package/lib/vendor/blamejs/lib/network-heartbeat.js +425 -0
- package/lib/vendor/blamejs/lib/network-nts.js +574 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +265 -0
- package/lib/vendor/blamejs/lib/network-smtp-policy.js +836 -0
- package/lib/vendor/blamejs/lib/network-tls.js +3126 -0
- package/lib/vendor/blamejs/lib/network.js +346 -0
- package/lib/vendor/blamejs/lib/nis2-report.js +181 -0
- package/lib/vendor/blamejs/lib/nist-crosswalk.js +293 -0
- package/lib/vendor/blamejs/lib/nonce-store.js +177 -0
- package/lib/vendor/blamejs/lib/notify.js +683 -0
- package/lib/vendor/blamejs/lib/ntp-check.js +458 -0
- package/lib/vendor/blamejs/lib/numeric-bounds.js +111 -0
- package/lib/vendor/blamejs/lib/numeric-checks.js +40 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +349 -0
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +488 -0
- package/lib/vendor/blamejs/lib/object-store/gcs-bucket-ops.js +351 -0
- package/lib/vendor/blamejs/lib/object-store/gcs.js +515 -0
- package/lib/vendor/blamejs/lib/object-store/http-put.js +153 -0
- package/lib/vendor/blamejs/lib/object-store/http-request.js +38 -0
- package/lib/vendor/blamejs/lib/object-store/index.js +197 -0
- package/lib/vendor/blamejs/lib/object-store/local.js +163 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +1133 -0
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +957 -0
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +420 -0
- package/lib/vendor/blamejs/lib/observability-tracer.js +395 -0
- package/lib/vendor/blamejs/lib/observability.js +720 -0
- package/lib/vendor/blamejs/lib/openapi-paths-builder.js +248 -0
- package/lib/vendor/blamejs/lib/openapi-schema-walk.js +192 -0
- package/lib/vendor/blamejs/lib/openapi-security.js +169 -0
- package/lib/vendor/blamejs/lib/openapi-yaml.js +154 -0
- package/lib/vendor/blamejs/lib/openapi.js +489 -0
- package/lib/vendor/blamejs/lib/otel-export.js +278 -0
- package/lib/vendor/blamejs/lib/outbox.js +547 -0
- package/lib/vendor/blamejs/lib/pagination.js +542 -0
- package/lib/vendor/blamejs/lib/parsers/index.js +91 -0
- package/lib/vendor/blamejs/lib/parsers/safe-env.js +642 -0
- package/lib/vendor/blamejs/lib/parsers/safe-ini.js +293 -0
- package/lib/vendor/blamejs/lib/parsers/safe-toml.js +784 -0
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +390 -0
- package/lib/vendor/blamejs/lib/parsers/safe-yaml.js +1015 -0
- package/lib/vendor/blamejs/lib/permissions.js +793 -0
- package/lib/vendor/blamejs/lib/pick.js +105 -0
- package/lib/vendor/blamejs/lib/pqc-agent.js +351 -0
- package/lib/vendor/blamejs/lib/pqc-gate.js +279 -0
- package/lib/vendor/blamejs/lib/pqc-software.js +271 -0
- package/lib/vendor/blamejs/lib/problem-details.js +482 -0
- package/lib/vendor/blamejs/lib/process-spawn.js +196 -0
- package/lib/vendor/blamejs/lib/promise-pool.js +162 -0
- package/lib/vendor/blamejs/lib/protobuf-encoder.js +190 -0
- package/lib/vendor/blamejs/lib/protocol-dispatcher.js +161 -0
- package/lib/vendor/blamejs/lib/public-suffix.js +403 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +154 -0
- package/lib/vendor/blamejs/lib/pubsub-redis.js +167 -0
- package/lib/vendor/blamejs/lib/pubsub.js +463 -0
- package/lib/vendor/blamejs/lib/queue-local.js +476 -0
- package/lib/vendor/blamejs/lib/queue-redis.js +745 -0
- package/lib/vendor/blamejs/lib/queue-sqs.js +319 -0
- package/lib/vendor/blamejs/lib/queue.js +1016 -0
- package/lib/vendor/blamejs/lib/redact.js +1007 -0
- package/lib/vendor/blamejs/lib/redis-client.js +520 -0
- package/lib/vendor/blamejs/lib/render.js +285 -0
- package/lib/vendor/blamejs/lib/request-helpers.js +767 -0
- package/lib/vendor/blamejs/lib/resource-access-lock.js +116 -0
- package/lib/vendor/blamejs/lib/restore-bundle.js +340 -0
- package/lib/vendor/blamejs/lib/restore-rollback.js +365 -0
- package/lib/vendor/blamejs/lib/restore.js +409 -0
- package/lib/vendor/blamejs/lib/retention.js +640 -0
- package/lib/vendor/blamejs/lib/retry.js +523 -0
- package/lib/vendor/blamejs/lib/router.js +1289 -0
- package/lib/vendor/blamejs/lib/safe-async.js +1184 -0
- package/lib/vendor/blamejs/lib/safe-buffer.js +562 -0
- package/lib/vendor/blamejs/lib/safe-decompress.js +297 -0
- package/lib/vendor/blamejs/lib/safe-dns.js +665 -0
- package/lib/vendor/blamejs/lib/safe-ical.js +634 -0
- package/lib/vendor/blamejs/lib/safe-icap.js +502 -0
- package/lib/vendor/blamejs/lib/safe-json.js +946 -0
- package/lib/vendor/blamejs/lib/safe-jsonpath.js +285 -0
- package/lib/vendor/blamejs/lib/safe-mime.js +831 -0
- package/lib/vendor/blamejs/lib/safe-mount-info.js +306 -0
- package/lib/vendor/blamejs/lib/safe-path.js +254 -0
- package/lib/vendor/blamejs/lib/safe-redirect.js +106 -0
- package/lib/vendor/blamejs/lib/safe-schema.js +1810 -0
- package/lib/vendor/blamejs/lib/safe-sieve.js +684 -0
- package/lib/vendor/blamejs/lib/safe-smtp.js +185 -0
- package/lib/vendor/blamejs/lib/safe-sql.js +363 -0
- package/lib/vendor/blamejs/lib/safe-url.js +428 -0
- package/lib/vendor/blamejs/lib/safe-vcard.js +473 -0
- package/lib/vendor/blamejs/lib/sandbox-worker.js +135 -0
- package/lib/vendor/blamejs/lib/sandbox.js +358 -0
- package/lib/vendor/blamejs/lib/scheduler.js +827 -0
- package/lib/vendor/blamejs/lib/sd-notify.js +269 -0
- package/lib/vendor/blamejs/lib/sec-cyber.js +214 -0
- package/lib/vendor/blamejs/lib/security-assert.js +395 -0
- package/lib/vendor/blamejs/lib/seeders.js +620 -0
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +309 -0
- package/lib/vendor/blamejs/lib/self-update.js +804 -0
- package/lib/vendor/blamejs/lib/server-timing.js +174 -0
- package/lib/vendor/blamejs/lib/session-device-binding.js +431 -0
- package/lib/vendor/blamejs/lib/session-stores.js +138 -0
- package/lib/vendor/blamejs/lib/session.js +1162 -0
- package/lib/vendor/blamejs/lib/slug.js +381 -0
- package/lib/vendor/blamejs/lib/sse.js +349 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +792 -0
- package/lib/vendor/blamejs/lib/standard-webhooks.js +183 -0
- package/lib/vendor/blamejs/lib/static.js +1249 -0
- package/lib/vendor/blamejs/lib/storage.js +1272 -0
- package/lib/vendor/blamejs/lib/stream-throttle.js +235 -0
- package/lib/vendor/blamejs/lib/structured-fields.js +244 -0
- package/lib/vendor/blamejs/lib/subject.js +667 -0
- package/lib/vendor/blamejs/lib/tcpa-10dlc.js +175 -0
- package/lib/vendor/blamejs/lib/template.js +931 -0
- package/lib/vendor/blamejs/lib/tenant-quota.js +545 -0
- package/lib/vendor/blamejs/lib/test-harness.js +275 -0
- package/lib/vendor/blamejs/lib/testing.js +1185 -0
- package/lib/vendor/blamejs/lib/time.js +578 -0
- package/lib/vendor/blamejs/lib/tls-exporter.js +239 -0
- package/lib/vendor/blamejs/lib/totp.js +318 -0
- package/lib/vendor/blamejs/lib/tracing.js +546 -0
- package/lib/vendor/blamejs/lib/uuid.js +207 -0
- package/lib/vendor/blamejs/lib/validate-opts.js +381 -0
- package/lib/vendor/blamejs/lib/vault/index.js +638 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +311 -0
- package/lib/vendor/blamejs/lib/vault/passphrase-source.js +198 -0
- package/lib/vendor/blamejs/lib/vault/rotate.js +803 -0
- package/lib/vendor/blamejs/lib/vault/seal-pem-file.js +471 -0
- package/lib/vendor/blamejs/lib/vault/wrap.js +296 -0
- package/lib/vendor/blamejs/lib/vault-aad.js +259 -0
- package/lib/vendor/blamejs/lib/vendor/.vendor-data-pubkey +4 -0
- package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +161 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.data.js +68 -0
- package/lib/vendor/blamejs/lib/vendor/bimi-trust-anchors.pem +33 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.data.js +1325 -0
- package/lib/vendor/blamejs/lib/vendor/common-passwords-top-10000.txt +10002 -0
- package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +9 -0
- package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +18 -0
- package/lib/vendor/blamejs/lib/vendor/pki.cjs +181 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +16382 -0
- package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5881 -0
- package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +328 -0
- package/lib/vendor/blamejs/lib/vendor/vendor-data-pubkey.js +16 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +520 -0
- package/lib/vendor/blamejs/lib/vex.js +630 -0
- package/lib/vendor/blamejs/lib/watcher.js +608 -0
- package/lib/vendor/blamejs/lib/web-push-vapid.js +322 -0
- package/lib/vendor/blamejs/lib/webhook.js +977 -0
- package/lib/vendor/blamejs/lib/websocket-channels.js +327 -0
- package/lib/vendor/blamejs/lib/websocket.js +1561 -0
- package/lib/vendor/blamejs/lib/wiki-concepts.js +338 -0
- package/lib/vendor/blamejs/lib/worker-pool.js +464 -0
- package/lib/vendor/blamejs/lib/ws-client.js +978 -0
- package/lib/vendor/blamejs/lib/xml-c14n.js +506 -0
- package/lib/vendor/blamejs/memory/specs/node-26-map-getorinsert-migration.md +164 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +19 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/README.md +88 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/build.sh +26 -0
- package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/project.yaml +28 -0
- package/lib/vendor/blamejs/package.json +81 -0
- package/lib/vendor/blamejs/release-notes/v0.0.x.json +310 -0
- package/lib/vendor/blamejs/release-notes/v0.1.x.json +1798 -0
- package/lib/vendor/blamejs/release-notes/v0.10.x.json +1288 -0
- package/lib/vendor/blamejs/release-notes/v0.11.x.json +2551 -0
- package/lib/vendor/blamejs/release-notes/v0.12.0.json +64 -0
- package/lib/vendor/blamejs/release-notes/v0.12.1.json +32 -0
- package/lib/vendor/blamejs/release-notes/v0.12.2.json +45 -0
- package/lib/vendor/blamejs/release-notes/v0.2.x.json +706 -0
- package/lib/vendor/blamejs/release-notes/v0.3.x.json +786 -0
- package/lib/vendor/blamejs/release-notes/v0.4.x.json +588 -0
- package/lib/vendor/blamejs/release-notes/v0.5.x.json +390 -0
- package/lib/vendor/blamejs/release-notes/v0.6.x.json +1947 -0
- package/lib/vendor/blamejs/release-notes/v0.7.x.json +3811 -0
- package/lib/vendor/blamejs/release-notes/v0.8.x.json +3318 -0
- package/lib/vendor/blamejs/release-notes/v0.9.x.json +2257 -0
- package/lib/vendor/blamejs/scripts/build-vendored-sbom.js +325 -0
- package/lib/vendor/blamejs/scripts/check-api-snapshot.js +62 -0
- package/lib/vendor/blamejs/scripts/check-changelog-extract.js +108 -0
- package/lib/vendor/blamejs/scripts/check-pack-against-gitignore.js +83 -0
- package/lib/vendor/blamejs/scripts/check-services.js +483 -0
- package/lib/vendor/blamejs/scripts/check-vendor-currency.js +349 -0
- package/lib/vendor/blamejs/scripts/consolidate-release-notes.js +216 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +275 -0
- package/lib/vendor/blamejs/scripts/generate-changelog-entry.js +577 -0
- package/lib/vendor/blamejs/scripts/generate-release-signing-key.js +79 -0
- package/lib/vendor/blamejs/scripts/publish-dep-confusion-placeholder.sh +101 -0
- package/lib/vendor/blamejs/scripts/refresh-api-snapshot.js +31 -0
- package/lib/vendor/blamejs/scripts/refresh-vendor-manifest.js +132 -0
- package/lib/vendor/blamejs/scripts/release.js +652 -0
- package/lib/vendor/blamejs/scripts/sha3-digest.js +62 -0
- package/lib/vendor/blamejs/scripts/sign-release-artifact.js +92 -0
- package/lib/vendor/blamejs/scripts/test-integration.js +181 -0
- package/lib/vendor/blamejs/scripts/test-wiki-integration.js +126 -0
- package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +77 -0
- package/lib/vendor/blamejs/scripts/vendor-data-gen.js +186 -0
- package/lib/vendor/blamejs/scripts/vendor-data-keygen.js +101 -0
- package/lib/vendor/blamejs/scripts/vendor-update.sh +278 -0
- package/lib/vendor/blamejs/test/00-primitives.js +19075 -0
- package/lib/vendor/blamejs/test/10-state.js +622 -0
- package/lib/vendor/blamejs/test/20-db.js +561 -0
- package/lib/vendor/blamejs/test/30-chain.js +2110 -0
- package/lib/vendor/blamejs/test/40-consumers.js +2453 -0
- package/lib/vendor/blamejs/test/50-integration.js +486 -0
- package/lib/vendor/blamejs/test/_helpers.js +10 -0
- package/lib/vendor/blamejs/test/_smoke-worker.js +69 -0
- package/lib/vendor/blamejs/test/fixtures/exploit-corpus/corpus.json +368 -0
- package/lib/vendor/blamejs/test/fixtures/http-client-stream-payload.txt +2 -0
- package/lib/vendor/blamejs/test/fixtures/worker-pool/echo.js +52 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle-worker.js +24 -0
- package/lib/vendor/blamejs/test/helpers/_codebase-shingle.js +203 -0
- package/lib/vendor/blamejs/test/helpers/_shape-match.js +513 -0
- package/lib/vendor/blamejs/test/helpers/check.js +36 -0
- package/lib/vendor/blamejs/test/helpers/cluster.js +70 -0
- package/lib/vendor/blamejs/test/helpers/db.js +143 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +207 -0
- package/lib/vendor/blamejs/test/helpers/fs-watch.js +101 -0
- package/lib/vendor/blamejs/test/helpers/http.js +14 -0
- package/lib/vendor/blamejs/test/helpers/index.js +93 -0
- package/lib/vendor/blamejs/test/helpers/json-round-trip.js +120 -0
- package/lib/vendor/blamejs/test/helpers/mocks.js +20 -0
- package/lib/vendor/blamejs/test/helpers/otel.js +13 -0
- package/lib/vendor/blamejs/test/helpers/services.js +380 -0
- package/lib/vendor/blamejs/test/helpers/wait.js +206 -0
- package/lib/vendor/blamejs/test/integration/cache.test.js +235 -0
- package/lib/vendor/blamejs/test/integration/cluster-provider-mysql.test.js +174 -0
- package/lib/vendor/blamejs/test/integration/federation-auth.test.js +611 -0
- package/lib/vendor/blamejs/test/integration/http-client.test.js +129 -0
- package/lib/vendor/blamejs/test/integration/log-stream.test.js +219 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +181 -0
- package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +152 -0
- package/lib/vendor/blamejs/test/integration/mail-smtp.test.js +161 -0
- package/lib/vendor/blamejs/test/integration/mtls-ca.test.js +289 -0
- package/lib/vendor/blamejs/test/integration/network-dns.test.js +123 -0
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/ntp-check.test.js +89 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +403 -0
- package/lib/vendor/blamejs/test/integration/pqc-pkcs8-forward-compat.test.js +271 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +137 -0
- package/lib/vendor/blamejs/test/integration/queue-redis.test.js +352 -0
- package/lib/vendor/blamejs/test/integration/redis-client-tls.test.js +96 -0
- package/lib/vendor/blamejs/test/integration/ssrf-guard.test.js +98 -0
- package/lib/vendor/blamejs/test/integration/websocket-permessage-deflate.test.js +261 -0
- package/lib/vendor/blamejs/test/integration/ws-client-roundtrip.test.js +230 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +211 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/a2a.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/access-lock.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +219 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +262 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-orchestrator.test.js +390 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-posture-chain.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-stream.test.js +227 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-tenant.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-trace.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-adverse-decision.test.js +44 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-content-detect.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-input.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-model-manifest.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ai-pref.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +1080 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +311 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive-zip-stream.test.js +291 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/archive.test.js +140 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +108 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/asyncapi.test.js +929 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-conflict-path.test.js +80 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-cve-defensive.test.js +176 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-daily-review.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-export-cadf.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-framework-namespaces.test.js +141 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-segregation.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-ml-dsa-65.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-use-store.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge-verifier.test.js +485 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-bot-challenge.test.js +331 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +352 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-lockout.test.js +572 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/auth-password-audit.test.js +61 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-bucket-ops.test.js +258 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-worker.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bearer-auth.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-chunked-malformed.test.js +131 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/body-parser-smuggling.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/boot-gates.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/breach-deadline.test.js +38 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +861 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/budr.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/bundler-engine.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache-status.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +871 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +891 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/canonical-json-jcs.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cdn-cache-control.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +550 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-api-key.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-audit-verify-chain.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-backup.test.js +135 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-config-drift.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-file-type.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-helpers.test.js +145 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-mtls.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-password.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-restore.test.js +160 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-retention.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-security.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cli-vault.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/client-hints.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +237 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +9600 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +575 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-cascade.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-eaa.test.js +36 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +712 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config-drift.test.js +97 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/config.test.js +424 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cors.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/cra-report.test.js +31 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/credential-hash.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-base64url.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-envelope.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke-pq.test.js +132 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hpke.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-mlkem768-x25519.test.js +129 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-namespace-hash.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-random-int.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-builder.test.js +96 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-nonce.test.js +401 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/daily-byte-quota.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/data-act.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection-extensions.test.js +226 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-collection.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-init-extensions.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-extensions.test.js +191 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-role-for.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-vacuum.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +184 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-row-policy.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/declare-view.test.js +303 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-dnssec-algorithm.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dns-null-mx.test.js +39 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dora.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dr-runbook.test.js +59 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr-state-rules.test.js +55 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +786 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/early-hints.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/events.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/exploit-replay.test.js +243 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +181 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-routing.test.js +531 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fapi2.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fda-21cfr11.test.js +156 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fdx.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +434 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +432 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/file-type.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +887 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/forensic-snapshot.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/fsm.test.js +375 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gcs-bucket-ops.test.js +321 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +41 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/graphql-federation.test.js +32 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-agent-registry.test.js +87 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-all.test.js +328 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +694 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-dsn.test.js +296 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-email.test.js +234 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-envelope.test.js +192 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-payload.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-event-bus-topic.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +386 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html-wcag.test.js +859 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +357 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-json.test.js +317 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-id.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-list-unsubscribe.test.js +214 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-compose.test.js +111 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-move.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-query.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-reply.test.js +86 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-mail-sieve.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +301 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +265 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-message-id.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +161 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-posture-chain.test.js +100 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-saga-config.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-smtp-command.test.js +269 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-snapshot-envelope.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-stream-args.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +288 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-tenant-id.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-trace-context.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-xml.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-yaml.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/hal.test.js +51 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/honeytoken.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +37 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +692 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +225 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +991 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-mspa.test.js +63 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/iab-tcf.test.js +73 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +612 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/importmap-integrity.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +166 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +29 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jose-jwe-experimental.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-api.test.js +58 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/json-round-trip-helper.test.js +110 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +118 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/local-db-thin.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-cloudwatch.test.js +489 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp-grpc.test.js +207 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/log-stream-otlp.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/lro.test.js +65 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +417 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-arf.test.js +208 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +910 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +502 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +680 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-canspam.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +297 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +514 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy-tlsrpt.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +199 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +627 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-feedback-id.test.js +56 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-greylist.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +217 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-mdn.test.js +334 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-rbl.test.js +271 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-require-tls.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +215 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +336 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +732 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +840 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +285 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +394 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-tls.test.js +147 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-spam-score.test.js +204 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-srs.test.js +152 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +279 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +323 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-unsubscribe.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +439 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp-tool-registry.test.js +202 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +155 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-shadow-registry.test.js +112 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +224 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/middleware-compose-pipeline.test.js +278 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/money.test.js +376 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-paths.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nel.test.js +200 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-allowlist.test.js +106 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-byte-quota.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +372 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +635 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-heartbeat-passive.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-build-options.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls-ct-inclusion.test.js +179 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +447 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +369 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nis2-report.test.js +21 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/nist-crosswalk.test.js +42 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/no-cache.test.js +98 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/notify.test.js +707 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/numeric-bounds.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +72 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/openapi.test.js +877 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pagination.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +324 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +546 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +153 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-software.test.js +94 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/problem-details.test.js +195 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/process-spawn.test.js +62 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/promise-pool.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protected-resource-metadata.test.js +68 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protobuf-encoder.test.js +138 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/protocol-dispatcher.test.js +174 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/public-suffix.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +232 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-dlq-extend-lease.test.js +178 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +322 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-priority-rate-progress.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-sqs.test.js +300 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-cluster.test.js +338 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-registry.test.js +75 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +246 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +130 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +335 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/request-log.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-auth-cache-control.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/require-mtls.test.js +34 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/resource-access-lock.test.js +52 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-floor.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retry.test.js +535 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-cross-origin-redirect.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-loops.test.js +163 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-async-parallel.test.js +170 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +248 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +451 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +289 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-icap.test.js +206 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-jsonpath.test.js +104 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +339 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-mount-info.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +78 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-sieve.test.js +123 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-smtp.test.js +95 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-idn-homograph.test.js +77 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +257 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-slo.test.js +249 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sandbox.test.js +228 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scheduler-exactly-once.test.js +238 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +700 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-notify.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sec-cyber.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +107 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +175 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/seeders.test.js +816 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update-standalone-verifier.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/server-timing.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +247 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +295 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/shape-match.test.js +142 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +952 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-multipart-sse.test.js +441 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/slug.test.js +330 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/smtp-policy.test.js +233 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/source-comment-blocks.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/speculation-rules.test.js +319 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse.test.js +148 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +283 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/standard-webhooks.test.js +67 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +266 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/step-up.test.js +487 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-chunk-scratch.test.js +0 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +773 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/stream-throttle.test.js +173 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/structured-fields.test.js +180 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tcpa-10dlc.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +89 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +571 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/test-harness.test.js +190 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing-request.test.js +119 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/testing.test.js +522 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +151 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-exporter.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-ct.test.js +275 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-ocsp-verify.test.js +105 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-pinset-drift.test.js +35 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tls-preferred-groups.test.js +81 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/tracing.test.js +280 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/uuid.test.js +93 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-aad.test.js +277 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vault-seal-pem-file.test.js +252 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-data.test.js +149 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vendor-manifest.test.js +92 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/vex.test.js +661 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +308 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/web-push-vapid.test.js +144 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +674 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-channels.test.js +360 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool.test.js +302 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +349 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +717 -0
- package/lib/vendor/blamejs/test/layer-5-integration/bundler-output.test.js +444 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +597 -0
- package/lib/vendor/blamejs/test/layer-5-integration/security-chaos.test.js +308 -0
- package/lib/vendor/blamejs/test/smoke.js +431 -0
- package/lib/webhooks.js +305 -0
- package/package.json +43 -0
|
@@ -0,0 +1,2141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* oauth — OAuth 2 / OIDC client.
|
|
4
|
+
*
|
|
5
|
+
* "Login with Google / GitHub / Microsoft / Apple / etc." — table
|
|
6
|
+
* stakes for B2C and SSO B2B. Without this, operators reach for
|
|
7
|
+
* `passport-*` packages and we lose control of the request flow.
|
|
8
|
+
*
|
|
9
|
+
* The framework's PQC-first stance applies to outbound crypto we
|
|
10
|
+
* emit. OIDC IdPs sign ID tokens with classical-algo JWS (RS256,
|
|
11
|
+
* ES256, etc.) — third-party crypto we MUST verify. This module
|
|
12
|
+
* carries an OIDC-specific verifier for that interop case;
|
|
13
|
+
* `lib/auth/jwt.js` remains PQC-only for tokens the FRAMEWORK signs.
|
|
14
|
+
*
|
|
15
|
+
* Public API:
|
|
16
|
+
*
|
|
17
|
+
* var oauth = b.auth.oauth.create({
|
|
18
|
+
* provider: "google", // preset
|
|
19
|
+
* clientId: "...", clientSecret: "...",
|
|
20
|
+
* redirectUri: "https://app/auth/callback",
|
|
21
|
+
* scope: ["openid", "email", "profile"],
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // Or generic:
|
|
25
|
+
* var oauth = b.auth.oauth.create({
|
|
26
|
+
* issuer: "https://auth.example.com", // OIDC discovery
|
|
27
|
+
* clientId, clientSecret, redirectUri, scope: [...],
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Step 1 — Build the authorize URL. Operator persists the
|
|
31
|
+
* // returned `verifier` and `state` in their session.
|
|
32
|
+
* var auth = await oauth.authorizationUrl();
|
|
33
|
+
* // → { url, state, nonce, verifier, challenge }
|
|
34
|
+
* res.redirect(302, auth.url);
|
|
35
|
+
*
|
|
36
|
+
* // Step 2 — In the callback handler, verify state + exchange code.
|
|
37
|
+
* var stored = req.session.get("oauth-state");
|
|
38
|
+
* if (req.query.state !== stored.state) throw new ForbiddenError();
|
|
39
|
+
* var tokens = await oauth.exchangeCode({
|
|
40
|
+
* code: req.query.code,
|
|
41
|
+
* state: req.query.state,
|
|
42
|
+
* verifier: stored.verifier,
|
|
43
|
+
* });
|
|
44
|
+
* // → { accessToken, idToken, refreshToken, tokenType, expiresIn,
|
|
45
|
+
* // scope, claims, profile }
|
|
46
|
+
*
|
|
47
|
+
* // Step 3 — operator decides: create user, link to existing, etc.
|
|
48
|
+
*
|
|
49
|
+
* // Refresh:
|
|
50
|
+
* var fresh = await oauth.refreshAccessToken(tokens.refreshToken);
|
|
51
|
+
*
|
|
52
|
+
* // Revoke (RFC 7009; not all providers support it — checked at
|
|
53
|
+
* // discovery, throws if unavailable):
|
|
54
|
+
* await oauth.revokeToken(tokens.refreshToken, { type: "refresh_token" });
|
|
55
|
+
*
|
|
56
|
+
* // Userinfo (OIDC standard endpoint):
|
|
57
|
+
* var profile = await oauth.fetchUserInfo(tokens.accessToken);
|
|
58
|
+
*
|
|
59
|
+
* Vendor presets (b.auth.oauth.PRESETS):
|
|
60
|
+
* google OIDC discovery via accounts.google.com
|
|
61
|
+
* microsoft OIDC via login.microsoftonline.com (common tenant)
|
|
62
|
+
* apple "Sign in with Apple" — RSA-PSS signed ID tokens
|
|
63
|
+
* auth0 OIDC via {tenant}.auth0.com (operator passes tenant)
|
|
64
|
+
* keycloak OIDC via {host}/realms/{realm}/.well-known/...
|
|
65
|
+
* github OAuth 2 only (NOT OIDC — no ID token; GitHub uses
|
|
66
|
+
* a userinfo-equivalent endpoint /user)
|
|
67
|
+
* generic operator passes endpoints manually
|
|
68
|
+
*
|
|
69
|
+
* Discovery:
|
|
70
|
+
* When `issuer` (or a preset's discovery URL) is set, the client
|
|
71
|
+
* fetches `/.well-known/openid-configuration` on first use and
|
|
72
|
+
* caches authorization_endpoint, token_endpoint, jwks_uri,
|
|
73
|
+
* userinfo_endpoint, revocation_endpoint, supported algos. Cache
|
|
74
|
+
* default 1 hour; operators tune via `discoveryCacheMs`.
|
|
75
|
+
*
|
|
76
|
+
* PKCE:
|
|
77
|
+
* ON BY DEFAULT. RFC 7636. The framework refuses to build an
|
|
78
|
+
* authorization URL without PKCE unless the operator explicitly
|
|
79
|
+
* passes `pkce: false` (and even then it logs a warning). Operators
|
|
80
|
+
* on legacy IdPs that don't support PKCE — vanishingly rare in 2026
|
|
81
|
+
* — must opt out explicitly.
|
|
82
|
+
*
|
|
83
|
+
* State + nonce:
|
|
84
|
+
* Generated fresh per-call. The framework returns them but doesn't
|
|
85
|
+
* store them — that's the operator's session's job. The state
|
|
86
|
+
* verifies the OAuth callback came from the same browser session
|
|
87
|
+
* (CSRF defense); the nonce verifies the ID token wasn't replayed
|
|
88
|
+
* from a different login flow.
|
|
89
|
+
*
|
|
90
|
+
* ID token verification (when an ID token is present):
|
|
91
|
+
* - Header alg must be on `acceptedAlgorithms` (default RS256, RS384,
|
|
92
|
+
* RS512, ES256, ES384, ES512, PS256, PS384, PS512). HS256 is
|
|
93
|
+
* refused — symmetric-keyed ID tokens are an anti-pattern.
|
|
94
|
+
* - kid lookup against the discovered JWKS.
|
|
95
|
+
* - Signature verification via node:crypto for the matching algo.
|
|
96
|
+
* - Claim validation: iss matches issuer, aud contains clientId,
|
|
97
|
+
* exp not past, iat not future, nonce matches the call's nonce.
|
|
98
|
+
*
|
|
99
|
+
* HTTPS:
|
|
100
|
+
* All endpoints must be https except localhost during dev. The
|
|
101
|
+
* discovery / token / userinfo / revocation / jwks fetches go
|
|
102
|
+
* through `b.httpClient` which enforces the framework's PQC-locked
|
|
103
|
+
* posture by default (operators with mixed-protocol setups pass
|
|
104
|
+
* `allowedProtocols`).
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
var nodeCrypto = require("node:crypto");
|
|
108
|
+
var cache = require("../cache");
|
|
109
|
+
var C = require("../constants");
|
|
110
|
+
var safeAsync = require("../safe-async");
|
|
111
|
+
var bCrypto = require("../crypto");
|
|
112
|
+
var { generateBytes, timingSafeEqual: cryptoTimingSafeEqual } = bCrypto;
|
|
113
|
+
var httpClient = require("../http-client");
|
|
114
|
+
var safeJson = require("../safe-json");
|
|
115
|
+
var safeUrl = require("../safe-url");
|
|
116
|
+
var { URL } = require("node:url");
|
|
117
|
+
var { defineClass } = require("../framework-error");
|
|
118
|
+
var lazyRequire = require("../lazy-require");
|
|
119
|
+
// Shared JOSE defenses (CVE-2026-22817 alg/kty cross-check +
|
|
120
|
+
// CVE-2026-23552 constant-time iss compare). Top-of-file per project
|
|
121
|
+
// convention §3; no circular load — jwt-external requires nothing from
|
|
122
|
+
// oauth.
|
|
123
|
+
var jwtExternal = require("./jwt-external");
|
|
124
|
+
var audit = lazyRequire(function () { return require("../audit"); });
|
|
125
|
+
|
|
126
|
+
// Cap on responses parsed from upstream OAuth providers. Token /
|
|
127
|
+
// userinfo / discovery responses are tiny in spec; 256 KiB leaves
|
|
128
|
+
// huge headroom and prevents a hostile (or compromised) upstream
|
|
129
|
+
// from staging a parse-bomb against the framework.
|
|
130
|
+
var OAUTH_MAX_RESPONSE_BYTES = C.BYTES.kib(256);
|
|
131
|
+
|
|
132
|
+
var OAuthError = defineClass("OAuthError", { alwaysPermanent: true });
|
|
133
|
+
|
|
134
|
+
// Vendor presets. Each entry has either { issuer } (OIDC) or explicit
|
|
135
|
+
// endpoints { authorizationEndpoint, tokenEndpoint, userinfoEndpoint }.
|
|
136
|
+
var PRESETS = Object.freeze({
|
|
137
|
+
google: {
|
|
138
|
+
issuer: "https://accounts.google.com",
|
|
139
|
+
defaultScope: ["openid", "email", "profile"],
|
|
140
|
+
isOidc: true,
|
|
141
|
+
},
|
|
142
|
+
microsoft: {
|
|
143
|
+
issuer: "https://login.microsoftonline.com/common/v2.0",
|
|
144
|
+
defaultScope: ["openid", "email", "profile"],
|
|
145
|
+
isOidc: true,
|
|
146
|
+
},
|
|
147
|
+
apple: {
|
|
148
|
+
issuer: "https://appleid.apple.com",
|
|
149
|
+
defaultScope: ["openid", "email", "name"],
|
|
150
|
+
isOidc: true,
|
|
151
|
+
// Apple wants form_post response_mode (browser POSTs back to redirect).
|
|
152
|
+
responseMode: "form_post",
|
|
153
|
+
},
|
|
154
|
+
auth0: {
|
|
155
|
+
// Operator passes auth0Domain: "tenant.auth0.com" via opts; we
|
|
156
|
+
// expand it into the issuer URL.
|
|
157
|
+
issuerTemplate: function (opts) {
|
|
158
|
+
if (!opts.auth0Domain) {
|
|
159
|
+
throw new OAuthError("auth-oauth/auth0-domain",
|
|
160
|
+
"auth0 preset requires opts.auth0Domain ('your-tenant.auth0.com')");
|
|
161
|
+
}
|
|
162
|
+
return "https://" + opts.auth0Domain;
|
|
163
|
+
},
|
|
164
|
+
defaultScope: ["openid", "email", "profile"],
|
|
165
|
+
isOidc: true,
|
|
166
|
+
},
|
|
167
|
+
keycloak: {
|
|
168
|
+
// Keycloak realms: opts.keycloakUrl + opts.keycloakRealm
|
|
169
|
+
issuerTemplate: function (opts) {
|
|
170
|
+
if (!opts.keycloakUrl || !opts.keycloakRealm) {
|
|
171
|
+
throw new OAuthError("auth-oauth/keycloak-config",
|
|
172
|
+
"keycloak preset requires opts.keycloakUrl and opts.keycloakRealm");
|
|
173
|
+
}
|
|
174
|
+
return opts.keycloakUrl.replace(/\/$/, "") + "/realms/" + opts.keycloakRealm;
|
|
175
|
+
},
|
|
176
|
+
defaultScope: ["openid", "email", "profile"],
|
|
177
|
+
isOidc: true,
|
|
178
|
+
},
|
|
179
|
+
github: {
|
|
180
|
+
// GitHub is OAuth 2 only — no OIDC, no ID tokens, no JWKS.
|
|
181
|
+
authorizationEndpoint: "https://github.com/login/oauth/authorize",
|
|
182
|
+
tokenEndpoint: "https://github.com/login/oauth/access_token",
|
|
183
|
+
userinfoEndpoint: "https://api.github.com/user",
|
|
184
|
+
defaultScope: ["read:user", "user:email"],
|
|
185
|
+
isOidc: false,
|
|
186
|
+
},
|
|
187
|
+
generic: {
|
|
188
|
+
// Operator-defined endpoints (or issuer-driven discovery). The
|
|
189
|
+
// preset itself adds nothing — its presence makes provider:'generic'
|
|
190
|
+
// a valid explicit selector instead of falling through to "unknown
|
|
191
|
+
// provider preset". Operators pass authorizationEndpoint /
|
|
192
|
+
// tokenEndpoint / userinfoEndpoint (or issuer + isOidc:true to
|
|
193
|
+
// discover) on opts.
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
var DEFAULT_ACCEPTED_ALGS = Object.freeze([
|
|
198
|
+
"RS256", "RS384", "RS512",
|
|
199
|
+
"ES256", "ES384", "ES512",
|
|
200
|
+
"PS256", "PS384", "PS512",
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
var DEFAULT_DISCOVERY_CACHE_MS = C.TIME.hours(1);
|
|
204
|
+
var DEFAULT_CLOCK_SKEW_MS = C.TIME.minutes(1);
|
|
205
|
+
|
|
206
|
+
// Random material lengths. PKCE verifier per RFC 7636 §4.1 needs >= 256
|
|
207
|
+
// bits of entropy; 32 bytes hits that exactly. State + nonce are 16
|
|
208
|
+
// bytes (128-bit unguessability) which is the minimum recommended by
|
|
209
|
+
// OAuth 2.0 Threat Model §4.4.1.8 / §4.4.1.13.
|
|
210
|
+
var PKCE_VERIFIER_BYTES = C.BYTES.bytes(32);
|
|
211
|
+
var STATE_NONCE_BYTES = C.BYTES.bytes(16);
|
|
212
|
+
// JOSE PSS salt lengths (RFC 7518 §3.5) match the hash-output size:
|
|
213
|
+
// PS256/SHA-256 → 32, PS384/SHA-384 → 48, PS512/SHA-512 → 64.
|
|
214
|
+
var PSS_SALT_BYTES_SHA256 = C.BYTES.bytes(32);
|
|
215
|
+
var PSS_SALT_BYTES_SHA384 = C.BYTES.bytes(48);
|
|
216
|
+
var PSS_SALT_BYTES_SHA512 = C.BYTES.bytes(64);
|
|
217
|
+
|
|
218
|
+
// RFC 8628 §3.4 — device_code length cap. The spec doesn't fix a max
|
|
219
|
+
// length but 8 KiB comfortably accommodates any legitimate base64url
|
|
220
|
+
// CSPRNG output and refuses pathological payloads.
|
|
221
|
+
var MAX_DEVICE_CODE_BYTES = C.BYTES.kib(8);
|
|
222
|
+
// RFC 8628 §3.4 — 5s is the spec-documented MINIMUM polling interval.
|
|
223
|
+
var MIN_DEVICE_POLL_INTERVAL_SEC = 5; // allow:raw-time-literal — RFC 8628 §3.4 spec floor
|
|
224
|
+
// OIDC Back-Channel Logout §2.6 — replay defense via jti store catches
|
|
225
|
+
// duplicate-jti reuse, but pre-v0.9.x an old captured logout-token
|
|
226
|
+
// with a fresh jti could still pass. Enforce iat freshness against
|
|
227
|
+
// this floor (operator-tunable).
|
|
228
|
+
var DEFAULT_LOGOUT_TOKEN_MAX_AGE_SEC = C.TIME.minutes(5) / C.TIME.seconds(1);
|
|
229
|
+
|
|
230
|
+
// RFC 8693 §3 — registered token-type URNs for token exchange.
|
|
231
|
+
// Operators with custom URNs pass allowCustomTokenType:true with a
|
|
232
|
+
// documented downstream contract.
|
|
233
|
+
var RFC_8693_TOKEN_TYPES = Object.freeze([
|
|
234
|
+
"urn:ietf:params:oauth:token-type:access_token",
|
|
235
|
+
"urn:ietf:params:oauth:token-type:refresh_token",
|
|
236
|
+
"urn:ietf:params:oauth:token-type:id_token",
|
|
237
|
+
"urn:ietf:params:oauth:token-type:saml1",
|
|
238
|
+
"urn:ietf:params:oauth:token-type:saml2",
|
|
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",
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
// ---- helpers ----
|
|
247
|
+
|
|
248
|
+
function _b64urlEncode(buf) { return bCrypto.toBase64Url(buf); }
|
|
249
|
+
|
|
250
|
+
function _b64urlDecode(s) {
|
|
251
|
+
if (typeof s !== "string") throw new OAuthError("auth-oauth/bad-base64", "expected base64url string");
|
|
252
|
+
try { return bCrypto.fromBase64Url(s); }
|
|
253
|
+
catch (_e) { throw new OAuthError("auth-oauth/bad-base64", "segment is not valid base64url"); }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function _generateRandomToken(bytes) {
|
|
257
|
+
return _b64urlEncode(generateBytes(bytes));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function _generatePkce() {
|
|
261
|
+
// RFC 7636: code_verifier is 43–128 chars [A-Za-z0-9-._~].
|
|
262
|
+
// base64url of 32 random bytes = 43 chars, all valid.
|
|
263
|
+
var verifier = _b64urlEncode(generateBytes(PKCE_VERIFIER_BYTES));
|
|
264
|
+
var challenge = _b64urlEncode(nodeCrypto.createHash("sha256").update(verifier).digest());
|
|
265
|
+
return { verifier: verifier, challenge: challenge };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function _validateUrl(url, allowHttp, label) {
|
|
269
|
+
if (typeof url !== "string" || url.length === 0) {
|
|
270
|
+
throw new OAuthError("auth-oauth/bad-url", label + ": URL is required");
|
|
271
|
+
}
|
|
272
|
+
// RFC 9700 §4.1.1 — redirect URIs MUST be HTTPS, with an exception
|
|
273
|
+
// for `http://localhost` and `http://127.0.0.1[:port]` to enable
|
|
274
|
+
// local development. Pre-v0.8.33 operators developing on localhost
|
|
275
|
+
// had to set `allowHttp: true` globally, which loosens the gate
|
|
276
|
+
// for ALL operator-supplied URLs (issuer, discovery, token, etc.).
|
|
277
|
+
// Now: when the URL is loopback, accept HTTP without flipping the
|
|
278
|
+
// global flag.
|
|
279
|
+
var isLocalhostHttp = false;
|
|
280
|
+
try {
|
|
281
|
+
var parsed = new URL(url); // allow:raw-new-url — RFC 9700 §4.1.1 localhost-exception lookup; safeUrl re-validates below for non-localhost paths
|
|
282
|
+
// Strip trailing root-zone dot before the localhost compare.
|
|
283
|
+
// RFC 1034 §3.1 — `localhost.` resolves identically to `localhost`;
|
|
284
|
+
// without the strip, an attacker who registers `evil.com` as a
|
|
285
|
+
// public OAuth issuer and supplies `http://localhost./...` (with
|
|
286
|
+
// a trailing dot) slips past the equality check on a name that
|
|
287
|
+
// some DNS configurations resolve to a different target than the
|
|
288
|
+
// operator expects.
|
|
289
|
+
var rawHost = parsed.hostname || "";
|
|
290
|
+
while (rawHost.length > 0 && rawHost.charAt(rawHost.length - 1) === ".") {
|
|
291
|
+
rawHost = rawHost.slice(0, -1);
|
|
292
|
+
}
|
|
293
|
+
if (parsed.protocol === "http:" &&
|
|
294
|
+
(rawHost === "localhost" ||
|
|
295
|
+
rawHost === "127.0.0.1" ||
|
|
296
|
+
rawHost === "[::1]" ||
|
|
297
|
+
rawHost === "::1")) {
|
|
298
|
+
isLocalhostHttp = true;
|
|
299
|
+
}
|
|
300
|
+
} catch (_e) { /* malformed; let safeUrl surface the canonical error below */ }
|
|
301
|
+
if (isLocalhostHttp) return url;
|
|
302
|
+
|
|
303
|
+
// Operator-supplied OAuth issuer / endpoint URL — route through
|
|
304
|
+
// safeUrl so the scheme allowlist is consistent with the rest of the
|
|
305
|
+
// framework's outbound gates. Map safe-url's error codes to the
|
|
306
|
+
// domain-specific oauth codes operators already key alerts on.
|
|
307
|
+
try {
|
|
308
|
+
safeUrl.parse(url, {
|
|
309
|
+
allowedProtocols: allowHttp ? safeUrl.ALLOW_HTTP_ALL : safeUrl.ALLOW_HTTP_TLS,
|
|
310
|
+
});
|
|
311
|
+
} catch (e) {
|
|
312
|
+
if (e && e.code === "safe-url/protocol-disallowed") {
|
|
313
|
+
throw new OAuthError("auth-oauth/insecure-url",
|
|
314
|
+
label + ": must be https" + (allowHttp ? " or http" : " (or http://localhost for dev)") +
|
|
315
|
+
" (got '" + url + "')");
|
|
316
|
+
}
|
|
317
|
+
throw new OAuthError("auth-oauth/bad-url",
|
|
318
|
+
label + ": invalid URL '" + url + "'");
|
|
319
|
+
}
|
|
320
|
+
return url;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ---- JOSE alg → node:crypto verify parameters ----
|
|
324
|
+
|
|
325
|
+
function _verifyParamsForAlg(alg) {
|
|
326
|
+
// Returns { hash, padding, dsaEncoding } for node:crypto.verify.
|
|
327
|
+
if (alg === "RS256") return { hash: "sha256", padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
|
328
|
+
if (alg === "RS384") return { hash: "sha384", padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
|
329
|
+
if (alg === "RS512") return { hash: "sha512", padding: nodeCrypto.constants.RSA_PKCS1_PADDING };
|
|
330
|
+
if (alg === "PS256") return { hash: "sha256", padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: PSS_SALT_BYTES_SHA256 };
|
|
331
|
+
if (alg === "PS384") return { hash: "sha384", padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: PSS_SALT_BYTES_SHA384 };
|
|
332
|
+
if (alg === "PS512") return { hash: "sha512", padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: PSS_SALT_BYTES_SHA512 };
|
|
333
|
+
if (alg === "ES256") return { hash: "sha256", dsaEncoding: "ieee-p1363" };
|
|
334
|
+
if (alg === "ES384") return { hash: "sha384", dsaEncoding: "ieee-p1363" };
|
|
335
|
+
if (alg === "ES512") return { hash: "sha512", dsaEncoding: "ieee-p1363" };
|
|
336
|
+
throw new OAuthError("auth-oauth/unsupported-alg",
|
|
337
|
+
"alg '" + alg + "' is not supported for ID-token verification");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ---- JWKS → KeyObject ----
|
|
341
|
+
|
|
342
|
+
function _jwkToKey(jwk) {
|
|
343
|
+
// node's createPublicKey accepts JWK directly since Node 16.
|
|
344
|
+
try { return nodeCrypto.createPublicKey({ key: jwk, format: "jwk" }); }
|
|
345
|
+
catch (e) {
|
|
346
|
+
throw new OAuthError("auth-oauth/bad-jwk",
|
|
347
|
+
"could not import JWK (kid=" + (jwk && jwk.kid) + "): " + ((e && e.message) || String(e)));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ---- core ----
|
|
352
|
+
|
|
353
|
+
function create(opts) {
|
|
354
|
+
opts = opts || {};
|
|
355
|
+
var clientId = opts.clientId;
|
|
356
|
+
var clientSecret = opts.clientSecret || null; // public clients can omit
|
|
357
|
+
var redirectUri = opts.redirectUri;
|
|
358
|
+
// OAuth 2.1 baseline (draft-ietf-oauth-v2-1) makes PKCE mandatory for
|
|
359
|
+
// ALL clients (not just public clients). The framework refuses
|
|
360
|
+
// pkce: false outright — the prior "warn and continue" path was a
|
|
361
|
+
// pre-1.0 leniency that's now closed. Operators integrating with
|
|
362
|
+
// genuinely-broken legacy IdPs that don't accept code_challenge can
|
|
363
|
+
// strip the parameters at their own ingress; the framework primitive
|
|
364
|
+
// does not ship that escape hatch.
|
|
365
|
+
if (opts.pkce === false) {
|
|
366
|
+
throw new OAuthError("auth-oauth/pkce-required",
|
|
367
|
+
"create: pkce: false is refused. OAuth 2.1 (draft-ietf-oauth-v2-1) " +
|
|
368
|
+
"requires PKCE for all clients. Remove the opt or upgrade the IdP.");
|
|
369
|
+
}
|
|
370
|
+
var pkce = true;
|
|
371
|
+
var clockSkewMs = typeof opts.clockSkewMs === "number" ? opts.clockSkewMs : DEFAULT_CLOCK_SKEW_MS;
|
|
372
|
+
var discoveryCacheMs = typeof opts.discoveryCacheMs === "number"
|
|
373
|
+
? opts.discoveryCacheMs : DEFAULT_DISCOVERY_CACHE_MS;
|
|
374
|
+
var acceptedAlgorithms = Array.isArray(opts.acceptedAlgorithms) && opts.acceptedAlgorithms.length > 0
|
|
375
|
+
? opts.acceptedAlgorithms.slice() : DEFAULT_ACCEPTED_ALGS.slice();
|
|
376
|
+
var allowHttp = !!opts.allowHttp; // localhost dev opt-in (scheme)
|
|
377
|
+
var allowInternal = opts.allowInternal != null ? opts.allowInternal : null; // localhost dev opt-in (SSRF gate)
|
|
378
|
+
var httpClientOpts = opts.httpClient || {};
|
|
379
|
+
var responseMode = opts.responseMode || null;
|
|
380
|
+
// v0.9.5 — client-level opt-out for the kid-less JWKS-of-one
|
|
381
|
+
// refusal added in v0.9.4. Surfaced at the create() level (not
|
|
382
|
+
// per-verifyIdToken-call) so it threads through every code path
|
|
383
|
+
// that lands on verifyIdToken — _normalizeTokens for exchangeCode
|
|
384
|
+
// / pollDeviceCode / exchangeToken / refreshAccessToken, JARM
|
|
385
|
+
// wrapper, and the public verifyIdToken entry point. Operators
|
|
386
|
+
// with non-conforming IdPs set this once at client construction.
|
|
387
|
+
var allowKidlessJwks = opts.allowKidlessJwks === true;
|
|
388
|
+
|
|
389
|
+
if (!clientId) {
|
|
390
|
+
throw new OAuthError("auth-oauth/no-client-id", "create: opts.clientId is required");
|
|
391
|
+
}
|
|
392
|
+
if (!redirectUri) {
|
|
393
|
+
throw new OAuthError("auth-oauth/no-redirect-uri", "create: opts.redirectUri is required");
|
|
394
|
+
}
|
|
395
|
+
_validateUrl(redirectUri, allowHttp, "redirectUri");
|
|
396
|
+
|
|
397
|
+
// Resolve preset → effective config.
|
|
398
|
+
var preset = null;
|
|
399
|
+
if (opts.provider) {
|
|
400
|
+
if (!Object.prototype.hasOwnProperty.call(PRESETS, opts.provider)) {
|
|
401
|
+
throw new OAuthError("auth-oauth/unknown-provider",
|
|
402
|
+
"unknown provider preset '" + opts.provider + "' (known: " +
|
|
403
|
+
Object.keys(PRESETS).join(", ") + ")");
|
|
404
|
+
}
|
|
405
|
+
preset = PRESETS[opts.provider];
|
|
406
|
+
}
|
|
407
|
+
var isOidc = (preset && typeof preset.isOidc === "boolean") ? preset.isOidc
|
|
408
|
+
: (opts.isOidc !== undefined ? !!opts.isOidc : true);
|
|
409
|
+
var issuer = opts.issuer
|
|
410
|
+
|| (preset && typeof preset.issuerTemplate === "function" && preset.issuerTemplate(opts))
|
|
411
|
+
|| (preset && preset.issuer)
|
|
412
|
+
|| null;
|
|
413
|
+
// OIDC Core §15.5 — issuer is a URL the framework subsequently uses
|
|
414
|
+
// as the OP identity in discovery + JWT iss comparisons. An operator
|
|
415
|
+
// typo in opts.auth0Domain / opts.keycloakUrl flows into the preset's
|
|
416
|
+
// issuerTemplate output verbatim; without validation that mistake
|
|
417
|
+
// reaches discovery + the iss compare. Re-route through _validateUrl
|
|
418
|
+
// so the issuer the framework will trust later is well-formed before
|
|
419
|
+
// any network round-trip.
|
|
420
|
+
if (issuer) _validateUrl(issuer, allowHttp, "issuer");
|
|
421
|
+
var scope = Array.isArray(opts.scope) && opts.scope.length > 0
|
|
422
|
+
? opts.scope.slice()
|
|
423
|
+
: (preset && preset.defaultScope ? preset.defaultScope.slice() : ["openid"]);
|
|
424
|
+
if (!responseMode && preset && preset.responseMode) responseMode = preset.responseMode;
|
|
425
|
+
|
|
426
|
+
// Endpoints — either from preset (explicit), discovery, or operator opts.
|
|
427
|
+
var staticEndpoints = {
|
|
428
|
+
authorizationEndpoint: opts.authorizationEndpoint || (preset && preset.authorizationEndpoint) || null,
|
|
429
|
+
tokenEndpoint: opts.tokenEndpoint || (preset && preset.tokenEndpoint) || null,
|
|
430
|
+
userinfoEndpoint: opts.userinfoEndpoint || (preset && preset.userinfoEndpoint) || null,
|
|
431
|
+
revocationEndpoint: opts.revocationEndpoint || (preset && preset.revocationEndpoint) || null,
|
|
432
|
+
jwksUri: opts.jwksUri || (preset && preset.jwksUri) || null,
|
|
433
|
+
endSessionEndpoint: opts.endSessionEndpoint || (preset && preset.endSessionEndpoint) || null,
|
|
434
|
+
checkSessionIframe: opts.checkSessionIframe || (preset && preset.checkSessionIframe) || null,
|
|
435
|
+
pushedAuthorizationRequestEndpoint:
|
|
436
|
+
opts.pushedAuthorizationRequestEndpoint ||
|
|
437
|
+
(preset && preset.pushedAuthorizationRequestEndpoint) || null,
|
|
438
|
+
backchannelAuthenticationEndpoint:
|
|
439
|
+
opts.backchannelAuthenticationEndpoint ||
|
|
440
|
+
(preset && preset.backchannelAuthenticationEndpoint) || null,
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
// Discovery + JWKS caches use b.cache.create + .wrap so concurrent
|
|
444
|
+
// boot races for the same IdP collapse to one fetch (single-flight)
|
|
445
|
+
// and operators can wire b.audit / observability without us
|
|
446
|
+
// re-implementing those hooks here. Per-cache namespaces are unique
|
|
447
|
+
// per oauth-client instance (clientId-scoped) so two clients pointed
|
|
448
|
+
// at different IdPs don't collide in a future cluster-cache backend.
|
|
449
|
+
var jwksCacheMs = typeof opts.jwksCacheMs === "number" ? opts.jwksCacheMs : DEFAULT_DISCOVERY_CACHE_MS;
|
|
450
|
+
var _discoveryCache = cache.create({
|
|
451
|
+
namespace: "oauth.discovery." + clientId,
|
|
452
|
+
ttlMs: discoveryCacheMs,
|
|
453
|
+
});
|
|
454
|
+
var _jwksCache = cache.create({
|
|
455
|
+
namespace: "oauth.jwks." + clientId,
|
|
456
|
+
ttlMs: jwksCacheMs,
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
async function _fetchJson(url, fetchOpts) {
|
|
460
|
+
fetchOpts = fetchOpts || {};
|
|
461
|
+
var hc = httpClient;
|
|
462
|
+
var req = Object.assign({
|
|
463
|
+
url: url,
|
|
464
|
+
method: "GET",
|
|
465
|
+
}, fetchOpts);
|
|
466
|
+
if (allowHttp) req.allowedProtocols = safeUrl.ALLOW_HTTP_ALL;
|
|
467
|
+
if (allowInternal !== null) req.allowInternal = allowInternal;
|
|
468
|
+
Object.assign(req, httpClientOpts);
|
|
469
|
+
var res = await hc.request(req);
|
|
470
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
471
|
+
var bodyText = res.body ? res.body.toString("utf8") : "";
|
|
472
|
+
throw new OAuthError("auth-oauth/http-" + res.statusCode,
|
|
473
|
+
url + " returned " + res.statusCode + ": " + bodyText.slice(0, 500));
|
|
474
|
+
}
|
|
475
|
+
if (!res.body) return null;
|
|
476
|
+
try { return safeJson.parse(res.body.toString("utf8"), { maxBytes: OAUTH_MAX_RESPONSE_BYTES }); }
|
|
477
|
+
catch (e) {
|
|
478
|
+
throw new OAuthError("auth-oauth/bad-json",
|
|
479
|
+
url + " response not JSON: " + ((e && e.message) || String(e)));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function _discover() {
|
|
484
|
+
if (!isOidc || !issuer) return null;
|
|
485
|
+
return _discoveryCache.wrap("config", async function () {
|
|
486
|
+
var url = issuer.replace(/\/$/, "") + "/.well-known/openid-configuration";
|
|
487
|
+
_validateUrl(url, allowHttp, "discovery url");
|
|
488
|
+
var config = await _fetchJson(url);
|
|
489
|
+
if (!config || typeof config !== "object") {
|
|
490
|
+
throw new OAuthError("auth-oauth/bad-discovery", "discovery document missing");
|
|
491
|
+
}
|
|
492
|
+
if (config.issuer && config.issuer !== issuer) {
|
|
493
|
+
throw new OAuthError("auth-oauth/issuer-mismatch",
|
|
494
|
+
"discovery issuer '" + config.issuer + "' does not match configured issuer '" + issuer + "'");
|
|
495
|
+
}
|
|
496
|
+
return config;
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
async function _resolveEndpoint(name) {
|
|
501
|
+
if (staticEndpoints[name]) return staticEndpoints[name];
|
|
502
|
+
var config = await _discover();
|
|
503
|
+
if (!config) {
|
|
504
|
+
throw new OAuthError("auth-oauth/no-endpoint",
|
|
505
|
+
name + " endpoint not configured and no OIDC discovery available");
|
|
506
|
+
}
|
|
507
|
+
// OIDC discovery uses snake_case key names.
|
|
508
|
+
var snake = ({
|
|
509
|
+
authorizationEndpoint: "authorization_endpoint",
|
|
510
|
+
tokenEndpoint: "token_endpoint",
|
|
511
|
+
userinfoEndpoint: "userinfo_endpoint",
|
|
512
|
+
revocationEndpoint: "revocation_endpoint",
|
|
513
|
+
jwksUri: "jwks_uri",
|
|
514
|
+
endSessionEndpoint: "end_session_endpoint",
|
|
515
|
+
checkSessionIframe: "check_session_iframe",
|
|
516
|
+
pushedAuthorizationRequestEndpoint: "pushed_authorization_request_endpoint",
|
|
517
|
+
backchannelAuthenticationEndpoint: "backchannel_authentication_endpoint",
|
|
518
|
+
introspectionEndpoint: "introspection_endpoint",
|
|
519
|
+
registrationEndpoint: "registration_endpoint",
|
|
520
|
+
deviceAuthorizationEndpoint: "device_authorization_endpoint",
|
|
521
|
+
})[name];
|
|
522
|
+
var endpoint = config[snake];
|
|
523
|
+
if (!endpoint) {
|
|
524
|
+
throw new OAuthError("auth-oauth/no-endpoint",
|
|
525
|
+
name + " not present in discovery document");
|
|
526
|
+
}
|
|
527
|
+
return endpoint;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async function authorizationUrl(uopts) {
|
|
531
|
+
uopts = uopts || {};
|
|
532
|
+
var endpoint = await _resolveEndpoint("authorizationEndpoint");
|
|
533
|
+
// CVE-2026-34511 — PKCE verifier leak via state. The state token is
|
|
534
|
+
// an opaque CSPRNG output; the PKCE verifier is generated separately
|
|
535
|
+
// and returned in its own field for the caller to store. The
|
|
536
|
+
// `code_verifier` is NEVER concatenated into `state` and `state`
|
|
537
|
+
// never carries operator-supplied PII. PKCE-S256 is the default
|
|
538
|
+
// (pkce: false throws above); _generatePkce() emits
|
|
539
|
+
// base64url(SHA-256(verifier)) per RFC 7636.
|
|
540
|
+
var state = uopts.state || _generateRandomToken(STATE_NONCE_BYTES);
|
|
541
|
+
var nonce = uopts.nonce || (isOidc ? _generateRandomToken(STATE_NONCE_BYTES) : null);
|
|
542
|
+
var pkceVals = pkce ? _generatePkce() : null;
|
|
543
|
+
var params = new URLSearchParams();
|
|
544
|
+
params.set("response_type", "code");
|
|
545
|
+
params.set("client_id", clientId);
|
|
546
|
+
params.set("redirect_uri", redirectUri);
|
|
547
|
+
params.set("scope", scope.join(" "));
|
|
548
|
+
params.set("state", state);
|
|
549
|
+
if (nonce) params.set("nonce", nonce);
|
|
550
|
+
if (pkceVals) {
|
|
551
|
+
params.set("code_challenge", pkceVals.challenge);
|
|
552
|
+
params.set("code_challenge_method", "S256");
|
|
553
|
+
}
|
|
554
|
+
if (responseMode) params.set("response_mode", responseMode);
|
|
555
|
+
if (uopts.prompt) params.set("prompt", uopts.prompt);
|
|
556
|
+
if (uopts.loginHint) params.set("login_hint", uopts.loginHint);
|
|
557
|
+
if (uopts.maxAge != null) params.set("max_age", String(uopts.maxAge));
|
|
558
|
+
// Operator-supplied additional params (audience, resource, etc.).
|
|
559
|
+
if (uopts.extraParams && typeof uopts.extraParams === "object") {
|
|
560
|
+
var ek = Object.keys(uopts.extraParams);
|
|
561
|
+
for (var i = 0; i < ek.length; i++) params.set(ek[i], String(uopts.extraParams[ek[i]]));
|
|
562
|
+
}
|
|
563
|
+
var sep = endpoint.indexOf("?") === -1 ? "?" : "&";
|
|
564
|
+
return {
|
|
565
|
+
url: endpoint + sep + params.toString(),
|
|
566
|
+
state: state,
|
|
567
|
+
nonce: nonce,
|
|
568
|
+
verifier: pkceVals ? pkceVals.verifier : null,
|
|
569
|
+
challenge: pkceVals ? pkceVals.challenge : null,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async function exchangeCode(eopts) {
|
|
574
|
+
eopts = eopts || {};
|
|
575
|
+
if (!eopts.code) {
|
|
576
|
+
throw new OAuthError("auth-oauth/no-code", "exchangeCode: opts.code is required");
|
|
577
|
+
}
|
|
578
|
+
if (pkce && !eopts.verifier) {
|
|
579
|
+
throw new OAuthError("auth-oauth/no-verifier",
|
|
580
|
+
"exchangeCode: opts.verifier is required when PKCE is on (default)");
|
|
581
|
+
}
|
|
582
|
+
// Nonce enforcement on OIDC paths. authorizationUrl() always
|
|
583
|
+
// emits a nonce when isOidc; if the operator forgot to thread it
|
|
584
|
+
// through to exchangeCode, _normalizeTokens silently skipped the
|
|
585
|
+
// nonce check on the ID token and a captured token from another
|
|
586
|
+
// browser session could be replayed without detection. Throw
|
|
587
|
+
// loudly so the operator sees the bug at config time, not at
|
|
588
|
+
// first-replay-attempt time.
|
|
589
|
+
if (isOidc && eopts.nonce === undefined && eopts.skipNonceCheck !== true) {
|
|
590
|
+
throw new OAuthError("auth-oauth/no-nonce",
|
|
591
|
+
"exchangeCode: nonce is required on OIDC flows. Pass the " +
|
|
592
|
+
"value returned from authorizationUrl() through to exchangeCode " +
|
|
593
|
+
"({ code, state, verifier, nonce }). Operators with a deliberate " +
|
|
594
|
+
"no-nonce flow must pass `skipNonceCheck: true` (audited reason).");
|
|
595
|
+
}
|
|
596
|
+
var endpoint = await _resolveEndpoint("tokenEndpoint");
|
|
597
|
+
var body = new URLSearchParams();
|
|
598
|
+
body.set("grant_type", "authorization_code");
|
|
599
|
+
body.set("code", eopts.code);
|
|
600
|
+
body.set("redirect_uri", redirectUri);
|
|
601
|
+
body.set("client_id", clientId);
|
|
602
|
+
if (clientSecret) body.set("client_secret", clientSecret);
|
|
603
|
+
if (eopts.verifier) body.set("code_verifier", eopts.verifier);
|
|
604
|
+
|
|
605
|
+
var tokens = await _postForm(endpoint, body);
|
|
606
|
+
return await _normalizeTokens(tokens, { nonce: eopts.nonce, skipNonceCheck: eopts.skipNonceCheck });
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
async function refreshAccessToken(refreshToken, ropts) {
|
|
610
|
+
ropts = ropts || {};
|
|
611
|
+
if (!refreshToken) {
|
|
612
|
+
throw new OAuthError("auth-oauth/no-refresh-token",
|
|
613
|
+
"refreshAccessToken: refresh token is required");
|
|
614
|
+
}
|
|
615
|
+
// OAuth 2.1 §6.1 / RFC 9700 §4.13 — refresh-token replay defense.
|
|
616
|
+
// Operator passes a `seen(refreshToken)` callback that returns
|
|
617
|
+
// truthy when the SAME refresh_token has been presented before.
|
|
618
|
+
// The framework refuses the request loudly because OAuth 2.1
|
|
619
|
+
// mandates one-time-use refresh tokens for public + non-sender-
|
|
620
|
+
// constrained confidential clients. Operators with sender-
|
|
621
|
+
// constrained tokens (DPoP / mTLS) can opt out by NOT supplying
|
|
622
|
+
// a seen callback.
|
|
623
|
+
//
|
|
624
|
+
// Atomic check-and-insert (audit 2026-05-11) — pre-v0.9.3 the
|
|
625
|
+
// check ran via `ropts.seen(token)` which was a check-then-act
|
|
626
|
+
// race: two concurrent refresh requests landed on the same
|
|
627
|
+
// event-loop tick could both see `seen === false` and both POST
|
|
628
|
+
// to the token endpoint, neither flagging the replay. The
|
|
629
|
+
// framework-wide checkAndInsert contract (lib/nonce-store.js,
|
|
630
|
+
// lib/auth/jwt.js) is: returns `true` when the value was UNSEEN
|
|
631
|
+
// and is now recorded (first sighting); returns `false` when
|
|
632
|
+
// already present (replay). The legacy `seen` callback returned
|
|
633
|
+
// the opposite (true means seen-already); both surfaces are
|
|
634
|
+
// supported but normalize to a single `alreadySeen` boolean
|
|
635
|
+
// below.
|
|
636
|
+
var alreadySeen = false;
|
|
637
|
+
if (typeof ropts.checkAndInsert === "function") {
|
|
638
|
+
var nowMs = Date.now();
|
|
639
|
+
// 24h max refresh-token TTL — operators with shorter TTLs
|
|
640
|
+
// should configure their store's own expiry policy.
|
|
641
|
+
var expireAtMs = nowMs + C.TIME.hours(24);
|
|
642
|
+
var inserted;
|
|
643
|
+
try { inserted = await ropts.checkAndInsert(refreshToken, expireAtMs); }
|
|
644
|
+
catch (e) {
|
|
645
|
+
throw new OAuthError("auth-oauth/seen-callback-failed",
|
|
646
|
+
"refreshAccessToken: checkAndInsert() callback threw: " + ((e && e.message) || String(e)));
|
|
647
|
+
}
|
|
648
|
+
// Spec contract: inserted===true → first sighting (OK);
|
|
649
|
+
// inserted===false → replay. v0.9.3 had this inverted, which
|
|
650
|
+
// broke every first refresh attempt for operators reusing an
|
|
651
|
+
// existing b.nonceStore-style backend. (Reported 2026-05-12.)
|
|
652
|
+
alreadySeen = inserted === false;
|
|
653
|
+
} else if (typeof ropts.seen === "function") {
|
|
654
|
+
// Legacy non-atomic path. Documented as a check-then-act race;
|
|
655
|
+
// operators sharing a single-writer store (Redis SETNX, DB
|
|
656
|
+
// INSERT ON CONFLICT) MUST migrate to checkAndInsert. Stays
|
|
657
|
+
// here for backwards-compat with existing operator code.
|
|
658
|
+
try { alreadySeen = await ropts.seen(refreshToken); }
|
|
659
|
+
catch (e) {
|
|
660
|
+
throw new OAuthError("auth-oauth/seen-callback-failed",
|
|
661
|
+
"refreshAccessToken: seen() callback threw: " + ((e && e.message) || String(e)));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (alreadySeen === true) {
|
|
665
|
+
throw new OAuthError("auth-oauth/refresh-token-replay",
|
|
666
|
+
"refreshAccessToken: refresh token has been presented before — refused " +
|
|
667
|
+
"(OAuth 2.1 §6.1 / RFC 9700 §4.13 one-time-use defense). The operator MUST " +
|
|
668
|
+
"treat this as a token-theft signal: revoke the refresh-token family + force " +
|
|
669
|
+
"the user to re-authenticate.");
|
|
670
|
+
}
|
|
671
|
+
var endpoint = await _resolveEndpoint("tokenEndpoint");
|
|
672
|
+
var body = new URLSearchParams();
|
|
673
|
+
body.set("grant_type", "refresh_token");
|
|
674
|
+
body.set("refresh_token", refreshToken);
|
|
675
|
+
body.set("client_id", clientId);
|
|
676
|
+
if (clientSecret) body.set("client_secret", clientSecret);
|
|
677
|
+
var tokens = await _postForm(endpoint, body);
|
|
678
|
+
// Refreshed tokens may not include a new id_token; verification
|
|
679
|
+
// is conditional. We surface rotation explicitly so the operator's
|
|
680
|
+
// store can swap the old refresh_token for the new one and feed
|
|
681
|
+
// the new one to the next seen() check.
|
|
682
|
+
var normalized = await _normalizeTokens(tokens, { skipNonceCheck: true });
|
|
683
|
+
if (normalized.refreshToken && normalized.refreshToken !== refreshToken) {
|
|
684
|
+
normalized.refreshTokenRotated = true;
|
|
685
|
+
normalized.previousRefreshToken = refreshToken;
|
|
686
|
+
} else {
|
|
687
|
+
normalized.refreshTokenRotated = false;
|
|
688
|
+
}
|
|
689
|
+
return normalized;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* @primitive b.auth.oauth.parseCallback
|
|
694
|
+
* @signature b.auth.oauth.parseCallback(query, opts?)
|
|
695
|
+
* @since 0.8.70
|
|
696
|
+
* @related b.auth.oauth.parseJarmResponse, b.fapi2.assertCallback
|
|
697
|
+
*
|
|
698
|
+
* Parses the OP's redirect-back query/form parameters and applies
|
|
699
|
+
* RFC 9207 OAuth 2.0 Authorization Server Issuer Identification
|
|
700
|
+
* cross-checks. The `iss` parameter the OP echoes on the callback
|
|
701
|
+
* MUST match the configured issuer; mismatches surface as a
|
|
702
|
+
* deterministic refusal (mix-up / IdP-substitution defense per
|
|
703
|
+
* RFC 9207 §2.3).
|
|
704
|
+
*
|
|
705
|
+
* The framework refuses the callback when:
|
|
706
|
+
* - an `error` param is present (OP-side authorization failure)
|
|
707
|
+
* - `iss` is present but does NOT match the configured issuer
|
|
708
|
+
* - `state` is supplied to opts.expectedState and doesn't match
|
|
709
|
+
*
|
|
710
|
+
* Returns `{ code, state, iss }` for the happy path. Operators feed
|
|
711
|
+
* `code` + their stored `verifier` + `nonce` to `exchangeCode`.
|
|
712
|
+
*
|
|
713
|
+
* The OP advertises support via `authorization_response_iss_parameter_supported`
|
|
714
|
+
* in discovery; the framework reads it once at the first parseCallback
|
|
715
|
+
* call and refuses missing-`iss` callbacks under FAPI 2.0 posture
|
|
716
|
+
* regardless (per FAPI 2.0 §5.4.2).
|
|
717
|
+
*
|
|
718
|
+
* @opts
|
|
719
|
+
* {
|
|
720
|
+
* expectedState?: string, // value returned by authorizationUrl()
|
|
721
|
+
* requireIssParam?: boolean, // refuse callbacks lacking iss (default: read OP discovery; FAPI 2.0 forces true)
|
|
722
|
+
* }
|
|
723
|
+
*
|
|
724
|
+
* @example
|
|
725
|
+
* app.get("/oauth/callback", async function (req, res) {
|
|
726
|
+
* var url = new URL(req.url, "http://placeholder.invalid");
|
|
727
|
+
* var params = Object.fromEntries(url.searchParams);
|
|
728
|
+
* var parsed = await oauth.parseCallback(params, { expectedState: req.session.oauthState });
|
|
729
|
+
* var tokens = await oauth.exchangeCode({ code: parsed.code,
|
|
730
|
+
* verifier: req.session.pkceVerifier, nonce: req.session.oidcNonce });
|
|
731
|
+
* });
|
|
732
|
+
*/
|
|
733
|
+
async function parseCallback(query, popts) {
|
|
734
|
+
popts = popts || {};
|
|
735
|
+
if (!query || typeof query !== "object") {
|
|
736
|
+
throw new OAuthError("auth-oauth/bad-callback",
|
|
737
|
+
"parseCallback: query must be an object of param key→value");
|
|
738
|
+
}
|
|
739
|
+
if (typeof query.error === "string" && query.error.length > 0) {
|
|
740
|
+
var aerr = new OAuthError("auth-oauth/op-error",
|
|
741
|
+
"parseCallback: OP returned error '" + query.error + "'" +
|
|
742
|
+
(query.error_description ? ": " + query.error_description : ""));
|
|
743
|
+
aerr.opError = query.error;
|
|
744
|
+
aerr.opErrorDescription = query.error_description || null;
|
|
745
|
+
throw aerr;
|
|
746
|
+
}
|
|
747
|
+
// RFC 9207 — when the OP echoes `iss`, cross-check it against the
|
|
748
|
+
// configured issuer. Defends against the mix-up attack where an
|
|
749
|
+
// honest-but-curious OP receives a code intended for a different
|
|
750
|
+
// OP. The cross-check is critical for OPs with multi-tenant
|
|
751
|
+
// shared clients.
|
|
752
|
+
var requireIss = popts.requireIssParam === true;
|
|
753
|
+
if (!requireIss) {
|
|
754
|
+
// OP discovery may advertise support; check once.
|
|
755
|
+
var disc = null;
|
|
756
|
+
try { disc = await _discover(); } catch (_e) { /* discovery already failed elsewhere; let exchangeCode surface it */ }
|
|
757
|
+
if (disc && disc.authorization_response_iss_parameter_supported === true) {
|
|
758
|
+
requireIss = true;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (typeof query.iss === "string" && query.iss.length > 0) {
|
|
762
|
+
if (query.iss !== issuer) {
|
|
763
|
+
throw new OAuthError("auth-oauth/iss-mismatch-callback",
|
|
764
|
+
"parseCallback: callback iss '" + query.iss + "' does not match " +
|
|
765
|
+
"configured issuer '" + issuer + "' (RFC 9207 §2.3 mix-up defense)");
|
|
766
|
+
}
|
|
767
|
+
} else if (requireIss) {
|
|
768
|
+
throw new OAuthError("auth-oauth/missing-iss-callback",
|
|
769
|
+
"parseCallback: OP advertises authorization_response_iss_parameter_supported " +
|
|
770
|
+
"but the callback omitted `iss` — refused (RFC 9207 / FAPI 2.0 §5.4.2)");
|
|
771
|
+
}
|
|
772
|
+
if (popts.expectedState !== undefined && popts.expectedState !== null) {
|
|
773
|
+
// Constant-time compare on the CSRF state token. Project
|
|
774
|
+
// discipline (auth/dpop.js, mail-srs.js, webhook.js) is
|
|
775
|
+
// timingSafeEqual for any secret-shaped value compared
|
|
776
|
+
// against attacker-controlled input. (Audit 2026-05-11.)
|
|
777
|
+
if (typeof query.state !== "string" ||
|
|
778
|
+
!cryptoTimingSafeEqual(query.state, popts.expectedState)) {
|
|
779
|
+
throw new OAuthError("auth-oauth/state-mismatch",
|
|
780
|
+
"parseCallback: state mismatch (CSRF defense) — expected and " +
|
|
781
|
+
"supplied state values do not match");
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (typeof query.code !== "string" || query.code.length === 0) {
|
|
785
|
+
throw new OAuthError("auth-oauth/no-code-in-callback",
|
|
786
|
+
"parseCallback: callback missing `code` parameter");
|
|
787
|
+
}
|
|
788
|
+
return { code: query.code, state: query.state || null, iss: query.iss || issuer };
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* @primitive b.auth.oauth.parseJarmResponse
|
|
793
|
+
* @signature b.auth.oauth.parseJarmResponse(responseJwt, opts?)
|
|
794
|
+
* @since 0.8.70
|
|
795
|
+
* @related b.auth.oauth.parseCallback, b.fapi2.assertCallback
|
|
796
|
+
*
|
|
797
|
+
* JWT Authorization Response Mode (JARM, OAuth 2.0 JARM spec).
|
|
798
|
+
* When `response_mode` is `query.jwt` / `fragment.jwt` /
|
|
799
|
+
* `form_post.jwt`, the OP delivers the authorization response as a
|
|
800
|
+
* signed JWT in a single `response` parameter instead of as bare
|
|
801
|
+
* query/form params. This primitive verifies the JWS against the
|
|
802
|
+
* OP's JWKS, validates `iss` / `aud` / `exp` / `nbf`, and returns
|
|
803
|
+
* the inner params (`code` / `state` / `iss` / `error`) as if they
|
|
804
|
+
* had been the raw query.
|
|
805
|
+
*
|
|
806
|
+
* The verified params then flow through `parseCallback` for the
|
|
807
|
+
* normal RFC 9207 + state-CSRF + error-refusal pipeline.
|
|
808
|
+
*
|
|
809
|
+
* @opts
|
|
810
|
+
* {
|
|
811
|
+
* expectedState?: string,
|
|
812
|
+
* acceptedAlgs?: string[], // default: framework's accepted set
|
|
813
|
+
* maxClockSkewMs?: number,
|
|
814
|
+
* }
|
|
815
|
+
*
|
|
816
|
+
* @example
|
|
817
|
+
* app.get("/oauth/callback", async function (req, res) {
|
|
818
|
+
* var jwt = new URL(req.url, "x:/").searchParams.get("response");
|
|
819
|
+
* var params = await oauth.parseJarmResponse(jwt, { expectedState: req.session.oauthState });
|
|
820
|
+
* var tokens = await oauth.exchangeCode({ code: params.code,
|
|
821
|
+
* verifier: req.session.pkceVerifier, nonce: req.session.oidcNonce });
|
|
822
|
+
* });
|
|
823
|
+
*/
|
|
824
|
+
async function parseJarmResponse(responseJwt, jopts) {
|
|
825
|
+
jopts = jopts || {};
|
|
826
|
+
if (typeof responseJwt !== "string" || responseJwt.length === 0) {
|
|
827
|
+
throw new OAuthError("auth-oauth/no-jarm-response",
|
|
828
|
+
"parseJarmResponse: response JWT required");
|
|
829
|
+
}
|
|
830
|
+
if (responseJwt.split(".").length !== 3) {
|
|
831
|
+
throw new OAuthError("auth-oauth/malformed-jarm-response",
|
|
832
|
+
"parseJarmResponse: response is not a 3-segment JWS");
|
|
833
|
+
}
|
|
834
|
+
// Reuse verifyIdToken's JWKS-lookup + signature path. JARM
|
|
835
|
+
// responses share the OP's signing keypair; the checks differ
|
|
836
|
+
// only in claim validation (no nonce, audience = clientId, no
|
|
837
|
+
// ID-token-specific claims). We wrap verifyIdToken with the
|
|
838
|
+
// skip-nonce flag and apply JARM-specific claim checks below.
|
|
839
|
+
var verified = await verifyIdToken(responseJwt, {
|
|
840
|
+
skipNonceCheck: true,
|
|
841
|
+
acceptedAlgs: jopts.acceptedAlgs,
|
|
842
|
+
maxClockSkewMs: jopts.maxClockSkewMs,
|
|
843
|
+
});
|
|
844
|
+
var c = verified.claims;
|
|
845
|
+
// Per JARM §4: `iss` MUST match the OP issuer; `aud` MUST contain
|
|
846
|
+
// the client_id; `exp` enforced (verifyIdToken already does);
|
|
847
|
+
// `nonce` MUST NOT be present (JARM responses are not ID tokens).
|
|
848
|
+
if (Object.prototype.hasOwnProperty.call(c, "nonce")) {
|
|
849
|
+
throw new OAuthError("auth-oauth/jarm-forbidden-nonce",
|
|
850
|
+
"parseJarmResponse: JARM responses MUST NOT carry `nonce` (JARM §4)");
|
|
851
|
+
}
|
|
852
|
+
return await parseCallback({
|
|
853
|
+
code: c.code,
|
|
854
|
+
state: c.state,
|
|
855
|
+
iss: c.iss,
|
|
856
|
+
error: c.error,
|
|
857
|
+
error_description: c.error_description,
|
|
858
|
+
}, { expectedState: jopts.expectedState, requireIssParam: jopts.requireIssParam });
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// OIDC requires fetchUserInfo to be called AFTER the id_token has
|
|
862
|
+
// been verified and its sub claim is known — otherwise the
|
|
863
|
+
// userinfo response can't be cross-checked against the id_token's
|
|
864
|
+
// sub, and a hostile IdP could swap the userinfo for a different
|
|
865
|
+
// user. RFC 7662 §3 doesn't mandate the cross-check but every OIDC
|
|
866
|
+
// conformance suite requires it. We refuse to call userinfo when
|
|
867
|
+
// isOidc=true unless the caller threaded the verified idTokenSub
|
|
868
|
+
// (or explicitly opted out via skipSubCheck for a non-OIDC OAuth
|
|
869
|
+
// 2.0 server presented as isOidc=false).
|
|
870
|
+
async function fetchUserInfo(accessToken, ufiOpts) {
|
|
871
|
+
ufiOpts = ufiOpts || {};
|
|
872
|
+
if (!accessToken) {
|
|
873
|
+
throw new OAuthError("auth-oauth/no-access-token",
|
|
874
|
+
"fetchUserInfo: access token is required");
|
|
875
|
+
}
|
|
876
|
+
if (isOidc && ufiOpts.idTokenSub === undefined && ufiOpts.skipSubCheck !== true) {
|
|
877
|
+
throw new OAuthError("auth-oauth/userinfo-no-id-token-sub",
|
|
878
|
+
"fetchUserInfo: OIDC providers require ufiOpts.idTokenSub " +
|
|
879
|
+
"(the verified sub claim from the id_token returned by " +
|
|
880
|
+
"exchangeCode) so the userinfo response can be cross-checked. " +
|
|
881
|
+
"Pass { idTokenSub: tokens.idToken.payload.sub } or, for non-" +
|
|
882
|
+
"OIDC OAuth 2.0 deployments mis-flagged as isOidc, opt out " +
|
|
883
|
+
"explicitly with { skipSubCheck: true } and an audited reason.");
|
|
884
|
+
}
|
|
885
|
+
var endpoint = await _resolveEndpoint("userinfoEndpoint");
|
|
886
|
+
var profile = await _fetchJson(endpoint, {
|
|
887
|
+
headers: {
|
|
888
|
+
"Authorization": "Bearer " + accessToken,
|
|
889
|
+
"Accept": "application/json",
|
|
890
|
+
"User-Agent": "blamejs",
|
|
891
|
+
},
|
|
892
|
+
});
|
|
893
|
+
if (isOidc && ufiOpts.idTokenSub !== undefined && profile && profile.sub !== ufiOpts.idTokenSub) {
|
|
894
|
+
throw new OAuthError("auth-oauth/userinfo-sub-mismatch",
|
|
895
|
+
"fetchUserInfo: userinfo.sub (" + profile.sub + ") does not match " +
|
|
896
|
+
"the id_token sub (" + ufiOpts.idTokenSub + ") — possible token " +
|
|
897
|
+
"substitution attack");
|
|
898
|
+
}
|
|
899
|
+
return profile;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
async function revokeToken(token, ropts) {
|
|
903
|
+
if (!token) {
|
|
904
|
+
throw new OAuthError("auth-oauth/no-token", "revokeToken: token is required");
|
|
905
|
+
}
|
|
906
|
+
ropts = ropts || {};
|
|
907
|
+
var endpoint = await _resolveEndpoint("revocationEndpoint");
|
|
908
|
+
var body = new URLSearchParams();
|
|
909
|
+
body.set("token", token);
|
|
910
|
+
if (ropts.type) body.set("token_type_hint", ropts.type);
|
|
911
|
+
body.set("client_id", clientId);
|
|
912
|
+
if (clientSecret) body.set("client_secret", clientSecret);
|
|
913
|
+
var hc = httpClient;
|
|
914
|
+
var req = {
|
|
915
|
+
url: endpoint,
|
|
916
|
+
method: "POST",
|
|
917
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
918
|
+
body: Buffer.from(body.toString(), "utf8"),
|
|
919
|
+
};
|
|
920
|
+
if (allowHttp) req.allowedProtocols = safeUrl.ALLOW_HTTP_ALL;
|
|
921
|
+
if (allowInternal !== null) req.allowInternal = allowInternal;
|
|
922
|
+
Object.assign(req, httpClientOpts);
|
|
923
|
+
var res = await hc.request(req);
|
|
924
|
+
// RFC 7009: 200 even if the token was already revoked / unknown.
|
|
925
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
926
|
+
throw new OAuthError("auth-oauth/revoke-failed",
|
|
927
|
+
"revocation returned " + res.statusCode);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
async function _postForm(endpoint, body) {
|
|
932
|
+
var hc = httpClient;
|
|
933
|
+
var req = {
|
|
934
|
+
url: endpoint,
|
|
935
|
+
method: "POST",
|
|
936
|
+
headers: {
|
|
937
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
938
|
+
"Accept": "application/json",
|
|
939
|
+
},
|
|
940
|
+
body: Buffer.from(body.toString(), "utf8"),
|
|
941
|
+
};
|
|
942
|
+
if (allowHttp) req.allowedProtocols = safeUrl.ALLOW_HTTP_ALL;
|
|
943
|
+
if (allowInternal !== null) req.allowInternal = allowInternal;
|
|
944
|
+
Object.assign(req, httpClientOpts);
|
|
945
|
+
var res = await hc.request(req);
|
|
946
|
+
var text = res.body ? res.body.toString("utf8") : "";
|
|
947
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
948
|
+
throw new OAuthError("auth-oauth/token-error-" + res.statusCode,
|
|
949
|
+
endpoint + " returned " + res.statusCode + ": " + text.slice(0, 500));
|
|
950
|
+
}
|
|
951
|
+
var parsed;
|
|
952
|
+
try { parsed = safeJson.parse(text, { maxBytes: OAUTH_MAX_RESPONSE_BYTES }); }
|
|
953
|
+
catch (e) {
|
|
954
|
+
throw new OAuthError("auth-oauth/bad-token-json",
|
|
955
|
+
"token endpoint response not JSON: " + ((e && e.message) || String(e)));
|
|
956
|
+
}
|
|
957
|
+
return parsed;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
async function _normalizeTokens(raw, vopts) {
|
|
961
|
+
vopts = vopts || {};
|
|
962
|
+
var tokens = {
|
|
963
|
+
accessToken: raw.access_token,
|
|
964
|
+
tokenType: raw.token_type || "Bearer",
|
|
965
|
+
expiresIn: raw.expires_in || null,
|
|
966
|
+
refreshToken: raw.refresh_token || null,
|
|
967
|
+
idToken: raw.id_token || null,
|
|
968
|
+
// RFC 6749 §3.3 — scope is space-separated, ONLY U+0020.
|
|
969
|
+
// `\s+` previously matched U+0085 NEL, U+00A0 NBSP, etc., so a
|
|
970
|
+
// hostile AS returning `scope: "admin<NEL>read"` would
|
|
971
|
+
// surface as `["admin", "read"]` and the operator's scope
|
|
972
|
+
// allowlist saw two distinct scopes. Spec-strict split on
|
|
973
|
+
// single-space + reject scope tokens that contain non-token
|
|
974
|
+
// chars. (Audit 2026-05-11.)
|
|
975
|
+
scope: raw.scope ? raw.scope.split(" ").filter(function (s) { return s.length > 0; }) : scope.slice(),
|
|
976
|
+
raw: raw,
|
|
977
|
+
};
|
|
978
|
+
if (tokens.idToken && isOidc) {
|
|
979
|
+
var v = await verifyIdToken(tokens.idToken, {
|
|
980
|
+
nonce: vopts.nonce,
|
|
981
|
+
skipNonceCheck: vopts.skipNonceCheck,
|
|
982
|
+
});
|
|
983
|
+
tokens.claims = v.claims;
|
|
984
|
+
tokens.profile = {
|
|
985
|
+
sub: v.claims.sub,
|
|
986
|
+
email: v.claims.email,
|
|
987
|
+
name: v.claims.name,
|
|
988
|
+
picture: v.claims.picture,
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
return tokens;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
async function _getJwks() {
|
|
995
|
+
return _jwksCache.wrap("keys", async function () {
|
|
996
|
+
var jwksUri = await _resolveEndpoint("jwksUri");
|
|
997
|
+
var jwks = await _fetchJson(jwksUri);
|
|
998
|
+
if (!jwks || !Array.isArray(jwks.keys)) {
|
|
999
|
+
throw new OAuthError("auth-oauth/bad-jwks", "JWKS response missing 'keys' array");
|
|
1000
|
+
}
|
|
1001
|
+
return jwks.keys;
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
async function verifyIdToken(idToken, vopts) {
|
|
1006
|
+
vopts = vopts || {};
|
|
1007
|
+
if (typeof idToken !== "string") {
|
|
1008
|
+
throw new OAuthError("auth-oauth/no-id-token", "verifyIdToken: idToken must be a string");
|
|
1009
|
+
}
|
|
1010
|
+
var parts = idToken.split(".");
|
|
1011
|
+
// CVE-2026-29000 / CVE-2026-22817 / CVE-2026-23993 — mirror
|
|
1012
|
+
// jwt-external's 5-segment JWE refusal. A 5-segment compact
|
|
1013
|
+
// serialization is a JWE (RFC 7516); verifyIdToken is a JWS verifier
|
|
1014
|
+
// and a JWE shape reaching here is the confused-deputy class an OP
|
|
1015
|
+
// shipping JWE id_tokens would exercise. Operators with JWE
|
|
1016
|
+
// id_tokens wire a separate JWE handler at their KMS — never on
|
|
1017
|
+
// this verifier path.
|
|
1018
|
+
if (parts.length === 5) {
|
|
1019
|
+
try { audit().safeEmit({
|
|
1020
|
+
action: "jwt.jwe.refused",
|
|
1021
|
+
outcome: "denied",
|
|
1022
|
+
metadata: { reason: "jwe-on-jws-verifier", primitive: "oauth.verifyIdToken" },
|
|
1023
|
+
}); } catch (_e) { /* drop-silent — observability sink */ }
|
|
1024
|
+
throw new OAuthError("auth-oauth/jwe-refused",
|
|
1025
|
+
"5-segment JWE id_token refused — verifyIdToken only handles JWS " +
|
|
1026
|
+
"(CVE-2026-29000 / CVE-2026-23993 / CVE-2026-22817 / CVE-2026-34950 JWE-bypass class)");
|
|
1027
|
+
}
|
|
1028
|
+
if (parts.length !== 3) {
|
|
1029
|
+
throw new OAuthError("auth-oauth/malformed-jwt", "ID token does not have 3 parts");
|
|
1030
|
+
}
|
|
1031
|
+
var header, payload;
|
|
1032
|
+
try {
|
|
1033
|
+
header = safeJson.parse(_b64urlDecode(parts[0]).toString("utf8"), { maxBytes: OAUTH_MAX_RESPONSE_BYTES });
|
|
1034
|
+
payload = safeJson.parse(_b64urlDecode(parts[1]).toString("utf8"), { maxBytes: OAUTH_MAX_RESPONSE_BYTES });
|
|
1035
|
+
} catch (e) {
|
|
1036
|
+
throw new OAuthError("auth-oauth/malformed-jwt",
|
|
1037
|
+
"ID token header/payload base64 decode failed: " + ((e && e.message) || String(e)));
|
|
1038
|
+
}
|
|
1039
|
+
if (!header || typeof header.alg !== "string") {
|
|
1040
|
+
throw new OAuthError("auth-oauth/malformed-jwt", "ID token header missing 'alg'");
|
|
1041
|
+
}
|
|
1042
|
+
// CVE-2026-23993 — refuse unknown alg BEFORE any key resolution.
|
|
1043
|
+
// The acceptedAlgorithms list is the operator's posture; an alg
|
|
1044
|
+
// outside it never reaches the JWKS lookup or node:crypto.verify.
|
|
1045
|
+
if (acceptedAlgorithms.indexOf(header.alg) === -1) {
|
|
1046
|
+
throw new OAuthError("auth-oauth/alg-not-accepted",
|
|
1047
|
+
"ID token signed with '" + header.alg + "' which is not in the accepted-algorithm list " +
|
|
1048
|
+
"(CVE-2026-23993 — refused before key lookup)");
|
|
1049
|
+
}
|
|
1050
|
+
// RFC 7515 §4.1.11 — refuse JWS with `crit` header. Every other
|
|
1051
|
+
// verifier in the framework (jwt.js, jwt-external.js, dpop.js)
|
|
1052
|
+
// refuses; verifyIdToken previously silently ignored, letting an
|
|
1053
|
+
// attacker-controlled OP ship critical extensions the verifier
|
|
1054
|
+
// doesn't understand. (Audit 2026-05-11.)
|
|
1055
|
+
if (header.crit !== undefined && header.crit !== null) {
|
|
1056
|
+
throw new OAuthError("auth-oauth/crit-not-supported",
|
|
1057
|
+
"ID token JWS header carries 'crit' extension list; this verifier does not " +
|
|
1058
|
+
"support any critical extensions and refuses per RFC 7515 §4.1.11");
|
|
1059
|
+
}
|
|
1060
|
+
var keys = await _getJwks();
|
|
1061
|
+
var match = null;
|
|
1062
|
+
if (header.kid) {
|
|
1063
|
+
for (var i = 0; i < keys.length; i++) {
|
|
1064
|
+
if (keys[i].kid === header.kid) { match = keys[i]; break; }
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
// Pre-v0.9.4 fell back to keys[0] when the token carried NO kid
|
|
1068
|
+
// and the JWKS had exactly one key. This is a latent vector
|
|
1069
|
+
// during JWKS rotation: an attacker who can ship a kid-less
|
|
1070
|
+
// token gets the lone key during the window the rotated-out
|
|
1071
|
+
// key was still cached at the IdP but the rotated-in key is
|
|
1072
|
+
// already published. Refuse kid-less tokens unconditionally —
|
|
1073
|
+
// every modern IdP includes kid; absent kid is a spec smell.
|
|
1074
|
+
// (Audit 2026-05-11.) Operators with non-conforming IdPs that
|
|
1075
|
+
// genuinely emit kid-less tokens can opt out via
|
|
1076
|
+
// vopts.allowKidlessJwks = true with a logged warning.
|
|
1077
|
+
if (!match) {
|
|
1078
|
+
// Operator opt-out reads from EITHER the per-call vopts OR the
|
|
1079
|
+
// client-level config — `_normalizeTokens` calls verifyIdToken
|
|
1080
|
+
// with a reduced vopts ({ nonce, skipNonceCheck }), so a
|
|
1081
|
+
// per-call opt would not reach the standard exchangeCode /
|
|
1082
|
+
// pollDeviceCode / exchangeToken / refreshAccessToken flows.
|
|
1083
|
+
// The client-level `create({ allowKidlessJwks: true })` fills
|
|
1084
|
+
// that gap. (v0.9.5 follow-up to the v0.9.4 audit fix.)
|
|
1085
|
+
var allowKidless = vopts.allowKidlessJwks === true || allowKidlessJwks;
|
|
1086
|
+
if (!header.kid && keys.length === 1 && allowKidless) {
|
|
1087
|
+
match = keys[0];
|
|
1088
|
+
} else {
|
|
1089
|
+
throw new OAuthError("auth-oauth/no-matching-key",
|
|
1090
|
+
header.kid
|
|
1091
|
+
? "no JWKS key matches header.kid='" + header.kid + "'"
|
|
1092
|
+
: "ID token has no kid header; framework refuses kid-less " +
|
|
1093
|
+
"tokens to defend against JWKS-rotation key-pick attacks " +
|
|
1094
|
+
"(pass `allowKidlessJwks: true` to b.auth.oauth.create() — " +
|
|
1095
|
+
"client-level — if your IdP genuinely emits kid-less tokens; " +
|
|
1096
|
+
"or vopts.allowKidlessJwks: true on a single verifyIdToken " +
|
|
1097
|
+
"call)");
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
// CVE-2026-22817 — cross-check JWS alg against the resolved JWK's
|
|
1101
|
+
// kty (and crv for EC). Without this an attacker-controlled
|
|
1102
|
+
// `alg: "HS256"` against an RSA-kty JWK would hand the public-key
|
|
1103
|
+
// bytes to node:crypto.verify as an HMAC secret. Routed through the
|
|
1104
|
+
// shared helper so every JWT verifier (oauth / jwt-external /
|
|
1105
|
+
// oid4vci / sd-jwt-vc / openid-federation) enforces the same check.
|
|
1106
|
+
jwtExternal._assertAlgKtyMatch(header.alg, match);
|
|
1107
|
+
var keyObject = _jwkToKey(match);
|
|
1108
|
+
var params = _verifyParamsForAlg(header.alg);
|
|
1109
|
+
var signingInput = parts[0] + "." + parts[1];
|
|
1110
|
+
var sig = _b64urlDecode(parts[2]);
|
|
1111
|
+
var verifyOpts = { key: keyObject };
|
|
1112
|
+
if (params.padding !== undefined) verifyOpts.padding = params.padding;
|
|
1113
|
+
if (params.saltLength !== undefined) verifyOpts.saltLength = params.saltLength;
|
|
1114
|
+
if (params.dsaEncoding !== undefined) verifyOpts.dsaEncoding = params.dsaEncoding;
|
|
1115
|
+
// nodeCrypto.verify panics on key/sig shape mismatch (e.g. an
|
|
1116
|
+
// ES256 signature attempted against an RS256 key returned by a
|
|
1117
|
+
// hostile or buggy IdP with duplicate kids). Wrap so the panic
|
|
1118
|
+
// becomes a typed AuthError, matching the discipline in
|
|
1119
|
+
// jwt-external.js + dpop.js. (Audit 2026-05-11.)
|
|
1120
|
+
var verified;
|
|
1121
|
+
try {
|
|
1122
|
+
verified = nodeCrypto.verify(params.hash, Buffer.from(signingInput, "ascii"), verifyOpts, sig);
|
|
1123
|
+
} catch (verifyErr) {
|
|
1124
|
+
throw new OAuthError("auth-oauth/bad-signature",
|
|
1125
|
+
"ID token signature verification raised: " +
|
|
1126
|
+
((verifyErr && verifyErr.message) || String(verifyErr)));
|
|
1127
|
+
}
|
|
1128
|
+
if (!verified) {
|
|
1129
|
+
throw new OAuthError("auth-oauth/bad-signature", "ID token signature verification failed");
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Claim validation.
|
|
1133
|
+
var now = Math.floor(Date.now() / C.TIME.seconds(1));
|
|
1134
|
+
var skewSec = Math.floor(clockSkewMs / C.TIME.seconds(1));
|
|
1135
|
+
// OIDC Back-Channel Logout 1.0 §2.4 — logout tokens have no `exp`
|
|
1136
|
+
// claim; freshness comes from `iat` + jti-replay window. Operators
|
|
1137
|
+
// verifying logout tokens pass `skipExpCheck: true`. ID tokens
|
|
1138
|
+
// never set this and continue to require `exp`.
|
|
1139
|
+
if (!vopts.skipExpCheck) {
|
|
1140
|
+
if (typeof payload.exp !== "number" || payload.exp + skewSec < now) {
|
|
1141
|
+
throw new OAuthError("auth-oauth/expired", "ID token expired (exp=" + payload.exp + ", now=" + now + ")");
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if (typeof payload.iat === "number" && payload.iat - skewSec > now) {
|
|
1145
|
+
throw new OAuthError("auth-oauth/iat-future", "ID token iat is in the future");
|
|
1146
|
+
}
|
|
1147
|
+
if (typeof payload.nbf === "number" && payload.nbf - skewSec > now) {
|
|
1148
|
+
throw new OAuthError("auth-oauth/nbf-future", "ID token nbf is in the future");
|
|
1149
|
+
}
|
|
1150
|
+
if (issuer) {
|
|
1151
|
+
// CVE-2026-23552 — cross-realm / cross-issuer JWT acceptance. The
|
|
1152
|
+
// expected issuer is operator-supplied; payload.iss is attacker-
|
|
1153
|
+
// controlled bytes. Constant-time compare defeats prefix-timing
|
|
1154
|
+
// narrowing. Emit a DISTINCT audit event (separate from the
|
|
1155
|
+
// bad-signature failure) so detection signals on cross-realm
|
|
1156
|
+
// probes independently of generic verification failures.
|
|
1157
|
+
if (typeof payload.iss !== "string" ||
|
|
1158
|
+
!jwtExternal._issuerMatches(payload.iss, issuer)) {
|
|
1159
|
+
try { audit().safeEmit({
|
|
1160
|
+
action: "jwt.iss.mismatch",
|
|
1161
|
+
outcome: "denied",
|
|
1162
|
+
metadata: {
|
|
1163
|
+
expectedIssuer: issuer,
|
|
1164
|
+
presentedIssuer: typeof payload.iss === "string" ? payload.iss : null,
|
|
1165
|
+
reason: "cross-realm-jwt-refused",
|
|
1166
|
+
primitive: "oauth.verifyIdToken",
|
|
1167
|
+
},
|
|
1168
|
+
}); } catch (_e) { /* drop-silent — observability sink */ }
|
|
1169
|
+
throw new OAuthError("auth-oauth/iss-mismatch",
|
|
1170
|
+
"ID token iss '" + payload.iss + "' does not match expected '" + issuer +
|
|
1171
|
+
"' (CVE-2026-23552 — cross-realm refused)");
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
var aud = Array.isArray(payload.aud) ? payload.aud : (payload.aud ? [payload.aud] : []);
|
|
1175
|
+
if (aud.indexOf(clientId) === -1) {
|
|
1176
|
+
throw new OAuthError("auth-oauth/aud-mismatch",
|
|
1177
|
+
"ID token aud does not contain clientId '" + clientId + "'");
|
|
1178
|
+
}
|
|
1179
|
+
if (vopts.nonce && !vopts.skipNonceCheck) {
|
|
1180
|
+
// Constant-time nonce compare — secret-shaped value matched
|
|
1181
|
+
// against attacker-controlled payload. (Audit 2026-05-11.)
|
|
1182
|
+
if (typeof payload.nonce !== "string" ||
|
|
1183
|
+
!cryptoTimingSafeEqual(payload.nonce, vopts.nonce)) {
|
|
1184
|
+
throw new OAuthError("auth-oauth/nonce-mismatch",
|
|
1185
|
+
"ID token nonce mismatch (replay protection)");
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
return { header: header, claims: payload };
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// ---- OIDC RP-Initiated Logout (OpenID Connect Session Mgmt 1.0) ----
|
|
1192
|
+
//
|
|
1193
|
+
// The IdP exposes an `end_session_endpoint` in its discovery doc;
|
|
1194
|
+
// the RP-initiated logout flow redirects the user to that endpoint
|
|
1195
|
+
// with the id_token_hint + post_logout_redirect_uri so the IdP
|
|
1196
|
+
// terminates the IdP session and bounces the user back to the
|
|
1197
|
+
// operator's app. Operators wire this on their /logout route.
|
|
1198
|
+
async function endSessionUrl(uopts) {
|
|
1199
|
+
uopts = uopts || {};
|
|
1200
|
+
var endpoint;
|
|
1201
|
+
try { endpoint = await _resolveEndpoint("endSessionEndpoint"); }
|
|
1202
|
+
catch (_e) {
|
|
1203
|
+
throw new OAuthError("auth-oauth/no-end-session-endpoint",
|
|
1204
|
+
"endSessionUrl: IdP discovery doc has no end_session_endpoint " +
|
|
1205
|
+
"(set opts.endSessionEndpoint on create() if the IdP doesn't publish it)");
|
|
1206
|
+
}
|
|
1207
|
+
var params = new URLSearchParams();
|
|
1208
|
+
if (uopts.idTokenHint) params.set("id_token_hint", uopts.idTokenHint);
|
|
1209
|
+
if (uopts.postLogoutRedirectUri) {
|
|
1210
|
+
// OIDC RP-Init Logout §3.1 — postLogoutRedirectUri is operator-
|
|
1211
|
+
// supplied; an operator typo could ship `http://` or
|
|
1212
|
+
// `javascript:`. Route through the framework's URL gate before
|
|
1213
|
+
// emitting so the URL is validated the same way as every other
|
|
1214
|
+
// operator-supplied OAuth URL (audit 2026-05-15).
|
|
1215
|
+
_validateUrl(uopts.postLogoutRedirectUri, allowHttp, "postLogoutRedirectUri");
|
|
1216
|
+
params.set("post_logout_redirect_uri", uopts.postLogoutRedirectUri);
|
|
1217
|
+
}
|
|
1218
|
+
if (uopts.state) params.set("state", uopts.state);
|
|
1219
|
+
if (uopts.logoutHint) params.set("logout_hint", uopts.logoutHint);
|
|
1220
|
+
if (uopts.uiLocales) params.set("ui_locales", uopts.uiLocales);
|
|
1221
|
+
if (uopts.clientId !== false) params.set("client_id", clientId);
|
|
1222
|
+
if (uopts.extraParams && typeof uopts.extraParams === "object") {
|
|
1223
|
+
// OIDC RP-Init Logout §3.1 — extraParams carries operator-
|
|
1224
|
+
// controlled key/value pairs. Refuse keys that collide with
|
|
1225
|
+
// first-class params so an operator typo / library-merge can't
|
|
1226
|
+
// smuggle a second `post_logout_redirect_uri` past the
|
|
1227
|
+
// _validateUrl gate above. Defense-in-depth — the operator
|
|
1228
|
+
// controls extraParams, so this is a config-time invariant, not
|
|
1229
|
+
// an attacker-input filter.
|
|
1230
|
+
var RESERVED_END_SESSION_PARAMS = {
|
|
1231
|
+
"id_token_hint": 1,
|
|
1232
|
+
"post_logout_redirect_uri": 1,
|
|
1233
|
+
"state": 1,
|
|
1234
|
+
"logout_hint": 1,
|
|
1235
|
+
"ui_locales": 1,
|
|
1236
|
+
"client_id": 1,
|
|
1237
|
+
};
|
|
1238
|
+
var ek = Object.keys(uopts.extraParams);
|
|
1239
|
+
for (var i = 0; i < ek.length; i++) {
|
|
1240
|
+
if (RESERVED_END_SESSION_PARAMS[ek[i]]) {
|
|
1241
|
+
throw new OAuthError("auth-oauth/end-session-reserved-extra-param",
|
|
1242
|
+
"endSessionUrl: extraParams key '" + ek[i] + "' collides with a first-class " +
|
|
1243
|
+
"RP-Init Logout parameter — pass it through the named field instead");
|
|
1244
|
+
}
|
|
1245
|
+
params.set(ek[i], String(uopts.extraParams[ek[i]]));
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
var qs = params.toString();
|
|
1249
|
+
if (qs.length === 0) return endpoint;
|
|
1250
|
+
var sep = endpoint.indexOf("?") === -1 ? "?" : "&";
|
|
1251
|
+
return endpoint + sep + qs;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// ---- OAuth 2.0 Pushed Authorization Requests (RFC 9126) ----
|
|
1255
|
+
//
|
|
1256
|
+
// PAR: the client POSTs the authorization-request parameters
|
|
1257
|
+
// directly to the IdP's PAR endpoint (mTLS or client-secret
|
|
1258
|
+
// authenticated) and gets back a `request_uri` it then puts in the
|
|
1259
|
+
// browser-side redirect to /authorize. Defends against parameter
|
|
1260
|
+
// tampering by an MITM at the user-agent + against URL-length
|
|
1261
|
+
// overflow on long authorization requests.
|
|
1262
|
+
async function pushAuthorizationRequest(uopts) {
|
|
1263
|
+
uopts = uopts || {};
|
|
1264
|
+
var endpoint;
|
|
1265
|
+
try { endpoint = await _resolveEndpoint("pushedAuthorizationRequestEndpoint"); }
|
|
1266
|
+
catch (_e) {
|
|
1267
|
+
throw new OAuthError("auth-oauth/no-par-endpoint",
|
|
1268
|
+
"pushAuthorizationRequest: IdP discovery doc has no " +
|
|
1269
|
+
"pushed_authorization_request_endpoint (set opts.pushedAuthorizationRequestEndpoint " +
|
|
1270
|
+
"on create() if the IdP doesn't publish it)");
|
|
1271
|
+
}
|
|
1272
|
+
// Build the same param set authorizationUrl would emit, then POST
|
|
1273
|
+
// it to PAR instead of putting it in the redirect URL.
|
|
1274
|
+
var state = uopts.state || _generateRandomToken(STATE_NONCE_BYTES);
|
|
1275
|
+
var nonce = uopts.nonce || (isOidc ? _generateRandomToken(STATE_NONCE_BYTES) : null);
|
|
1276
|
+
var pkceVals = _generatePkce();
|
|
1277
|
+
var body = new URLSearchParams();
|
|
1278
|
+
body.set("response_type", "code");
|
|
1279
|
+
body.set("client_id", clientId);
|
|
1280
|
+
body.set("redirect_uri", redirectUri);
|
|
1281
|
+
body.set("scope", scope.join(" "));
|
|
1282
|
+
body.set("state", state);
|
|
1283
|
+
if (nonce) body.set("nonce", nonce);
|
|
1284
|
+
body.set("code_challenge", pkceVals.challenge);
|
|
1285
|
+
body.set("code_challenge_method", "S256");
|
|
1286
|
+
if (responseMode) body.set("response_mode", responseMode);
|
|
1287
|
+
if (uopts.prompt) body.set("prompt", uopts.prompt);
|
|
1288
|
+
if (uopts.loginHint) body.set("login_hint", uopts.loginHint);
|
|
1289
|
+
if (uopts.maxAge != null) body.set("max_age", String(uopts.maxAge));
|
|
1290
|
+
if (clientSecret) body.set("client_secret", clientSecret);
|
|
1291
|
+
if (uopts.extraParams && typeof uopts.extraParams === "object") {
|
|
1292
|
+
var ek = Object.keys(uopts.extraParams);
|
|
1293
|
+
for (var i = 0; i < ek.length; i++) body.set(ek[i], String(uopts.extraParams[ek[i]]));
|
|
1294
|
+
}
|
|
1295
|
+
var rv = await _postForm(endpoint, body);
|
|
1296
|
+
if (!rv || typeof rv.request_uri !== "string" || rv.request_uri.length === 0) {
|
|
1297
|
+
throw new OAuthError("auth-oauth/par-bad-response",
|
|
1298
|
+
"pushAuthorizationRequest: IdP did not return a request_uri (got " +
|
|
1299
|
+
JSON.stringify(rv).slice(0, 200) + ")"); // allow:raw-byte-literal — error-message snippet length
|
|
1300
|
+
}
|
|
1301
|
+
// Build the browser-side redirect URL: /authorize?client_id=...&request_uri=...
|
|
1302
|
+
var authzEndpoint = await _resolveEndpoint("authorizationEndpoint");
|
|
1303
|
+
var qs = new URLSearchParams();
|
|
1304
|
+
qs.set("client_id", clientId);
|
|
1305
|
+
qs.set("request_uri", rv.request_uri);
|
|
1306
|
+
var sep = authzEndpoint.indexOf("?") === -1 ? "?" : "&";
|
|
1307
|
+
return {
|
|
1308
|
+
url: authzEndpoint + sep + qs.toString(),
|
|
1309
|
+
state: state,
|
|
1310
|
+
nonce: nonce,
|
|
1311
|
+
verifier: pkceVals.verifier,
|
|
1312
|
+
challenge: pkceVals.challenge,
|
|
1313
|
+
requestUri: rv.request_uri,
|
|
1314
|
+
expiresIn: typeof rv.expires_in === "number" ? rv.expires_in : null,
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// ---- OIDC Front-Channel Logout 1.0 ----
|
|
1319
|
+
//
|
|
1320
|
+
// The IdP renders an iframe pointing at the RP's
|
|
1321
|
+
// frontchannel_logout_uri with `iss` + `sid` query params; the RP's
|
|
1322
|
+
// iframe-served endpoint clears the local session for that sid and
|
|
1323
|
+
// returns a no-content / blank page. Operators stand up a single
|
|
1324
|
+
// /oidc/frontchannel-logout route, parse the request, and call
|
|
1325
|
+
// `parseFrontchannelLogoutRequest(req)` to extract the validated
|
|
1326
|
+
// (iss, sid) tuple to feed their session-store deletion.
|
|
1327
|
+
//
|
|
1328
|
+
// The IdP advertises support via `frontchannel_logout_supported`
|
|
1329
|
+
// and `frontchannel_logout_session_required` in discovery; the RP
|
|
1330
|
+
// registers `frontchannel_logout_uri` + `frontchannel_logout_session_required`
|
|
1331
|
+
// at client-registration time. We don't auto-register here — the
|
|
1332
|
+
// RP's registration step is operator-side; this surface only
|
|
1333
|
+
// handles the runtime parse.
|
|
1334
|
+
function parseFrontchannelLogoutRequest(req) {
|
|
1335
|
+
if (!req || !req.url) {
|
|
1336
|
+
throw new OAuthError("auth-oauth/bad-frontchannel-logout-req",
|
|
1337
|
+
"parseFrontchannelLogoutRequest: req with url required");
|
|
1338
|
+
}
|
|
1339
|
+
var u;
|
|
1340
|
+
try { u = new URL(req.url, "http://placeholder.invalid"); } // allow:raw-new-url — req.url is the framework-normalized path; placeholder base provides a synthetic origin for relative-path parse
|
|
1341
|
+
catch (_e) {
|
|
1342
|
+
throw new OAuthError("auth-oauth/bad-frontchannel-logout-url",
|
|
1343
|
+
"parseFrontchannelLogoutRequest: malformed request URL");
|
|
1344
|
+
}
|
|
1345
|
+
var iss = u.searchParams.get("iss");
|
|
1346
|
+
var sid = u.searchParams.get("sid");
|
|
1347
|
+
// RFC 0 invariant: `iss` MUST match the configured issuer when
|
|
1348
|
+
// present (defends against an attacker-controlled IdP forging a
|
|
1349
|
+
// logout for a session at a different IdP). `sid` is required
|
|
1350
|
+
// when the RP registered with frontchannel_logout_session_required=true;
|
|
1351
|
+
// we surface it either way and let the operator decide.
|
|
1352
|
+
// CVE-2026-23552 — constant-time issuer compare. Defeats prefix-
|
|
1353
|
+
// timing narrowing against the configured issuer string; iss is
|
|
1354
|
+
// attacker-controlled query-param input.
|
|
1355
|
+
if (iss && (typeof issuer !== "string" || !jwtExternal._issuerMatches(iss, issuer))) {
|
|
1356
|
+
try { audit().safeEmit({
|
|
1357
|
+
action: "jwt.iss.mismatch",
|
|
1358
|
+
outcome: "denied",
|
|
1359
|
+
metadata: {
|
|
1360
|
+
expectedIssuer: issuer,
|
|
1361
|
+
presentedIssuer: iss,
|
|
1362
|
+
reason: "frontchannel-logout-cross-realm",
|
|
1363
|
+
primitive: "oauth.parseFrontchannelLogoutRequest",
|
|
1364
|
+
},
|
|
1365
|
+
}); } catch (_e) { /* drop-silent — observability sink */ }
|
|
1366
|
+
throw new OAuthError("auth-oauth/frontchannel-logout-iss-mismatch",
|
|
1367
|
+
"parseFrontchannelLogoutRequest: iss \"" + iss +
|
|
1368
|
+
"\" does not match configured issuer \"" + issuer +
|
|
1369
|
+
"\" (CVE-2026-23552 — cross-realm refused)");
|
|
1370
|
+
}
|
|
1371
|
+
return { iss: iss || issuer, sid: sid || null };
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// ---- OIDC Back-Channel Logout 1.0 ----
|
|
1375
|
+
//
|
|
1376
|
+
// The IdP POSTs an `application/x-www-form-urlencoded` body with
|
|
1377
|
+
// `logout_token=<jwt>` to the RP's backchannel_logout_uri. The
|
|
1378
|
+
// logout token is a JWT with:
|
|
1379
|
+
// header.typ = "logout+jwt"
|
|
1380
|
+
// payload.iss = the IdP issuer
|
|
1381
|
+
// payload.aud = the RP's client_id
|
|
1382
|
+
// payload.iat = recent timestamp
|
|
1383
|
+
// payload.jti = unique id (replay-cache key)
|
|
1384
|
+
// payload.events = { "http://schemas.openid.net/event/backchannel-logout": {} }
|
|
1385
|
+
// payload.sub OR payload.sid (one of)
|
|
1386
|
+
// MUST NOT contain `nonce`
|
|
1387
|
+
//
|
|
1388
|
+
// The RP verifies the JWS using the IdP's JWKS, validates each
|
|
1389
|
+
// claim, and destroys every session for the matching sub or sid.
|
|
1390
|
+
//
|
|
1391
|
+
// Replay defense: operators provide a `seen({jti, iat}) -> Promise<bool>`
|
|
1392
|
+
// callback that returns true the FIRST time it sees a (jti, iss)
|
|
1393
|
+
// pair within the operator's chosen window (typical: 5 minutes).
|
|
1394
|
+
// Subsequent calls with the same (jti, iss) return false and the
|
|
1395
|
+
// RP rejects the duplicate. The framework does not maintain the
|
|
1396
|
+
// store — operators wire b.cache or b.db.
|
|
1397
|
+
async function verifyBackchannelLogoutToken(logoutToken, vopts) {
|
|
1398
|
+
vopts = vopts || {};
|
|
1399
|
+
if (typeof logoutToken !== "string" || logoutToken.length === 0) {
|
|
1400
|
+
throw new OAuthError("auth-oauth/bad-logout-token",
|
|
1401
|
+
"verifyBackchannelLogoutToken: logoutToken must be a non-empty string");
|
|
1402
|
+
}
|
|
1403
|
+
var parts = logoutToken.split(".");
|
|
1404
|
+
if (parts.length !== 3) {
|
|
1405
|
+
throw new OAuthError("auth-oauth/malformed-logout-token",
|
|
1406
|
+
"verifyBackchannelLogoutToken: logout_token must be a 3-segment JWS");
|
|
1407
|
+
}
|
|
1408
|
+
var headerObj;
|
|
1409
|
+
try { headerObj = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8")); } // allow:bare-json-parse — pre-verify header parse to look up the typ; the JWS signature is verified by verifyIdToken below
|
|
1410
|
+
catch (_e) {
|
|
1411
|
+
throw new OAuthError("auth-oauth/bad-logout-header",
|
|
1412
|
+
"verifyBackchannelLogoutToken: malformed header");
|
|
1413
|
+
}
|
|
1414
|
+
if (headerObj.typ !== "logout+jwt") {
|
|
1415
|
+
throw new OAuthError("auth-oauth/wrong-typ",
|
|
1416
|
+
"verifyBackchannelLogoutToken: header.typ must be \"logout+jwt\" (got \"" +
|
|
1417
|
+
headerObj.typ + "\")");
|
|
1418
|
+
}
|
|
1419
|
+
// Reuse verifyIdToken's signature-verification path. It looks up
|
|
1420
|
+
// the IdP JWKS and checks the JWS — same trust anchor.
|
|
1421
|
+
var verified = await verifyIdToken(logoutToken, {
|
|
1422
|
+
issuer: issuer,
|
|
1423
|
+
clientId: clientId,
|
|
1424
|
+
acceptedAlgs: vopts.acceptedAlgs,
|
|
1425
|
+
jwksUri: vopts.jwksUri,
|
|
1426
|
+
maxClockSkewMs: vopts.maxClockSkewMs,
|
|
1427
|
+
// Logout tokens have no nonce — disable the nonce check that
|
|
1428
|
+
// verifyIdToken would otherwise enforce on id_tokens.
|
|
1429
|
+
skipNonceCheck: true,
|
|
1430
|
+
// Logout tokens have no exp claim per OIDC Back-Channel Logout
|
|
1431
|
+
// §2.4 — the freshness gate is iat + jti-replay window.
|
|
1432
|
+
skipExpCheck: true,
|
|
1433
|
+
});
|
|
1434
|
+
var claims = verified.claims;
|
|
1435
|
+
|
|
1436
|
+
// §2.6 — events claim presence + correct shape
|
|
1437
|
+
if (!claims.events || typeof claims.events !== "object" ||
|
|
1438
|
+
!claims.events["http://schemas.openid.net/event/backchannel-logout"]) {
|
|
1439
|
+
throw new OAuthError("auth-oauth/missing-logout-event",
|
|
1440
|
+
"verifyBackchannelLogoutToken: payload.events missing http://schemas.openid.net/event/backchannel-logout");
|
|
1441
|
+
}
|
|
1442
|
+
// §2.6 — nonce MUST NOT be present (nonce is for ID tokens only)
|
|
1443
|
+
if (Object.prototype.hasOwnProperty.call(claims, "nonce")) {
|
|
1444
|
+
throw new OAuthError("auth-oauth/forbidden-nonce",
|
|
1445
|
+
"verifyBackchannelLogoutToken: payload.nonce is forbidden in logout tokens (§2.6)");
|
|
1446
|
+
}
|
|
1447
|
+
// §2.4 — sub OR sid REQUIRED (at least one)
|
|
1448
|
+
if (!claims.sub && !claims.sid) {
|
|
1449
|
+
throw new OAuthError("auth-oauth/no-sub-or-sid",
|
|
1450
|
+
"verifyBackchannelLogoutToken: payload must include sub or sid");
|
|
1451
|
+
}
|
|
1452
|
+
// OIDC Back-Channel Logout §2.6 — iat freshness gate. Logout tokens
|
|
1453
|
+
// have no exp claim; freshness rests entirely on iat plus a
|
|
1454
|
+
// replay-cache window. A captured old logout-token with a fresh jti
|
|
1455
|
+
// (never seen by THIS RP's replay store, e.g. cleared across a
|
|
1456
|
+
// restart) would otherwise pass. Refuse iat older than
|
|
1457
|
+
// opts.maxAgeSec (default 5 minutes) — matches the standard 5-min
|
|
1458
|
+
// jti-replay-cache window operators ship.
|
|
1459
|
+
var logoutMaxAgeSec = typeof vopts.maxAgeSec === "number"
|
|
1460
|
+
? vopts.maxAgeSec
|
|
1461
|
+
: DEFAULT_LOGOUT_TOKEN_MAX_AGE_SEC;
|
|
1462
|
+
var nowSecLogout = Math.floor(Date.now() / C.TIME.seconds(1));
|
|
1463
|
+
if (typeof claims.iat !== "number") {
|
|
1464
|
+
throw new OAuthError("auth-oauth/logout-token-no-iat",
|
|
1465
|
+
"verifyBackchannelLogoutToken: payload.iat required (OIDC BCL §2.4)");
|
|
1466
|
+
}
|
|
1467
|
+
if (claims.iat + logoutMaxAgeSec < nowSecLogout) {
|
|
1468
|
+
throw new OAuthError("auth-oauth/logout-token-too-old",
|
|
1469
|
+
"verifyBackchannelLogoutToken: payload.iat=" + claims.iat +
|
|
1470
|
+
" is older than maxAgeSec=" + logoutMaxAgeSec +
|
|
1471
|
+
" (OIDC BCL §2.6 — old logout-token refused)");
|
|
1472
|
+
}
|
|
1473
|
+
// Replay defense — atomic checkAndInsert when the operator supplies
|
|
1474
|
+
// a b.nonceStore-shaped backend, fallback to the legacy
|
|
1475
|
+
// seen()-callback when supplied. The atomic shape closes the
|
|
1476
|
+
// race-class first surfaced for refresh-token rotation in v0.9.3:
|
|
1477
|
+
// two simultaneous deliveries of the same logout_token both pass
|
|
1478
|
+
// the seen() check and both run the operator's session-destroy
|
|
1479
|
+
// handler. atomicReplayStore.checkAndInsert(jti, expireAtMs)
|
|
1480
|
+
// returns true if it WAS the first insert, false on duplicate.
|
|
1481
|
+
if (vopts.atomicReplayStore && typeof vopts.atomicReplayStore.checkAndInsert === "function") {
|
|
1482
|
+
if (typeof claims.jti !== "string" || claims.jti.length === 0) {
|
|
1483
|
+
throw new OAuthError("auth-oauth/no-jti",
|
|
1484
|
+
"verifyBackchannelLogoutToken: jti required when atomicReplayStore is configured");
|
|
1485
|
+
}
|
|
1486
|
+
var expireAtMs = (nowSecLogout + logoutMaxAgeSec * 2) * C.TIME.seconds(1);
|
|
1487
|
+
var inserted;
|
|
1488
|
+
try { inserted = await vopts.atomicReplayStore.checkAndInsert(claims.jti, expireAtMs); }
|
|
1489
|
+
catch (e) {
|
|
1490
|
+
throw new OAuthError("auth-oauth/replay-store-failed",
|
|
1491
|
+
"verifyBackchannelLogoutToken: atomicReplayStore.checkAndInsert threw: " +
|
|
1492
|
+
((e && e.message) || String(e)));
|
|
1493
|
+
}
|
|
1494
|
+
if (inserted === false) {
|
|
1495
|
+
throw new OAuthError("auth-oauth/logout-token-replay",
|
|
1496
|
+
"verifyBackchannelLogoutToken: jti '" + claims.jti +
|
|
1497
|
+
"' already seen — replay refused (atomic)");
|
|
1498
|
+
}
|
|
1499
|
+
} else if (typeof vopts.seen === "function") {
|
|
1500
|
+
if (typeof claims.jti !== "string" || claims.jti.length === 0) {
|
|
1501
|
+
throw new OAuthError("auth-oauth/no-jti",
|
|
1502
|
+
"verifyBackchannelLogoutToken: jti required when a seen() callback is configured");
|
|
1503
|
+
}
|
|
1504
|
+
var first;
|
|
1505
|
+
try { first = await vopts.seen({ jti: claims.jti, iss: claims.iss, iat: claims.iat }); }
|
|
1506
|
+
catch (e) {
|
|
1507
|
+
throw new OAuthError("auth-oauth/seen-callback-failed",
|
|
1508
|
+
"verifyBackchannelLogoutToken: seen() callback threw: " + ((e && e.message) || String(e)));
|
|
1509
|
+
}
|
|
1510
|
+
if (first === false) {
|
|
1511
|
+
throw new OAuthError("auth-oauth/logout-token-replay",
|
|
1512
|
+
"verifyBackchannelLogoutToken: jti already seen — replay refused");
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
return {
|
|
1516
|
+
iss: claims.iss,
|
|
1517
|
+
aud: claims.aud,
|
|
1518
|
+
sub: claims.sub || null,
|
|
1519
|
+
sid: claims.sid || null,
|
|
1520
|
+
jti: claims.jti || null,
|
|
1521
|
+
iat: claims.iat || null,
|
|
1522
|
+
events: claims.events,
|
|
1523
|
+
claims: claims,
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
// ---- OIDC Session Management 1.0 — check_session_iframe ----
|
|
1528
|
+
//
|
|
1529
|
+
// The IdP advertises a `check_session_iframe` URL in discovery.
|
|
1530
|
+
// The RP loads it inside an iframe and posts `<client_id>
|
|
1531
|
+
// <session_state>` messages to it; the iframe responds with
|
|
1532
|
+
// "changed" / "unchanged" / "error" so the RP can periodically
|
|
1533
|
+
// poll without a full network round-trip.
|
|
1534
|
+
//
|
|
1535
|
+
// This builder returns the iframe URL plus a small client-side
|
|
1536
|
+
// helper string operators embed in their HTML to drive the
|
|
1537
|
+
// postMessage handshake. The framework does not host the iframe —
|
|
1538
|
+
// the IdP does. Operators that want CSP-compliant inline scripts
|
|
1539
|
+
// emit the helper through the framework's nonce middleware.
|
|
1540
|
+
async function checkSessionIframeUrl() {
|
|
1541
|
+
var url;
|
|
1542
|
+
try { url = await _resolveEndpoint("checkSessionIframe"); }
|
|
1543
|
+
catch (_e) {
|
|
1544
|
+
throw new OAuthError("auth-oauth/no-check-session-iframe",
|
|
1545
|
+
"checkSessionIframeUrl: IdP discovery doc has no check_session_iframe " +
|
|
1546
|
+
"(set opts.checkSessionIframe on create() if the IdP doesn't publish it)");
|
|
1547
|
+
}
|
|
1548
|
+
return url;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/**
|
|
1552
|
+
* @primitive b.auth.oauth.introspectToken
|
|
1553
|
+
* @signature b.auth.oauth.introspectToken(token, opts?)
|
|
1554
|
+
* @since 0.8.77
|
|
1555
|
+
* @related b.middleware.bearerAuth
|
|
1556
|
+
*
|
|
1557
|
+
* RFC 7662 OAuth 2.0 Token Introspection. Resource-server side
|
|
1558
|
+
* primitive: POSTs to the AS's introspection endpoint with the
|
|
1559
|
+
* presented token and returns the active/inactive verdict + claims.
|
|
1560
|
+
* `active: false` SHOULD be treated as token-invalid regardless of
|
|
1561
|
+
* other fields (RFC 7662 §2.2). When the AS supports `token_type_hint`,
|
|
1562
|
+
* pass `opts.tokenTypeHint` ("access_token" or "refresh_token") to
|
|
1563
|
+
* speed up the lookup; the AS may ignore the hint.
|
|
1564
|
+
*
|
|
1565
|
+
* @opts
|
|
1566
|
+
* {
|
|
1567
|
+
* tokenTypeHint?: "access_token" | "refresh_token",
|
|
1568
|
+
* }
|
|
1569
|
+
*
|
|
1570
|
+
* @example
|
|
1571
|
+
* var verdict = await oauth.introspectToken(bearer);
|
|
1572
|
+
* if (!verdict.active) throw new Error("invalid_token");
|
|
1573
|
+
*/
|
|
1574
|
+
async function introspectToken(token, iopts) {
|
|
1575
|
+
iopts = iopts || {};
|
|
1576
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
1577
|
+
throw new OAuthError("auth-oauth/bad-introspect",
|
|
1578
|
+
"introspectToken: token must be a non-empty string");
|
|
1579
|
+
}
|
|
1580
|
+
var endpoint;
|
|
1581
|
+
try { endpoint = await _resolveEndpoint("introspectionEndpoint"); }
|
|
1582
|
+
catch (_e) {
|
|
1583
|
+
throw new OAuthError("auth-oauth/no-introspection-endpoint",
|
|
1584
|
+
"introspectToken: AS does not advertise introspection_endpoint " +
|
|
1585
|
+
"(set opts.introspectionEndpoint on create() if it's static)");
|
|
1586
|
+
}
|
|
1587
|
+
var body = new URLSearchParams();
|
|
1588
|
+
body.set("token", token);
|
|
1589
|
+
if (iopts.tokenTypeHint) body.set("token_type_hint", iopts.tokenTypeHint);
|
|
1590
|
+
body.set("client_id", clientId);
|
|
1591
|
+
if (clientSecret) body.set("client_secret", clientSecret);
|
|
1592
|
+
var parsed = await _postForm(endpoint, body);
|
|
1593
|
+
// RFC 7662 §2.2 — `active` is the only required field; coerce
|
|
1594
|
+
// every other interpretation through it.
|
|
1595
|
+
if (typeof parsed.active !== "boolean") {
|
|
1596
|
+
throw new OAuthError("auth-oauth/bad-introspect-response",
|
|
1597
|
+
"introspectToken: response missing required `active` boolean");
|
|
1598
|
+
}
|
|
1599
|
+
return parsed;
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
/**
|
|
1603
|
+
* @primitive b.auth.oauth.registerClient
|
|
1604
|
+
* @signature b.auth.oauth.registerClient(metadata, opts?)
|
|
1605
|
+
* @since 0.8.77
|
|
1606
|
+
* @related b.auth.oauth.introspectToken
|
|
1607
|
+
*
|
|
1608
|
+
* RFC 7591 OAuth 2.0 Dynamic Client Registration. POSTs the
|
|
1609
|
+
* client metadata to the AS's `registration_endpoint` and returns
|
|
1610
|
+
* the issued `client_id` + (for confidential clients) `client_secret`
|
|
1611
|
+
* + `registration_access_token` + `registration_client_uri`.
|
|
1612
|
+
*
|
|
1613
|
+
* The framework refuses to register a client without an explicit
|
|
1614
|
+
* `redirect_uris` array — RFC 7591 §2 makes it OPTIONAL but every
|
|
1615
|
+
* security-sensitive deployment needs it; mis-registering with an
|
|
1616
|
+
* empty list lets any redirect_uri be assigned later by the AS.
|
|
1617
|
+
*
|
|
1618
|
+
* @opts
|
|
1619
|
+
* {
|
|
1620
|
+
* initialAccessToken?: string, // RFC 7591 §3 — bearer for the registration endpoint
|
|
1621
|
+
* }
|
|
1622
|
+
*
|
|
1623
|
+
* @example
|
|
1624
|
+
* var rv = await oauth.registerClient({
|
|
1625
|
+
* redirect_uris: ["https://rp.example/cb"],
|
|
1626
|
+
* token_endpoint_auth_method: "client_secret_basic",
|
|
1627
|
+
* grant_types: ["authorization_code", "refresh_token"],
|
|
1628
|
+
* response_types: ["code"],
|
|
1629
|
+
* client_name: "Example RP",
|
|
1630
|
+
* });
|
|
1631
|
+
* // rv.client_id / rv.client_secret / rv.registration_access_token
|
|
1632
|
+
*/
|
|
1633
|
+
async function registerClient(metadata, ropts) {
|
|
1634
|
+
ropts = ropts || {};
|
|
1635
|
+
if (!metadata || typeof metadata !== "object") {
|
|
1636
|
+
throw new OAuthError("auth-oauth/bad-register",
|
|
1637
|
+
"registerClient: metadata must be an object");
|
|
1638
|
+
}
|
|
1639
|
+
if (!Array.isArray(metadata.redirect_uris) || metadata.redirect_uris.length === 0) {
|
|
1640
|
+
throw new OAuthError("auth-oauth/register-no-redirect-uris",
|
|
1641
|
+
"registerClient: metadata.redirect_uris must be a non-empty array " +
|
|
1642
|
+
"(RFC 7591 §2 makes it optional, but registering without explicit URIs " +
|
|
1643
|
+
"creates an open-redirect surface)");
|
|
1644
|
+
}
|
|
1645
|
+
// RFC 7591 §2 / RFC 9700 §4.1.1 — every redirect_uri MUST be a
|
|
1646
|
+
// valid https:// URL (or http://localhost for dev). Pre-v0.9.x the
|
|
1647
|
+
// gate only enforced presence; an operator copying a config with
|
|
1648
|
+
// `http://app.example` or `javascript:` would ship that string to
|
|
1649
|
+
// the AS, which then permanently associates the open-redirect
|
|
1650
|
+
// surface with the registered client_id. Validate at registration
|
|
1651
|
+
// time so the bad URL never reaches the AS.
|
|
1652
|
+
for (var ri = 0; ri < metadata.redirect_uris.length; ri++) {
|
|
1653
|
+
_validateUrl(metadata.redirect_uris[ri], allowHttp,
|
|
1654
|
+
"metadata.redirect_uris[" + ri + "]");
|
|
1655
|
+
}
|
|
1656
|
+
var endpoint;
|
|
1657
|
+
try { endpoint = await _resolveEndpoint("registrationEndpoint"); }
|
|
1658
|
+
catch (_e) {
|
|
1659
|
+
throw new OAuthError("auth-oauth/no-registration-endpoint",
|
|
1660
|
+
"registerClient: AS does not advertise registration_endpoint");
|
|
1661
|
+
}
|
|
1662
|
+
var hc = httpClient;
|
|
1663
|
+
var headers = {
|
|
1664
|
+
"Content-Type": "application/json",
|
|
1665
|
+
"Accept": "application/json",
|
|
1666
|
+
};
|
|
1667
|
+
if (ropts.initialAccessToken) {
|
|
1668
|
+
headers["Authorization"] = "Bearer " + ropts.initialAccessToken;
|
|
1669
|
+
}
|
|
1670
|
+
var req = {
|
|
1671
|
+
url: endpoint,
|
|
1672
|
+
method: "POST",
|
|
1673
|
+
headers: headers,
|
|
1674
|
+
body: Buffer.from(safeJson.stringify(metadata), "utf8"),
|
|
1675
|
+
};
|
|
1676
|
+
if (allowHttp) req.allowedProtocols = safeUrl.ALLOW_HTTP_ALL;
|
|
1677
|
+
if (allowInternal !== null) req.allowInternal = allowInternal;
|
|
1678
|
+
Object.assign(req, httpClientOpts);
|
|
1679
|
+
var res = await hc.request(req);
|
|
1680
|
+
var text = res.body ? res.body.toString("utf8") : "";
|
|
1681
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
1682
|
+
throw new OAuthError("auth-oauth/register-failed-" + res.statusCode,
|
|
1683
|
+
"registerClient: " + res.statusCode + ": " + text.slice(0, 500));
|
|
1684
|
+
}
|
|
1685
|
+
var parsed;
|
|
1686
|
+
try { parsed = safeJson.parse(text, { maxBytes: OAUTH_MAX_RESPONSE_BYTES }); }
|
|
1687
|
+
catch (e) {
|
|
1688
|
+
throw new OAuthError("auth-oauth/bad-register-response",
|
|
1689
|
+
"registerClient: response not JSON: " + ((e && e.message) || String(e)));
|
|
1690
|
+
}
|
|
1691
|
+
if (typeof parsed.client_id !== "string" || parsed.client_id.length === 0) {
|
|
1692
|
+
throw new OAuthError("auth-oauth/register-no-client-id",
|
|
1693
|
+
"registerClient: response missing client_id");
|
|
1694
|
+
}
|
|
1695
|
+
return parsed;
|
|
1696
|
+
}
|
|
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
|
+
|
|
1823
|
+
/**
|
|
1824
|
+
* @primitive b.auth.oauth.deviceAuthorization
|
|
1825
|
+
* @signature b.auth.oauth.deviceAuthorization(opts?)
|
|
1826
|
+
* @since 0.8.77
|
|
1827
|
+
* @related b.auth.oauth.pollDeviceCode
|
|
1828
|
+
*
|
|
1829
|
+
* RFC 8628 OAuth 2.0 Device Authorization Grant. Initiates the
|
|
1830
|
+
* device-code flow by POSTing to the AS's device_authorization
|
|
1831
|
+
* endpoint. Returns `{ device_code, user_code, verification_uri,
|
|
1832
|
+
* verification_uri_complete?, expires_in, interval }`. The caller
|
|
1833
|
+
* displays `user_code` + `verification_uri` to the user, then polls
|
|
1834
|
+
* via `pollDeviceCode(device_code, { interval })`.
|
|
1835
|
+
*
|
|
1836
|
+
* @opts
|
|
1837
|
+
* {
|
|
1838
|
+
* scope?: string[], // override the client's default scope set
|
|
1839
|
+
* }
|
|
1840
|
+
*
|
|
1841
|
+
* @example
|
|
1842
|
+
* var auth = await oauth.deviceAuthorization();
|
|
1843
|
+
* console.log("Visit " + auth.verification_uri + " and enter " + auth.user_code);
|
|
1844
|
+
* var tokens = await oauth.pollDeviceCode(auth.device_code, { interval: auth.interval });
|
|
1845
|
+
*/
|
|
1846
|
+
async function deviceAuthorization(dopts) {
|
|
1847
|
+
dopts = dopts || {};
|
|
1848
|
+
var endpoint;
|
|
1849
|
+
try { endpoint = await _resolveEndpoint("deviceAuthorizationEndpoint"); }
|
|
1850
|
+
catch (_e) {
|
|
1851
|
+
throw new OAuthError("auth-oauth/no-device-endpoint",
|
|
1852
|
+
"deviceAuthorization: AS does not advertise device_authorization_endpoint");
|
|
1853
|
+
}
|
|
1854
|
+
var body = new URLSearchParams();
|
|
1855
|
+
body.set("client_id", clientId);
|
|
1856
|
+
if (clientSecret) body.set("client_secret", clientSecret);
|
|
1857
|
+
var scopes = Array.isArray(dopts.scope) ? dopts.scope : scope;
|
|
1858
|
+
if (scopes && scopes.length > 0) body.set("scope", scopes.join(" "));
|
|
1859
|
+
var parsed = await _postForm(endpoint, body);
|
|
1860
|
+
if (typeof parsed.device_code !== "string" ||
|
|
1861
|
+
typeof parsed.user_code !== "string" ||
|
|
1862
|
+
typeof parsed.verification_uri !== "string") {
|
|
1863
|
+
throw new OAuthError("auth-oauth/bad-device-response",
|
|
1864
|
+
"deviceAuthorization: response missing device_code / user_code / verification_uri");
|
|
1865
|
+
}
|
|
1866
|
+
return parsed;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
/**
|
|
1870
|
+
* @primitive b.auth.oauth.pollDeviceCode
|
|
1871
|
+
* @signature b.auth.oauth.pollDeviceCode(deviceCode, opts?)
|
|
1872
|
+
* @since 0.8.77
|
|
1873
|
+
* @related b.auth.oauth.deviceAuthorization
|
|
1874
|
+
*
|
|
1875
|
+
* Polls the token endpoint with grant_type=urn:ietf:params:oauth:
|
|
1876
|
+
* grant-type:device_code per RFC 8628 §3.4-§3.5. Honors the slow_down
|
|
1877
|
+
* error by extending the interval; returns the token response on
|
|
1878
|
+
* success; throws on expired_token / access_denied.
|
|
1879
|
+
*
|
|
1880
|
+
* @opts
|
|
1881
|
+
* {
|
|
1882
|
+
* interval?: number, // seconds — default from deviceAuthorization()
|
|
1883
|
+
* maxWaitMs?: number, // total budget (default 600s)
|
|
1884
|
+
* }
|
|
1885
|
+
*
|
|
1886
|
+
* @example
|
|
1887
|
+
* var auth = await oauth.deviceAuthorization();
|
|
1888
|
+
* var tokens = await oauth.pollDeviceCode(auth.device_code, { interval: auth.interval });
|
|
1889
|
+
*/
|
|
1890
|
+
async function pollDeviceCode(deviceCode, popts) {
|
|
1891
|
+
popts = popts || {};
|
|
1892
|
+
if (typeof deviceCode !== "string" || deviceCode.length === 0) {
|
|
1893
|
+
throw new OAuthError("auth-oauth/bad-device-code",
|
|
1894
|
+
"pollDeviceCode: deviceCode must be a non-empty string");
|
|
1895
|
+
}
|
|
1896
|
+
// RFC 8628 §3.4 — device_code is server-generated and opaque to the
|
|
1897
|
+
// client, but the polling loop POSTs it on every iteration. Without
|
|
1898
|
+
// a length cap an attacker who controls the device_code source
|
|
1899
|
+
// (e.g. a hostile AS in a CIBA-style misconfig) can amplify the
|
|
1900
|
+
// outbound HTTP body across N polls. The 8 KiB cap matches RFC 8628
|
|
1901
|
+
// §6.1's "alphanumeric with sufficient entropy" — even base64url
|
|
1902
|
+
// 512-bit codes fit comfortably.
|
|
1903
|
+
if (deviceCode.length > MAX_DEVICE_CODE_BYTES) {
|
|
1904
|
+
throw new OAuthError("auth-oauth/device-code-too-large",
|
|
1905
|
+
"pollDeviceCode: deviceCode exceeds " + MAX_DEVICE_CODE_BYTES + " bytes " +
|
|
1906
|
+
"(RFC 8628 §3.4 — opaque server-generated code, no legitimate need for length above the cap)");
|
|
1907
|
+
}
|
|
1908
|
+
var endpoint = await _resolveEndpoint("tokenEndpoint");
|
|
1909
|
+
// RFC 8628 §3.4 — "If no value is provided, clients MUST use 5 as
|
|
1910
|
+
// the default" and §3.5 directs clients to use slow_down responses
|
|
1911
|
+
// to extend the interval. A 1s floor violates the spec's "5
|
|
1912
|
+
// RECOMMENDED" and amplifies AS load. Enforce 5s minimum.
|
|
1913
|
+
var interval = Math.max(MIN_DEVICE_POLL_INTERVAL_SEC, popts.interval || MIN_DEVICE_POLL_INTERVAL_SEC);
|
|
1914
|
+
var deadline = Date.now() + (popts.maxWaitMs || C.TIME.minutes(10));
|
|
1915
|
+
while (Date.now() < deadline) {
|
|
1916
|
+
var body = new URLSearchParams();
|
|
1917
|
+
body.set("grant_type", "urn:ietf:params:oauth:grant-type:device_code");
|
|
1918
|
+
body.set("device_code", deviceCode);
|
|
1919
|
+
body.set("client_id", clientId);
|
|
1920
|
+
if (clientSecret) body.set("client_secret", clientSecret);
|
|
1921
|
+
var hc = httpClient;
|
|
1922
|
+
var req = {
|
|
1923
|
+
url: endpoint,
|
|
1924
|
+
method: "POST",
|
|
1925
|
+
headers: {
|
|
1926
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1927
|
+
"Accept": "application/json",
|
|
1928
|
+
},
|
|
1929
|
+
body: Buffer.from(body.toString(), "utf8"),
|
|
1930
|
+
};
|
|
1931
|
+
if (allowHttp) req.allowedProtocols = safeUrl.ALLOW_HTTP_ALL;
|
|
1932
|
+
if (allowInternal !== null) req.allowInternal = allowInternal;
|
|
1933
|
+
Object.assign(req, httpClientOpts);
|
|
1934
|
+
var res = await hc.request(req);
|
|
1935
|
+
var text = res.body ? res.body.toString("utf8") : "";
|
|
1936
|
+
var parsed;
|
|
1937
|
+
try { parsed = safeJson.parse(text, { maxBytes: OAUTH_MAX_RESPONSE_BYTES }); }
|
|
1938
|
+
catch (_e) { parsed = null; }
|
|
1939
|
+
if (res.statusCode >= 200 && res.statusCode < 300 && parsed && parsed.access_token) {
|
|
1940
|
+
return await _normalizeTokens(parsed, popts);
|
|
1941
|
+
}
|
|
1942
|
+
// RFC 8628 §3.5 — error codes that should keep polling.
|
|
1943
|
+
var err = parsed && parsed.error;
|
|
1944
|
+
if (err === "authorization_pending") {
|
|
1945
|
+
await safeAsync.sleep(C.TIME.seconds(interval));
|
|
1946
|
+
continue;
|
|
1947
|
+
}
|
|
1948
|
+
if (err === "slow_down") {
|
|
1949
|
+
interval += 5;
|
|
1950
|
+
await safeAsync.sleep(C.TIME.seconds(interval));
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
// Terminal errors.
|
|
1954
|
+
throw new OAuthError("auth-oauth/device-" + (err || "unknown"),
|
|
1955
|
+
"pollDeviceCode: " + (parsed && parsed.error_description ? parsed.error_description : text.slice(0, 200))); // allow:raw-byte-literal — 200-char error-snippet cap, not bytes
|
|
1956
|
+
}
|
|
1957
|
+
throw new OAuthError("auth-oauth/device-poll-timeout",
|
|
1958
|
+
"pollDeviceCode: exceeded maxWaitMs " + (popts.maxWaitMs || C.TIME.minutes(10)));
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
/**
|
|
1962
|
+
* @primitive b.auth.oauth.exchangeToken
|
|
1963
|
+
* @signature b.auth.oauth.exchangeToken(opts)
|
|
1964
|
+
* @since 0.8.77
|
|
1965
|
+
* @related b.auth.oauth.introspectToken
|
|
1966
|
+
*
|
|
1967
|
+
* RFC 8693 OAuth 2.0 Token Exchange. Trades a subject token (and
|
|
1968
|
+
* optionally an actor token for delegation chains) for a new
|
|
1969
|
+
* access token with different audience / scopes / authorization
|
|
1970
|
+
* context. Used by middleware tier services that need to call
|
|
1971
|
+
* downstream APIs on behalf of an upstream caller.
|
|
1972
|
+
*
|
|
1973
|
+
* @opts
|
|
1974
|
+
* {
|
|
1975
|
+
* subjectToken: string, // required
|
|
1976
|
+
* subjectTokenType: string, // required — RFC 8693 §3 URN
|
|
1977
|
+
* actorToken?: string, // delegation actor
|
|
1978
|
+
* actorTokenType?: string, // RFC 8693 §3 URN
|
|
1979
|
+
* audience?: string,
|
|
1980
|
+
* resource?: string,
|
|
1981
|
+
* scope?: string[],
|
|
1982
|
+
* requestedTokenType?: string, // default: access_token URN
|
|
1983
|
+
* }
|
|
1984
|
+
*
|
|
1985
|
+
* @example
|
|
1986
|
+
* var newTokens = await oauth.exchangeToken({
|
|
1987
|
+
* subjectToken: upstreamAccessToken,
|
|
1988
|
+
* subjectTokenType: "urn:ietf:params:oauth:token-type:access_token",
|
|
1989
|
+
* audience: "https://downstream.example.com",
|
|
1990
|
+
* });
|
|
1991
|
+
*/
|
|
1992
|
+
async function exchangeToken(xopts) {
|
|
1993
|
+
xopts = xopts || {};
|
|
1994
|
+
if (typeof xopts.subjectToken !== "string" || xopts.subjectToken.length === 0) {
|
|
1995
|
+
throw new OAuthError("auth-oauth/bad-exchange",
|
|
1996
|
+
"exchangeToken: opts.subjectToken required");
|
|
1997
|
+
}
|
|
1998
|
+
if (typeof xopts.subjectTokenType !== "string") {
|
|
1999
|
+
throw new OAuthError("auth-oauth/bad-exchange",
|
|
2000
|
+
"exchangeToken: opts.subjectTokenType required (RFC 8693 §3 URN)");
|
|
2001
|
+
}
|
|
2002
|
+
// RFC 8693 §3 — the token-type URN identifies the requested format
|
|
2003
|
+
// (access_token / refresh_token / id_token / saml2 / saml1 / jwt).
|
|
2004
|
+
// Pre-v0.9.x accepted any string, which let an attacker-controlled
|
|
2005
|
+
// service or operator-mistyped value reach the AS verbatim. Refuse
|
|
2006
|
+
// anything outside the RFC 8693 §3 list unless the operator
|
|
2007
|
+
// explicitly opts in via { allowCustomTokenType: true } with a
|
|
2008
|
+
// documented downstream contract.
|
|
2009
|
+
if (RFC_8693_TOKEN_TYPES.indexOf(xopts.subjectTokenType) === -1 &&
|
|
2010
|
+
xopts.allowCustomTokenType !== true) {
|
|
2011
|
+
throw new OAuthError("auth-oauth/bad-subject-token-type",
|
|
2012
|
+
"exchangeToken: subjectTokenType '" + xopts.subjectTokenType + "' not in RFC 8693 §3 " +
|
|
2013
|
+
"(allowed: " + RFC_8693_TOKEN_TYPES.join(", ") + "); pass `allowCustomTokenType: true` " +
|
|
2014
|
+
"to accept operator-defined URNs");
|
|
2015
|
+
}
|
|
2016
|
+
if (xopts.actorTokenType &&
|
|
2017
|
+
RFC_8693_TOKEN_TYPES.indexOf(xopts.actorTokenType) === -1 &&
|
|
2018
|
+
xopts.allowCustomTokenType !== true) {
|
|
2019
|
+
throw new OAuthError("auth-oauth/bad-actor-token-type",
|
|
2020
|
+
"exchangeToken: actorTokenType '" + xopts.actorTokenType + "' not in RFC 8693 §3");
|
|
2021
|
+
}
|
|
2022
|
+
var endpoint = await _resolveEndpoint("tokenEndpoint");
|
|
2023
|
+
var body = new URLSearchParams();
|
|
2024
|
+
body.set("grant_type", "urn:ietf:params:oauth:grant-type:token-exchange");
|
|
2025
|
+
body.set("subject_token", xopts.subjectToken);
|
|
2026
|
+
body.set("subject_token_type", xopts.subjectTokenType);
|
|
2027
|
+
body.set("client_id", clientId);
|
|
2028
|
+
if (clientSecret) body.set("client_secret", clientSecret);
|
|
2029
|
+
if (xopts.actorToken) body.set("actor_token", xopts.actorToken);
|
|
2030
|
+
if (xopts.actorTokenType) body.set("actor_token_type", xopts.actorTokenType);
|
|
2031
|
+
if (xopts.audience) body.set("audience", xopts.audience);
|
|
2032
|
+
if (xopts.resource) body.set("resource", xopts.resource);
|
|
2033
|
+
if (xopts.scope && xopts.scope.length > 0) {
|
|
2034
|
+
body.set("scope", xopts.scope.join(" "));
|
|
2035
|
+
}
|
|
2036
|
+
if (xopts.requestedTokenType) {
|
|
2037
|
+
body.set("requested_token_type", xopts.requestedTokenType);
|
|
2038
|
+
}
|
|
2039
|
+
var parsed = await _postForm(endpoint, body);
|
|
2040
|
+
return await _normalizeTokens(parsed, xopts);
|
|
2041
|
+
}
|
|
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
|
+
|
|
2097
|
+
return {
|
|
2098
|
+
authorizationUrl: authorizationUrl,
|
|
2099
|
+
exchangeCode: exchangeCode,
|
|
2100
|
+
refreshAccessToken: refreshAccessToken,
|
|
2101
|
+
fetchUserInfo: fetchUserInfo,
|
|
2102
|
+
revokeToken: revokeToken,
|
|
2103
|
+
verifyIdToken: verifyIdToken,
|
|
2104
|
+
discover: _discover,
|
|
2105
|
+
endSessionUrl: endSessionUrl,
|
|
2106
|
+
pushAuthorizationRequest: pushAuthorizationRequest,
|
|
2107
|
+
parseFrontchannelLogoutRequest: parseFrontchannelLogoutRequest,
|
|
2108
|
+
verifyBackchannelLogoutToken: verifyBackchannelLogoutToken,
|
|
2109
|
+
checkSessionIframeUrl: checkSessionIframeUrl,
|
|
2110
|
+
parseCallback: parseCallback,
|
|
2111
|
+
parseJarmResponse: parseJarmResponse,
|
|
2112
|
+
introspectToken: introspectToken,
|
|
2113
|
+
registerClient: registerClient,
|
|
2114
|
+
readClient: readClient,
|
|
2115
|
+
updateClient: updateClient,
|
|
2116
|
+
deleteClient: deleteClient,
|
|
2117
|
+
deviceAuthorization: deviceAuthorization,
|
|
2118
|
+
pollDeviceCode: pollDeviceCode,
|
|
2119
|
+
exchangeToken: exchangeToken,
|
|
2120
|
+
nativeSsoExchange: nativeSsoExchange,
|
|
2121
|
+
// Diagnostic / power-user surface
|
|
2122
|
+
issuer: issuer,
|
|
2123
|
+
clientId: clientId,
|
|
2124
|
+
redirectUri: redirectUri,
|
|
2125
|
+
scope: scope,
|
|
2126
|
+
isOidc: isOidc,
|
|
2127
|
+
};
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
module.exports = {
|
|
2131
|
+
create: create,
|
|
2132
|
+
PRESETS: PRESETS,
|
|
2133
|
+
OAuthError: OAuthError,
|
|
2134
|
+
DEFAULT_ACCEPTED_ALGS: DEFAULT_ACCEPTED_ALGS,
|
|
2135
|
+
// Internal helpers exposed for tests
|
|
2136
|
+
_generatePkce: _generatePkce,
|
|
2137
|
+
_generateRandomToken: _generateRandomToken,
|
|
2138
|
+
_b64urlEncode: _b64urlEncode,
|
|
2139
|
+
_b64urlDecode: _b64urlDecode,
|
|
2140
|
+
_verifyParamsForAlg: _verifyParamsForAlg,
|
|
2141
|
+
};
|