@blamejs/blamejs-shop 0.5.12 → 0.5.13

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.
Files changed (521) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/lib/asset-manifest.json +1 -1
  3. package/lib/vendor/MANIFEST.json +632 -416
  4. package/lib/vendor/blamejs/.clusterfuzzlite/Dockerfile +21 -10
  5. package/lib/vendor/blamejs/.clusterfuzzlite/build.sh +30 -9
  6. package/lib/vendor/blamejs/.github/dependabot.yml +8 -0
  7. package/lib/vendor/blamejs/.github/workflows/actions-lint.yml +3 -3
  8. package/lib/vendor/blamejs/.github/workflows/cflite_batch.yml +7 -2
  9. package/lib/vendor/blamejs/.github/workflows/cflite_pr.yml +7 -2
  10. package/lib/vendor/blamejs/.github/workflows/ci.yml +21 -21
  11. package/lib/vendor/blamejs/.github/workflows/codeql.yml +3 -3
  12. package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +4 -4
  13. package/lib/vendor/blamejs/.github/workflows/release-container.yml +7 -7
  14. package/lib/vendor/blamejs/.github/workflows/scorecard.yml +3 -3
  15. package/lib/vendor/blamejs/.github/workflows/sha-to-tag-verify.yml +1 -1
  16. package/lib/vendor/blamejs/.gitignore +2 -0
  17. package/lib/vendor/blamejs/CHANGELOG.md +122 -0
  18. package/lib/vendor/blamejs/NOTICE +26 -1
  19. package/lib/vendor/blamejs/SECURITY.md +1 -1
  20. package/lib/vendor/blamejs/api-snapshot.json +207 -5
  21. package/lib/vendor/blamejs/docker/keycloak/realm-blamejs-test.json +29 -0
  22. package/lib/vendor/blamejs/examples/wiki/DEPLOY.md +1 -0
  23. package/lib/vendor/blamejs/examples/wiki/lib/build-app.js +31 -0
  24. package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +109 -0
  25. package/lib/vendor/blamejs/examples/wiki/nginx.conf +106 -0
  26. package/lib/vendor/blamejs/examples/wiki/package-lock.json +1 -1
  27. package/lib/vendor/blamejs/examples/wiki/test/e2e.js +92 -0
  28. package/lib/vendor/blamejs/fuzz/asn1-der.fuzz.js +28 -0
  29. package/lib/vendor/blamejs/fuzz/cms-codec.fuzz.js +17 -0
  30. package/lib/vendor/blamejs/fuzz/link-header.fuzz.js +18 -0
  31. package/lib/vendor/blamejs/index.js +6 -0
  32. package/lib/vendor/blamejs/lib/a2a.js +4 -4
  33. package/lib/vendor/blamejs/lib/agent-idempotency.js +18 -2
  34. package/lib/vendor/blamejs/lib/agent-saga.js +36 -8
  35. package/lib/vendor/blamejs/lib/agent-snapshot.js +63 -6
  36. package/lib/vendor/blamejs/lib/app-shutdown.js +23 -20
  37. package/lib/vendor/blamejs/lib/archive-read.js +8 -0
  38. package/lib/vendor/blamejs/lib/archive-tar.js +12 -2
  39. package/lib/vendor/blamejs/lib/arg-parser.js +49 -5
  40. package/lib/vendor/blamejs/lib/asn1-der.js +12 -0
  41. package/lib/vendor/blamejs/lib/audit-sign.js +47 -1
  42. package/lib/vendor/blamejs/lib/audit.js +71 -8
  43. package/lib/vendor/blamejs/lib/auth/ciba.js +14 -2
  44. package/lib/vendor/blamejs/lib/auth/fido-mds3.js +58 -47
  45. package/lib/vendor/blamejs/lib/auth/jwt.js +34 -0
  46. package/lib/vendor/blamejs/lib/auth/oauth.js +90 -13
  47. package/lib/vendor/blamejs/lib/auth/oid4vci.js +20 -17
  48. package/lib/vendor/blamejs/lib/auth/openid-federation.js +214 -23
  49. package/lib/vendor/blamejs/lib/auth/saml.js +38 -20
  50. package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +10 -4
  51. package/lib/vendor/blamejs/lib/backup/bundle.js +13 -3
  52. package/lib/vendor/blamejs/lib/backup/index.js +16 -5
  53. package/lib/vendor/blamejs/lib/backup/manifest.js +18 -5
  54. package/lib/vendor/blamejs/lib/base32.js +21 -0
  55. package/lib/vendor/blamejs/lib/break-glass.js +19 -10
  56. package/lib/vendor/blamejs/lib/bundler.js +5 -1
  57. package/lib/vendor/blamejs/lib/cache-status.js +3 -7
  58. package/lib/vendor/blamejs/lib/calendar.js +21 -1
  59. package/lib/vendor/blamejs/lib/cbor.js +12 -1
  60. package/lib/vendor/blamejs/lib/cert.js +29 -11
  61. package/lib/vendor/blamejs/lib/cli.js +94 -24
  62. package/lib/vendor/blamejs/lib/cloud-events.js +2 -2
  63. package/lib/vendor/blamejs/lib/cluster-provider-db.js +13 -3
  64. package/lib/vendor/blamejs/lib/codepoint-class.js +85 -0
  65. package/lib/vendor/blamejs/lib/compliance-ai-act-prohibited.js +8 -4
  66. package/lib/vendor/blamejs/lib/compliance-ai-act-transparency.js +19 -4
  67. package/lib/vendor/blamejs/lib/compliance-ai-act.js +6 -8
  68. package/lib/vendor/blamejs/lib/content-credentials.js +1 -1
  69. package/lib/vendor/blamejs/lib/content-digest.js +2 -1
  70. package/lib/vendor/blamejs/lib/cookies.js +13 -3
  71. package/lib/vendor/blamejs/lib/cose.js +31 -16
  72. package/lib/vendor/blamejs/lib/crypto-field.js +32 -0
  73. package/lib/vendor/blamejs/lib/crypto-oprf.js +11 -2
  74. package/lib/vendor/blamejs/lib/crypto-xwing.js +22 -4
  75. package/lib/vendor/blamejs/lib/crypto.js +314 -19
  76. package/lib/vendor/blamejs/lib/csp.js +11 -3
  77. package/lib/vendor/blamejs/lib/daemon.js +270 -24
  78. package/lib/vendor/blamejs/lib/dark-patterns.js +5 -2
  79. package/lib/vendor/blamejs/lib/db-declare-view.js +2 -2
  80. package/lib/vendor/blamejs/lib/db-query.js +34 -7
  81. package/lib/vendor/blamejs/lib/db.js +18 -6
  82. package/lib/vendor/blamejs/lib/dbsc.js +17 -0
  83. package/lib/vendor/blamejs/lib/dev.js +21 -2
  84. package/lib/vendor/blamejs/lib/dsr.js +157 -102
  85. package/lib/vendor/blamejs/lib/external-db.js +41 -2
  86. package/lib/vendor/blamejs/lib/file-upload.js +25 -7
  87. package/lib/vendor/blamejs/lib/flag-providers.js +27 -15
  88. package/lib/vendor/blamejs/lib/forms.js +113 -27
  89. package/lib/vendor/blamejs/lib/gate-contract.js +23 -10
  90. package/lib/vendor/blamejs/lib/gdpr-ropa.js +8 -1
  91. package/lib/vendor/blamejs/lib/guard-agent-registry.js +1 -1
  92. package/lib/vendor/blamejs/lib/guard-archive.js +2 -2
  93. package/lib/vendor/blamejs/lib/guard-cidr.js +27 -1
  94. package/lib/vendor/blamejs/lib/guard-graphql.js +30 -11
  95. package/lib/vendor/blamejs/lib/guard-html.js +26 -53
  96. package/lib/vendor/blamejs/lib/guard-image.js +24 -5
  97. package/lib/vendor/blamejs/lib/guard-imap-command.js +10 -2
  98. package/lib/vendor/blamejs/lib/guard-jmap.js +19 -3
  99. package/lib/vendor/blamejs/lib/guard-jwt.js +18 -2
  100. package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +2 -2
  101. package/lib/vendor/blamejs/lib/guard-managesieve-command.js +10 -2
  102. package/lib/vendor/blamejs/lib/guard-markdown.js +11 -24
  103. package/lib/vendor/blamejs/lib/guard-pdf.js +18 -2
  104. package/lib/vendor/blamejs/lib/guard-pop3-command.js +10 -2
  105. package/lib/vendor/blamejs/lib/guard-regex.js +8 -5
  106. package/lib/vendor/blamejs/lib/guard-svg.js +31 -32
  107. package/lib/vendor/blamejs/lib/guard-time.js +1 -1
  108. package/lib/vendor/blamejs/lib/guard-uuid.js +2 -2
  109. package/lib/vendor/blamejs/lib/html-balance.js +3 -3
  110. package/lib/vendor/blamejs/lib/http-client-cache.js +82 -4
  111. package/lib/vendor/blamejs/lib/http-client-cookie-jar.js +16 -1
  112. package/lib/vendor/blamejs/lib/http-client.js +39 -7
  113. package/lib/vendor/blamejs/lib/http-message-signature.js +35 -1
  114. package/lib/vendor/blamejs/lib/i18n-messageformat.js +31 -6
  115. package/lib/vendor/blamejs/lib/i18n.js +35 -21
  116. package/lib/vendor/blamejs/lib/incident-report.js +8 -1
  117. package/lib/vendor/blamejs/lib/ip-utils.js +11 -1
  118. package/lib/vendor/blamejs/lib/json-path.js +23 -1
  119. package/lib/vendor/blamejs/lib/jwk.js +20 -3
  120. package/lib/vendor/blamejs/lib/link-header.js +2 -1
  121. package/lib/vendor/blamejs/lib/log-stream-otlp-grpc.js +5 -1
  122. package/lib/vendor/blamejs/lib/log-stream-otlp.js +5 -1
  123. package/lib/vendor/blamejs/lib/log-stream.js +8 -3
  124. package/lib/vendor/blamejs/lib/mail-auth.js +123 -22
  125. package/lib/vendor/blamejs/lib/mail-bimi.js +1 -1
  126. package/lib/vendor/blamejs/lib/mail-bounce.js +8 -0
  127. package/lib/vendor/blamejs/lib/mail-crypto-pgp.js +46 -10
  128. package/lib/vendor/blamejs/lib/mail-dav.js +22 -3
  129. package/lib/vendor/blamejs/lib/mail-dkim.js +96 -30
  130. package/lib/vendor/blamejs/lib/mail-helo.js +6 -1
  131. package/lib/vendor/blamejs/lib/mail-scan.js +23 -16
  132. package/lib/vendor/blamejs/lib/mail-send-deliver.js +34 -8
  133. package/lib/vendor/blamejs/lib/mail-server-imap.js +3 -4
  134. package/lib/vendor/blamejs/lib/mail-server-managesieve.js +7 -11
  135. package/lib/vendor/blamejs/lib/mail-server-mx.js +93 -7
  136. package/lib/vendor/blamejs/lib/mail-server-registry.js +8 -2
  137. package/lib/vendor/blamejs/lib/mail.js +15 -5
  138. package/lib/vendor/blamejs/lib/metrics.js +268 -63
  139. package/lib/vendor/blamejs/lib/middleware/age-gate.js +7 -1
  140. package/lib/vendor/blamejs/lib/middleware/asyncapi-serve.js +1 -1
  141. package/lib/vendor/blamejs/lib/middleware/body-parser.js +19 -4
  142. package/lib/vendor/blamejs/lib/middleware/bot-guard.js +5 -9
  143. package/lib/vendor/blamejs/lib/middleware/csp-report.js +40 -8
  144. package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +24 -10
  145. package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -5
  146. package/lib/vendor/blamejs/lib/middleware/index.js +6 -1
  147. package/lib/vendor/blamejs/lib/middleware/nel.js +1 -1
  148. package/lib/vendor/blamejs/lib/middleware/openapi-serve.js +1 -1
  149. package/lib/vendor/blamejs/lib/middleware/rate-limit.js +12 -1
  150. package/lib/vendor/blamejs/lib/middleware/require-bound-key.js +1 -1
  151. package/lib/vendor/blamejs/lib/middleware/scim-server.js +1 -1
  152. package/lib/vendor/blamejs/lib/middleware/tus-upload.js +6 -2
  153. package/lib/vendor/blamejs/lib/mtls-ca.js +18 -2
  154. package/lib/vendor/blamejs/lib/network-dns-resolver.js +11 -1
  155. package/lib/vendor/blamejs/lib/network-dns.js +21 -2
  156. package/lib/vendor/blamejs/lib/network-tls.js +69 -7
  157. package/lib/vendor/blamejs/lib/nonce-store.js +9 -1
  158. package/lib/vendor/blamejs/lib/object-store/gcs.js +7 -0
  159. package/lib/vendor/blamejs/lib/object-store/sigv4.js +31 -0
  160. package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +1 -1
  161. package/lib/vendor/blamejs/lib/outbox.js +1 -1
  162. package/lib/vendor/blamejs/lib/parsers/safe-ini.js +9 -1
  163. package/lib/vendor/blamejs/lib/parsers/safe-toml.js +10 -1
  164. package/lib/vendor/blamejs/lib/permissions.js +6 -3
  165. package/lib/vendor/blamejs/lib/pid-probe.js +55 -0
  166. package/lib/vendor/blamejs/lib/pqc-agent.js +8 -1
  167. package/lib/vendor/blamejs/lib/pqc-software.js +3 -3
  168. package/lib/vendor/blamejs/lib/pubsub.js +20 -9
  169. package/lib/vendor/blamejs/lib/queue-local.js +8 -1
  170. package/lib/vendor/blamejs/lib/queue-redis.js +5 -0
  171. package/lib/vendor/blamejs/lib/redact.js +54 -0
  172. package/lib/vendor/blamejs/lib/restore-bundle.js +10 -2
  173. package/lib/vendor/blamejs/lib/restore.js +19 -0
  174. package/lib/vendor/blamejs/lib/retention.js +12 -4
  175. package/lib/vendor/blamejs/lib/router.js +60 -6
  176. package/lib/vendor/blamejs/lib/safe-buffer.js +69 -0
  177. package/lib/vendor/blamejs/lib/safe-decompress.js +8 -2
  178. package/lib/vendor/blamejs/lib/safe-dns.js +25 -20
  179. package/lib/vendor/blamejs/lib/safe-ical.js +7 -3
  180. package/lib/vendor/blamejs/lib/safe-mime.js +12 -1
  181. package/lib/vendor/blamejs/lib/safe-object.js +80 -0
  182. package/lib/vendor/blamejs/lib/safe-path.js +52 -4
  183. package/lib/vendor/blamejs/lib/safe-redirect.js +10 -2
  184. package/lib/vendor/blamejs/lib/safe-schema.js +4 -1
  185. package/lib/vendor/blamejs/lib/safe-sieve.js +33 -1
  186. package/lib/vendor/blamejs/lib/safe-sql.js +88 -0
  187. package/lib/vendor/blamejs/lib/safe-vcard.js +7 -3
  188. package/lib/vendor/blamejs/lib/scheduler.js +8 -1
  189. package/lib/vendor/blamejs/lib/security-assert.js +6 -6
  190. package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +74 -27
  191. package/lib/vendor/blamejs/lib/self-update.js +517 -87
  192. package/lib/vendor/blamejs/lib/server-timing.js +2 -4
  193. package/lib/vendor/blamejs/lib/session.js +34 -12
  194. package/lib/vendor/blamejs/lib/sql.js +15 -1
  195. package/lib/vendor/blamejs/lib/ssrf-guard.js +52 -0
  196. package/lib/vendor/blamejs/lib/static.js +55 -13
  197. package/lib/vendor/blamejs/lib/storage.js +8 -2
  198. package/lib/vendor/blamejs/lib/subject.js +6 -1
  199. package/lib/vendor/blamejs/lib/tenant-quota.js +52 -13
  200. package/lib/vendor/blamejs/lib/tls-exporter.js +3 -3
  201. package/lib/vendor/blamejs/lib/tsa.js +34 -13
  202. package/lib/vendor/blamejs/lib/uri-template.js +8 -1
  203. package/lib/vendor/blamejs/lib/vault/index.js +13 -0
  204. package/lib/vendor/blamejs/lib/vc.js +44 -5
  205. package/lib/vendor/blamejs/lib/vendor/MANIFEST.json +26 -26
  206. package/lib/vendor/blamejs/lib/vendor/noble-ciphers.cjs +792 -1
  207. package/lib/vendor/blamejs/lib/vendor/noble-curves.cjs +3443 -1
  208. package/lib/vendor/blamejs/lib/vendor/noble-post-quantum.cjs +2737 -1
  209. package/lib/vendor/blamejs/lib/vendor/pki.cjs +39550 -13
  210. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.dat +55 -53
  211. package/lib/vendor/blamejs/lib/vendor/public-suffix-list.data.js +5551 -5554
  212. package/lib/vendor/blamejs/lib/vendor/simplewebauthn-server.cjs +22459 -32
  213. package/lib/vendor/blamejs/lib/watcher.js +89 -17
  214. package/lib/vendor/blamejs/lib/webhook-dispatcher.js +25 -3
  215. package/lib/vendor/blamejs/lib/webhook.js +12 -2
  216. package/lib/vendor/blamejs/lib/ws-client.js +32 -1
  217. package/lib/vendor/blamejs/lib/xml-c14n.js +29 -1
  218. package/lib/vendor/blamejs/oss-fuzz/projects/blamejs/Dockerfile +1 -1
  219. package/lib/vendor/blamejs/package-lock.json +2 -2
  220. package/lib/vendor/blamejs/package.json +1 -1
  221. package/lib/vendor/blamejs/release-notes/v0.16.x.json +1462 -0
  222. package/lib/vendor/blamejs/release-notes/v0.17.0.json +31 -0
  223. package/lib/vendor/blamejs/release-notes/v0.17.1.json +31 -0
  224. package/lib/vendor/blamejs/release-notes/v0.17.10.json +30 -0
  225. package/lib/vendor/blamejs/release-notes/v0.17.11.json +22 -0
  226. package/lib/vendor/blamejs/release-notes/v0.17.12.json +18 -0
  227. package/lib/vendor/blamejs/release-notes/v0.17.13.json +77 -0
  228. package/lib/vendor/blamejs/release-notes/v0.17.14.json +40 -0
  229. package/lib/vendor/blamejs/release-notes/v0.17.15.json +18 -0
  230. package/lib/vendor/blamejs/release-notes/v0.17.16.json +27 -0
  231. package/lib/vendor/blamejs/release-notes/v0.17.17.json +31 -0
  232. package/lib/vendor/blamejs/release-notes/v0.17.18.json +18 -0
  233. package/lib/vendor/blamejs/release-notes/v0.17.19.json +18 -0
  234. package/lib/vendor/blamejs/release-notes/v0.17.2.json +26 -0
  235. package/lib/vendor/blamejs/release-notes/v0.17.20.json +43 -0
  236. package/lib/vendor/blamejs/release-notes/v0.17.21.json +26 -0
  237. package/lib/vendor/blamejs/release-notes/v0.17.22.json +18 -0
  238. package/lib/vendor/blamejs/release-notes/v0.17.3.json +43 -0
  239. package/lib/vendor/blamejs/release-notes/v0.17.4.json +35 -0
  240. package/lib/vendor/blamejs/release-notes/v0.17.5.json +26 -0
  241. package/lib/vendor/blamejs/release-notes/v0.17.6.json +26 -0
  242. package/lib/vendor/blamejs/release-notes/v0.17.7.json +35 -0
  243. package/lib/vendor/blamejs/release-notes/v0.17.8.json +22 -0
  244. package/lib/vendor/blamejs/release-notes/v0.17.9.json +26 -0
  245. package/lib/vendor/blamejs/scripts/validate-source-comment-blocks.js +4 -0
  246. package/lib/vendor/blamejs/scripts/vendor-update.sh +293 -9
  247. package/lib/vendor/blamejs/socket.yml +94 -0
  248. package/lib/vendor/blamejs/test/00-primitives.js +605 -0
  249. package/lib/vendor/blamejs/test/helpers/drivers.js +12 -9
  250. package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +90 -1
  251. package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +122 -1
  252. package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +30 -0
  253. package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +29 -0
  254. package/lib/vendor/blamejs/test/integration/dsr-cluster-mysql.test.js +479 -0
  255. package/lib/vendor/blamejs/test/integration/dsr-cluster-pg.test.js +487 -0
  256. package/lib/vendor/blamejs/test/integration/federation-auth.test.js +313 -5
  257. package/lib/vendor/blamejs/test/integration/mail-dkim.test.js +80 -0
  258. package/lib/vendor/blamejs/test/integration/openid-federation-chain.test.js +275 -0
  259. package/lib/vendor/blamejs/test/integration/webhook-dispatcher-pg.test.js +96 -0
  260. package/lib/vendor/blamejs/test/layer-0-primitives/a2a-tasks.test.js +401 -0
  261. package/lib/vendor/blamejs/test/layer-0-primitives/acme.test.js +1649 -2
  262. package/lib/vendor/blamejs/test/layer-0-primitives/age-gate.test.js +33 -0
  263. package/lib/vendor/blamejs/test/layer-0-primitives/agent-idempotency.test.js +0 -0
  264. package/lib/vendor/blamejs/test/layer-0-primitives/agent-saga.test.js +60 -0
  265. package/lib/vendor/blamejs/test/layer-0-primitives/agent-snapshot.test.js +524 -0
  266. package/lib/vendor/blamejs/test/layer-0-primitives/api-encrypt.test.js +891 -0
  267. package/lib/vendor/blamejs/test/layer-0-primitives/app-shutdown.test.js +128 -1
  268. package/lib/vendor/blamejs/test/layer-0-primitives/archive-adapters.test.js +271 -0
  269. package/lib/vendor/blamejs/test/layer-0-primitives/archive-read.test.js +628 -1
  270. package/lib/vendor/blamejs/test/layer-0-primitives/archive-tar-read.test.js +0 -0
  271. package/lib/vendor/blamejs/test/layer-0-primitives/archive-wrap.test.js +227 -0
  272. package/lib/vendor/blamejs/test/layer-0-primitives/arg-parser.test.js +15 -0
  273. package/lib/vendor/blamejs/test/layer-0-primitives/asn1-der.test.js +73 -0
  274. package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file.test.js +114 -0
  275. package/lib/vendor/blamejs/test/layer-0-primitives/audit-sign-anchor.test.js +141 -0
  276. package/lib/vendor/blamejs/test/layer-0-primitives/audit-tools.test.js +624 -0
  277. package/lib/vendor/blamejs/test/layer-0-primitives/audit.test.js +769 -0
  278. package/lib/vendor/blamejs/test/layer-0-primitives/auth-jwt-defenses.test.js +436 -0
  279. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth.test.js +1697 -0
  280. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oid4vp.test.js +495 -0
  281. package/lib/vendor/blamejs/test/layer-0-primitives/auth-password.test.js +500 -0
  282. package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml.test.js +1809 -0
  283. package/lib/vendor/blamejs/test/layer-0-primitives/auth-status-list.test.js +243 -1
  284. package/lib/vendor/blamejs/test/layer-0-primitives/backup-index.test.js +2226 -0
  285. package/lib/vendor/blamejs/test/layer-0-primitives/backup-manifest-signature.test.js +183 -0
  286. package/lib/vendor/blamejs/test/layer-0-primitives/base32.test.js +105 -0
  287. package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +693 -0
  288. package/lib/vendor/blamejs/test/layer-0-primitives/cache.test.js +603 -0
  289. package/lib/vendor/blamejs/test/layer-0-primitives/calendar.test.js +557 -0
  290. package/lib/vendor/blamejs/test/layer-0-primitives/case-insensitive-security-token-guard.test.js +214 -0
  291. package/lib/vendor/blamejs/test/layer-0-primitives/cbor.test.js +25 -0
  292. package/lib/vendor/blamejs/test/layer-0-primitives/cert.test.js +843 -6
  293. package/lib/vendor/blamejs/test/layer-0-primitives/ciba-authreqid-binding.test.js +77 -2
  294. package/lib/vendor/blamejs/test/layer-0-primitives/ciba.test.js +866 -0
  295. package/lib/vendor/blamejs/test/layer-0-primitives/clear-site-data.test.js +18 -0
  296. package/lib/vendor/blamejs/test/layer-0-primitives/cli-erase.test.js +19 -0
  297. package/lib/vendor/blamejs/test/layer-0-primitives/cli.test.js +2790 -0
  298. package/lib/vendor/blamejs/test/layer-0-primitives/cluster-lease-renewal-ttl.test.js +114 -0
  299. package/lib/vendor/blamejs/test/layer-0-primitives/cluster.test.js +1032 -0
  300. package/lib/vendor/blamejs/test/layer-0-primitives/cms-codec.test.js +575 -0
  301. package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +739 -1
  302. package/lib/vendor/blamejs/test/layer-0-primitives/codepoint-class.test.js +8 -0
  303. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-ai-act.test.js +194 -0
  304. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-lifecycle-proto-key.test.js +104 -0
  305. package/lib/vendor/blamejs/test/layer-0-primitives/compliance-sanctions.test.js +254 -0
  306. package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials.test.js +613 -3
  307. package/lib/vendor/blamejs/test/layer-0-primitives/cookies.test.js +140 -0
  308. package/lib/vendor/blamejs/test/layer-0-primitives/cose.test.js +407 -0
  309. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-adversarial.test.js +352 -0
  310. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +32 -0
  311. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field.test.js +575 -0
  312. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-files-parallel.test.js +10 -0
  313. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-hash-stream.test.js +24 -0
  314. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-oprf.test.js +17 -0
  315. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-spki-pin.test.js +183 -0
  316. package/lib/vendor/blamejs/test/layer-0-primitives/crypto-xwing.test.js +48 -0
  317. package/lib/vendor/blamejs/test/layer-0-primitives/crypto.test.js +345 -0
  318. package/lib/vendor/blamejs/test/layer-0-primitives/csp-report.test.js +58 -0
  319. package/lib/vendor/blamejs/test/layer-0-primitives/csv.test.js +48 -0
  320. package/lib/vendor/blamejs/test/layer-0-primitives/daemon.test.js +459 -1
  321. package/lib/vendor/blamejs/test/layer-0-primitives/dark-patterns.test.js +19 -0
  322. package/lib/vendor/blamejs/test/layer-0-primitives/db-query.test.js +1019 -0
  323. package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +77 -0
  324. package/lib/vendor/blamejs/test/layer-0-primitives/db.test.js +1621 -0
  325. package/lib/vendor/blamejs/test/layer-0-primitives/ddl-change-control.test.js +462 -3
  326. package/lib/vendor/blamejs/test/layer-0-primitives/dnssec.test.js +323 -0
  327. package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-verify.test.js +562 -0
  328. package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +901 -1
  329. package/lib/vendor/blamejs/test/layer-0-primitives/dual-control.test.js +249 -0
  330. package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +561 -0
  331. package/lib/vendor/blamejs/test/layer-0-primitives/external-db.test.js +1214 -0
  332. package/lib/vendor/blamejs/test/layer-0-primitives/fal.test.js +21 -0
  333. package/lib/vendor/blamejs/test/layer-0-primitives/fedcm-dbsc.test.js +46 -20
  334. package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +147 -3
  335. package/lib/vendor/blamejs/test/layer-0-primitives/fido-mds3.test.js +907 -38
  336. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +29 -0
  337. package/lib/vendor/blamejs/test/layer-0-primitives/file-upload.test.js +1135 -0
  338. package/lib/vendor/blamejs/test/layer-0-primitives/flag.test.js +30 -0
  339. package/lib/vendor/blamejs/test/layer-0-primitives/forms.test.js +747 -0
  340. package/lib/vendor/blamejs/test/layer-0-primitives/gate-contract.test.js +1855 -0
  341. package/lib/vendor/blamejs/test/layer-0-primitives/gdpr-ropa.test.js +30 -0
  342. package/lib/vendor/blamejs/test/layer-0-primitives/guard-archive.test.js +86 -0
  343. package/lib/vendor/blamejs/test/layer-0-primitives/guard-auth.test.js +117 -0
  344. package/lib/vendor/blamejs/test/layer-0-primitives/guard-cidr.test.js +151 -0
  345. package/lib/vendor/blamejs/test/layer-0-primitives/guard-domain.test.js +422 -0
  346. package/lib/vendor/blamejs/test/layer-0-primitives/guard-filename.test.js +424 -0
  347. package/lib/vendor/blamejs/test/layer-0-primitives/{guard-gate-disposition-coverage.test.js → guard-gate-disposition.test.js} +1 -1
  348. package/lib/vendor/blamejs/test/layer-0-primitives/guard-graphql.test.js +340 -0
  349. package/lib/vendor/blamejs/test/layer-0-primitives/guard-html.test.js +29 -0
  350. package/lib/vendor/blamejs/test/layer-0-primitives/guard-idempotency-key.test.js +28 -0
  351. package/lib/vendor/blamejs/test/layer-0-primitives/guard-image.test.js +519 -45
  352. package/lib/vendor/blamejs/test/layer-0-primitives/guard-imap-command.test.js +0 -0
  353. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jmap.test.js +51 -0
  354. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jsonpath.test.js +66 -0
  355. package/lib/vendor/blamejs/test/layer-0-primitives/guard-jwt.test.js +436 -0
  356. package/lib/vendor/blamejs/test/layer-0-primitives/guard-managesieve-command.test.js +25 -0
  357. package/lib/vendor/blamejs/test/layer-0-primitives/guard-markdown.test.js +16 -0
  358. package/lib/vendor/blamejs/test/layer-0-primitives/guard-mime.test.js +86 -0
  359. package/lib/vendor/blamejs/test/layer-0-primitives/guard-oauth.test.js +140 -0
  360. package/lib/vendor/blamejs/test/layer-0-primitives/guard-pdf.test.js +80 -0
  361. package/lib/vendor/blamejs/test/layer-0-primitives/guard-pop3-command.test.js +27 -0
  362. package/lib/vendor/blamejs/test/layer-0-primitives/guard-regex.test.js +99 -0
  363. package/lib/vendor/blamejs/test/layer-0-primitives/guard-shell.test.js +76 -0
  364. package/lib/vendor/blamejs/test/layer-0-primitives/{guard-sql-coverage.test.js → guard-sql.test.js} +3 -3
  365. package/lib/vendor/blamejs/test/layer-0-primitives/guard-svg.test.js +524 -0
  366. package/lib/vendor/blamejs/test/layer-0-primitives/guard-template.test.js +71 -0
  367. package/lib/vendor/blamejs/test/layer-0-primitives/guard-time.test.js +92 -0
  368. package/lib/vendor/blamejs/test/layer-0-primitives/guard-uuid.test.js +86 -0
  369. package/lib/vendor/blamejs/test/layer-0-primitives/html-balance.test.js +38 -0
  370. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache-authorization.test.js +347 -0
  371. package/lib/vendor/blamejs/test/layer-0-primitives/http-client-cache.test.js +908 -0
  372. package/lib/vendor/blamejs/test/layer-0-primitives/http-client.test.js +2874 -0
  373. package/lib/vendor/blamejs/test/layer-0-primitives/http-message-signature.test.js +643 -0
  374. package/lib/vendor/blamejs/test/layer-0-primitives/i18n-messageformat.test.js +38 -0
  375. package/lib/vendor/blamejs/test/layer-0-primitives/i18n.test.js +1153 -1
  376. package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +494 -0
  377. package/lib/vendor/blamejs/test/layer-0-primitives/ip-utils.test.js +30 -0
  378. package/lib/vendor/blamejs/test/layer-0-primitives/jsdoc-example-execution.test.js +177 -0
  379. package/lib/vendor/blamejs/test/layer-0-primitives/json-path.test.js +103 -0
  380. package/lib/vendor/blamejs/test/layer-0-primitives/json-schema.test.js +214 -0
  381. package/lib/vendor/blamejs/test/layer-0-primitives/jwk.test.js +41 -0
  382. package/lib/vendor/blamejs/test/layer-0-primitives/jwt-external.test.js +276 -0
  383. package/lib/vendor/blamejs/test/layer-0-primitives/keychain.test.js +0 -0
  384. package/lib/vendor/blamejs/test/layer-0-primitives/log-stream.test.js +241 -0
  385. package/lib/vendor/blamejs/test/layer-0-primitives/log.test.js +106 -0
  386. package/lib/vendor/blamejs/test/layer-0-primitives/mail-agent.test.js +441 -0
  387. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +2136 -4
  388. package/lib/vendor/blamejs/test/layer-0-primitives/mail-bimi.test.js +759 -12
  389. package/lib/vendor/blamejs/test/layer-0-primitives/mail-bounce.test.js +811 -1
  390. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp-experimental.test.js +401 -3
  391. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-pgp.test.js +751 -8
  392. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto-smime.test.js +864 -0
  393. package/lib/vendor/blamejs/test/layer-0-primitives/mail-crypto.test.js +110 -0
  394. package/lib/vendor/blamejs/test/layer-0-primitives/mail-dav.test.js +830 -1
  395. package/lib/vendor/blamejs/test/layer-0-primitives/mail-deploy.test.js +635 -86
  396. package/lib/vendor/blamejs/test/layer-0-primitives/mail-dkim.test.js +818 -0
  397. package/lib/vendor/blamejs/test/layer-0-primitives/mail-helo.test.js +27 -0
  398. package/lib/vendor/blamejs/test/layer-0-primitives/mail-scan.test.js +199 -0
  399. package/lib/vendor/blamejs/test/layer-0-primitives/mail-send-deliver.test.js +686 -2
  400. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-imap.test.js +778 -0
  401. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +1438 -0
  402. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-managesieve.test.js +449 -6
  403. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +773 -2
  404. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-pop3.test.js +623 -3
  405. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-rate-limit.test.js +55 -0
  406. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-registry.test.js +13 -0
  407. package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-submission.test.js +877 -1
  408. package/lib/vendor/blamejs/test/layer-0-primitives/mail-sieve.test.js +326 -0
  409. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store-fts.test.js +55 -0
  410. package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +242 -0
  411. package/lib/vendor/blamejs/test/layer-0-primitives/mail.test.js +1596 -2
  412. package/lib/vendor/blamejs/test/layer-0-primitives/mcp.test.js +440 -4
  413. package/lib/vendor/blamejs/test/layer-0-primitives/metrics-snapshot.test.js +89 -0
  414. package/lib/vendor/blamejs/test/layer-0-primitives/metrics.test.js +1230 -0
  415. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-assetlinks.test.js +113 -0
  416. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-body-parser.test.js +1169 -0
  417. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-bot-disclose.test.js +123 -0
  418. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-cookies.test.js +108 -0
  419. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-gpc.test.js +121 -0
  420. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-headers.test.js +163 -0
  421. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-host-allowlist.test.js +154 -0
  422. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-require-content-type.test.js +127 -0
  423. package/lib/vendor/blamejs/test/layer-0-primitives/{middleware-tus-upload-coverage.test.js → middleware-tus-upload.test.js} +45 -0
  424. package/lib/vendor/blamejs/test/layer-0-primitives/middleware-web-app-manifest.test.js +132 -0
  425. package/lib/vendor/blamejs/test/layer-0-primitives/mtls-ca-crl-fingerprint-only.test.js +92 -0
  426. package/lib/vendor/blamejs/test/layer-0-primitives/network-dns-resolver.test.js +65 -0
  427. package/lib/vendor/blamejs/test/layer-0-primitives/network-dns.test.js +1727 -1
  428. package/lib/vendor/blamejs/test/layer-0-primitives/network-nts.test.js +699 -0
  429. package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy.test.js +1181 -0
  430. package/lib/vendor/blamejs/test/layer-0-primitives/network-tls.test.js +1876 -3
  431. package/lib/vendor/blamejs/test/layer-0-primitives/network.test.js +32 -0
  432. package/lib/vendor/blamejs/test/layer-0-primitives/nonce-store-enforce-replay-nonboolean.test.js +81 -0
  433. package/lib/vendor/blamejs/test/layer-0-primitives/ntp-check.test.js +122 -0
  434. package/lib/vendor/blamejs/test/layer-0-primitives/observability-tracing.test.js +378 -0
  435. package/lib/vendor/blamejs/test/layer-0-primitives/observability.test.js +77 -0
  436. package/lib/vendor/blamejs/test/layer-0-primitives/oid4vci.test.js +1016 -0
  437. package/lib/vendor/blamejs/test/layer-0-primitives/openid-federation.test.js +946 -0
  438. package/lib/vendor/blamejs/test/layer-0-primitives/outbox.test.js +818 -0
  439. package/lib/vendor/blamejs/test/layer-0-primitives/own-property-membership-guards.test.js +109 -0
  440. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-safe-toml.test.js +543 -0
  441. package/lib/vendor/blamejs/test/layer-0-primitives/{parsers-safe-yaml-coverage.test.js → parsers-safe-yaml.test.js} +2 -2
  442. package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +73 -0
  443. package/lib/vendor/blamejs/test/layer-0-primitives/passkey.test.js +649 -0
  444. package/lib/vendor/blamejs/test/layer-0-primitives/permissions.test.js +524 -0
  445. package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +22 -0
  446. package/lib/vendor/blamejs/test/layer-0-primitives/pubsub.test.js +295 -0
  447. package/lib/vendor/blamejs/test/layer-0-primitives/queue-flow-repeat.test.js +36 -0
  448. package/lib/vendor/blamejs/test/layer-0-primitives/queue.test.js +83 -0
  449. package/lib/vendor/blamejs/test/layer-0-primitives/rate-limit-xff-spoofing.test.js +34 -0
  450. package/lib/vendor/blamejs/test/layer-0-primitives/redact.test.js +1031 -0
  451. package/lib/vendor/blamejs/test/layer-0-primitives/request-helpers.test.js +94 -0
  452. package/lib/vendor/blamejs/test/layer-0-primitives/require-bound-key.test.js +401 -0
  453. package/lib/vendor/blamejs/test/layer-0-primitives/restore-empty-manifest-wipe.test.js +154 -0
  454. package/lib/vendor/blamejs/test/layer-0-primitives/retention.test.js +600 -0
  455. package/lib/vendor/blamejs/test/layer-0-primitives/router-host-path-injection.test.js +231 -0
  456. package/lib/vendor/blamejs/test/layer-0-primitives/router-tls0rtt.test.js +21 -0
  457. package/lib/vendor/blamejs/test/layer-0-primitives/router.test.js +1277 -0
  458. package/lib/vendor/blamejs/test/layer-0-primitives/safe-async.test.js +133 -0
  459. package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer-linear-scans.test.js +14 -0
  460. package/lib/vendor/blamejs/test/layer-0-primitives/safe-buffer.test.js +80 -0
  461. package/lib/vendor/blamejs/test/layer-0-primitives/safe-decompress.test.js +28 -0
  462. package/lib/vendor/blamejs/test/layer-0-primitives/safe-dns.test.js +245 -0
  463. package/lib/vendor/blamejs/test/layer-0-primitives/safe-ical.test.js +32 -0
  464. package/lib/vendor/blamejs/test/layer-0-primitives/safe-ini.test.js +376 -0
  465. package/lib/vendor/blamejs/test/layer-0-primitives/safe-json.test.js +175 -0
  466. package/lib/vendor/blamejs/test/layer-0-primitives/safe-mime.test.js +518 -0
  467. package/lib/vendor/blamejs/test/layer-0-primitives/safe-object.test.js +62 -0
  468. package/lib/vendor/blamejs/test/layer-0-primitives/safe-path.test.js +30 -0
  469. package/lib/vendor/blamejs/test/layer-0-primitives/safe-schema.test.js +104 -0
  470. package/lib/vendor/blamejs/test/layer-0-primitives/safe-sql.test.js +142 -0
  471. package/lib/vendor/blamejs/test/layer-0-primitives/safe-url.test.js +112 -0
  472. package/lib/vendor/blamejs/test/layer-0-primitives/safe-vcard.test.js +26 -0
  473. package/lib/vendor/blamejs/test/layer-0-primitives/scheduler.test.js +157 -0
  474. package/lib/vendor/blamejs/test/layer-0-primitives/scim-server.test.js +396 -0
  475. package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +930 -0
  476. package/lib/vendor/blamejs/test/layer-0-primitives/security-assert.test.js +755 -82
  477. package/lib/vendor/blamejs/test/layer-0-primitives/self-update.test.js +1053 -0
  478. package/lib/vendor/blamejs/test/layer-0-primitives/session-device-binding.test.js +200 -0
  479. package/lib/vendor/blamejs/test/layer-0-primitives/session-strict-binding-missing-failclosed.test.js +110 -0
  480. package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +636 -0
  481. package/lib/vendor/blamejs/test/layer-0-primitives/sql-offset-without-limit.test.js +94 -0
  482. package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +1190 -0
  483. package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +21 -0
  484. package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +850 -0
  485. package/lib/vendor/blamejs/test/layer-0-primitives/storage-presigned-url.test.js +146 -0
  486. package/lib/vendor/blamejs/test/layer-0-primitives/storage.test.js +621 -0
  487. package/lib/vendor/blamejs/test/layer-0-primitives/subject.test.js +47 -0
  488. package/lib/vendor/blamejs/test/layer-0-primitives/template.test.js +582 -0
  489. package/lib/vendor/blamejs/test/layer-0-primitives/tenant-quota.test.js +587 -25
  490. package/lib/vendor/blamejs/test/layer-0-primitives/time.test.js +11 -0
  491. package/lib/vendor/blamejs/test/layer-0-primitives/tsa.test.js +524 -63
  492. package/lib/vendor/blamejs/test/layer-0-primitives/uri-template.test.js +27 -0
  493. package/lib/vendor/blamejs/test/layer-0-primitives/vault-rotate.test.js +866 -0
  494. package/lib/vendor/blamejs/test/layer-0-primitives/vault.test.js +74 -0
  495. package/lib/vendor/blamejs/test/layer-0-primitives/vc.test.js +215 -0
  496. package/lib/vendor/blamejs/test/layer-0-primitives/watcher.test.js +550 -0
  497. package/lib/vendor/blamejs/test/layer-0-primitives/webhook-dispatcher.test.js +388 -0
  498. package/lib/vendor/blamejs/test/layer-0-primitives/webhook.test.js +262 -0
  499. package/lib/vendor/blamejs/test/layer-0-primitives/websocket.test.js +1294 -0
  500. package/lib/vendor/blamejs/test/layer-0-primitives/ws-client.test.js +629 -0
  501. package/lib/vendor/blamejs/test/layer-0-primitives/xml-c14n-attr-normalization.test.js +164 -0
  502. package/lib/vendor/blamejs/test/smoke.js +2 -0
  503. package/package.json +1 -1
  504. package/lib/vendor/blamejs/release-notes/v0.16.0.json +0 -44
  505. package/lib/vendor/blamejs/release-notes/v0.16.1.json +0 -36
  506. package/lib/vendor/blamejs/release-notes/v0.16.2.json +0 -71
  507. package/lib/vendor/blamejs/release-notes/v0.16.3.json +0 -71
  508. package/lib/vendor/blamejs/test/layer-0-primitives/acme-coverage.test.js +0 -441
  509. package/lib/vendor/blamejs/test/layer-0-primitives/auth-oauth-coverage.test.js +0 -482
  510. package/lib/vendor/blamejs/test/layer-0-primitives/auth-saml-coverage.test.js +0 -671
  511. package/lib/vendor/blamejs/test/layer-0-primitives/backup-index-coverage.test.js +0 -690
  512. package/lib/vendor/blamejs/test/layer-0-primitives/cli-coverage.test.js +0 -238
  513. package/lib/vendor/blamejs/test/layer-0-primitives/content-credentials-coverage.test.js +0 -319
  514. package/lib/vendor/blamejs/test/layer-0-primitives/keychain-coverage.test.js +0 -0
  515. package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth-coverage.test.js +0 -437
  516. package/lib/vendor/blamejs/test/layer-0-primitives/mcp-coverage.test.js +0 -443
  517. package/lib/vendor/blamejs/test/layer-0-primitives/metrics-coverage.test.js +0 -395
  518. package/lib/vendor/blamejs/test/layer-0-primitives/network-smtp-policy-coverage.test.js +0 -565
  519. package/lib/vendor/blamejs/test/layer-0-primitives/router-coverage.test.js +0 -592
  520. package/lib/vendor/blamejs/test/layer-0-primitives/sql-coverage.test.js +0 -422
  521. package/lib/vendor/blamejs/test/layer-0-primitives/test-coverage.test.js +0 -573
@@ -0,0 +1,1697 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * b.auth.oauth — config-time validation, offline builders, and loopback
6
+ * IdP round-trips.
7
+ *
8
+ * Complements oauth-callback.test.js (which exercises the network-driven
9
+ * discovery / PAR / attestation happy paths) by pinning the input-
10
+ * validation, option-default, and adversarial-shape branches that resolve
11
+ * WITHOUT a live IdP: create() gating, _validateUrl scheme/localhost
12
+ * rules, the pre-JWKS verifyIdToken refusals (JWE / crit / alg-allowlist /
13
+ * malformed), the per-method required-argument guards, the offline URL
14
+ * builders (authorizationUrl / endSessionUrl), parseFrontchannelLogoutRequest,
15
+ * the RFC 9396 granted-details cross-check edge cases, and the
16
+ * attestation builder/verifier validation surface.
17
+ *
18
+ * Every assertion drives the public b.auth.oauth surface. The offline
19
+ * sections never reach the network (each throws before endpoint
20
+ * resolution, builds a URL from a static endpoint, or is a pure helper);
21
+ * the loopback sections drive the error, wrong-state, backend-failure and
22
+ * adversarial branches that only resolve with a live IdP round-trip,
23
+ * against a 127.0.0.1 stand-in AS/JWKS server (allowHttp + allowInternal,
24
+ * never a real endpoint, never rejectUnauthorized:false) serving
25
+ * locally-signed ES256 id_tokens.
26
+ */
27
+
28
+ var crypto = require("node:crypto");
29
+ var http = require("node:http");
30
+ var helpers = require("../helpers");
31
+ var b = helpers.b;
32
+ var check = helpers.check;
33
+
34
+ var X = b.auth.oauth;
35
+
36
+ // Reject a SYNCHRONOUS call with a specific OAuthError code.
37
+ function rejects(label, fn, code) {
38
+ var threw = null;
39
+ try { fn(); } catch (e) { threw = e; }
40
+ check(label, threw !== null && threw.code === code);
41
+ }
42
+
43
+ // Reject an ASYNC call (Promise) with a specific OAuthError code.
44
+ async function arejects(label, fn, code) {
45
+ var threw = null;
46
+ try { await fn(); } catch (e) { threw = e; }
47
+ check(label, threw !== null && threw.code === code);
48
+ }
49
+
50
+ // A static-endpoint OIDC client: every endpoint is configured, so no code
51
+ // path here forces a discovery fetch.
52
+ function _staticOidcClient(extra) {
53
+ var opts = {
54
+ issuer: "https://idp.example",
55
+ clientId: "rp-cov",
56
+ clientSecret: "cov-secret",
57
+ redirectUri: "https://rp.example/cb",
58
+ isOidc: true,
59
+ authorizationEndpoint: "https://idp.example/auth",
60
+ tokenEndpoint: "https://idp.example/token",
61
+ userinfoEndpoint: "https://idp.example/userinfo",
62
+ revocationEndpoint: "https://idp.example/revoke",
63
+ endSessionEndpoint: "https://idp.example/logout",
64
+ jwksUri: "https://idp.example/jwks",
65
+ };
66
+ if (extra) { var k = Object.keys(extra); for (var i = 0; i < k.length; i++) opts[k[i]] = extra[k[i]]; }
67
+ return X.create(opts);
68
+ }
69
+
70
+ function _b64url(obj) {
71
+ return Buffer.from(JSON.stringify(obj), "utf8").toString("base64url");
72
+ }
73
+
74
+ // ================= loopback IdP round-trip scenarios =====================
75
+
76
+ function throws(label, fn, code) {
77
+ var e = null;
78
+ try { fn(); } catch (x) { e = x; }
79
+ check(label, e !== null && e.code === code);
80
+ }
81
+
82
+ async function athrows(label, fn, code) {
83
+ var e = null;
84
+ try { await fn(); } catch (x) { e = x; }
85
+ check(label, e !== null && e.code === code);
86
+ }
87
+
88
+ async function aresolves(label, fn) {
89
+ var ok = false;
90
+ var val = null;
91
+ try { val = await fn(); ok = true; } catch (_e) { ok = false; }
92
+ check(label, ok);
93
+ return val;
94
+ }
95
+
96
+ // ---- local signing key + JWKS material ----
97
+
98
+ var KID = "kid-cov2";
99
+ var KP = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
100
+ var PRIV = KP.privateKey;
101
+ var PUBJWK = Object.assign(KP.publicKey.export({ format: "jwk" }), { kid: KID, use: "sig", alg: "ES256" });
102
+
103
+ function _b64urlJson(o) { return Buffer.from(JSON.stringify(o), "utf8").toString("base64url"); }
104
+
105
+ function signToken(header, payload) {
106
+ var params = X._verifyParamsForAlg(header.alg);
107
+ var input = _b64urlJson(header) + "." + _b64urlJson(payload);
108
+ var o = { key: PRIV };
109
+ if (params.padding !== undefined) o.padding = params.padding;
110
+ if (params.saltLength !== undefined) o.saltLength = params.saltLength;
111
+ if (params.dsaEncoding !== undefined) o.dsaEncoding = params.dsaEncoding;
112
+ var sig = crypto.sign(params.hash, Buffer.from(input, "ascii"), o);
113
+ return input + "." + sig.toString("base64url");
114
+ }
115
+
116
+ // Build an ES256 id_token for `clientId`. `claims`/`hdr` override the
117
+ // defaults; a claim/header set to undefined is dropped from the JSON.
118
+ function mkToken(clientId, claims, hdr) {
119
+ var now = Math.floor(Date.now() / 1000);
120
+ var payload = { iss: "https://idp.example", aud: clientId, iat: now, exp: now + 3600 };
121
+ if (claims) { var ck = Object.keys(claims); for (var i = 0; i < ck.length; i++) payload[ck[i]] = claims[ck[i]]; }
122
+ var header = { alg: "ES256", typ: "JWT", kid: KID };
123
+ if (hdr) { var hk = Object.keys(hdr); for (var j = 0; j < hk.length; j++) header[hk[j]] = hdr[hk[j]]; }
124
+ if (header._noKid) { delete header.kid; delete header._noKid; }
125
+ return signToken(header, payload);
126
+ }
127
+
128
+ // Flip one decoded signature byte — keeps the 64-byte P1363 length so
129
+ // nodeCrypto.verify RETURNS false rather than throwing.
130
+ function tamperSig(tok) {
131
+ var p = tok.split(".");
132
+ var sig = Buffer.from(p[2], "base64url");
133
+ sig[0] = sig[0] ^ 0xff;
134
+ return p[0] + "." + p[1] + "." + sig.toString("base64url");
135
+ }
136
+
137
+ // ---- loopback stand-in AS ----
138
+
139
+ function makeServer() {
140
+ var routes = {};
141
+ function respond(res, r) {
142
+ var status = r.status || 200;
143
+ var body = r.text !== undefined ? r.text : JSON.stringify(r.json !== undefined ? r.json : {});
144
+ res.writeHead(status, { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) });
145
+ res.end(body);
146
+ }
147
+ var server = http.createServer(function (req, res) {
148
+ var u = new URL(req.url, "http://localhost");
149
+ var chunks = [];
150
+ req.on("data", function (c) { chunks.push(c); });
151
+ req.on("end", function () {
152
+ var r = routes[req.method + " " + u.pathname];
153
+ if (r === undefined) r = routes[u.pathname];
154
+ if (typeof r === "function") { r(req, res, Buffer.concat(chunks).toString("utf8"), u); return; }
155
+ if (r) { respond(res, r); return; }
156
+ res.writeHead(404, { "Content-Type": "application/json" }); res.end("{}");
157
+ });
158
+ });
159
+ return { server: server, routes: routes };
160
+ }
161
+
162
+ // Full-endpoint discovery-free OIDC client aimed at the loopback stand-in.
163
+ // The stand-in returns OAuth error responses with their spec HTTP status
164
+ // (4xx/5xx); b.httpClient rejects those by default, so surface them to the
165
+ // oauth layer with the documented `responseMode: "always-resolve"` opt so the
166
+ // module's own typed-error branches run (the default-path device-grant
167
+ // consequence of NOT setting this is reported separately as a defect).
168
+ function mk(base, clientId, extra) {
169
+ var opts = {
170
+ issuer: "https://idp.example",
171
+ clientId: clientId,
172
+ clientSecret: "sec",
173
+ redirectUri: "https://rp.example/cb",
174
+ isOidc: true,
175
+ allowHttp: true,
176
+ allowInternal: true,
177
+ httpClient: { responseMode: "always-resolve" },
178
+ jwksUri: base + "/jwks",
179
+ authorizationEndpoint: base + "/auth",
180
+ tokenEndpoint: base + "/token",
181
+ userinfoEndpoint: base + "/userinfo",
182
+ revocationEndpoint: base + "/revoke",
183
+ introspectionEndpoint: base + "/introspect",
184
+ registrationEndpoint: base + "/register",
185
+ deviceAuthorizationEndpoint: base + "/device",
186
+ endSessionEndpoint: base + "/logout",
187
+ checkSessionIframe: base + "/checksession",
188
+ };
189
+ if (extra) { var k = Object.keys(extra); for (var i = 0; i < k.length; i++) opts[k[i]] = extra[k[i]]; }
190
+ return X.create(opts);
191
+ }
192
+
193
+ // Minimal client with NO static endpoints + no discovery — every endpoint
194
+ // resolution lands on the "no-endpoint" refusal.
195
+ function mkBare(clientId, extra) {
196
+ var opts = { clientId: clientId, redirectUri: "https://rp.example/cb", isOidc: false };
197
+ if (extra) { var k = Object.keys(extra); for (var i = 0; i < k.length; i++) opts[k[i]] = extra[k[i]]; }
198
+ return X.create(opts);
199
+ }
200
+
201
+ // Discovery-backed client (issuer = the loopback /dfull tenant). Used for the
202
+ // introspection / registration / device endpoints, which resolve ONLY through
203
+ // OIDC discovery — create()'s staticEndpoints does not carry those three (see
204
+ // the reported defect); a discovery doc is the working configuration.
205
+ function mkDisc(base, clientId, extra) {
206
+ var opts = { issuer: base + "/dfull", clientId: clientId, clientSecret: "sec",
207
+ redirectUri: "https://rp.example/cb", isOidc: true, allowHttp: true, allowInternal: true,
208
+ httpClient: { responseMode: "always-resolve" } };
209
+ if (extra) { var k = Object.keys(extra); for (var i = 0; i < k.length; i++) opts[k[i]] = extra[k[i]]; }
210
+ return X.create(opts);
211
+ }
212
+
213
+ // Discovery document the mkDisc tenant serves.
214
+ function installDiscoveryDoc(base, routes) {
215
+ routes["/dfull/.well-known/openid-configuration"] = { json: {
216
+ issuer: base + "/dfull",
217
+ authorization_endpoint: base + "/auth",
218
+ token_endpoint: base + "/token",
219
+ jwks_uri: base + "/jwks",
220
+ userinfo_endpoint: base + "/userinfo",
221
+ revocation_endpoint: base + "/revoke",
222
+ introspection_endpoint: base + "/introspect",
223
+ registration_endpoint: base + "/register",
224
+ device_authorization_endpoint: base + "/device",
225
+ end_session_endpoint: base + "/logout",
226
+ check_session_iframe: base + "/checksession",
227
+ code_challenge_methods_supported: ["S256"],
228
+ } };
229
+ }
230
+
231
+ // =========================================================================
232
+
233
+ async function scenarioVerifyIdToken(base, routes) {
234
+ routes["/jwks"] = { json: { keys: [PUBJWK] } };
235
+ var CID = "cov2-verify";
236
+ var oa = mk(base, CID, { issuer: "https://idp.example" });
237
+
238
+ // Happy path — valid signature, all claims well-formed.
239
+ var ok = await aresolves("verifyIdToken: valid ES256 token resolves",
240
+ function () { return oa.verifyIdToken(mkToken(CID, { sub: "u1" })); });
241
+ check("verifyIdToken: returns header + claims", ok && ok.claims && ok.claims.sub === "u1" && ok.header.alg === "ES256");
242
+
243
+ var nowS = Math.floor(Date.now() / 1000);
244
+
245
+ await athrows("verifyIdToken: expired token refused",
246
+ function () { return oa.verifyIdToken(mkToken(CID, { exp: nowS - 100, iat: nowS - 200 })); },
247
+ "auth-oauth/expired");
248
+ await athrows("verifyIdToken: iat-in-future refused",
249
+ function () { return oa.verifyIdToken(mkToken(CID, { iat: nowS + 100000, exp: nowS + 200000 })); },
250
+ "auth-oauth/iat-future");
251
+ await athrows("verifyIdToken: nbf-in-future refused",
252
+ function () { return oa.verifyIdToken(mkToken(CID, { nbf: nowS + 100000 })); },
253
+ "auth-oauth/nbf-future");
254
+ await athrows("verifyIdToken: iss mismatch refused (CVE-2026-23552)",
255
+ function () { return oa.verifyIdToken(mkToken(CID, { iss: "https://evil.example" })); },
256
+ "auth-oauth/iss-mismatch");
257
+ // An OIDC client configured WITHOUT an issuer must REFUSE to verify an
258
+ // id_token — otherwise the iss check above is silently skipped and any iss
259
+ // (or none) is accepted (the exact cross-realm gap CVE-2026-23552 closes).
260
+ var oaNoIssuer = mk(base, CID, { issuer: undefined });
261
+ await athrows("verifyIdToken: OIDC client with no configured issuer refuses (iss unverifiable)",
262
+ function () { return oaNoIssuer.verifyIdToken(mkToken(CID, { sub: "u1", iss: "https://evil.example" })); },
263
+ "auth-oauth/issuer-required");
264
+ await athrows("verifyIdToken: aud not containing clientId refused",
265
+ function () { return oa.verifyIdToken(mkToken(CID, { aud: "someone-else" })); },
266
+ "auth-oauth/aud-mismatch");
267
+ await athrows("verifyIdToken: missing aud refused",
268
+ function () { return oa.verifyIdToken(mkToken(CID, { aud: undefined })); },
269
+ "auth-oauth/aud-mismatch");
270
+ await athrows("verifyIdToken: multi-aud without azp refused (OIDC §3.1.3.7)",
271
+ function () { return oa.verifyIdToken(mkToken(CID, { aud: [CID, "other-rp"] })); },
272
+ "auth-oauth/azp-required");
273
+ await athrows("verifyIdToken: azp not clientId refused",
274
+ function () { return oa.verifyIdToken(mkToken(CID, { azp: "other-rp" })); },
275
+ "auth-oauth/azp-mismatch");
276
+ await aresolves("verifyIdToken: multi-aud with matching azp accepted",
277
+ function () { return oa.verifyIdToken(mkToken(CID, { aud: [CID, "other-rp"], azp: CID })); });
278
+ // Single-aud token whose azp equals clientId is accepted (the azp-present-
279
+ // and-equal branch of OIDC Core §3.1.3.7 — distinct from the multi-aud path).
280
+ await aresolves("verifyIdToken: single-aud token with azp === clientId accepted",
281
+ function () { return oa.verifyIdToken(mkToken(CID, { azp: CID })); });
282
+ // A multi-aud token whose azp is a NON-string (type confusion) fails the
283
+ // `typeof azp !== "string"` guard and is refused as azp-required — the AS
284
+ // cannot dodge the authorized-party requirement by emitting a numeric azp.
285
+ await athrows("verifyIdToken: multi-aud with non-string azp refused (azp-required)",
286
+ function () { return oa.verifyIdToken(mkToken(CID, { aud: [CID, "other-rp"], azp: 123 })); },
287
+ "auth-oauth/azp-required");
288
+
289
+ // nonce branches
290
+ await aresolves("verifyIdToken: matching nonce accepted",
291
+ function () { return oa.verifyIdToken(mkToken(CID, { nonce: "n1" }), { nonce: "n1" }); });
292
+ await athrows("verifyIdToken: nonce mismatch refused",
293
+ function () { return oa.verifyIdToken(mkToken(CID, { nonce: "n1" }), { nonce: "n2" }); },
294
+ "auth-oauth/nonce-mismatch");
295
+ await athrows("verifyIdToken: expected nonce but token carries none refused",
296
+ function () { return oa.verifyIdToken(mkToken(CID, {}), { nonce: "n2" }); },
297
+ "auth-oauth/nonce-mismatch");
298
+
299
+ // signature branches
300
+ await athrows("verifyIdToken: tampered signature refused (verify → false)",
301
+ function () { return oa.verifyIdToken(tamperSig(mkToken(CID, {}))); },
302
+ "auth-oauth/bad-signature");
303
+ var malformedSig = (function () {
304
+ var p = mkToken(CID, {}).split(".");
305
+ return p[0] + "." + p[1] + "." + crypto.randomBytes(10).toString("base64url");
306
+ })();
307
+ await athrows("verifyIdToken: malformed-length signature refused",
308
+ function () { return oa.verifyIdToken(malformedSig); }, "auth-oauth/bad-signature");
309
+
310
+ // kid resolution branches
311
+ await athrows("verifyIdToken: unknown kid refused",
312
+ function () { return oa.verifyIdToken(mkToken(CID, {}, { kid: "nope" })); },
313
+ "auth-oauth/no-matching-key");
314
+ await athrows("verifyIdToken: kid-less token refused by default (JWKS-rotation defense)",
315
+ function () { return oa.verifyIdToken(mkToken(CID, {}, { _noKid: true })); },
316
+ "auth-oauth/no-matching-key");
317
+ await aresolves("verifyIdToken: kid-less token accepted with vopts.allowKidlessJwks",
318
+ function () { return oa.verifyIdToken(mkToken(CID, {}, { _noKid: true }), { allowKidlessJwks: true }); });
319
+
320
+ // alg/kty cross-check (CVE-2026-22817) — RS256 header against the EC JWK.
321
+ await athrows("verifyIdToken: alg/kty mismatch refused (RS256 header, EC JWK)",
322
+ function () {
323
+ var tok = _b64urlJson({ alg: "RS256", typ: "JWT", kid: KID }) + "." +
324
+ _b64urlJson({ iss: "https://idp.example", aud: CID, iat: nowS, exp: nowS + 3600 }) + ".AAAA";
325
+ return oa.verifyIdToken(tok);
326
+ }, "auth-jwt-external/alg-kty-mismatch");
327
+
328
+ // skipExpCheck is self-guarding — refused on a non-logout token.
329
+ await athrows("verifyIdToken: skipExpCheck on a non-logout token refused",
330
+ function () { return oa.verifyIdToken(mkToken(CID, {}), { skipExpCheck: true, skipNonceCheck: true }); },
331
+ "auth-oauth/skip-exp-check-not-allowed");
332
+
333
+ // client-level allowKidlessJwks threads through create()
334
+ var oaKidless = mk(base, "cov2-verify-kidless", { allowKidlessJwks: true });
335
+ await aresolves("verifyIdToken: client-level allowKidlessJwks accepts kid-less token",
336
+ function () { return oaKidless.verifyIdToken(mkToken("cov2-verify-kidless", {}, { _noKid: true })); });
337
+
338
+ // custom acceptedAlgorithms rejects an off-list alg before key lookup
339
+ var oaEcOnly = mk(base, "cov2-verify-ec", { acceptedAlgorithms: ["ES256"] });
340
+ await athrows("verifyIdToken: alg outside custom acceptedAlgorithms refused",
341
+ function () {
342
+ var tok = _b64urlJson({ alg: "RS512", typ: "JWT", kid: KID }) + "." +
343
+ _b64urlJson({ iss: "https://idp.example", aud: "cov2-verify-ec", iat: nowS, exp: nowS + 3600 }) + ".AAAA";
344
+ return oaEcOnly.verifyIdToken(tok);
345
+ }, "auth-oauth/alg-not-accepted");
346
+
347
+ // JWKS backend failures — fresh clients so the JWKS cache is unfilled.
348
+ routes["/jwks-500"] = { status: 500, text: "boom" };
349
+ routes["/jwks-nokeys"] = { json: { not_keys: [] } };
350
+ var oa500 = mk(base, "cov2-verify-j500", { jwksUri: base + "/jwks-500" });
351
+ await athrows("verifyIdToken: JWKS endpoint 500 surfaces http-500",
352
+ function () { return oa500.verifyIdToken(mkToken("cov2-verify-j500", {})); }, "auth-oauth/http-500");
353
+ var oaNoKeys = mk(base, "cov2-verify-jnk", { jwksUri: base + "/jwks-nokeys" });
354
+ await athrows("verifyIdToken: JWKS without keys[] refused",
355
+ function () { return oaNoKeys.verifyIdToken(mkToken("cov2-verify-jnk", {})); }, "auth-oauth/bad-jwks");
356
+ }
357
+
358
+ async function scenarioTokenFlows(base, routes) {
359
+ routes["/jwks"] = { json: { keys: [PUBJWK] } };
360
+ var CID = "cov2-token";
361
+ var oa = mk(base, CID);
362
+
363
+ // exchangeCode with a verifiable id_token (nonce enforced).
364
+ routes["/token"] = { json: {
365
+ access_token: "at-1", token_type: "Bearer", expires_in: 3600, refresh_token: "rt-1",
366
+ scope: "openid email",
367
+ id_token: mkToken(CID, { sub: "user-42", email: "u@x", nonce: "nz1" }),
368
+ } };
369
+ var tok = await aresolves("exchangeCode: full flow returns tokens + verified claims",
370
+ function () { return oa.exchangeCode({ code: "c", verifier: "v", nonce: "nz1" }); });
371
+ check("exchangeCode: accessToken + profile.sub + scope split",
372
+ tok && tok.accessToken === "at-1" && tok.profile.sub === "user-42" &&
373
+ tok.scope.length === 2 && tok.scope[0] === "openid");
374
+
375
+ // exchangeCode RFC 9396 granted-details subset accepted (strict).
376
+ routes["/token"] = { json: {
377
+ access_token: "at-2",
378
+ authorization_details: [{ type: "payment_initiation", actions: ["status"] }],
379
+ } };
380
+ var tokRar = await aresolves("exchangeCode: granted authorization_details subset accepted",
381
+ function () { return oa.exchangeCode({ code: "c", verifier: "v", skipNonceCheck: true,
382
+ authorizationDetails: [{ type: "payment_initiation", actions: ["initiate", "status"] }] }); });
383
+ check("exchangeCode: authorizationDetails surfaced", tokRar && Array.isArray(tokRar.authorizationDetails) &&
384
+ tokRar.authorizationDetails.length === 1);
385
+
386
+ // exchangeCode over-grant refused (strict).
387
+ routes["/token"] = { json: {
388
+ access_token: "at-3",
389
+ authorization_details: [{ type: "payment_initiation", actions: ["transfer"] }],
390
+ } };
391
+ await athrows("exchangeCode: granted over-grant refused (strict)",
392
+ function () { return oa.exchangeCode({ code: "c", verifier: "v", skipNonceCheck: true,
393
+ authorizationDetails: [{ type: "payment_initiation", actions: ["initiate", "status"] }] }); },
394
+ "auth-oauth/authorization-details-over-grant");
395
+
396
+ // exchangeCode over-grant SURFACED (verifyAuthorizationDetails:false).
397
+ var tokLax = await aresolves("exchangeCode: over-grant surfaced without refusal when verify disabled",
398
+ function () { return oa.exchangeCode({ code: "c", verifier: "v", skipNonceCheck: true,
399
+ verifyAuthorizationDetails: false,
400
+ authorizationDetails: [{ type: "payment_initiation", actions: ["initiate"] }] }); });
401
+ check("exchangeCode: non-strict surfaces granted details", tokLax && Array.isArray(tokLax.authorizationDetails));
402
+
403
+ // RFC 6749 §3.3 — the granted `scope` is delimited by U+0020 ONLY. A hostile
404
+ // AS that separates two scope tokens with a NON-space whitespace (here U+0085
405
+ // NEL) must NOT surface as two allowlist-matchable scopes; the pair stays a
406
+ // single opaque token so an operator scope allowlist can't be tricked into
407
+ // seeing a scope that was never granted. Drives the real exchangeCode path.
408
+ routes["/token"] = { json: { access_token: "at-scope", scope: "admin\u0085read" } };
409
+ var scTok = await aresolves("exchangeCode: hostile non-space scope separator stays one token (RFC 6749 §3.3)",
410
+ function () { return oa.exchangeCode({ code: "c", verifier: "v", skipNonceCheck: true }); });
411
+ check("exchangeCode: NEL-separated scope is not split into an allowlist bypass",
412
+ scTok && scTok.scope.length === 1 && scTok.scope[0] === "admin\u0085read" &&
413
+ scTok.scope.indexOf("admin") === -1 && scTok.scope.indexOf("read") === -1);
414
+
415
+ // RFC 6749 \u00a73.3 \u2014 a PRESENT but malformed `scope` ({ "scope": null }) is NOT
416
+ // an omitted scope. Only a truly ABSENT property mirrors the requested set;
417
+ // treating null as absent would copy the full requested scope (openid email)
418
+ // and report a grant the AS never made. A malformed scope falls through to
419
+ // zero (fail closed).
420
+ routes["/token"] = { json: { access_token: "at-null-scope", scope: null } };
421
+ var nullScTok = await aresolves("exchangeCode: null scope is malformed, not absent",
422
+ function () { return oa.exchangeCode({ code: "c", verifier: "v", skipNonceCheck: true }); });
423
+ check("exchangeCode: null scope grants ZERO scopes, not the requested set",
424
+ nullScTok && Array.isArray(nullScTok.scope) && nullScTok.scope.length === 0);
425
+
426
+ // _postForm backend-failure branches.
427
+ routes["/token"] = { status: 400, json: { error: "invalid_grant" } };
428
+ await athrows("exchangeCode: token endpoint non-2xx surfaces token-error-400",
429
+ function () { return oa.exchangeCode({ code: "c", verifier: "v", skipNonceCheck: true }); }, "auth-oauth/token-error-400");
430
+ routes["/token"] = { status: 200, text: "not-json-at-all" };
431
+ await athrows("exchangeCode: token endpoint non-JSON surfaces bad-token-json",
432
+ function () { return oa.exchangeCode({ code: "c", verifier: "v", skipNonceCheck: true }); }, "auth-oauth/bad-token-json");
433
+
434
+ // refreshAccessToken rotation branches (no callbacks → replay gate skipped).
435
+ routes["/token"] = { json: { access_token: "at-r", refresh_token: "rt-new" } };
436
+ var fr = await aresolves("refreshAccessToken: full flow with rotation",
437
+ function () { return oa.refreshAccessToken("rt-old"); });
438
+ check("refreshAccessToken: rotation flagged + previous captured",
439
+ fr && fr.refreshTokenRotated === true && fr.previousRefreshToken === "rt-old");
440
+ routes["/token"] = { json: { access_token: "at-r2" } };
441
+ var fr2 = await aresolves("refreshAccessToken: no new refresh_token → not rotated",
442
+ function () { return oa.refreshAccessToken("rt-old"); });
443
+ check("refreshAccessToken: rotation false when unchanged", fr2 && fr2.refreshTokenRotated === false);
444
+
445
+ // refresh with a fresh id_token verifies (skipNonceCheck path).
446
+ routes["/token"] = { json: { access_token: "at-r3", id_token: mkToken(CID, { sub: "user-42" }) } };
447
+ var fr3 = await aresolves("refreshAccessToken: refreshed id_token verified (skip-nonce)",
448
+ function () { return oa.refreshAccessToken("rt-old"); });
449
+ check("refreshAccessToken: refreshed profile surfaced", fr3 && fr3.profile && fr3.profile.sub === "user-42");
450
+
451
+ // exchangeToken (RFC 8693) full flow.
452
+ routes["/token"] = { json: { access_token: "at-x", token_type: "Bearer" } };
453
+ var xt = await aresolves("exchangeToken: full flow returns tokens",
454
+ function () { return oa.exchangeToken({ subjectToken: "s",
455
+ subjectTokenType: "urn:ietf:params:oauth:token-type:access_token",
456
+ audience: "https://down.example", resource: "https://rs.example", scope: ["a", "b"],
457
+ requestedTokenType: "urn:ietf:params:oauth:token-type:access_token" }); });
458
+ check("exchangeToken: returns accessToken", xt && xt.accessToken === "at-x");
459
+
460
+ // exchangeToken with a custom subjectTokenType permitted by opt-in.
461
+ await aresolves("exchangeToken: custom token type allowed with allowCustomTokenType",
462
+ function () { return oa.exchangeToken({ subjectToken: "s", subjectTokenType: "urn:acme:custom",
463
+ allowCustomTokenType: true }); });
464
+
465
+ // nativeSsoExchange composes exchangeToken.
466
+ routes["/token"] = { json: { access_token: "at-n" } };
467
+ var ns = await aresolves("nativeSsoExchange: full flow returns tokens",
468
+ function () { return oa.nativeSsoExchange({ deviceSecret: "ds", idToken: "idt", audience: "app2" }); });
469
+ check("nativeSsoExchange: returns accessToken", ns && ns.accessToken === "at-n");
470
+ }
471
+
472
+ async function scenarioUserinfoRevokeIntrospect(base, routes) {
473
+ routes["/jwks"] = { json: { keys: [PUBJWK] } };
474
+ var CID = "cov2-uri";
475
+ var oa = mk(base, CID);
476
+
477
+ routes["/userinfo"] = { json: { sub: "u1", email: "a@b" } };
478
+ var prof = await aresolves("fetchUserInfo: OIDC with matching idTokenSub returns profile",
479
+ function () { return oa.fetchUserInfo("at", { idTokenSub: "u1" }); });
480
+ check("fetchUserInfo: profile carries sub", prof && prof.sub === "u1");
481
+ await athrows("fetchUserInfo: userinfo.sub mismatch refused (substitution defense)",
482
+ function () { return oa.fetchUserInfo("at", { idTokenSub: "someone-else" }); },
483
+ "auth-oauth/userinfo-sub-mismatch");
484
+ await aresolves("fetchUserInfo: skipSubCheck bypasses the cross-check",
485
+ function () { return oa.fetchUserInfo("at", { skipSubCheck: true }); });
486
+
487
+ // non-OIDC client needs no idTokenSub.
488
+ var oaNonOidc = mk(base, "cov2-uri-nonoidc", { isOidc: false });
489
+ await aresolves("fetchUserInfo: non-OIDC client returns profile without cross-check",
490
+ function () { return oaNonOidc.fetchUserInfo("at"); });
491
+
492
+ // _fetchJson backend-failure branches.
493
+ routes["/userinfo"] = { status: 500, text: "boom" };
494
+ await athrows("fetchUserInfo: userinfo 500 surfaces http-500",
495
+ function () { return oa.fetchUserInfo("at", { skipSubCheck: true }); }, "auth-oauth/http-500");
496
+ routes["/userinfo"] = { status: 200, text: "<<not json>>" };
497
+ await athrows("fetchUserInfo: userinfo non-JSON surfaces bad-json",
498
+ function () { return oa.fetchUserInfo("at", { skipSubCheck: true }); }, "auth-oauth/bad-json");
499
+
500
+ // revokeToken success + failure.
501
+ routes["/revoke"] = { status: 200, json: {} };
502
+ await aresolves("revokeToken: 2xx resolves (RFC 7009)",
503
+ function () { return oa.revokeToken("tok", { type: "refresh_token" }); });
504
+ routes["/revoke"] = { status: 400, json: {} };
505
+ await athrows("revokeToken: non-2xx surfaces revoke-failed",
506
+ function () { return oa.revokeToken("tok"); }, "auth-oauth/revoke-failed");
507
+
508
+ // introspectToken success + bad-response + no-endpoint (discovery-backed —
509
+ // the introspection endpoint resolves only via OIDC discovery).
510
+ var oaI = mkDisc(base, "cov2-uri-introspect");
511
+ routes["/introspect"] = { json: { active: true, scope: "read" } };
512
+ var intr = await aresolves("introspectToken: active verdict returned",
513
+ function () { return oaI.introspectToken("tok", { tokenTypeHint: "access_token" }); });
514
+ check("introspectToken: active boolean surfaced", intr && intr.active === true);
515
+ routes["/introspect"] = { json: { scope: "read" } };
516
+ await athrows("introspectToken: response missing active boolean refused",
517
+ function () { return oaI.introspectToken("tok"); }, "auth-oauth/bad-introspect-response");
518
+
519
+ var oaBare = mkBare("cov2-uri-bare");
520
+ await athrows("introspectToken: no introspection endpoint refused",
521
+ function () { return oaBare.introspectToken("tok"); }, "auth-oauth/no-introspection-endpoint");
522
+ }
523
+
524
+ async function scenarioRegisterDcr(base, routes) {
525
+ var CID = "cov2-reg";
526
+ var oa = mk(base, CID);
527
+ // registration_endpoint resolves only via OIDC discovery.
528
+ var oaReg = mkDisc(base, "cov2-reg-disc");
529
+
530
+ routes["/register"] = { status: 201, json: { client_id: "new-client", client_secret: "cs" } };
531
+ var reg = await aresolves("registerClient: full flow returns client_id",
532
+ function () { return oaReg.registerClient({ redirect_uris: ["https://rp.example/cb"] },
533
+ { initialAccessToken: "iat-token" }); });
534
+ check("registerClient: client_id surfaced", reg && reg.client_id === "new-client");
535
+
536
+ routes["/register"] = { status: 400, text: "bad" };
537
+ await athrows("registerClient: non-2xx surfaces register-failed",
538
+ function () { return oaReg.registerClient({ redirect_uris: ["https://rp.example/cb"] }); },
539
+ "auth-oauth/register-failed-400");
540
+ routes["/register"] = { status: 201, text: "notjson" };
541
+ await athrows("registerClient: non-JSON response refused",
542
+ function () { return oaReg.registerClient({ redirect_uris: ["https://rp.example/cb"] }); },
543
+ "auth-oauth/bad-register-response");
544
+ routes["/register"] = { status: 201, json: { note: "no id here" } };
545
+ await athrows("registerClient: response missing client_id refused",
546
+ function () { return oaReg.registerClient({ redirect_uris: ["https://rp.example/cb"] }); },
547
+ "auth-oauth/register-no-client-id");
548
+
549
+ // _validateUrl empty-string redirect_uri → bad-url (before endpoint resolve).
550
+ await athrows("registerClient: empty-string redirect_uri refused (URL required)",
551
+ function () { return oaReg.registerClient({ redirect_uris: [""] }); }, "auth-oauth/bad-url");
552
+
553
+ var oaBare = mkBare("cov2-reg-bare");
554
+ await athrows("registerClient: no registration endpoint refused",
555
+ function () { return oaBare.registerClient({ redirect_uris: ["https://rp.example/cb"] }); },
556
+ "auth-oauth/no-registration-endpoint");
557
+
558
+ // DCR management (RFC 7592).
559
+ routes["GET /dcr"] = { json: { client_id: "c", redirect_uris: ["https://rp.example/cb"] } };
560
+ routes["PUT /dcr"] = { json: { client_id: "c", registration_access_token: "rotated" } };
561
+ routes["DELETE /dcr"] = { status: 204 };
562
+ var uri = base + "/dcr";
563
+ var rc = await aresolves("readClient: GET returns metadata",
564
+ function () { return oa.readClient(uri, "rat"); });
565
+ check("readClient: metadata surfaced", rc && rc.client_id === "c");
566
+ var uc = await aresolves("updateClient: PUT returns rotated metadata",
567
+ function () { return oa.updateClient(uri, "rat", { redirect_uris: ["https://rp.example/cb2"] }); });
568
+ check("updateClient: rotated token surfaced", uc && uc.registration_access_token === "rotated");
569
+ var del = await aresolves("deleteClient: 204 resolves true",
570
+ function () { return oa.deleteClient(uri, "rat"); });
571
+ check("deleteClient: returns true", del === true);
572
+
573
+ routes["DELETE /dcr"] = { status: 404 };
574
+ await athrows("deleteClient: 404 surfaces dcr-not-found",
575
+ function () { return oa.deleteClient(uri, "rat"); }, "auth-oauth/dcr-not-found");
576
+ routes["DELETE /dcr"] = { status: 500 };
577
+ await athrows("deleteClient: other status surfaces dcr-delete-failed",
578
+ function () { return oa.deleteClient(uri, "rat"); }, "auth-oauth/dcr-delete-failed-500");
579
+
580
+ routes["GET /dcr-500"] = { status: 500, text: "err" };
581
+ await athrows("readClient: non-2xx GET surfaces dcr-get-failed",
582
+ function () { return oa.readClient(base + "/dcr-500", "rat"); }, "auth-oauth/dcr-get-failed-500");
583
+ routes["GET /dcr-badjson"] = { status: 200, text: "notjson" };
584
+ await athrows("readClient: non-JSON GET surfaces dcr-bad-response",
585
+ function () { return oa.readClient(base + "/dcr-badjson", "rat"); }, "auth-oauth/dcr-bad-response");
586
+ }
587
+
588
+ async function scenarioDeviceAndPoll(base, routes) {
589
+ routes["/jwks"] = { json: { keys: [PUBJWK] } };
590
+ // device_authorization_endpoint resolves only via OIDC discovery.
591
+ var oa = mkDisc(base, "cov2-dev");
592
+
593
+ routes["/device"] = { json: { device_code: "dc-1", user_code: "UC-1", verification_uri: "https://verify.example" } };
594
+ var dev = await aresolves("deviceAuthorization: returns device_code + user_code",
595
+ function () { return oa.deviceAuthorization({ scope: ["openid"] }); });
596
+ check("deviceAuthorization: device_code surfaced", dev && dev.device_code === "dc-1");
597
+ routes["/device"] = { json: { note: "missing fields" } };
598
+ await athrows("deviceAuthorization: response missing fields refused",
599
+ function () { return oa.deviceAuthorization(); }, "auth-oauth/bad-device-response");
600
+ var oaBare = mkBare("cov2-dev-bare");
601
+ await athrows("deviceAuthorization: no device endpoint refused",
602
+ function () { return oaBare.deviceAuthorization(); }, "auth-oauth/no-device-endpoint");
603
+
604
+ // pollDeviceCode success on first poll (no sleep).
605
+ routes["/token"] = { json: { access_token: "at-poll" } };
606
+ var polled = await aresolves("pollDeviceCode: success on first poll",
607
+ function () { return oa.pollDeviceCode("dc-1"); });
608
+ check("pollDeviceCode: returns tokens", polled && polled.accessToken === "at-poll");
609
+
610
+ // pollDeviceCode terminal error.
611
+ routes["/token"] = { status: 400, json: { error: "access_denied", error_description: "user declined" } };
612
+ await athrows("pollDeviceCode: terminal error surfaced",
613
+ function () { return oa.pollDeviceCode("dc-1"); }, "auth-oauth/device-access_denied");
614
+
615
+ // pollDeviceCode authorization_pending → one 5s poll interval → timeout.
616
+ routes["/token"] = { json: { error: "authorization_pending" } };
617
+ await athrows("pollDeviceCode: authorization_pending then budget exhausted → timeout",
618
+ function () { return oa.pollDeviceCode("dc-1", { maxWaitMs: 1 }); }, "auth-oauth/device-poll-timeout");
619
+
620
+ // pollDeviceCode slow_down (RFC 8628 §3.5) → interval bumped, keeps polling
621
+ // → budget exhausted → timeout (proves the slow_down branch is taken, not
622
+ // surfaced as a terminal error).
623
+ routes["/token"] = { json: { error: "slow_down" } };
624
+ await athrows("pollDeviceCode: slow_down bumps the interval then times out",
625
+ function () { return oa.pollDeviceCode("dc-1", { maxWaitMs: 1 }); }, "auth-oauth/device-poll-timeout");
626
+ }
627
+
628
+ // pollDeviceCode over the DEFAULT b.httpClient (no `responseMode:
629
+ // "always-resolve"` workaround). RFC 8628 §3.5 / RFC 6749 §5.2 deliver
630
+ // authorization_pending / slow_down / terminal OAuth errors as HTTP 400.
631
+ // The poll loop must read `parsed.error` from that 400 body; if the token
632
+ // request runs in the default buffer mode, b.httpClient rejects the 4xx
633
+ // BEFORE the loop reads the error, so the grant aborts on the first poll
634
+ // (which is almost always authorization_pending). This scenario uses NO
635
+ // httpClient override, so it fails RED until pollDeviceCode itself requests
636
+ // always-resolve.
637
+ async function scenarioDeviceGrantDefaultHttp(base, routes) {
638
+ routes["/jwks"] = { json: { keys: [PUBJWK] } };
639
+ var oa = X.create({
640
+ clientId: "cov2-devgrant",
641
+ clientSecret: "sec",
642
+ redirectUri: "https://rp.example/cb",
643
+ isOidc: false,
644
+ allowHttp: true,
645
+ allowInternal: true,
646
+ tokenEndpoint: base + "/token",
647
+ });
648
+
649
+ // Terminal OAuth error delivered as HTTP 400 (RFC 6749 §5.2): the typed
650
+ // device-<error> branch must run, proving the 400 body was parsed rather
651
+ // than surfaced as a raw HTTP reject.
652
+ routes["/token"] = { status: 400, json: { error: "access_denied", error_description: "user declined" } };
653
+ await athrows("pollDeviceCode(default httpClient): HTTP 400 terminal error is parsed, not HTTP-thrown",
654
+ function () { return oa.pollDeviceCode("dc-1"); }, "auth-oauth/device-access_denied");
655
+
656
+ // authorization_pending delivered as HTTP 400: the loop must KEEP polling
657
+ // (not abort on the first poll), so a tight budget resolves to the device
658
+ // poll-timeout, never a raw HTTP reject.
659
+ routes["/token"] = { status: 400, json: { error: "authorization_pending" } };
660
+ await athrows("pollDeviceCode(default httpClient): HTTP 400 authorization_pending keeps polling → timeout",
661
+ function () { return oa.pollDeviceCode("dc-1", { maxWaitMs: 1 }); }, "auth-oauth/device-poll-timeout");
662
+ }
663
+
664
+ // A static (non-discovery) client that supplies introspection / registration
665
+ // / device_authorization endpoints as create() opts. create() must read
666
+ // those three snake-case-mapped endpoints into staticEndpoints so
667
+ // _resolveEndpoint finds them WITHOUT any OIDC discovery round-trip. Before
668
+ // the fix, staticEndpoints omitted them, so introspectToken / registerClient
669
+ // / deviceAuthorization all landed on their no-endpoint refusals — an
670
+ // operator setting opts.introspectionEndpoint (exactly what introspectToken's
671
+ // own refusal message tells them to do) still could not use the endpoint.
672
+ async function scenarioStaticExtraEndpoints(base, routes) {
673
+ routes["/jwks"] = { json: { keys: [PUBJWK] } };
674
+ var oa = X.create({
675
+ clientId: "cov2-static-extra",
676
+ clientSecret: "sec",
677
+ redirectUri: "https://rp.example/cb",
678
+ isOidc: false,
679
+ allowHttp: true,
680
+ allowInternal: true,
681
+ introspectionEndpoint: base + "/introspect",
682
+ registrationEndpoint: base + "/register",
683
+ deviceAuthorizationEndpoint: base + "/device",
684
+ });
685
+
686
+ routes["/introspect"] = { json: { active: true, scope: "openid" } };
687
+ var verdict = await aresolves("introspectToken: resolves via static opts.introspectionEndpoint (no discovery)",
688
+ function () { return oa.introspectToken("tok"); });
689
+ check("introspectToken(static): active surfaced", verdict && verdict.active === true);
690
+
691
+ routes["/register"] = { json: { client_id: "reg-static", client_secret: "s" } };
692
+ var reg = await aresolves("registerClient: resolves via static opts.registrationEndpoint (no discovery)",
693
+ function () { return oa.registerClient({ redirect_uris: ["https://rp.example/cb"] }); });
694
+ check("registerClient(static): client_id surfaced", reg && reg.client_id === "reg-static");
695
+
696
+ routes["/device"] = { json: { device_code: "dc-s", user_code: "UC-s", verification_uri: "https://verify.example" } };
697
+ var dev = await aresolves("deviceAuthorization: resolves via static opts.deviceAuthorizationEndpoint (no discovery)",
698
+ function () { return oa.deviceAuthorization({ scope: ["openid"] }); });
699
+ check("deviceAuthorization(static): device_code surfaced", dev && dev.device_code === "dc-s");
700
+ }
701
+
702
+ async function scenarioBackchannelLogout(base, routes) {
703
+ routes["/jwks"] = { json: { keys: [PUBJWK] } };
704
+ var CID = "cov2-bcl";
705
+ var oa = mk(base, CID);
706
+ var EVENT = "http://schemas.openid.net/event/backchannel-logout";
707
+
708
+ function logoutTok(claims, hdr) {
709
+ var now = Math.floor(Date.now() / 1000);
710
+ var payload = { iss: "https://idp.example", aud: CID, iat: now, jti: "jti-" + crypto.randomBytes(4).toString("hex"),
711
+ events: {}, sub: "u1" };
712
+ payload.events[EVENT] = {};
713
+ if (claims) { var ck = Object.keys(claims); for (var i = 0; i < ck.length; i++) payload[ck[i]] = claims[ck[i]]; }
714
+ var header = { alg: "ES256", typ: "logout+jwt", kid: KID };
715
+ if (hdr) { var hk = Object.keys(hdr); for (var j = 0; j < hk.length; j++) header[hk[j]] = hdr[hk[j]]; }
716
+ return signToken(header, payload);
717
+ }
718
+
719
+ // Valid logout token, no replay store.
720
+ var res = await aresolves("verifyBackchannelLogoutToken: valid token resolves",
721
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok()); });
722
+ check("verifyBackchannelLogoutToken: surfaces sub + events", res && res.sub === "u1" && res.events);
723
+
724
+ await athrows("verifyBackchannelLogoutToken: non-string refused",
725
+ function () { return oa.verifyBackchannelLogoutToken(123); }, "auth-oauth/bad-logout-token");
726
+ await athrows("verifyBackchannelLogoutToken: non-3-segment refused",
727
+ function () { return oa.verifyBackchannelLogoutToken("a.b"); }, "auth-oauth/malformed-logout-token");
728
+ await athrows("verifyBackchannelLogoutToken: wrong typ refused",
729
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok({}, { typ: "JWT" })); }, "auth-oauth/wrong-typ");
730
+ await athrows("verifyBackchannelLogoutToken: forbidden nonce refused (§2.6)",
731
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok({ nonce: "x" })); }, "auth-oauth/forbidden-nonce");
732
+ await athrows("verifyBackchannelLogoutToken: missing sub AND sid refused",
733
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok({ sub: undefined, sid: undefined })); },
734
+ "auth-oauth/no-sub-or-sid");
735
+ // Token-type confusion: a JWS that carries typ="logout+jwt" but NO
736
+ // back-channel-logout event is not a logout token. It must fail closed —
737
+ // verifyIdToken's skipExpCheck self-guard (which the wrapper relies on to
738
+ // waive the exp claim) refuses any token lacking the logout event, so a
739
+ // non-logout token cannot borrow the exp-waiver to slip an id_token in
740
+ // through the logout endpoint.
741
+ await athrows("verifyBackchannelLogoutToken: logout+jwt typ without the logout event refused (fail-closed)",
742
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok({ events: undefined })); },
743
+ "auth-oauth/skip-exp-check-not-allowed");
744
+
745
+ // stale logout iat (verifyIdToken freshness floor) — no exp on logout tokens.
746
+ var nowS = Math.floor(Date.now() / 1000);
747
+ await athrows("verifyBackchannelLogoutToken: stale iat refused (freshness floor)",
748
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok({ iat: nowS - 100000 })); },
749
+ "auth-oauth/logout-token-stale");
750
+ // widened maxAgeSec accepts an older-but-in-window token.
751
+ await aresolves("verifyBackchannelLogoutToken: widened maxAgeSec accepts older token",
752
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok({ iat: nowS - 400 }), { maxAgeSec: 100000 }); });
753
+
754
+ // atomic replay store branches.
755
+ await aresolves("verifyBackchannelLogoutToken: atomic first-sighting accepted",
756
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok(),
757
+ { atomicReplayStore: { checkAndInsert: function () { return true; } } }); });
758
+ await athrows("verifyBackchannelLogoutToken: atomic replay (falsy) refused",
759
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok(),
760
+ { atomicReplayStore: { checkAndInsert: function () { return 0; } } }); },
761
+ "auth-oauth/logout-token-replay");
762
+ await athrows("verifyBackchannelLogoutToken: atomic store throwing surfaces typed error",
763
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok(),
764
+ { atomicReplayStore: { checkAndInsert: function () { throw new Error("down"); } } }); },
765
+ "auth-oauth/replay-store-failed");
766
+ await athrows("verifyBackchannelLogoutToken: atomic store but missing jti refused",
767
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok({ jti: undefined }),
768
+ { atomicReplayStore: { checkAndInsert: function () { return true; } } }); },
769
+ "auth-oauth/no-jti");
770
+
771
+ // legacy seen() branches.
772
+ await aresolves("verifyBackchannelLogoutToken: seen() first-sighting accepted",
773
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok(), { seen: function () { return true; } }); });
774
+ await athrows("verifyBackchannelLogoutToken: seen() replay (falsy) refused",
775
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok(), { seen: function () { return 0; } }); },
776
+ "auth-oauth/logout-token-replay");
777
+ await athrows("verifyBackchannelLogoutToken: seen() throwing surfaces typed error",
778
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok(), { seen: function () { throw new Error("down"); } }); },
779
+ "auth-oauth/seen-callback-failed");
780
+ await athrows("verifyBackchannelLogoutToken: seen() but missing jti refused",
781
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok({ jti: undefined }), { seen: function () { return true; } }); },
782
+ "auth-oauth/no-jti");
783
+
784
+ // ---- pre-verify shape gates (fire before the JWS signature check) ----
785
+ await athrows("verifyBackchannelLogoutToken: oversized token refused (length cap before decode)",
786
+ function () { return oa.verifyBackchannelLogoutToken("a".repeat(300000)); },
787
+ "auth-oauth/logout-token-too-large");
788
+ var badHeaderTok = Buffer.from("not-json", "utf8").toString("base64url") + "." + _b64urlJson({}) + ".sig";
789
+ await athrows("verifyBackchannelLogoutToken: undecodable header refused",
790
+ function () { return oa.verifyBackchannelLogoutToken(badHeaderTok); }, "auth-oauth/bad-logout-header");
791
+
792
+ // ---- wrapper freshness floor STRICTER than verifyIdToken's (skew grace) ----
793
+ // iat that clears verifyIdToken's iat + maxAge + skew floor but fails the
794
+ // wrapper's own iat + maxAge (no skew) bound — the belt-and-suspenders check.
795
+ var nowBcl = Math.floor(Date.now() / 1000);
796
+ await athrows("verifyBackchannelLogoutToken: iat past the wrapper's own maxAge floor refused",
797
+ function () { return oa.verifyBackchannelLogoutToken(logoutTok({ iat: nowBcl - 130 }), { maxAgeSec: 100 }); },
798
+ "auth-oauth/logout-token-too-old");
799
+ }
800
+
801
+ async function scenarioJarm(base, routes) {
802
+ routes["/jwks"] = { json: { keys: [PUBJWK] } };
803
+ var CID = "cov2-jarm";
804
+ var oa = mk(base, CID);
805
+
806
+ function jarmTok(claims) {
807
+ var now = Math.floor(Date.now() / 1000);
808
+ var payload = { iss: "https://idp.example", aud: CID, iat: now, exp: now + 300 };
809
+ if (claims) { var ck = Object.keys(claims); for (var i = 0; i < ck.length; i++) payload[ck[i]] = claims[ck[i]]; }
810
+ return signToken({ alg: "ES256", typ: "JWT", kid: KID }, payload);
811
+ }
812
+
813
+ var res = await aresolves("parseJarmResponse: valid signed response returns params",
814
+ function () { return oa.parseJarmResponse(jarmTok({ code: "cc", state: "ss", iss: "https://idp.example" }),
815
+ { expectedState: "ss" }); });
816
+ check("parseJarmResponse: code/state surfaced", res && res.code === "cc" && res.state === "ss");
817
+
818
+ await athrows("parseJarmResponse: forbidden nonce claim refused (JARM §4)",
819
+ function () { return oa.parseJarmResponse(jarmTok({ code: "cc", nonce: "n" })); },
820
+ "auth-oauth/jarm-forbidden-nonce");
821
+ }
822
+
823
+ async function scenarioDiscovery(base, routes) {
824
+ // Each discovery client uses its own issuer path → isolated .well-known route.
825
+ function wk(seg, doc) { routes["/" + seg + "/.well-known/openid-configuration"] = doc; }
826
+ function disClient(seg, clientId, extra) {
827
+ var opts = { issuer: base + "/" + seg, clientId: clientId, clientSecret: "sec",
828
+ redirectUri: "https://rp.example/cb", isOidc: true, allowHttp: true, allowInternal: true };
829
+ if (extra) { var k = Object.keys(extra); for (var i = 0; i < k.length; i++) opts[k[i]] = extra[k[i]]; }
830
+ return X.create(opts);
831
+ }
832
+
833
+ // issuer mismatch between config + discovery doc.
834
+ wk("dmis", { json: { issuer: "https://evil.example", authorization_endpoint: base + "/auth",
835
+ token_endpoint: base + "/token", jwks_uri: base + "/jwks" } });
836
+ var oaMis = disClient("dmis", "cov2-dmis");
837
+ await athrows("discovery: doc issuer != configured issuer refused",
838
+ function () { return oaMis.authorizationUrl(); }, "auth-oauth/issuer-mismatch");
839
+
840
+ // discovery doc is not an object.
841
+ wk("dbad", { text: "123" });
842
+ var oaBadDisc = disClient("dbad", "cov2-dbad");
843
+ await athrows("discovery: non-object document refused",
844
+ function () { return oaBadDisc.authorizationUrl(); }, "auth-oauth/bad-discovery");
845
+
846
+ // discovery doc valid but missing the requested endpoint.
847
+ wk("dnoep", { json: { issuer: base + "/dnoep", authorization_endpoint: base + "/auth",
848
+ token_endpoint: base + "/token", jwks_uri: base + "/jwks" } });
849
+ var oaNoEp = disClient("dnoep", "cov2-dnoep");
850
+ await athrows("discovery: endpoint absent from document refused",
851
+ function () { return oaNoEp.revokeToken("t"); }, "auth-oauth/no-endpoint");
852
+ var cfg = await aresolves("discover(): returns the resolved configuration",
853
+ function () { return oaNoEp.discover(); });
854
+ check("discover(): config issuer surfaced", cfg && cfg.issuer === base + "/dnoep");
855
+
856
+ // parseCallback requireIss driven by discovery metadata.
857
+ wk("dmeta", { json: { issuer: base + "/dmeta", authorization_endpoint: base + "/auth",
858
+ token_endpoint: base + "/token", jwks_uri: base + "/jwks",
859
+ authorization_response_iss_parameter_supported: true } });
860
+ var oaMeta = disClient("dmeta", "cov2-dmeta");
861
+ await athrows("parseCallback: OP advertises iss-param support, missing iss refused",
862
+ function () { return oaMeta.parseCallback({ code: "c" }); }, "auth-oauth/missing-iss-callback");
863
+
864
+ // parseCallback tolerates discovery failure (catch branch) — code still parses.
865
+ wk("dcatch", { status: 500, text: "err" });
866
+ var oaCatch = disClient("dcatch", "cov2-dcatch");
867
+ var rv = await aresolves("parseCallback: discovery failure is swallowed, code returned",
868
+ function () { return oaCatch.parseCallback({ code: "ccc" }); });
869
+ check("parseCallback: code surfaced despite discovery failure", rv && rv.code === "ccc");
870
+
871
+ // No discovery available + no static endpoint → not-configured refusal.
872
+ var oaNone = X.create({ clientId: "cov2-none", redirectUri: "https://rp.example/cb", isOidc: false });
873
+ await athrows("resolveEndpoint: no static endpoint + no discovery refused",
874
+ function () { return oaNone.exchangeCode({ code: "c", verifier: "v" }); }, "auth-oauth/no-endpoint");
875
+
876
+ // OP discovery advertises code_challenge_methods_supported WITHOUT S256 →
877
+ // refuse the authorization request (RFC 9700 §4.13 — stripped-S256 / PKCE
878
+ // downgrade signature).
879
+ wk("ddown", { json: { issuer: base + "/ddown", authorization_endpoint: base + "/auth",
880
+ token_endpoint: base + "/token", jwks_uri: base + "/jwks",
881
+ code_challenge_methods_supported: ["plain"] } });
882
+ var oaDown = disClient("ddown", "cov2-ddown");
883
+ await athrows("authorizationUrl: OP advertising plain-only PKCE methods refused (downgrade defense)",
884
+ function () { return oaDown.authorizationUrl(); }, "auth-oauth/pkce-downgrade");
885
+ }
886
+
887
+ async function scenarioBuildersAndUrls(base, routes) {
888
+ // checkSessionIframeUrl full + refusal.
889
+ var oa = mk(base, "cov2-misc");
890
+ var csi = await aresolves("checkSessionIframeUrl: returns configured iframe URL",
891
+ function () { return oa.checkSessionIframeUrl(); });
892
+ check("checkSessionIframeUrl: URL surfaced", csi === base + "/checksession");
893
+
894
+ var oaBare = mkBare("cov2-misc-bare");
895
+ await athrows("checkSessionIframeUrl: no iframe endpoint refused",
896
+ function () { return oaBare.checkSessionIframeUrl(); }, "auth-oauth/no-check-session-iframe");
897
+ await athrows("endSessionUrl: no end_session endpoint refused",
898
+ function () { return oaBare.endSessionUrl({}); }, "auth-oauth/no-end-session-endpoint");
899
+ await athrows("pushAuthorizationRequest: no PAR endpoint refused",
900
+ function () { return oaBare.pushAuthorizationRequest({}); }, "auth-oauth/no-par-endpoint");
901
+
902
+ // PAR bad response (no request_uri).
903
+ routes["/par"] = { status: 201, json: { note: "no request_uri" } };
904
+ var oaPar = mk(base, "cov2-par", { pushedAuthorizationRequestEndpoint: base + "/par" });
905
+ await athrows("pushAuthorizationRequest: response without request_uri refused",
906
+ function () { return oaPar.pushAuthorizationRequest({}); }, "auth-oauth/par-bad-response");
907
+
908
+ // clientAttestationHeaders without an issuer + no audience → refusal.
909
+ var attKp = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
910
+ var instKp = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
911
+ var oaNoIss = mkBare("cov2-att-noaud");
912
+ throws("clientAttestationHeaders: missing audience + no issuer refused",
913
+ function () { return oaNoIss.clientAttestationHeaders({ attesterPrivateKey: attKp.privateKey,
914
+ instanceKeyJwk: instKp.publicKey.export({ format: "jwk" }), instancePrivateKey: instKp.privateKey }); },
915
+ "auth-oauth/attestation-no-aud");
916
+
917
+ // create(): a couple of remaining construction branches.
918
+ var apple = X.create({ provider: "apple", clientId: "a", redirectUri: "https://x/cb" });
919
+ check("create: apple preset threads responseMode + OIDC", apple.isOidc === true);
920
+ var ms = X.create({ provider: "microsoft", clientId: "a", redirectUri: "https://x/cb" });
921
+ check("create: microsoft preset is OIDC", ms.isOidc === true);
922
+ var tuned = X.create({ clientId: "a", redirectUri: "https://x/cb", isOidc: true,
923
+ acceptedAlgorithms: ["ES256"], discoveryCacheMs: 1000, jwksCacheMs: 1000, allowInternal: false });
924
+ check("create: custom accepted-algorithms + cache TTLs accepted", !!tuned);
925
+
926
+ // _validateUrl: a syntactically-invalid (non-protocol) URL → bad-url.
927
+ throws("create: syntactically invalid redirectUri refused",
928
+ function () { return X.create({ clientId: "a", redirectUri: "https://" }); }, "auth-oauth/bad-url");
929
+ }
930
+
931
+ // Sign an ES256 compact JWS with an arbitrary EC P-256 private key — used to
932
+ // craft adversarial attestation / PoP tokens the public builders refuse to
933
+ // emit (missing sub / cnf, forbidden claim shapes, expired PoP). ieee-p1363
934
+ // matches the attestation verifier's ES256 params so the crafted signature
935
+ // VERIFIES and the code reaches the semantic claim checks under test.
936
+ function _signEs256(privateKey, header, payload) {
937
+ var input = _b64urlJson(header) + "." + _b64urlJson(payload);
938
+ var sig = crypto.sign("sha256", Buffer.from(input, "ascii"), { key: privateKey, dsaEncoding: "ieee-p1363" });
939
+ return input + "." + sig.toString("base64url");
940
+ }
941
+
942
+ // Flip one signature byte on a compact JWS (keeps length so verify RETURNS
943
+ // false rather than throwing).
944
+ function _tamperJws(jws) {
945
+ var p = jws.split(".");
946
+ var s = Buffer.from(p[2], "base64url");
947
+ s[0] = s[0] ^ 0xff;
948
+ return p[0] + "." + p[1] + "." + s.toString("base64url");
949
+ }
950
+
951
+ // b.auth.oauth.buildClientAttestation / buildClientAttestationPop /
952
+ // verifyClientAttestation / clientAttestationHeaders — the attestation-based
953
+ // client-authentication surface (draft-ietf-oauth-attestation-based-client-
954
+ // auth-08). Pure crypto, no network: covers the key/alg resolution error
955
+ // wrappers, the _verifyAttestationJws adversarial-shape refusals, every
956
+ // verifier semantic gate (sub / cnf / nbf / client_id / PoP aud+jti+iat+exp /
957
+ // challenge / jti-replay), the async seenJti store, and the full valid
958
+ // round-trip return.
959
+ async function scenarioAttestationVerify() {
960
+ var attKp = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
961
+ var instKp = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
962
+ var attPub = attKp.publicKey.export({ format: "jwk" });
963
+ var instPub = instKp.publicKey.export({ format: "jwk" });
964
+ var AUD = "https://as.example";
965
+ var now = Math.floor(Date.now() / 1000);
966
+
967
+ // ---- buildClientAttestation: key + alg resolution error wrappers ----
968
+ rejects("buildClientAttestation: unusable attester key string refused (bad-key wrapper)",
969
+ function () { X.buildClientAttestation({ clientId: "w", attesterPrivateKey: "not-a-pem-key",
970
+ instanceKeyJwk: instPub }); }, "auth-oauth/attestation-bad-key");
971
+ rejects("buildClientAttestation: empty attester key string refused (no-key wrapper)",
972
+ function () { X.buildClientAttestation({ clientId: "w", attesterPrivateKey: "",
973
+ instanceKeyJwk: instPub }); }, "auth-oauth/attestation-no-key");
974
+ rejects("buildClientAttestation: symmetric algorithm refused (attestation alg allowlist)",
975
+ function () { X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey,
976
+ instanceKeyJwk: instPub, algorithm: "HS256" }); }, "auth-oauth/attestation-alg-not-accepted");
977
+ rejects("buildClientAttestation: RS256 alg on an EC attester key refused (alg/key mismatch)",
978
+ function () { X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey,
979
+ instanceKeyJwk: instPub, algorithm: "RS256" }); }, "auth-oauth/attestation-alg-key-mismatch");
980
+ rejects("buildClientAttestation: key-agreement (X25519) attester key refused (cannot sign)",
981
+ function () { X.buildClientAttestation({ clientId: "w",
982
+ attesterPrivateKey: crypto.generateKeyPairSync("x25519").privateKey,
983
+ instanceKeyJwk: instPub }); }, "auth-oauth/attestation-key-unsupported");
984
+
985
+ // ---- _verifyAttestationJws: adversarial attestation shapes ----
986
+ // (verifyClientAttestation runs the attestation JWS through it first; the
987
+ // pop arg is irrelevant because each refusal fires before the pop verify.)
988
+ var vopts = { attesterJwk: attPub, expectedAudience: AUD };
989
+ var dummyPop = "x.y.z";
990
+ await arejects("verifyClientAttestation: non-string attestation refused",
991
+ function () { return X.verifyClientAttestation(123, dummyPop, vopts); }, "auth-oauth/attestation-malformed");
992
+ await arejects("verifyClientAttestation: empty attestation refused",
993
+ function () { return X.verifyClientAttestation("", dummyPop, vopts); }, "auth-oauth/attestation-malformed");
994
+ await arejects("verifyClientAttestation: oversized attestation refused (header cap)",
995
+ function () { return X.verifyClientAttestation("a".repeat(17000), dummyPop, vopts); }, "auth-oauth/attestation-too-large");
996
+ await arejects("verifyClientAttestation: 5-segment JWE attestation refused",
997
+ function () { return X.verifyClientAttestation("a.b.c.d.e", dummyPop, vopts); }, "auth-oauth/attestation-jwe-refused");
998
+ await arejects("verifyClientAttestation: non-3-segment attestation refused",
999
+ function () { return X.verifyClientAttestation("a.b", dummyPop, vopts); }, "auth-oauth/attestation-malformed");
1000
+ await arejects("verifyClientAttestation: undecodable attestation header refused",
1001
+ function () { return X.verifyClientAttestation("!!!." + _b64urlJson({}) + ".sig", dummyPop, vopts); }, "auth-oauth/attestation-malformed");
1002
+ await arejects("verifyClientAttestation: attestation header missing alg refused",
1003
+ function () { return X.verifyClientAttestation(_b64urlJson({ typ: "x" }) + "." + _b64urlJson({}) + ".sig", dummyPop, vopts); },
1004
+ "auth-oauth/attestation-malformed");
1005
+ await arejects("verifyClientAttestation: attestation alg not in allowlist refused",
1006
+ function () { return X.verifyClientAttestation(_b64urlJson({ alg: "HS256" }) + "." + _b64urlJson({}) + ".sig", dummyPop, vopts); },
1007
+ "auth-oauth/attestation-alg-not-accepted");
1008
+ await arejects("verifyClientAttestation: attestation crit header refused (RFC 7515 §4.1.11)",
1009
+ function () { return X.verifyClientAttestation(_b64urlJson({ alg: "ES256", crit: ["x"] }) + "." + _b64urlJson({}) + ".sig", dummyPop, vopts); },
1010
+ "auth-oauth/attestation-crit-not-supported");
1011
+ var goodShapeAtt = _signEs256(attKp.privateKey, { alg: "ES256", typ: "oauth-client-attestation+jwt" },
1012
+ { sub: "w", cnf: { jwk: instPub }, iat: now, exp: now + 300 });
1013
+ await arejects("verifyClientAttestation: tampered attestation signature refused (verify → false)",
1014
+ function () { return X.verifyClientAttestation(_tamperJws(goodShapeAtt), dummyPop, vopts); },
1015
+ "auth-oauth/attestation-bad-signature");
1016
+
1017
+ // ---- verifyClientAttestation: semantic claim gates ----
1018
+ var att = X.buildClientAttestation({ clientId: "wallet", attesterPrivateKey: attKp.privateKey, instanceKeyJwk: instPub });
1019
+ var pop = X.buildClientAttestationPop({ instancePrivateKey: instKp.privateKey, audience: AUD });
1020
+
1021
+ // Explicit typing (RFC 8725 §3.11 / draft §6): a JWT with the right claims +
1022
+ // a valid signature by the right key but the WRONG `typ` (a bare "JWT" that a
1023
+ // co-signing key — e.g. a private_key_jwt client assertion — could also mint)
1024
+ // MUST be refused, not repurposed into the attestation / PoP slot. Both roles
1025
+ // are checked; the attestation carries its full valid claim set so only the
1026
+ // typ mismatch can fire, and the PoP is verified after a well-typed
1027
+ // attestation so the PoP typ gate is what refuses.
1028
+ var wrongTypAtt = _signEs256(attKp.privateKey, { alg: "ES256", typ: "JWT" },
1029
+ { sub: "wallet", cnf: { jwk: instPub }, iat: now, exp: now + 300 });
1030
+ await arejects("verifyClientAttestation: attestation with wrong typ refused (RFC 8725 §3.11 explicit typing)",
1031
+ function () { return X.verifyClientAttestation(wrongTypAtt, pop, vopts); },
1032
+ "auth-oauth/attestation-wrong-typ");
1033
+ var wrongTypPop = _signEs256(instKp.privateKey, { alg: "ES256", typ: "JWT" },
1034
+ { aud: AUD, jti: "typ-pop-1", iat: now });
1035
+ await arejects("verifyClientAttestation: PoP with wrong typ refused (RFC 8725 §3.11 explicit typing)",
1036
+ function () { return X.verifyClientAttestation(att, wrongTypPop, vopts); },
1037
+ "auth-oauth/attestation-wrong-typ");
1038
+
1039
+ // These hand-crafted attestations carry the correct `typ` so each reaches the
1040
+ // SEMANTIC gate under test — the explicit-typing gate (exercised separately
1041
+ // below) would otherwise fire first.
1042
+ var noSubAtt = _signEs256(attKp.privateKey, { alg: "ES256", typ: "oauth-client-attestation+jwt" }, { cnf: { jwk: instPub }, iat: now, exp: now + 300 });
1043
+ await arejects("verifyClientAttestation: attestation missing sub refused",
1044
+ function () { return X.verifyClientAttestation(noSubAtt, pop, vopts); }, "auth-oauth/attestation-no-sub");
1045
+ var noCnfAtt = _signEs256(attKp.privateKey, { alg: "ES256", typ: "oauth-client-attestation+jwt" }, { sub: "w", iat: now, exp: now + 300 });
1046
+ await arejects("verifyClientAttestation: attestation missing cnf.jwk refused (RFC 7800)",
1047
+ function () { return X.verifyClientAttestation(noCnfAtt, pop, vopts); }, "auth-oauth/attestation-no-cnf");
1048
+ var nbfAtt = _signEs256(attKp.privateKey, { alg: "ES256", typ: "oauth-client-attestation+jwt" }, { sub: "w", cnf: { jwk: instPub }, iat: now, exp: now + 300, nbf: now + 100000 });
1049
+ await arejects("verifyClientAttestation: attestation nbf in the future refused",
1050
+ function () { return X.verifyClientAttestation(nbfAtt, pop, vopts); }, "auth-oauth/attestation-not-yet-valid");
1051
+ await arejects("verifyClientAttestation: expectedClientId != attestation sub refused (draft §8 step 10)",
1052
+ function () { return X.verifyClientAttestation(att, pop, { attesterJwk: attPub, expectedAudience: AUD, expectedClientId: "someone-else" }); },
1053
+ "auth-oauth/attestation-client-id-mismatch");
1054
+
1055
+ var popWrongAud = X.buildClientAttestationPop({ instancePrivateKey: instKp.privateKey, audience: "https://other.example" });
1056
+ await arejects("verifyClientAttestation: PoP aud != expectedAudience refused (draft §8 step 7)",
1057
+ function () { return X.verifyClientAttestation(att, popWrongAud, vopts); }, "auth-oauth/attestation-pop-aud-mismatch");
1058
+ var popNoJti = _signEs256(instKp.privateKey, { alg: "ES256", typ: "oauth-client-attestation-pop+jwt" }, { aud: AUD, iat: now });
1059
+ await arejects("verifyClientAttestation: PoP missing jti refused",
1060
+ function () { return X.verifyClientAttestation(att, popNoJti, vopts); }, "auth-oauth/attestation-pop-no-jti");
1061
+ var popNoIat = _signEs256(instKp.privateKey, { alg: "ES256", typ: "oauth-client-attestation-pop+jwt" }, { aud: AUD, jti: "j1" });
1062
+ await arejects("verifyClientAttestation: PoP missing iat refused",
1063
+ function () { return X.verifyClientAttestation(att, popNoIat, vopts); }, "auth-oauth/attestation-pop-no-iat");
1064
+ var popExpired = _signEs256(instKp.privateKey, { alg: "ES256", typ: "oauth-client-attestation-pop+jwt" }, { aud: AUD, jti: "j2", iat: now, exp: now - 1000 });
1065
+ await arejects("verifyClientAttestation: PoP with exp in the past refused",
1066
+ function () { return X.verifyClientAttestation(att, popExpired, vopts); }, "auth-oauth/attestation-pop-expired");
1067
+ await arejects("verifyClientAttestation: server challenge unmatched by PoP refused (draft §8 step 5/6)",
1068
+ function () { return X.verifyClientAttestation(att, pop, { attesterJwk: attPub, expectedAudience: AUD, challenge: "srv-nonce" }); },
1069
+ "auth-oauth/attestation-pop-challenge-mismatch");
1070
+ await arejects("verifyClientAttestation: seenJti reporting replay (falsy) refused (draft §12.1)",
1071
+ function () { return X.verifyClientAttestation(att, pop, { attesterJwk: attPub, expectedAudience: AUD, seenJti: function () { return 0; } }); },
1072
+ "auth-oauth/attestation-pop-replay");
1073
+ await arejects("verifyClientAttestation: async seenJti resolving replay refused",
1074
+ function () { return X.verifyClientAttestation(att, pop, { attesterJwk: attPub, expectedAudience: AUD, seenJti: function () { return Promise.resolve(0); } }); },
1075
+ "auth-oauth/attestation-pop-replay");
1076
+
1077
+ // ---- full valid round-trip (the success return) ----
1078
+ var challPop = X.buildClientAttestationPop({ instancePrivateKey: instKp.privateKey, audience: AUD, challenge: "srv-nonce" });
1079
+ var okv = await aresolves("verifyClientAttestation: valid attestation + PoP verifies (numeric skew/maxAge, async seenJti)",
1080
+ function () { return X.verifyClientAttestation(att, challPop, { attesterJwk: attPub, expectedAudience: AUD,
1081
+ expectedClientId: "wallet", challenge: "srv-nonce", maxPopAgeSec: 600, clockSkewSec: 30,
1082
+ seenJti: function () { return Promise.resolve(true); } }); });
1083
+ check("verifyClientAttestation: surfaces clientId + cnfJwk + attestation + pop",
1084
+ okv && okv.clientId === "wallet" && okv.cnfJwk && okv.cnfJwk.kty === "EC" && okv.attestation && okv.pop);
1085
+
1086
+ // ---- clientAttestationHeaders: builds BOTH headers, verifies end-to-end ----
1087
+ var hdrClient = X.create({ clientId: "wallet", redirectUri: "https://rp.example/cb", isOidc: false });
1088
+ var pair = hdrClient.clientAttestationHeaders({ attesterPrivateKey: attKp.privateKey,
1089
+ instanceKeyJwk: instPub, instancePrivateKey: instKp.privateKey, audience: AUD,
1090
+ challenge: "hdr-challenge", expiresInSec: 120, popExpiresInSec: 90 });
1091
+ check("clientAttestationHeaders: emits both attestation + PoP header JWTs",
1092
+ pair && pair.headers["OAuth-Client-Attestation"] === pair.attestation &&
1093
+ pair.headers["OAuth-Client-Attestation-PoP"] === pair.pop);
1094
+ var hv = await aresolves("clientAttestationHeaders: emitted pair verifies against attester + cnf keys",
1095
+ function () { return X.verifyClientAttestation(pair.attestation, pair.pop, { attesterJwk: attPub,
1096
+ expectedAudience: AUD, expectedClientId: "wallet", challenge: "hdr-challenge" }); });
1097
+ check("clientAttestationHeaders: round-trip surfaces verified clientId", hv && hv.clientId === "wallet");
1098
+
1099
+ // ---- _publicCnfJwk: every asymmetric instance-key type (EC above; RSA/OKP) ----
1100
+ // The cnf claim MUST carry public halves only — a private component never
1101
+ // reaches it.
1102
+ var rsaPub = crypto.generateKeyPairSync("rsa", { modulusLength: 2048 }).publicKey.export({ format: "jwk" });
1103
+ var attRsa = X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey, instanceKeyJwk: rsaPub });
1104
+ var cnfRsa = JSON.parse(Buffer.from(attRsa.split(".")[1], "base64url").toString("utf8")).cnf.jwk;
1105
+ check("buildClientAttestation: RSA instance key shaped into cnf (n/e only, no private half)",
1106
+ cnfRsa.kty === "RSA" && cnfRsa.n && cnfRsa.e && cnfRsa.d === undefined);
1107
+ var okpPub = crypto.generateKeyPairSync("ed25519").publicKey.export({ format: "jwk" });
1108
+ var attOkp = X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey, instanceKeyJwk: okpPub });
1109
+ var cnfOkp = JSON.parse(Buffer.from(attOkp.split(".")[1], "base64url").toString("utf8")).cnf.jwk;
1110
+ check("buildClientAttestation: OKP instance key shaped into cnf (x only, no private half)",
1111
+ cnfOkp.kty === "OKP" && cnfOkp.x && cnfOkp.d === undefined);
1112
+
1113
+ // ---- EdDSA end-to-end (ATTESTATION_ALGS advertises EdDSA; the EC/RSA
1114
+ // paths above never exercise the alg=EdDSA verify branch: hash:null crypto
1115
+ // params + the OKP arm of the alg/kty cross-check + node's Ed25519 verify).
1116
+ // Both the Attester and the instance key are Ed25519, so the attestation
1117
+ // JWS AND the PoP JWS travel the EdDSA verify path.
1118
+ var edAttKp = crypto.generateKeyPairSync("ed25519");
1119
+ var edInstKp = crypto.generateKeyPairSync("ed25519");
1120
+ var edAttPub = edAttKp.publicKey.export({ format: "jwk" });
1121
+ var edInstPub = edInstKp.publicKey.export({ format: "jwk" });
1122
+ var edAtt = X.buildClientAttestation({ clientId: "ed-wallet",
1123
+ attesterPrivateKey: edAttKp.privateKey, instanceKeyJwk: edInstPub });
1124
+ check("buildClientAttestation: Ed25519 attester key infers an EdDSA-header JWS",
1125
+ JSON.parse(Buffer.from(edAtt.split(".")[0], "base64url").toString("utf8")).alg === "EdDSA");
1126
+ var edPop = X.buildClientAttestationPop({ instancePrivateKey: edInstKp.privateKey, audience: AUD });
1127
+ var edV = await aresolves("verifyClientAttestation: EdDSA attestation + PoP round-trip verifies",
1128
+ function () { return X.verifyClientAttestation(edAtt, edPop, { attesterJwk: edAttPub,
1129
+ expectedAudience: AUD, expectedClientId: "ed-wallet" }); });
1130
+ check("verifyClientAttestation: EdDSA round-trip surfaces clientId + OKP cnf",
1131
+ edV && edV.clientId === "ed-wallet" && edV.cnfJwk && edV.cnfJwk.kty === "OKP");
1132
+ // A tampered EdDSA PoP signature (one flipped byte, length preserved) must
1133
+ // RETURN false from node's verify → typed bad-signature, never a silent pass.
1134
+ await arejects("verifyClientAttestation: tampered EdDSA PoP signature refused (verify -> false)",
1135
+ function () { return X.verifyClientAttestation(edAtt, _tamperJws(edPop),
1136
+ { attesterJwk: edAttPub, expectedAudience: AUD }); },
1137
+ "auth-oauth/attestation-bad-signature");
1138
+ // alg/kty cross-check on the EdDSA arm: an EdDSA header verified against a
1139
+ // NON-OKP (EC) attester JWK is refused before any signature math.
1140
+ await arejects("verifyClientAttestation: EdDSA header against an EC attester JWK refused (alg/kty)",
1141
+ function () { return X.verifyClientAttestation(edAtt, edPop,
1142
+ { attesterJwk: instPub, expectedAudience: AUD }); },
1143
+ "auth-jwt-external/alg-kty-mismatch");
1144
+ }
1145
+
1146
+ // Network-free branches on clients that reach neither discovery nor a token
1147
+ // endpoint: authorizationUrl extraParams (reserved-key guard + pass-through),
1148
+ // parseCallback iss/state mismatch (requireIssParam skips discovery), and
1149
+ // parseFrontchannelLogoutRequest's malformed-URL refusal.
1150
+ async function scenarioOfflineExtras() {
1151
+ var ghx = X.create({ provider: "github", clientId: "gh-x", redirectUri: "https://rp.example/cb" });
1152
+ var extra = await ghx.authorizationUrl({ extraParams: { audience: "https://api.example", resource: "https://rs.example" } });
1153
+ var eu = new URL(extra.url);
1154
+ check("authorizationUrl: non-reserved extraParams appended verbatim",
1155
+ eu.searchParams.get("audience") === "https://api.example" && eu.searchParams.get("resource") === "https://rs.example");
1156
+ await arejects("authorizationUrl: extraParams colliding with a reserved key refused",
1157
+ function () { return ghx.authorizationUrl({ extraParams: { state: "smuggled" } }); },
1158
+ "auth-oauth/reserved-extra-param");
1159
+
1160
+ var sc = _staticOidcClient(); // issuer https://idp.example
1161
+ await arejects("parseCallback: callback iss != configured issuer refused (RFC 9207 mix-up)",
1162
+ function () { return sc.parseCallback({ code: "c", iss: "https://evil.example" }, { requireIssParam: true }); },
1163
+ "auth-oauth/iss-mismatch-callback");
1164
+ await arejects("parseCallback: state mismatch refused (CSRF defense)",
1165
+ function () { return sc.parseCallback({ code: "c", state: "wrong", iss: "https://idp.example" },
1166
+ { requireIssParam: true, expectedState: "right" }); }, "auth-oauth/state-mismatch");
1167
+
1168
+ rejects("parseFrontchannelLogoutRequest: malformed request URL refused",
1169
+ function () { sc.parseFrontchannelLogoutRequest({ url: "http://a b" }); },
1170
+ "auth-oauth/bad-frontchannel-logout-url");
1171
+ }
1172
+
1173
+ // PAR (RFC 9126). Plain form path (authorization_details as a JSON-string
1174
+ // form param + extraParams) and the RFC 9101 signed-request-object (JAR) path
1175
+ // where the same parameters travel as request-object claims and the form body
1176
+ // carries only `request` + client auth.
1177
+ async function scenarioParFlows(base, routes) {
1178
+ routes["/jwks"] = { json: { keys: [PUBJWK] } };
1179
+ var jarKp = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
1180
+ var oa = mk(base, "cov2-par-flow", { pushedAuthorizationRequestEndpoint: base + "/par" });
1181
+ routes["/par"] = { status: 201, json: { request_uri: "urn:ietf:params:oauth:request_uri:abc", expires_in: 90 } };
1182
+
1183
+ var par = await aresolves("pushAuthorizationRequest: plain flow returns request_uri + redirect URL",
1184
+ function () { return oa.pushAuthorizationRequest({
1185
+ authorizationDetails: [{ type: "payment_initiation", actions: ["initiate"] }],
1186
+ extraParams: { audience: "https://api.example" },
1187
+ prompt: "consent", loginHint: "u@x", maxAge: 120 }); });
1188
+ check("pushAuthorizationRequest: request_uri + expiresIn surfaced, plain path",
1189
+ par && par.requestUri === "urn:ietf:params:oauth:request_uri:abc" && par.expiresIn === 90 &&
1190
+ par.url.indexOf("request_uri=") !== -1 && par.requestObjectSent === false);
1191
+
1192
+ await athrows("pushAuthorizationRequest: extraParams colliding with a reserved key refused",
1193
+ function () { return oa.pushAuthorizationRequest({ extraParams: { redirect_uri: "https://evil.example" } }); },
1194
+ "auth-oauth/reserved-extra-param");
1195
+
1196
+ var sro = await aresolves("pushAuthorizationRequest: signed-request-object (JAR) flow returns request_uri",
1197
+ function () { return oa.pushAuthorizationRequest({
1198
+ signedRequestObject: { key: jarKp.privateKey },
1199
+ authorizationDetails: [{ type: "payment_initiation", actions: ["initiate"] }] }); });
1200
+ check("pushAuthorizationRequest: requestObjectSent flag set on the JAR path",
1201
+ sro && sro.requestObjectSent === true && sro.requestUri === "urn:ietf:params:oauth:request_uri:abc");
1202
+ }
1203
+
1204
+ // b.auth.oauth fetches go through the shared b.httpClient keep-alive agent;
1205
+ // its cached sockets (and the mock server) would otherwise keep the forked
1206
+ // worker's event loop open. Drain them inside run().
1207
+ async function _drainTcpHandles() {
1208
+ b.httpClient._resetForTest();
1209
+ if (typeof process.getActiveResourcesInfo !== "function") return;
1210
+ await helpers.waitUntil(function () {
1211
+ return process.getActiveResourcesInfo().filter(function (t) {
1212
+ return t === "TCPSocketWrap" || t === "TCPServerWrap";
1213
+ }).length === 0;
1214
+ }, { timeoutMs: 5000, label: "auth-oauth: TCP handle drain after _resetForTest" });
1215
+ }
1216
+
1217
+ async function run() {
1218
+ // ---- module surface ----
1219
+ check("oauth.create is fn", typeof X.create === "function");
1220
+ check("oauth.OAuthError is fn", typeof X.OAuthError === "function");
1221
+ check("oauth.PRESETS frozen", Object.isFrozen(X.PRESETS));
1222
+ check("oauth.PRESETS has the 7 vendors",
1223
+ ["google", "microsoft", "apple", "auth0", "keycloak", "github", "generic"]
1224
+ .every(function (p) { return Object.prototype.hasOwnProperty.call(X.PRESETS, p); }));
1225
+ check("oauth.DEFAULT_ACCEPTED_ALGS excludes HS256",
1226
+ X.DEFAULT_ACCEPTED_ALGS.indexOf("HS256") === -1 && X.DEFAULT_ACCEPTED_ALGS.indexOf("RS256") !== -1);
1227
+ check("oauth.ATTESTATION_ALGS includes EdDSA, excludes HS256",
1228
+ X.ATTESTATION_ALGS.indexOf("EdDSA") !== -1 && X.ATTESTATION_ALGS.indexOf("HS256") === -1);
1229
+
1230
+ // ---- create(): required-arg + posture gating ----
1231
+ rejects("create: pkce:false refused (OAuth 2.1)",
1232
+ function () { X.create({ clientId: "a", redirectUri: "https://x/cb", pkce: false }); },
1233
+ "auth-oauth/pkce-required");
1234
+ rejects("create: missing clientId refused",
1235
+ function () { X.create({ redirectUri: "https://x/cb" }); }, "auth-oauth/no-client-id");
1236
+ rejects("create: missing redirectUri refused",
1237
+ function () { X.create({ clientId: "a" }); }, "auth-oauth/no-redirect-uri");
1238
+ rejects("create: unknown provider preset refused",
1239
+ function () { X.create({ clientId: "a", redirectUri: "https://x/cb", provider: "nope" }); },
1240
+ "auth-oauth/unknown-provider");
1241
+ rejects("create: non-finite clockSkewMs refused (Infinity would disable exp gate)",
1242
+ function () { X.create({ clientId: "a", redirectUri: "https://x/cb", clockSkewMs: Infinity }); },
1243
+ "auth-oauth/bad-clock-skew");
1244
+ rejects("create: NaN clockSkewMs refused",
1245
+ function () { X.create({ clientId: "a", redirectUri: "https://x/cb", clockSkewMs: NaN }); },
1246
+ "auth-oauth/bad-clock-skew");
1247
+
1248
+ // ---- _validateUrl via redirectUri (scheme allowlist + localhost rule) ----
1249
+ rejects("create: http:// non-localhost redirect refused",
1250
+ function () { X.create({ clientId: "a", redirectUri: "http://evil.example/cb" }); },
1251
+ "auth-oauth/insecure-url");
1252
+ rejects("create: javascript: redirect refused",
1253
+ function () { X.create({ clientId: "a", redirectUri: "javascript:alert(1)" }); },
1254
+ "auth-oauth/insecure-url");
1255
+ check("create: http://localhost redirect accepted (dev exception)",
1256
+ !!X.create({ clientId: "a", redirectUri: "http://localhost:3000/cb" }));
1257
+ check("create: http://127.0.0.1 redirect accepted (dev exception)",
1258
+ !!X.create({ clientId: "a", redirectUri: "http://127.0.0.1/cb" }));
1259
+ check("create: http://[::1] redirect accepted (dev exception)",
1260
+ !!X.create({ clientId: "a", redirectUri: "http://[::1]:8080/cb" }));
1261
+ // RFC 1034 trailing-root-dot must still map to the loopback exception,
1262
+ // NOT slip an attacker-registered public 'localhost.' issuer past the gate.
1263
+ check("create: http://localhost./ trailing-dot accepted as loopback",
1264
+ !!X.create({ clientId: "a", redirectUri: "http://localhost./cb" }));
1265
+
1266
+ // ---- create(): preset issuer templating ----
1267
+ rejects("create: auth0 preset without auth0Domain refused",
1268
+ function () { X.create({ clientId: "a", redirectUri: "https://x/cb", provider: "auth0" }); },
1269
+ "auth-oauth/auth0-domain");
1270
+ rejects("create: keycloak preset without url/realm refused",
1271
+ function () { X.create({ clientId: "a", redirectUri: "https://x/cb", provider: "keycloak" }); },
1272
+ "auth-oauth/keycloak-config");
1273
+ var a0 = X.create({ clientId: "a", redirectUri: "https://x/cb", provider: "auth0", auth0Domain: "t.auth0.com" });
1274
+ check("create: auth0 preset expands issuer", a0.issuer === "https://t.auth0.com");
1275
+ var kc = X.create({ clientId: "a", redirectUri: "https://x/cb",
1276
+ provider: "keycloak", keycloakUrl: "https://kc.example/", keycloakRealm: "r" });
1277
+ check("create: keycloak preset expands issuer + strips trailing slash",
1278
+ kc.issuer === "https://kc.example/realms/r");
1279
+ var gh = X.create({ provider: "github", clientId: "gh", redirectUri: "https://rp.example/cb" });
1280
+ check("create: github preset is non-OIDC", gh.isOidc === false && gh.issuer === null);
1281
+
1282
+ // ---- helper: _verifyParamsForAlg ----
1283
+ check("_verifyParamsForAlg RS256 → RSA PKCS1 sha256",
1284
+ X._verifyParamsForAlg("RS256").hash === "sha256" && X._verifyParamsForAlg("RS256").padding !== undefined);
1285
+ check("_verifyParamsForAlg PS256 → PSS saltLength set",
1286
+ X._verifyParamsForAlg("PS256").saltLength === 32);
1287
+ check("_verifyParamsForAlg ES256 → ieee-p1363 dsaEncoding",
1288
+ X._verifyParamsForAlg("ES256").dsaEncoding === "ieee-p1363");
1289
+ rejects("_verifyParamsForAlg EdDSA refused for ID-token verify",
1290
+ function () { X._verifyParamsForAlg("EdDSA"); }, "auth-oauth/unsupported-alg");
1291
+ rejects("_verifyParamsForAlg HS256 refused (no symmetric ID tokens)",
1292
+ function () { X._verifyParamsForAlg("HS256"); }, "auth-oauth/unsupported-alg");
1293
+
1294
+ // ---- helper: base64url + PKCE ----
1295
+ check("_b64urlEncode/_b64urlDecode round-trips",
1296
+ X._b64urlDecode(X._b64urlEncode(Buffer.from("hello-oauth"))).toString("utf8") === "hello-oauth");
1297
+ rejects("_b64urlDecode refuses non-base64url input",
1298
+ function () { X._b64urlDecode("@@@@"); }, "auth-oauth/bad-base64");
1299
+ var pkce = X._generatePkce();
1300
+ check("_generatePkce: verifier is 43 base64url chars (RFC 7636 32-byte)", pkce.verifier.length === 43);
1301
+ check("_generatePkce: challenge is base64url(SHA-256(verifier))",
1302
+ pkce.challenge === crypto.createHash("sha256").update(pkce.verifier).digest("base64url"));
1303
+ check("_generateRandomToken(16) → 22 base64url chars (128-bit)",
1304
+ X._generateRandomToken(16).length === 22);
1305
+
1306
+ // ---- verifyIdToken: pre-JWKS refusals (no network reached) ----
1307
+ var oa = _staticOidcClient();
1308
+ await arejects("verifyIdToken: non-string refused",
1309
+ function () { return oa.verifyIdToken(123); }, "auth-oauth/no-id-token");
1310
+ await arejects("verifyIdToken: 5-segment JWE refused (CVE-2026-29000 class)",
1311
+ function () { return oa.verifyIdToken("a.b.c.d.e"); }, "auth-oauth/jwe-refused");
1312
+ await arejects("verifyIdToken: 2-segment token refused",
1313
+ function () { return oa.verifyIdToken("a.b"); }, "auth-oauth/malformed-jwt");
1314
+ await arejects("verifyIdToken: undecodable header refused",
1315
+ function () { return oa.verifyIdToken("!!!." + _b64url({}) + ".sig"); }, "auth-oauth/malformed-jwt");
1316
+ await arejects("verifyIdToken: header missing alg refused",
1317
+ function () { return oa.verifyIdToken(_b64url({ typ: "JWT" }) + "." + _b64url({}) + ".sig"); },
1318
+ "auth-oauth/malformed-jwt");
1319
+ await arejects("verifyIdToken: alg not in accepted list refused before key lookup",
1320
+ function () { return oa.verifyIdToken(_b64url({ alg: "HS256" }) + "." + _b64url({}) + ".sig"); },
1321
+ "auth-oauth/alg-not-accepted");
1322
+ await arejects("verifyIdToken: crit header refused (RFC 7515 §4.1.11)",
1323
+ function () { return oa.verifyIdToken(_b64url({ alg: "RS256", crit: ["x"] }) + "." + _b64url({}) + ".sig"); },
1324
+ "auth-oauth/crit-not-supported");
1325
+
1326
+ // ---- authorizationUrl: offline (static endpoints / non-OIDC) ----
1327
+ var ghAuth = await gh.authorizationUrl();
1328
+ var ghUrl = new URL(ghAuth.url);
1329
+ check("authorizationUrl: non-OIDC emits no nonce", ghAuth.nonce === null);
1330
+ check("authorizationUrl: always S256 PKCE",
1331
+ ghUrl.searchParams.get("code_challenge_method") === "S256" &&
1332
+ ghUrl.searchParams.get("code_challenge") === ghAuth.challenge);
1333
+ check("authorizationUrl: verifier is the 43-char PKCE verifier", ghAuth.verifier.length === 43);
1334
+ check("authorizationUrl: carries response_type/client_id/redirect_uri/state",
1335
+ ghUrl.searchParams.get("response_type") === "code" &&
1336
+ ghUrl.searchParams.get("client_id") === "gh" &&
1337
+ ghUrl.searchParams.get("redirect_uri") === "https://rp.example/cb" &&
1338
+ ghUrl.searchParams.get("state") === ghAuth.state);
1339
+
1340
+ var ghAuth2 = await gh.authorizationUrl({ prompt: "consent", loginHint: "a@b.co", maxAge: 60 });
1341
+ var ghUrl2 = new URL(ghAuth2.url);
1342
+ check("authorizationUrl: prompt/login_hint/max_age threaded through",
1343
+ ghUrl2.searchParams.get("prompt") === "consent" &&
1344
+ ghUrl2.searchParams.get("login_hint") === "a@b.co" &&
1345
+ ghUrl2.searchParams.get("max_age") === "60");
1346
+
1347
+ var ghState = await gh.authorizationUrl({ state: "given-state", nonce: "given-nonce" });
1348
+ check("authorizationUrl: caller-supplied state is used verbatim",
1349
+ ghState.state === "given-state" && new URL(ghState.url).searchParams.get("state") === "given-state");
1350
+
1351
+ // static OIDC client: nonce present, discovery NOT fetched (static endpoint)
1352
+ var oidcAuth = await oa.authorizationUrl();
1353
+ check("authorizationUrl: OIDC client emits a fresh nonce",
1354
+ typeof oidcAuth.nonce === "string" && oidcAuth.nonce.length > 0 &&
1355
+ new URL(oidcAuth.url).searchParams.get("nonce") === oidcAuth.nonce);
1356
+
1357
+ // separator selection when the endpoint already carries a query string
1358
+ var oaQ = _staticOidcClient({ authorizationEndpoint: "https://idp.example/auth?foo=1" });
1359
+ var qAuth = await oaQ.authorizationUrl();
1360
+ check("authorizationUrl: appends with & when endpoint has an existing query",
1361
+ qAuth.url.indexOf("/auth?foo=1&") !== -1);
1362
+
1363
+ // responseMode threaded (github + explicit opt, offline)
1364
+ var ghRm = X.create({ provider: "github", clientId: "gh", redirectUri: "https://rp.example/cb", responseMode: "form_post" });
1365
+ check("authorizationUrl: response_mode from opts emitted",
1366
+ new URL((await ghRm.authorizationUrl()).url).searchParams.get("response_mode") === "form_post");
1367
+
1368
+ // An explicit create() scope array overrides the preset default and is
1369
+ // reflected verbatim (space-joined) in the authorization request.
1370
+ var ghScoped = X.create({ provider: "github", clientId: "gh-scope",
1371
+ redirectUri: "https://rp.example/cb", scope: ["read:user", "repo", "gist"] });
1372
+ check("authorizationUrl: explicit create() scope array reflected space-joined",
1373
+ new URL((await ghScoped.authorizationUrl()).url).searchParams.get("scope") === "read:user repo gist" &&
1374
+ ghScoped.scope.length === 3);
1375
+
1376
+ // RFC 9396 authorization_details validation on authorizationUrl
1377
+ var rar = await gh.authorizationUrl({ authorizationDetails: [{ type: "payment_initiation", actions: ["initiate"] }] });
1378
+ check("authorizationUrl: serializes valid authorization_details",
1379
+ /[?&]authorization_details=/.test(rar.url) && Array.isArray(rar.authorizationDetails));
1380
+ await arejects("authorizationUrl: non-array authorization_details refused",
1381
+ function () { return gh.authorizationUrl({ authorizationDetails: "nope" }); },
1382
+ "auth-oauth/bad-authorization-details");
1383
+ await arejects("authorizationUrl: entry missing type refused",
1384
+ function () { return gh.authorizationUrl({ authorizationDetails: [{ noType: 1 }] }); },
1385
+ "auth-oauth/bad-authorization-details");
1386
+ await arejects("authorizationUrl: non-object entry refused",
1387
+ function () { return gh.authorizationUrl({ authorizationDetails: [42] }); },
1388
+ "auth-oauth/bad-authorization-details");
1389
+
1390
+ // ---- exchangeCode: required-arg + OIDC nonce gate ----
1391
+ await arejects("exchangeCode: missing code refused",
1392
+ function () { return oa.exchangeCode({}); }, "auth-oauth/no-code");
1393
+ await arejects("exchangeCode: missing verifier refused when PKCE on",
1394
+ function () { return oa.exchangeCode({ code: "c" }); }, "auth-oauth/no-verifier");
1395
+ await arejects("exchangeCode: OIDC flow without nonce refused",
1396
+ function () { return oa.exchangeCode({ code: "c", verifier: "v" }); }, "auth-oauth/no-nonce");
1397
+ await arejects("exchangeCode: empty-string nonce refused (falsy slips === undefined guard)",
1398
+ function () { return oa.exchangeCode({ code: "c", verifier: "v", nonce: "" }); }, "auth-oauth/no-nonce");
1399
+
1400
+ // ---- refreshAccessToken: replay defense (correct-shape callbacks) ----
1401
+ await arejects("refreshAccessToken: missing token refused",
1402
+ function () { return oa.refreshAccessToken(""); }, "auth-oauth/no-refresh-token");
1403
+ await arejects("refreshAccessToken: seen()===true refuses replay",
1404
+ function () { return oa.refreshAccessToken("rt", { seen: function () { return true; } }); },
1405
+ "auth-oauth/refresh-token-replay");
1406
+ await arejects("refreshAccessToken: checkAndInsert()===false refuses replay",
1407
+ function () { return oa.refreshAccessToken("rt", { checkAndInsert: function () { return false; } }); },
1408
+ "auth-oauth/refresh-token-replay");
1409
+ await arejects("refreshAccessToken: checkAndInsert throwing surfaces typed error",
1410
+ function () { return oa.refreshAccessToken("rt", { checkAndInsert: function () { throw new Error("store down"); } }); },
1411
+ "auth-oauth/seen-callback-failed");
1412
+ await arejects("refreshAccessToken: seen throwing surfaces typed error",
1413
+ function () { return oa.refreshAccessToken("rt", { seen: function () { throw new Error("store down"); } }); },
1414
+ "auth-oauth/seen-callback-failed");
1415
+
1416
+ // ---- exchangeToken / nativeSsoExchange: RFC 8693 token-type gate ----
1417
+ await arejects("exchangeToken: missing subjectToken refused",
1418
+ function () { return oa.exchangeToken({}); }, "auth-oauth/bad-exchange");
1419
+ await arejects("exchangeToken: missing subjectTokenType refused",
1420
+ function () { return oa.exchangeToken({ subjectToken: "s" }); }, "auth-oauth/bad-exchange");
1421
+ await arejects("exchangeToken: subjectTokenType outside RFC 8693 §3 refused",
1422
+ function () { return oa.exchangeToken({ subjectToken: "s", subjectTokenType: "urn:custom" }); },
1423
+ "auth-oauth/bad-subject-token-type");
1424
+ await arejects("exchangeToken: actorTokenType outside RFC 8693 §3 refused",
1425
+ function () { return oa.exchangeToken({ subjectToken: "s",
1426
+ subjectTokenType: "urn:ietf:params:oauth:token-type:access_token",
1427
+ actorToken: "a", actorTokenType: "urn:bad" }); },
1428
+ "auth-oauth/bad-actor-token-type");
1429
+ await arejects("nativeSsoExchange: missing deviceSecret refused",
1430
+ function () { return oa.nativeSsoExchange({ idToken: "x" }); }, "auth-oauth/bad-native-sso");
1431
+ await arejects("nativeSsoExchange: missing idToken refused",
1432
+ function () { return oa.nativeSsoExchange({ deviceSecret: "x" }); }, "auth-oauth/bad-native-sso");
1433
+
1434
+ // ---- device grant / introspection / revoke / userinfo required-arg ----
1435
+ await arejects("pollDeviceCode: empty deviceCode refused",
1436
+ function () { return oa.pollDeviceCode(""); }, "auth-oauth/bad-device-code");
1437
+ await arejects("pollDeviceCode: over-long deviceCode refused (amplification cap)",
1438
+ function () { return oa.pollDeviceCode("a".repeat(9000)); }, "auth-oauth/device-code-too-large");
1439
+ await arejects("introspectToken: empty token refused",
1440
+ function () { return oa.introspectToken(""); }, "auth-oauth/bad-introspect");
1441
+ await arejects("revokeToken: missing token refused",
1442
+ function () { return oa.revokeToken(""); }, "auth-oauth/no-token");
1443
+ await arejects("fetchUserInfo: missing access token refused",
1444
+ function () { return oa.fetchUserInfo(""); }, "auth-oauth/no-access-token");
1445
+ await arejects("fetchUserInfo: OIDC requires verified idTokenSub cross-check",
1446
+ function () { return oa.fetchUserInfo("at"); }, "auth-oauth/userinfo-no-id-token-sub");
1447
+
1448
+ // ---- dynamic client registration (RFC 7591/7592) required-arg ----
1449
+ await arejects("registerClient: non-object metadata refused",
1450
+ function () { return oa.registerClient(null); }, "auth-oauth/bad-register");
1451
+ await arejects("registerClient: empty redirect_uris refused (open-redirect surface)",
1452
+ function () { return oa.registerClient({}); }, "auth-oauth/register-no-redirect-uris");
1453
+ await arejects("registerClient: http redirect_uri refused",
1454
+ function () { return oa.registerClient({ redirect_uris: ["http://evil.example"] }); },
1455
+ "auth-oauth/insecure-url");
1456
+ await arejects("updateClient: non-object metadata refused",
1457
+ function () { return oa.updateClient("https://as/reg", "tok", null); }, "auth-oauth/bad-update");
1458
+ await arejects("updateClient: empty redirect_uris refused",
1459
+ function () { return oa.updateClient("https://as/reg", "tok", {}); }, "auth-oauth/update-no-redirect-uris");
1460
+ await arejects("readClient: empty registrationClientUri refused",
1461
+ function () { return oa.readClient("", "tok"); }, "auth-oauth/bad-registration-client-uri");
1462
+ await arejects("readClient: empty registrationAccessToken refused",
1463
+ function () { return oa.readClient("https://as/reg", ""); }, "auth-oauth/bad-registration-access-token");
1464
+ await arejects("deleteClient: http registrationClientUri refused",
1465
+ function () { return oa.deleteClient("http://evil/reg", "tok"); }, "auth-oauth/insecure-url");
1466
+
1467
+ // ---- endSessionUrl: offline builder (static end_session_endpoint) ----
1468
+ var esUrl = await oa.endSessionUrl({ idTokenHint: "idt", state: "s1",
1469
+ postLogoutRedirectUri: "https://rp.example/done", logoutHint: "u@x", uiLocales: "en" });
1470
+ var esU = new URL(esUrl);
1471
+ check("endSessionUrl: threads id_token_hint/state/logout_hint/ui_locales + client_id",
1472
+ esU.searchParams.get("id_token_hint") === "idt" &&
1473
+ esU.searchParams.get("state") === "s1" &&
1474
+ esU.searchParams.get("post_logout_redirect_uri") === "https://rp.example/done" &&
1475
+ esU.searchParams.get("logout_hint") === "u@x" &&
1476
+ esU.searchParams.get("ui_locales") === "en" &&
1477
+ esU.searchParams.get("client_id") === "rp-cov");
1478
+ var esNoCid = await oa.endSessionUrl({ clientId: false });
1479
+ check("endSessionUrl: clientId:false omits client_id (bare endpoint)",
1480
+ new URL(esNoCid).searchParams.get("client_id") === null);
1481
+ await arejects("endSessionUrl: javascript: post_logout_redirect_uri refused",
1482
+ function () { return oa.endSessionUrl({ postLogoutRedirectUri: "javascript:alert(1)" }); },
1483
+ "auth-oauth/insecure-url");
1484
+ await arejects("endSessionUrl: extraParams colliding with a first-class key refused",
1485
+ function () { return oa.endSessionUrl({ extraParams: { state: "smuggled" } }); },
1486
+ "auth-oauth/end-session-reserved-extra-param");
1487
+
1488
+ // ---- parseFrontchannelLogoutRequest: synchronous, no network ----
1489
+ rejects("parseFrontchannelLogoutRequest: missing req refused",
1490
+ function () { oa.parseFrontchannelLogoutRequest(null); }, "auth-oauth/bad-frontchannel-logout-req");
1491
+ var fcl = oa.parseFrontchannelLogoutRequest({ url: "/fc?iss=https%3A%2F%2Fidp.example&sid=sess-1" });
1492
+ check("parseFrontchannelLogoutRequest: matching iss returns (iss, sid)",
1493
+ fcl.iss === "https://idp.example" && fcl.sid === "sess-1");
1494
+ var fclNoIss = oa.parseFrontchannelLogoutRequest({ url: "/fc?sid=sess-2" });
1495
+ check("parseFrontchannelLogoutRequest: absent iss defaults to configured issuer, sid surfaced",
1496
+ fclNoIss.iss === "https://idp.example" && fclNoIss.sid === "sess-2");
1497
+ rejects("parseFrontchannelLogoutRequest: cross-realm iss refused (CVE-2026-23552)",
1498
+ function () { oa.parseFrontchannelLogoutRequest({ url: "/fc?iss=https%3A%2F%2Fevil.example" }); },
1499
+ "auth-oauth/frontchannel-logout-iss-mismatch");
1500
+
1501
+ // ---- parseCallback: shape guards (network-free forms) ----
1502
+ await arejects("parseCallback: non-object query refused",
1503
+ function () { return oa.parseCallback(null); }, "auth-oauth/bad-callback");
1504
+ await arejects("parseCallback: missing code refused (iss supplied + requireIssParam skips discovery)",
1505
+ function () { return oa.parseCallback({ iss: "https://idp.example" }, { requireIssParam: true }); },
1506
+ "auth-oauth/no-code-in-callback");
1507
+ await arejects("parseCallback: OP error param refused",
1508
+ function () { return oa.parseCallback({ error: "access_denied" }, { requireIssParam: true }); },
1509
+ "auth-oauth/op-error");
1510
+ // The refusal surfaces the OP-supplied error + error_description on the
1511
+ // thrown error so operators can branch on `opError` (documented fields).
1512
+ var opErr = null;
1513
+ try { await oa.parseCallback({ error: "access_denied", error_description: "user declined consent" },
1514
+ { requireIssParam: true }); } catch (e) { opErr = e; }
1515
+ check("parseCallback: op-error carries opError + opErrorDescription fields",
1516
+ opErr && opErr.code === "auth-oauth/op-error" && opErr.opError === "access_denied" &&
1517
+ opErr.opErrorDescription === "user declined consent");
1518
+
1519
+ // ---- parseJarmResponse: shape guards ----
1520
+ await arejects("parseJarmResponse: empty response refused",
1521
+ function () { return oa.parseJarmResponse(""); }, "auth-oauth/no-jarm-response");
1522
+ await arejects("parseJarmResponse: non-3-segment response refused",
1523
+ function () { return oa.parseJarmResponse("not.a.jws.token.here"); }, "auth-oauth/malformed-jarm-response");
1524
+
1525
+ // ---- RFC 9396 granted-details cross-check: edge cases ----
1526
+ check("crossCheck: null granted → null",
1527
+ X._crossCheckGrantedAuthorizationDetails(null, null, true) === null);
1528
+ check("crossCheck: undefined granted → null",
1529
+ X._crossCheckGrantedAuthorizationDetails(undefined, [{ type: "x" }], true) === null);
1530
+ check("crossCheck: no requested → granted passed through verbatim",
1531
+ JSON.stringify(X._crossCheckGrantedAuthorizationDetails([{ type: "x" }], null, true)) === JSON.stringify([{ type: "x" }]));
1532
+ rejects("crossCheck: non-array granted refused",
1533
+ function () { X._crossCheckGrantedAuthorizationDetails({ type: "x" }, null, true); },
1534
+ "auth-oauth/bad-granted-authorization-details");
1535
+ rejects("crossCheck: granted entry not a typed object refused",
1536
+ function () { X._crossCheckGrantedAuthorizationDetails([{ noType: 1 }], [{ type: "x" }], true); },
1537
+ "auth-oauth/bad-granted-authorization-details");
1538
+ var reqDetails = [{ type: "payment_initiation", actions: ["initiate", "status"], locations: ["https://rs/pay"] }];
1539
+ var subset = X._crossCheckGrantedAuthorizationDetails(
1540
+ [{ type: "payment_initiation", actions: ["status"] }], reqDetails, true);
1541
+ check("crossCheck: granted subset accepted", Array.isArray(subset) && subset.length === 1);
1542
+ rejects("crossCheck: over-granted datatypes field refused",
1543
+ function () { X._crossCheckGrantedAuthorizationDetails(
1544
+ [{ type: "payment_initiation", datatypes: ["ssn"] }], reqDetails, true); },
1545
+ "auth-oauth/authorization-details-over-grant");
1546
+ var big = [];
1547
+ for (var bi = 0; bi < 20000; bi++) big.push({ type: "payment_initiation", locations: ["https://rs.example/very/long/path/segment/" + bi] });
1548
+ rejects("crossCheck: oversized granted payload refused (parse-bomb cap)",
1549
+ function () { X._crossCheckGrantedAuthorizationDetails(big, [{ type: "payment_initiation" }], false); },
1550
+ "auth-oauth/granted-authorization-details-too-large");
1551
+ // Granted a type that was never requested → over-grant (non-strict surfaces).
1552
+ var unreqType = X._crossCheckGrantedAuthorizationDetails(
1553
+ [{ type: "never-requested" }], [{ type: "asked" }], false);
1554
+ check("crossCheck: unrequested granted type surfaced under non-strict",
1555
+ Array.isArray(unreqType) && unreqType.length === 1);
1556
+ // A granted subfield delivered as a NON-array scalar the request never
1557
+ // constrained is an over-grant (RFC 9396 §7 — AS returned a scalar where a
1558
+ // RAR array field is defined and the request omitted it).
1559
+ var scalarSub = X._crossCheckGrantedAuthorizationDetails(
1560
+ [{ type: "t", locations: "https://rs.example/one" }], [{ type: "t" }], false);
1561
+ check("crossCheck: unconstrained non-array granted subfield surfaced under non-strict",
1562
+ Array.isArray(scalarSub) && scalarSub.length === 1);
1563
+ // A granted scalar EQUAL to the requested scalar is lenient-accepted (a
1564
+ // non-conforming-but-equal AS output is not treated as broadening).
1565
+ var scalarEq = X._crossCheckGrantedAuthorizationDetails(
1566
+ [{ type: "t", actions: "read" }], [{ type: "t", actions: "read" }], true);
1567
+ check("crossCheck: matching non-array granted scalar accepted under strict",
1568
+ Array.isArray(scalarEq) && scalarEq.length === 1);
1569
+
1570
+ // ---- attestation builders: validation + claim shaping ----
1571
+ var attKp = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
1572
+ var instKp = crypto.generateKeyPairSync("ec", { namedCurve: "P-256" });
1573
+ var instPub = instKp.publicKey.export({ format: "jwk" });
1574
+ var attPub = attKp.publicKey.export({ format: "jwk" });
1575
+
1576
+ rejects("buildClientAttestation: missing clientId refused",
1577
+ function () { X.buildClientAttestation({ attesterPrivateKey: attKp.privateKey, instanceKeyJwk: instPub }); },
1578
+ "auth-oauth/attestation-no-client-id");
1579
+ rejects("buildClientAttestation: missing attesterPrivateKey refused",
1580
+ function () { X.buildClientAttestation({ clientId: "w", instanceKeyJwk: instPub }); },
1581
+ "auth-oauth/attestation-no-attester-key");
1582
+ rejects("buildClientAttestation: missing instanceKeyJwk refused",
1583
+ function () { X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey }); },
1584
+ "auth-oauth/attestation-bad-cnf");
1585
+ rejects("buildClientAttestation: symmetric cnf kty refused (no private half in cnf)",
1586
+ function () { X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey,
1587
+ instanceKeyJwk: { kty: "oct", k: "AAAA" } }); },
1588
+ "auth-oauth/attestation-bad-cnf");
1589
+ rejects("buildClientAttestation: non-finite nbf refused",
1590
+ function () { X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey,
1591
+ instanceKeyJwk: instPub, nbf: Infinity }); }, "auth-oauth/attestation-bad-nbf");
1592
+ rejects("buildClientAttestation: non-finite iat refused",
1593
+ function () { X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey,
1594
+ instanceKeyJwk: instPub, iat: NaN }); }, "auth-oauth/attestation-bad-iat");
1595
+ rejects("buildClientAttestation: zero expiresInSec refused (positive-int)",
1596
+ function () { X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey,
1597
+ instanceKeyJwk: instPub, expiresInSec: 0 }); }, "auth-oauth/attestation-bad-expiry");
1598
+
1599
+ // extraClaims cannot overwrite spec-reserved fields (sub/iat/exp/cnf/nbf)
1600
+ var attExtra = X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey,
1601
+ instanceKeyJwk: instPub, iat: 1000, expiresInSec: 60, nbf: 100,
1602
+ extraClaims: { sub: "HIJACK", exp: 9, role: "admin" } });
1603
+ var attPayload = JSON.parse(Buffer.from(attExtra.split(".")[1], "base64url").toString("utf8"));
1604
+ check("buildClientAttestation: extraClaims cannot override sub/exp; passes non-reserved keys",
1605
+ attPayload.sub === "w" && attPayload.exp === 1060 && attPayload.role === "admin" &&
1606
+ attPayload.iat === 1000 && attPayload.nbf === 100);
1607
+
1608
+ rejects("buildClientAttestationPop: missing audience refused",
1609
+ function () { X.buildClientAttestationPop({ instancePrivateKey: instKp.privateKey }); },
1610
+ "auth-oauth/attestation-pop-no-aud");
1611
+ rejects("buildClientAttestationPop: missing instancePrivateKey refused",
1612
+ function () { X.buildClientAttestationPop({ audience: "https://as" }); },
1613
+ "auth-oauth/attestation-pop-no-instance-key");
1614
+ var popShaped = X.buildClientAttestationPop({ instancePrivateKey: instKp.privateKey, audience: "https://as",
1615
+ expiresInSec: 120, challenge: "srv-ch", jti: "fixed-jti" });
1616
+ var popPayload = JSON.parse(Buffer.from(popShaped.split(".")[1], "base64url").toString("utf8"));
1617
+ check("buildClientAttestationPop: exp/challenge/jti overrides shaped into payload",
1618
+ typeof popPayload.exp === "number" && popPayload.challenge === "srv-ch" && popPayload.jti === "fixed-jti");
1619
+
1620
+ // ---- verifyClientAttestation: validation + freshness gates ----
1621
+ var att = X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey, instanceKeyJwk: instPub });
1622
+ var pop = X.buildClientAttestationPop({ instancePrivateKey: instKp.privateKey, audience: "https://as" });
1623
+ await arejects("verifyClientAttestation: missing attesterJwk refused",
1624
+ function () { return X.verifyClientAttestation(att, pop, { expectedAudience: "https://as" }); },
1625
+ "auth-oauth/attestation-no-attester-jwk");
1626
+ await arejects("verifyClientAttestation: missing expectedAudience refused",
1627
+ function () { return X.verifyClientAttestation(att, pop, { attesterJwk: attPub }); },
1628
+ "auth-oauth/attestation-no-expected-aud");
1629
+ await arejects("verifyClientAttestation: non-finite clockSkewSec refused (would disable exp gate)",
1630
+ function () { return X.verifyClientAttestation(att, pop, { attesterJwk: attPub,
1631
+ expectedAudience: "https://as", clockSkewSec: Infinity }); }, "auth-oauth/bad-clock-skew");
1632
+ await arejects("verifyClientAttestation: non-finite maxPopAgeSec refused",
1633
+ function () { return X.verifyClientAttestation(att, pop, { attesterJwk: attPub,
1634
+ expectedAudience: "https://as", maxPopAgeSec: Infinity }); }, "auth-oauth/bad-pop-max-age");
1635
+ var expiredAtt = X.buildClientAttestation({ clientId: "w", attesterPrivateKey: attKp.privateKey,
1636
+ instanceKeyJwk: instPub, iat: 1000, expiresInSec: 60 });
1637
+ await arejects("verifyClientAttestation: expired attestation refused",
1638
+ function () { return X.verifyClientAttestation(expiredAtt, pop, { attesterJwk: attPub, expectedAudience: "https://as" }); },
1639
+ "auth-oauth/attestation-expired");
1640
+ var stalePop = X.buildClientAttestationPop({ instancePrivateKey: instKp.privateKey, audience: "https://as", iat: 1000 });
1641
+ await arejects("verifyClientAttestation: stale PoP iat refused",
1642
+ function () { return X.verifyClientAttestation(att, stalePop, { attesterJwk: attPub, expectedAudience: "https://as" }); },
1643
+ "auth-oauth/attestation-pop-stale");
1644
+ var futurePop = X.buildClientAttestationPop({ instancePrivateKey: instKp.privateKey, audience: "https://as",
1645
+ iat: Math.floor(Date.now() / 1000) + 100000 });
1646
+ await arejects("verifyClientAttestation: future PoP iat refused",
1647
+ function () { return X.verifyClientAttestation(att, futurePop, { attesterJwk: attPub, expectedAudience: "https://as" }); },
1648
+ "auth-oauth/attestation-pop-iat-future");
1649
+ await arejects("verifyClientAttestation: seenJti callback throwing surfaces typed error",
1650
+ function () {
1651
+ var freshPop = X.buildClientAttestationPop({ instancePrivateKey: instKp.privateKey, audience: "https://as" });
1652
+ return X.verifyClientAttestation(att, freshPop, { attesterJwk: attPub, expectedAudience: "https://as",
1653
+ seenJti: function () { throw new Error("store down"); } });
1654
+ }, "auth-oauth/attestation-pop-seen-callback-failed");
1655
+
1656
+ // Attestation verifier semantics + full round-trip, and the remaining
1657
+ // network-free builder/parser branches.
1658
+ await scenarioAttestationVerify();
1659
+ await scenarioOfflineExtras();
1660
+
1661
+ console.log("auth-oauth offline checks passed");
1662
+
1663
+ // ---- loopback IdP round-trips (stand-in AS on 127.0.0.1) ----
1664
+ var made = makeServer();
1665
+ var server = made.server;
1666
+ var routes = made.routes;
1667
+ await new Promise(function (r) { server.listen(0, "127.0.0.1", r); });
1668
+ var base = "http://127.0.0.1:" + server.address().port;
1669
+ installDiscoveryDoc(base, routes);
1670
+ try {
1671
+ await scenarioVerifyIdToken(base, routes);
1672
+ await scenarioTokenFlows(base, routes);
1673
+ await scenarioUserinfoRevokeIntrospect(base, routes);
1674
+ await scenarioRegisterDcr(base, routes);
1675
+ await scenarioDeviceAndPoll(base, routes);
1676
+ await scenarioDeviceGrantDefaultHttp(base, routes);
1677
+ await scenarioStaticExtraEndpoints(base, routes);
1678
+ await scenarioParFlows(base, routes);
1679
+ await scenarioBackchannelLogout(base, routes);
1680
+ await scenarioJarm(base, routes);
1681
+ await scenarioDiscovery(base, routes);
1682
+ await scenarioBuildersAndUrls(base, routes);
1683
+ } finally {
1684
+ server.close();
1685
+ await _drainTcpHandles();
1686
+ }
1687
+ console.log("auth-oauth loopback checks passed");
1688
+ }
1689
+
1690
+ module.exports = { run: run };
1691
+
1692
+ if (require.main === module) {
1693
+ run().then(
1694
+ function () { console.log("OK — " + helpers.getChecks() + " checks passed"); },
1695
+ function (e) { console.error("FAIL:", e.stack || e); process.exit(1); }
1696
+ );
1697
+ }