@udibo/oauth2 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +174 -0
  3. package/SECURITY.md +77 -0
  4. package/esm/_dnt.polyfills.d.ts +17 -0
  5. package/esm/_dnt.polyfills.js +15 -0
  6. package/esm/adapters/hono/_common.d.ts +66 -0
  7. package/esm/adapters/hono/_common.js +108 -0
  8. package/esm/adapters/hono/authorization-server.d.ts +211 -0
  9. package/esm/adapters/hono/authorization-server.js +214 -0
  10. package/esm/adapters/hono/bff/auth-request-store.d.ts +164 -0
  11. package/esm/adapters/hono/bff/auth-request-store.js +154 -0
  12. package/esm/adapters/hono/bff/bff.d.ts +852 -0
  13. package/esm/adapters/hono/bff/bff.js +1194 -0
  14. package/esm/adapters/hono/bff/cookie-prefix.d.ts +72 -0
  15. package/esm/adapters/hono/bff/cookie-prefix.js +85 -0
  16. package/esm/adapters/hono/bff/mod.d.ts +19 -0
  17. package/esm/adapters/hono/bff/mod.js +19 -0
  18. package/esm/adapters/hono/bff/proxy.d.ts +108 -0
  19. package/esm/adapters/hono/bff/proxy.js +314 -0
  20. package/esm/adapters/hono/bff/session-store.d.ts +250 -0
  21. package/esm/adapters/hono/bff/session-store.js +216 -0
  22. package/esm/adapters/hono/bff/testing.d.ts +165 -0
  23. package/esm/adapters/hono/bff/testing.js +254 -0
  24. package/esm/adapters/hono/identity.d.ts +117 -0
  25. package/esm/adapters/hono/identity.js +215 -0
  26. package/esm/adapters/hono/log.d.ts +49 -0
  27. package/esm/adapters/hono/log.js +66 -0
  28. package/esm/adapters/hono/resource-server.d.ts +101 -0
  29. package/esm/adapters/hono/resource-server.js +108 -0
  30. package/esm/client/_http.d.ts +91 -0
  31. package/esm/client/_http.js +303 -0
  32. package/esm/client/base.d.ts +184 -0
  33. package/esm/client/base.js +93 -0
  34. package/esm/client/bff-client.d.ts +235 -0
  35. package/esm/client/bff-client.js +280 -0
  36. package/esm/client/browser-storage.d.ts +152 -0
  37. package/esm/client/browser-storage.js +246 -0
  38. package/esm/client/check.d.ts +114 -0
  39. package/esm/client/check.js +97 -0
  40. package/esm/client/direct-client.d.ts +707 -0
  41. package/esm/client/direct-client.js +1237 -0
  42. package/esm/client/discovery-cache.d.ts +138 -0
  43. package/esm/client/discovery-cache.js +124 -0
  44. package/esm/client/events.d.ts +54 -0
  45. package/esm/client/events.js +37 -0
  46. package/esm/client/mod.d.ts +35 -0
  47. package/esm/client/mod.js +33 -0
  48. package/esm/client/storage.d.ts +127 -0
  49. package/esm/client/storage.js +81 -0
  50. package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.d.ts +29 -0
  51. package/esm/deps/jsr.io/@std/assert/1.0.19/almost_equals.js +47 -0
  52. package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.d.ts +28 -0
  53. package/esm/deps/jsr.io/@std/assert/1.0.19/array_includes.js +65 -0
  54. package/esm/deps/jsr.io/@std/assert/1.0.19/assert.d.ts +16 -0
  55. package/esm/deps/jsr.io/@std/assert/1.0.19/assert.js +23 -0
  56. package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.d.ts +25 -0
  57. package/esm/deps/jsr.io/@std/assert/1.0.19/assertion_error.js +30 -0
  58. package/esm/deps/jsr.io/@std/assert/1.0.19/equal.d.ts +24 -0
  59. package/esm/deps/jsr.io/@std/assert/1.0.19/equal.js +193 -0
  60. package/esm/deps/jsr.io/@std/assert/1.0.19/equals.d.ts +36 -0
  61. package/esm/deps/jsr.io/@std/assert/1.0.19/equals.js +61 -0
  62. package/esm/deps/jsr.io/@std/assert/1.0.19/exists.d.ts +17 -0
  63. package/esm/deps/jsr.io/@std/assert/1.0.19/exists.js +27 -0
  64. package/esm/deps/jsr.io/@std/assert/1.0.19/fail.d.ts +14 -0
  65. package/esm/deps/jsr.io/@std/assert/1.0.19/fail.js +20 -0
  66. package/esm/deps/jsr.io/@std/assert/1.0.19/false.d.ts +18 -0
  67. package/esm/deps/jsr.io/@std/assert/1.0.19/false.js +23 -0
  68. package/esm/deps/jsr.io/@std/assert/1.0.19/greater.d.ts +19 -0
  69. package/esm/deps/jsr.io/@std/assert/1.0.19/greater.js +29 -0
  70. package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.d.ts +19 -0
  71. package/esm/deps/jsr.io/@std/assert/1.0.19/greater_or_equal.js +29 -0
  72. package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.d.ts +22 -0
  73. package/esm/deps/jsr.io/@std/assert/1.0.19/instance_of.js +52 -0
  74. package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.d.ts +24 -0
  75. package/esm/deps/jsr.io/@std/assert/1.0.19/is_error.js +53 -0
  76. package/esm/deps/jsr.io/@std/assert/1.0.19/less.d.ts +18 -0
  77. package/esm/deps/jsr.io/@std/assert/1.0.19/less.js +28 -0
  78. package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.d.ts +19 -0
  79. package/esm/deps/jsr.io/@std/assert/1.0.19/less_or_equal.js +29 -0
  80. package/esm/deps/jsr.io/@std/assert/1.0.19/match.d.ts +17 -0
  81. package/esm/deps/jsr.io/@std/assert/1.0.19/match.js +26 -0
  82. package/esm/deps/jsr.io/@std/assert/1.0.19/mod.d.ts +43 -0
  83. package/esm/deps/jsr.io/@std/assert/1.0.19/mod.js +45 -0
  84. package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.d.ts +26 -0
  85. package/esm/deps/jsr.io/@std/assert/1.0.19/not_equals.js +39 -0
  86. package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.d.ts +19 -0
  87. package/esm/deps/jsr.io/@std/assert/1.0.19/not_instance_of.js +29 -0
  88. package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.d.ts +17 -0
  89. package/esm/deps/jsr.io/@std/assert/1.0.19/not_match.js +26 -0
  90. package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.d.ts +22 -0
  91. package/esm/deps/jsr.io/@std/assert/1.0.19/not_strict_equals.js +32 -0
  92. package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.d.ts +28 -0
  93. package/esm/deps/jsr.io/@std/assert/1.0.19/object_match.js +177 -0
  94. package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.d.ts +41 -0
  95. package/esm/deps/jsr.io/@std/assert/1.0.19/rejects.js +53 -0
  96. package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.d.ts +23 -0
  97. package/esm/deps/jsr.io/@std/assert/1.0.19/strict_equals.js +58 -0
  98. package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.d.ts +17 -0
  99. package/esm/deps/jsr.io/@std/assert/1.0.19/string_includes.js +26 -0
  100. package/esm/deps/jsr.io/@std/assert/1.0.19/throws.d.ts +44 -0
  101. package/esm/deps/jsr.io/@std/assert/1.0.19/throws.js +44 -0
  102. package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.d.ts +14 -0
  103. package/esm/deps/jsr.io/@std/assert/1.0.19/unimplemented.js +20 -0
  104. package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.d.ts +14 -0
  105. package/esm/deps/jsr.io/@std/assert/1.0.19/unreachable.js +20 -0
  106. package/esm/deps/jsr.io/@std/async/1.5.0/delay.d.ts +44 -0
  107. package/esm/deps/jsr.io/@std/async/1.5.0/delay.js +84 -0
  108. package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.d.ts +39 -0
  109. package/esm/deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js +61 -0
  110. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.d.ts +22 -0
  111. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common16.js +51 -0
  112. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.d.ts +34 -0
  113. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common32.js +192 -0
  114. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.d.ts +34 -0
  115. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common64.js +113 -0
  116. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.d.ts +3 -0
  117. package/esm/deps/jsr.io/@std/encoding/1.0.11/_common_detach.js +13 -0
  118. package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.d.ts +8 -0
  119. package/esm/deps/jsr.io/@std/encoding/1.0.11/_types.js +2 -0
  120. package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.d.ts +1 -0
  121. package/esm/deps/jsr.io/@std/encoding/1.0.11/_validate_binary_like.js +26 -0
  122. package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.d.ts +60 -0
  123. package/esm/deps/jsr.io/@std/encoding/1.0.11/ascii85.js +152 -0
  124. package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.d.ts +39 -0
  125. package/esm/deps/jsr.io/@std/encoding/1.0.11/base32.js +87 -0
  126. package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.d.ts +39 -0
  127. package/esm/deps/jsr.io/@std/encoding/1.0.11/base58.js +131 -0
  128. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.d.ts +39 -0
  129. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64.js +82 -0
  130. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.d.ts +39 -0
  131. package/esm/deps/jsr.io/@std/encoding/1.0.11/base64url.js +72 -0
  132. package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.d.ts +38 -0
  133. package/esm/deps/jsr.io/@std/encoding/1.0.11/hex.js +87 -0
  134. package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.d.ts +97 -0
  135. package/esm/deps/jsr.io/@std/encoding/1.0.11/mod.js +99 -0
  136. package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.d.ts +119 -0
  137. package/esm/deps/jsr.io/@std/encoding/1.0.11/varint.js +209 -0
  138. package/esm/deps/jsr.io/@std/http/1.1.3/status.d.ts +343 -0
  139. package/esm/deps/jsr.io/@std/http/1.1.3/status.js +343 -0
  140. package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.d.ts +159 -0
  141. package/esm/deps/jsr.io/@std/internal/1.0.14/assertion_state.js +223 -0
  142. package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.d.ts +79 -0
  143. package/esm/deps/jsr.io/@std/internal/1.0.14/build_message.js +116 -0
  144. package/esm/deps/jsr.io/@std/internal/1.0.14/diff.d.ts +139 -0
  145. package/esm/deps/jsr.io/@std/internal/1.0.14/diff.js +277 -0
  146. package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.d.ts +98 -0
  147. package/esm/deps/jsr.io/@std/internal/1.0.14/diff_str.js +181 -0
  148. package/esm/deps/jsr.io/@std/internal/1.0.14/format.d.ts +29 -0
  149. package/esm/deps/jsr.io/@std/internal/1.0.14/format.js +68 -0
  150. package/esm/deps/jsr.io/@std/internal/1.0.14/styles.d.ts +158 -0
  151. package/esm/deps/jsr.io/@std/internal/1.0.14/styles.js +206 -0
  152. package/esm/deps/jsr.io/@std/internal/1.0.14/types.d.ts +24 -0
  153. package/esm/deps/jsr.io/@std/internal/1.0.14/types.js +3 -0
  154. package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.d.ts +91 -0
  155. package/esm/deps/jsr.io/@std/testing/1.0.20/_test_suite.js +347 -0
  156. package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.d.ts +382 -0
  157. package/esm/deps/jsr.io/@std/testing/1.0.20/bdd.js +1110 -0
  158. package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.d.ts +363 -0
  159. package/esm/deps/jsr.io/@udibo/http-error/0.11.1/mod.js +616 -0
  160. package/esm/errors.d.ts +203 -0
  161. package/esm/errors.js +287 -0
  162. package/esm/identity/captcha.d.ts +147 -0
  163. package/esm/identity/captcha.js +64 -0
  164. package/esm/identity/delivery.d.ts +96 -0
  165. package/esm/identity/delivery.js +41 -0
  166. package/esm/identity/errors.d.ts +39 -0
  167. package/esm/identity/errors.js +46 -0
  168. package/esm/identity/events.d.ts +242 -0
  169. package/esm/identity/events.js +39 -0
  170. package/esm/identity/external/_shared.d.ts +86 -0
  171. package/esm/identity/external/_shared.js +107 -0
  172. package/esm/identity/external/_token-exchange.d.ts +61 -0
  173. package/esm/identity/external/_token-exchange.js +179 -0
  174. package/esm/identity/external/apple-client-secret.d.ts +82 -0
  175. package/esm/identity/external/apple-client-secret.js +133 -0
  176. package/esm/identity/external/apple.d.ts +64 -0
  177. package/esm/identity/external/apple.js +248 -0
  178. package/esm/identity/external/discord.d.ts +38 -0
  179. package/esm/identity/external/discord.js +62 -0
  180. package/esm/identity/external/errors.d.ts +48 -0
  181. package/esm/identity/external/errors.js +36 -0
  182. package/esm/identity/external/flow.d.ts +107 -0
  183. package/esm/identity/external/flow.js +158 -0
  184. package/esm/identity/external/github.d.ts +42 -0
  185. package/esm/identity/external/github.js +164 -0
  186. package/esm/identity/external/google.d.ts +52 -0
  187. package/esm/identity/external/google.js +38 -0
  188. package/esm/identity/external/mod.d.ts +63 -0
  189. package/esm/identity/external/mod.js +61 -0
  190. package/esm/identity/external/oauth2.d.ts +110 -0
  191. package/esm/identity/external/oauth2.js +158 -0
  192. package/esm/identity/external/oidc.d.ts +81 -0
  193. package/esm/identity/external/oidc.js +250 -0
  194. package/esm/identity/external/provider.d.ts +132 -0
  195. package/esm/identity/external/provider.js +12 -0
  196. package/esm/identity/hibp.d.ts +67 -0
  197. package/esm/identity/hibp.js +87 -0
  198. package/esm/identity/identifier.d.ts +44 -0
  199. package/esm/identity/identifier.js +47 -0
  200. package/esm/identity/lockout.d.ts +155 -0
  201. package/esm/identity/lockout.js +117 -0
  202. package/esm/identity/mfa/mod.d.ts +42 -0
  203. package/esm/identity/mfa/mod.js +42 -0
  204. package/esm/identity/mfa/recovery-codes.d.ts +96 -0
  205. package/esm/identity/mfa/recovery-codes.js +80 -0
  206. package/esm/identity/mfa/service.d.ts +338 -0
  207. package/esm/identity/mfa/service.js +351 -0
  208. package/esm/identity/mfa/totp.d.ts +186 -0
  209. package/esm/identity/mfa/totp.js +194 -0
  210. package/esm/identity/migration.d.ts +155 -0
  211. package/esm/identity/migration.js +243 -0
  212. package/esm/identity/mod.d.ts +65 -0
  213. package/esm/identity/mod.js +64 -0
  214. package/esm/identity/otp.d.ts +255 -0
  215. package/esm/identity/otp.js +245 -0
  216. package/esm/identity/password-policy.d.ts +45 -0
  217. package/esm/identity/password-policy.js +60 -0
  218. package/esm/identity/password.d.ts +171 -0
  219. package/esm/identity/password.js +160 -0
  220. package/esm/identity/rate-limit.d.ts +192 -0
  221. package/esm/identity/rate-limit.js +168 -0
  222. package/esm/identity/service.d.ts +579 -0
  223. package/esm/identity/service.js +835 -0
  224. package/esm/identity/session.d.ts +122 -0
  225. package/esm/identity/session.js +48 -0
  226. package/esm/identity/token-flow.d.ts +214 -0
  227. package/esm/identity/token-flow.js +173 -0
  228. package/esm/models/authentication.d.ts +24 -0
  229. package/esm/models/authentication.js +1 -0
  230. package/esm/models/authorization-code.d.ts +63 -0
  231. package/esm/models/authorization-code.js +1 -0
  232. package/esm/models/authorization.d.ts +165 -0
  233. package/esm/models/authorization.js +160 -0
  234. package/esm/models/client.d.ts +78 -0
  235. package/esm/models/client.js +1 -0
  236. package/esm/models/device-authorization.d.ts +30 -0
  237. package/esm/models/device-authorization.js +1 -0
  238. package/esm/models/responses.d.ts +93 -0
  239. package/esm/models/responses.js +1 -0
  240. package/esm/models/scope.d.ts +81 -0
  241. package/esm/models/scope.js +125 -0
  242. package/esm/models/token.d.ts +135 -0
  243. package/esm/models/token.js +1 -0
  244. package/esm/package.json +3 -0
  245. package/esm/react/callback.d.ts +52 -0
  246. package/esm/react/callback.js +71 -0
  247. package/esm/react/components/class-names.d.ts +91 -0
  248. package/esm/react/components/class-names.js +118 -0
  249. package/esm/react/components/internal.d.ts +62 -0
  250. package/esm/react/components/internal.js +102 -0
  251. package/esm/react/components/mfa-challenge-form.d.ts +66 -0
  252. package/esm/react/components/mfa-challenge-form.js +46 -0
  253. package/esm/react/components/mfa-enrollment-form.d.ts +83 -0
  254. package/esm/react/components/mfa-enrollment-form.js +41 -0
  255. package/esm/react/components/mod.d.ts +103 -0
  256. package/esm/react/components/mod.js +93 -0
  257. package/esm/react/components/request-password-reset-form.d.ts +61 -0
  258. package/esm/react/components/request-password-reset-form.js +41 -0
  259. package/esm/react/components/reset-password-form.d.ts +66 -0
  260. package/esm/react/components/reset-password-form.js +56 -0
  261. package/esm/react/components/sign-in-form.d.ts +74 -0
  262. package/esm/react/components/sign-in-form.js +43 -0
  263. package/esm/react/components/sign-up-form.d.ts +85 -0
  264. package/esm/react/components/sign-up-form.js +57 -0
  265. package/esm/react/components/types.d.ts +208 -0
  266. package/esm/react/components/types.js +14 -0
  267. package/esm/react/components/use-auth-form.d.ts +118 -0
  268. package/esm/react/components/use-auth-form.js +172 -0
  269. package/esm/react/components/user-menu.d.ts +79 -0
  270. package/esm/react/components/user-menu.js +73 -0
  271. package/esm/react/context.d.ts +51 -0
  272. package/esm/react/context.js +16 -0
  273. package/esm/react/mod.d.ts +26 -0
  274. package/esm/react/mod.js +26 -0
  275. package/esm/react/provider.d.ts +40 -0
  276. package/esm/react/provider.js +189 -0
  277. package/esm/react/require-auth.d.ts +31 -0
  278. package/esm/react/require-auth.js +37 -0
  279. package/esm/react/testing.d.ts +322 -0
  280. package/esm/react/testing.js +461 -0
  281. package/esm/react/use-authorization.d.ts +31 -0
  282. package/esm/react/use-authorization.js +36 -0
  283. package/esm/react/use-oauth2.d.ts +112 -0
  284. package/esm/react/use-oauth2.js +127 -0
  285. package/esm/server/authorization-server.d.ts +736 -0
  286. package/esm/server/authorization-server.js +1304 -0
  287. package/esm/server/client-authentication.d.ts +28 -0
  288. package/esm/server/client-authentication.js +51 -0
  289. package/esm/server/grants/authorization-code.d.ts +179 -0
  290. package/esm/server/grants/authorization-code.js +232 -0
  291. package/esm/server/grants/client-credentials.d.ts +52 -0
  292. package/esm/server/grants/client-credentials.js +53 -0
  293. package/esm/server/grants/device-authorization.d.ts +103 -0
  294. package/esm/server/grants/device-authorization.js +156 -0
  295. package/esm/server/grants/grant.d.ts +165 -0
  296. package/esm/server/grants/grant.js +173 -0
  297. package/esm/server/grants/password.d.ts +80 -0
  298. package/esm/server/grants/password.js +92 -0
  299. package/esm/server/grants/refresh-token.d.ts +80 -0
  300. package/esm/server/grants/refresh-token.js +136 -0
  301. package/esm/server/introspection-token-reader.d.ts +153 -0
  302. package/esm/server/introspection-token-reader.js +164 -0
  303. package/esm/server/jwks-token-reader.d.ts +263 -0
  304. package/esm/server/jwks-token-reader.js +508 -0
  305. package/esm/server/mod.d.ts +23 -0
  306. package/esm/server/mod.js +15 -0
  307. package/esm/server/public-suffix/mod.d.ts +62 -0
  308. package/esm/server/public-suffix/mod.js +103 -0
  309. package/esm/server/public-suffix/rules.d.ts +23 -0
  310. package/esm/server/public-suffix/rules.js +23 -0
  311. package/esm/server/redirect-uri.d.ts +187 -0
  312. package/esm/server/redirect-uri.js +331 -0
  313. package/esm/server/resource-server.d.ts +286 -0
  314. package/esm/server/resource-server.js +365 -0
  315. package/esm/server/services/authorization-code.d.ts +84 -0
  316. package/esm/server/services/authorization-code.js +35 -0
  317. package/esm/server/services/client.d.ts +35 -0
  318. package/esm/server/services/client.js +1 -0
  319. package/esm/server/services/device-authorization.d.ts +134 -0
  320. package/esm/server/services/device-authorization.js +7 -0
  321. package/esm/server/services/token.d.ts +242 -0
  322. package/esm/server/services/token.js +78 -0
  323. package/esm/server/services/user.d.ts +29 -0
  324. package/esm/server/services/user.js +7 -0
  325. package/esm/server/signing-keys.d.ts +201 -0
  326. package/esm/server/signing-keys.js +225 -0
  327. package/esm/server/utils/hash.d.ts +6 -0
  328. package/esm/server/utils/hash.js +11 -0
  329. package/esm/testing/contract/authorization-code.d.ts +37 -0
  330. package/esm/testing/contract/authorization-code.js +93 -0
  331. package/esm/testing/contract/client.d.ts +85 -0
  332. package/esm/testing/contract/client.js +126 -0
  333. package/esm/testing/contract/device-authorization.d.ts +37 -0
  334. package/esm/testing/contract/device-authorization.js +193 -0
  335. package/esm/testing/contract/lockout-store.d.ts +47 -0
  336. package/esm/testing/contract/lockout-store.js +122 -0
  337. package/esm/testing/contract/mfa-store.d.ts +50 -0
  338. package/esm/testing/contract/mfa-store.js +226 -0
  339. package/esm/testing/contract/mod.d.ts +39 -0
  340. package/esm/testing/contract/mod.js +39 -0
  341. package/esm/testing/contract/otp-store.d.ts +48 -0
  342. package/esm/testing/contract/otp-store.js +197 -0
  343. package/esm/testing/contract/rate-limit-store.d.ts +52 -0
  344. package/esm/testing/contract/rate-limit-store.js +105 -0
  345. package/esm/testing/contract/token-flow-store.d.ts +54 -0
  346. package/esm/testing/contract/token-flow-store.js +179 -0
  347. package/esm/testing/contract/token-reader.d.ts +77 -0
  348. package/esm/testing/contract/token-reader.js +78 -0
  349. package/esm/testing/contract/token.d.ts +61 -0
  350. package/esm/testing/contract/token.js +287 -0
  351. package/esm/testing/contract/user.d.ts +63 -0
  352. package/esm/testing/contract/user.js +75 -0
  353. package/esm/testing/mod.d.ts +34 -0
  354. package/esm/testing/mod.js +34 -0
  355. package/esm/testing/server.d.ts +197 -0
  356. package/esm/testing/server.js +193 -0
  357. package/esm/testing/services.d.ts +373 -0
  358. package/esm/testing/services.js +703 -0
  359. package/esm/utils/_buffer.d.ts +10 -0
  360. package/esm/utils/_buffer.js +12 -0
  361. package/esm/utils/_timing-safe.d.ts +13 -0
  362. package/esm/utils/_timing-safe.js +22 -0
  363. package/esm/utils/authentication-context.d.ts +3 -0
  364. package/esm/utils/authentication-context.js +14 -0
  365. package/esm/utils/basic-auth.d.ts +50 -0
  366. package/esm/utils/basic-auth.js +106 -0
  367. package/esm/utils/crypto.d.ts +97 -0
  368. package/esm/utils/crypto.js +162 -0
  369. package/esm/utils/pkce.d.ts +151 -0
  370. package/esm/utils/pkce.js +179 -0
  371. package/esm/utils/text.d.ts +24 -0
  372. package/esm/utils/text.js +28 -0
  373. package/esm/utils/url.d.ts +66 -0
  374. package/esm/utils/url.js +90 -0
  375. package/package.json +92 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * TOTP primitives (RFC 6238 over RFC 4226 HOTP) built on Web Crypto — no
3
+ * dependencies beyond `@std`. Generate a secret, render the `otpauth://` URI an
4
+ * authenticator app scans, and verify submitted codes with a constant-time
5
+ * comparison.
6
+ *
7
+ * Verification returns the matched time-step counter so the caller can enforce
8
+ * single use: persist the last accepted step per user and reject any code whose
9
+ * `matchedStep` is `<=` the stored value (see {@link verifyTotpCode}). The
10
+ * package owns the math; replay state, like all storage, stays app-owned.
11
+ *
12
+ * @module
13
+ */
14
+ import { decodeBase32, encodeBase32 } from "../../deps/jsr.io/@std/encoding/1.0.11/base32.js";
15
+ import { toArrayBuffer } from "../../utils/_buffer.js";
16
+ import { timingSafeMatchIndex } from "../../utils/_timing-safe.js";
17
+ const DEFAULT_DIGITS = 6;
18
+ const DEFAULT_PERIOD_SECONDS = 30;
19
+ const DEFAULT_ALGORITHM = "SHA-1";
20
+ const DEFAULT_WINDOWS = 1;
21
+ function normalizeTotpSecret(base32) {
22
+ return base32.toUpperCase().replaceAll(/[\s=]/g, "");
23
+ }
24
+ /**
25
+ * Assert that TOTP parameters are usable: `digits` and `periodSeconds` must be
26
+ * positive integers and `windows` a non-negative integer. Every TOTP entry
27
+ * point applies this, so a corrupted or miswired configuration fails loudly
28
+ * instead of silently rejecting every code.
29
+ *
30
+ * @throws {TypeError} on the first invalid value.
31
+ */
32
+ export function validateTotpParams(params) {
33
+ const { digits, periodSeconds, windows } = params;
34
+ if (digits !== undefined && (!Number.isInteger(digits) || digits <= 0)) {
35
+ throw new TypeError("totp digits must be a positive integer");
36
+ }
37
+ if (periodSeconds !== undefined &&
38
+ (!Number.isInteger(periodSeconds) || periodSeconds <= 0)) {
39
+ throw new TypeError("totp period must be a positive integer");
40
+ }
41
+ if (windows !== undefined && (!Number.isInteger(windows) || windows < 0)) {
42
+ throw new TypeError("totp windows must be a non-negative integer");
43
+ }
44
+ }
45
+ /**
46
+ * Decode a base32 TOTP secret into raw key bytes. Tolerates lowercase,
47
+ * whitespace, and missing `=` padding (authenticator conventions vary).
48
+ *
49
+ * @throws {TypeError} if the value is not valid RFC 4648 base32.
50
+ */
51
+ export function decodeTotpSecret(base32) {
52
+ const normalized = normalizeTotpSecret(base32);
53
+ const padded = normalized.padEnd(normalized.length + ((8 - (normalized.length % 8)) % 8), "=");
54
+ return decodeBase32(padded);
55
+ }
56
+ /**
57
+ * Generate a random 160-bit TOTP secret (the RFC 4226 recommended size and
58
+ * what authenticator apps expect). Store it encrypted at rest — it is a
59
+ * symmetric key, not a hash: anyone who reads it can mint valid codes.
60
+ *
61
+ * @example Enroll a user
62
+ * ```ts
63
+ * const { base32 } = generateTotpSecret();
64
+ * const uri = buildOtpauthUri({
65
+ * secret: base32,
66
+ * issuer: "Example",
67
+ * accountName: "user@example.com",
68
+ * });
69
+ * // render `uri` as a QR code; store `base32` encrypted
70
+ * ```
71
+ */
72
+ export function generateTotpSecret() {
73
+ const secret = crypto.getRandomValues(new Uint8Array(20));
74
+ return { secret, base32: encodeBase32(secret).replaceAll("=", "") };
75
+ }
76
+ /**
77
+ * Build the `otpauth://totp/...` provisioning URI an enrollment screen renders
78
+ * as a QR code. Follows the de-facto Google Authenticator format: the issuer
79
+ * appears both as the URI-encoded label prefix (`Issuer:account`) and as the
80
+ * `issuer` query parameter, so apps that read either convention group the
81
+ * entry correctly.
82
+ *
83
+ * @throws {TypeError} if the secret is not base32 or `digits`/`periodSeconds`
84
+ * are not positive integers, so a corrupted value cannot inject URI parameters.
85
+ */
86
+ export function buildOtpauthUri(options) {
87
+ validateTotpParams(options);
88
+ const digits = options.digits ?? DEFAULT_DIGITS;
89
+ const periodSeconds = options.periodSeconds ?? DEFAULT_PERIOD_SECONDS;
90
+ const algorithm = options.algorithm ?? DEFAULT_ALGORITHM;
91
+ const secret = normalizeTotpSecret(options.secret);
92
+ if (!/^[A-Z2-7]+$/.test(secret)) {
93
+ throw new TypeError("otpauth secret must be base32");
94
+ }
95
+ const label = `${encodeURIComponent(options.issuer)}:${encodeURIComponent(options.accountName)}`;
96
+ const query = [
97
+ `secret=${secret}`,
98
+ `issuer=${encodeURIComponent(options.issuer)}`,
99
+ `algorithm=${algorithm.replaceAll("-", "")}`,
100
+ `digits=${digits}`,
101
+ `period=${periodSeconds}`,
102
+ ].join("&");
103
+ return `otpauth://totp/${label}?${query}`;
104
+ }
105
+ function importHotpKey(key, algorithm) {
106
+ return crypto.subtle.importKey("raw", toArrayBuffer(key), { name: "HMAC", hash: algorithm }, false, ["sign"]);
107
+ }
108
+ async function hotp(hmacKey, counter, digits) {
109
+ const message = new Uint8Array(8);
110
+ new DataView(message.buffer).setBigUint64(0, BigInt(counter));
111
+ const mac = new Uint8Array(await crypto.subtle.sign("HMAC", hmacKey, message));
112
+ const offset = mac[mac.length - 1] & 0x0f;
113
+ const binary = ((mac[offset] & 0x7f) << 24) |
114
+ (mac[offset + 1] << 16) |
115
+ (mac[offset + 2] << 8) |
116
+ mac[offset + 3];
117
+ return (binary % 10 ** digits).toString().padStart(digits, "0");
118
+ }
119
+ function toKeyBytes(secret) {
120
+ return typeof secret === "string" ? decodeTotpSecret(secret) : secret;
121
+ }
122
+ function timeStep(timestamp, periodSeconds) {
123
+ return Math.floor(timestamp / 1000 / periodSeconds);
124
+ }
125
+ /**
126
+ * Compute the TOTP code for a moment in time — what an authenticator app
127
+ * displays. Exported for tests and for tooling that needs to mint codes (e.g.
128
+ * an enrollment simulator); server-side verification should go through
129
+ * {@link verifyTotpCode}, which also handles the clock-skew window and
130
+ * constant-time comparison.
131
+ *
132
+ * @example Mint the current code in a test
133
+ * ```ts
134
+ * const code = await generateTotpCode({ secret: base32 });
135
+ * ```
136
+ *
137
+ * @throws {TypeError} if `digits` or `periodSeconds` is not a positive
138
+ * integer, or the secret is not valid base32.
139
+ */
140
+ export async function generateTotpCode(options) {
141
+ validateTotpParams(options);
142
+ const algorithm = options.algorithm ?? DEFAULT_ALGORITHM;
143
+ const hmacKey = await importHotpKey(toKeyBytes(options.secret), algorithm);
144
+ return await hotp(hmacKey, timeStep(options.timestamp ?? Date.now(), options.periodSeconds ?? DEFAULT_PERIOD_SECONDS), options.digits ?? DEFAULT_DIGITS);
145
+ }
146
+ /**
147
+ * Verify a submitted TOTP code against the secret, accepting `windows` steps
148
+ * of clock skew on either side. Code comparison is constant-time, and every
149
+ * candidate step is checked even after a match, so timing does not reveal
150
+ * which step (if any) matched. When adjacent steps happen to produce the same
151
+ * code, the newest step wins, so the replay guard below never rejects a fresh
152
+ * code over a stale tie.
153
+ *
154
+ * **Replay protection is the caller's half of the contract:** a valid result
155
+ * includes `matchedStep`; store the last accepted step and reject any code
156
+ * whose `matchedStep` is `<=` that value ({@link MfaService} in this module
157
+ * does this for you). Without it, a code stays valid for the whole skew
158
+ * window and can be replayed.
159
+ *
160
+ * @example Verify with replay rejection
161
+ * ```ts
162
+ * const result = await verifyTotpCode({ secret, code });
163
+ * if (result.valid && result.matchedStep > lastAcceptedStep) {
164
+ * lastAcceptedStep = result.matchedStep; // persist atomically
165
+ * // accepted
166
+ * }
167
+ * ```
168
+ *
169
+ * @throws {TypeError} if `digits`/`periodSeconds` is not a positive integer,
170
+ * `windows` is not a non-negative integer, or the secret is not valid base32.
171
+ */
172
+ export async function verifyTotpCode(options) {
173
+ validateTotpParams(options);
174
+ const digits = options.digits ?? DEFAULT_DIGITS;
175
+ const periodSeconds = options.periodSeconds ?? DEFAULT_PERIOD_SECONDS;
176
+ const algorithm = options.algorithm ?? DEFAULT_ALGORITHM;
177
+ const windows = options.windows ?? DEFAULT_WINDOWS;
178
+ const code = options.code.replaceAll(/\s/g, "");
179
+ if (!/^\d+$/.test(code) || code.length !== digits) {
180
+ return { valid: false };
181
+ }
182
+ const hmacKey = await importHotpKey(toKeyBytes(options.secret), algorithm);
183
+ const step = timeStep(options.timestamp ?? Date.now(), periodSeconds);
184
+ const candidates = [];
185
+ for (let offset = -windows; offset <= windows; offset++) {
186
+ if (step + offset >= 0)
187
+ candidates.push(step + offset);
188
+ }
189
+ const expected = await Promise.all(candidates.map((candidate) => hotp(hmacKey, candidate, digits)));
190
+ const index = timingSafeMatchIndex(expected, code);
191
+ return index === undefined
192
+ ? { valid: false }
193
+ : { valid: true, matchedStep: candidates[index] };
194
+ }
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Password-hash import primitives — the migration on-ramp for apps moving off
3
+ * another auth system without forcing a password reset.
4
+ *
5
+ * The model is **upgrade-on-login**: keep each imported user's existing
6
+ * (foreign-format) password hash, verify it on their first sign-in, and — on a
7
+ * match — transparently rehash the password into this package's native format
8
+ * ({@link PasswordIdentityService}, PBKDF2-SHA-256) and store that. The foreign
9
+ * hash is then cleared. Users never see a reset; the migration completes one
10
+ * login at a time. Wire it via {@link IdentityService}'s `legacyVerifiers`
11
+ * option plus the optional `getLegacyCredential`/`clearLegacyCredential` store
12
+ * hooks, plus atomic `replaceCredential` for upgrade writes.
13
+ *
14
+ * **Which algorithms ship built-in vs. bring-your-own (BYO).** The package's
15
+ * hard constraint is zero new runtime dependencies — Web Crypto only. So the
16
+ * only built-in verifier is {@link pbkdf2Verifier} (PBKDF2 is a Web Crypto
17
+ * primitive). **bcrypt, argon2, and scrypt cannot be implemented dep-free** —
18
+ * bcrypt and argon2 have no Web Crypto primitive, and scrypt is not exposed by
19
+ * Web Crypto or `@std/crypto`. Those are **BYO**: you implement the
20
+ * {@link LegacyPasswordVerifier} seam with your app's own bcrypt/argon2/scrypt
21
+ * dependency and pass it in. {@link parsePhc} is provided to parse the PHC /
22
+ * modular-crypt strings those algorithms store, so a BYO verifier is a few
23
+ * lines. See the migration guide for worked bcrypt/argon2 examples.
24
+ *
25
+ * @module
26
+ */
27
+ import "../_dnt.polyfills.js";
28
+ /**
29
+ * A verifier for one foreign password-hash format, used during
30
+ * upgrade-on-login. Connectors identify the formats they handle by sniffing the
31
+ * stored hash string in {@link LegacyPasswordVerifier.canVerify}; the first
32
+ * verifier in {@link IdentityService}'s `legacyVerifiers` list whose `canVerify`
33
+ * returns `true` is asked to {@link LegacyPasswordVerifier.verify} the password.
34
+ *
35
+ * Implement this to bring your own bcrypt/argon2/scrypt verifier (using your
36
+ * app's dependency) — the package only ships {@link pbkdf2Verifier} built-in.
37
+ *
38
+ * @example Bring-your-own bcrypt (app adds an `npm:bcryptjs` dependency)
39
+ * ```ts
40
+ * import { compare } from "bcryptjs";
41
+ * import type { LegacyPasswordVerifier } from "@udibo/oauth2/identity/migration";
42
+ *
43
+ * const bcryptVerifier: LegacyPasswordVerifier = {
44
+ * id: "bcrypt",
45
+ * canVerify: (phc) => /^\$2[aby]?\$/.test(phc),
46
+ * verify: (password, phc) => compare(password, phc),
47
+ * };
48
+ * ```
49
+ */
50
+ export interface LegacyPasswordVerifier {
51
+ /**
52
+ * Stable identifier for this format (e.g. `"bcrypt"`, `"argon2id"`,
53
+ * `"pbkdf2"`). Surfaced on the `password.upgraded` audit event so you can see
54
+ * which format a migrated user came from.
55
+ */
56
+ readonly id: string;
57
+ /**
58
+ * Cheap, synchronous sniff of whether this verifier recognizes `phc` (its
59
+ * prefix/shape). Must not throw — return `false` for anything unrecognized or
60
+ * malformed. Selection stops at the first verifier that returns `true`.
61
+ */
62
+ canVerify(phc: string): boolean;
63
+ /**
64
+ * Verify `password` against the stored foreign hash `phc`. Called only when
65
+ * {@link LegacyPasswordVerifier.canVerify} returned `true`. Resolve `false`
66
+ * (never throw) on a mismatch or a malformed hash. Use a constant-time
67
+ * comparison.
68
+ */
69
+ verify(password: string, phc: string): Promise<boolean>;
70
+ }
71
+ /**
72
+ * The first verifier in `verifiers` (in order) whose `canVerify` accepts `phc`,
73
+ * or `undefined` if none do. A verifier that throws from `canVerify` is skipped
74
+ * rather than propagated, so one misbehaving connector can't break selection.
75
+ */
76
+ export declare function findLegacyVerifier(verifiers: readonly LegacyPasswordVerifier[], phc: string): LegacyPasswordVerifier | undefined;
77
+ /**
78
+ * Verify `password` against a stored foreign hash `phc` using the first matching
79
+ * verifier in `verifiers`. Resolves `false` when no verifier recognizes `phc` or
80
+ * the matched verifier rejects the password. Useful for a bulk-import
81
+ * dry-run/validation pass; {@link IdentityService.signIn} uses the same
82
+ * selection internally for upgrade-on-login.
83
+ */
84
+ export declare function verifyLegacyPassword(verifiers: readonly LegacyPasswordVerifier[], password: string, phc: string): Promise<boolean>;
85
+ /**
86
+ * A parsed PHC / modular-crypt string ({@link parsePhc}). The salt and hash
87
+ * segments are decoded from base64 (the PHC "B64" alphabet, padding optional).
88
+ */
89
+ export interface PhcHash {
90
+ /** Algorithm identifier, e.g. `"argon2id"`, `"scrypt"`, `"pbkdf2-sha256"`. */
91
+ id: string;
92
+ /** The `v=` version field, if present (e.g. argon2's `19`). */
93
+ version?: number;
94
+ /** The comma-separated `k=v` parameter field, e.g. argon2's `{ m, t, p }`. */
95
+ params: Record<string, string>;
96
+ /** Decoded salt bytes, if a salt segment was present. */
97
+ salt?: Uint8Array;
98
+ /** Decoded hash/checksum bytes, if a hash segment was present. */
99
+ hash?: Uint8Array;
100
+ }
101
+ /**
102
+ * Parse a PHC / modular-crypt string (`$id[$v=n][$k=v,...]$salt$hash`) into its
103
+ * parts. Returns `null` for anything that isn't a `$`-led PHC string. Salt and
104
+ * hash are decoded as base64 (padding optional). This is the parsing helper for
105
+ * writing a bring-your-own argon2/scrypt {@link LegacyPasswordVerifier}: parse
106
+ * the stored string, feed `params`/`salt`/`hash` to your KDF dependency, and
107
+ * constant-time compare.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * const parsed = parsePhc("$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$aGFzaA");
112
+ * parsed?.id; // "argon2id"
113
+ * parsed?.version; // 19
114
+ * parsed?.params.m; // "65536"
115
+ * ```
116
+ */
117
+ export declare function parsePhc(phc: string): PhcHash | null;
118
+ /** Options for {@link pbkdf2Verifier}. */
119
+ export interface Pbkdf2VerifierOptions {
120
+ /** Verifier id surfaced on the `password.upgraded` event. Defaults to `"pbkdf2"`. */
121
+ id?: string;
122
+ }
123
+ /**
124
+ * Built-in {@link LegacyPasswordVerifier} for PBKDF2 hashes — the only family
125
+ * this package can verify dep-free (PBKDF2 is a Web Crypto primitive). Handles
126
+ * two common encodings, auto-detected:
127
+ *
128
+ * - **Django** — `pbkdf2_<digest>$<iterations>$<salt>$<base64 hash>`, where the
129
+ * salt is used as raw UTF-8 bytes and the hash is standard base64. This is
130
+ * Django's `PBKDF2PasswordHasher` output.
131
+ * - **PHC** — `$pbkdf2-<digest>$i=<iterations>$<base64 salt>$<base64 hash>`,
132
+ * with base64 (padding optional) salt and hash.
133
+ *
134
+ * `<digest>` is one of `sha1`, `sha256`, `sha512`. The derived-key length is
135
+ * taken from the stored hash. Comparison is constant-time. On a match,
136
+ * {@link IdentityService} rehashes into its native PBKDF2 format and clears the
137
+ * imported hash.
138
+ *
139
+ * A hash outside the accepted bounds is declined rather than verified —
140
+ * `canVerify` returns `false` and `verify` resolves `false`, which reads to your
141
+ * app as a wrong password. The bounds are more than 1,000,000 iterations and a
142
+ * stored checksum shorter than 16 bytes.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * import { IdentityService } from "@udibo/oauth2/identity";
147
+ * import { pbkdf2Verifier } from "@udibo/oauth2/identity/migration";
148
+ *
149
+ * const identity = new IdentityService({
150
+ * users: store, // implements getLegacyCredential/clearLegacyCredential
151
+ * legacyVerifiers: [pbkdf2Verifier()],
152
+ * });
153
+ * ```
154
+ */
155
+ export declare function pbkdf2Verifier(options?: Pbkdf2VerifierOptions): LegacyPasswordVerifier;
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Password-hash import primitives — the migration on-ramp for apps moving off
3
+ * another auth system without forcing a password reset.
4
+ *
5
+ * The model is **upgrade-on-login**: keep each imported user's existing
6
+ * (foreign-format) password hash, verify it on their first sign-in, and — on a
7
+ * match — transparently rehash the password into this package's native format
8
+ * ({@link PasswordIdentityService}, PBKDF2-SHA-256) and store that. The foreign
9
+ * hash is then cleared. Users never see a reset; the migration completes one
10
+ * login at a time. Wire it via {@link IdentityService}'s `legacyVerifiers`
11
+ * option plus the optional `getLegacyCredential`/`clearLegacyCredential` store
12
+ * hooks, plus atomic `replaceCredential` for upgrade writes.
13
+ *
14
+ * **Which algorithms ship built-in vs. bring-your-own (BYO).** The package's
15
+ * hard constraint is zero new runtime dependencies — Web Crypto only. So the
16
+ * only built-in verifier is {@link pbkdf2Verifier} (PBKDF2 is a Web Crypto
17
+ * primitive). **bcrypt, argon2, and scrypt cannot be implemented dep-free** —
18
+ * bcrypt and argon2 have no Web Crypto primitive, and scrypt is not exposed by
19
+ * Web Crypto or `@std/crypto`. Those are **BYO**: you implement the
20
+ * {@link LegacyPasswordVerifier} seam with your app's own bcrypt/argon2/scrypt
21
+ * dependency and pass it in. {@link parsePhc} is provided to parse the PHC /
22
+ * modular-crypt strings those algorithms store, so a BYO verifier is a few
23
+ * lines. See the migration guide for worked bcrypt/argon2 examples.
24
+ *
25
+ * @module
26
+ */
27
+ import "../_dnt.polyfills.js";
28
+ import { decodeBase64 } from "../deps/jsr.io/@std/encoding/1.0.11/mod.js";
29
+ import { timingSafeEqual } from "../deps/jsr.io/@std/crypto/1.1.0/timing_safe_equal.js";
30
+ /**
31
+ * The first verifier in `verifiers` (in order) whose `canVerify` accepts `phc`,
32
+ * or `undefined` if none do. A verifier that throws from `canVerify` is skipped
33
+ * rather than propagated, so one misbehaving connector can't break selection.
34
+ */
35
+ export function findLegacyVerifier(verifiers, phc) {
36
+ return verifiers.find((verifier) => {
37
+ try {
38
+ return verifier.canVerify(phc);
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ });
44
+ }
45
+ /**
46
+ * Verify `password` against a stored foreign hash `phc` using the first matching
47
+ * verifier in `verifiers`. Resolves `false` when no verifier recognizes `phc` or
48
+ * the matched verifier rejects the password. Useful for a bulk-import
49
+ * dry-run/validation pass; {@link IdentityService.signIn} uses the same
50
+ * selection internally for upgrade-on-login.
51
+ */
52
+ export async function verifyLegacyPassword(verifiers, password, phc) {
53
+ const verifier = findLegacyVerifier(verifiers, phc);
54
+ if (!verifier)
55
+ return false;
56
+ try {
57
+ return await verifier.verify(password, phc);
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ function decodeB64(segment) {
64
+ const padded = segment + "=".repeat((4 - (segment.length % 4)) % 4);
65
+ return decodeBase64(padded);
66
+ }
67
+ /**
68
+ * Parse a PHC / modular-crypt string (`$id[$v=n][$k=v,...]$salt$hash`) into its
69
+ * parts. Returns `null` for anything that isn't a `$`-led PHC string. Salt and
70
+ * hash are decoded as base64 (padding optional). This is the parsing helper for
71
+ * writing a bring-your-own argon2/scrypt {@link LegacyPasswordVerifier}: parse
72
+ * the stored string, feed `params`/`salt`/`hash` to your KDF dependency, and
73
+ * constant-time compare.
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * const parsed = parsePhc("$argon2id$v=19$m=65536,t=3,p=4$c29tZXNhbHQ$aGFzaA");
78
+ * parsed?.id; // "argon2id"
79
+ * parsed?.version; // 19
80
+ * parsed?.params.m; // "65536"
81
+ * ```
82
+ */
83
+ export function parsePhc(phc) {
84
+ if (typeof phc !== "string" || !phc.startsWith("$"))
85
+ return null;
86
+ const fields = phc.split("$").slice(1);
87
+ if (fields.length === 0 || fields[0] === "")
88
+ return null;
89
+ const id = fields[0];
90
+ let version;
91
+ const params = {};
92
+ const rest = fields.slice(1);
93
+ let index = 0;
94
+ if (rest[index]?.startsWith("v=")) {
95
+ version = Number(rest[index].slice(2));
96
+ index++;
97
+ }
98
+ if (rest[index] && rest[index].includes("=")) {
99
+ for (const pair of rest[index].split(",")) {
100
+ const eq = pair.indexOf("=");
101
+ if (eq === -1)
102
+ continue;
103
+ params[pair.slice(0, eq)] = pair.slice(eq + 1);
104
+ }
105
+ index++;
106
+ }
107
+ const saltSegment = rest[index++];
108
+ const hashSegment = rest[index];
109
+ const result = { id, params };
110
+ if (version !== undefined)
111
+ result.version = version;
112
+ try {
113
+ if (saltSegment)
114
+ result.salt = decodeB64(saltSegment);
115
+ if (hashSegment)
116
+ result.hash = decodeB64(hashSegment);
117
+ }
118
+ catch {
119
+ return null;
120
+ }
121
+ return result;
122
+ }
123
+ const PBKDF2_DIGEST = {
124
+ sha1: "SHA-1",
125
+ sha256: "SHA-256",
126
+ sha512: "SHA-512",
127
+ };
128
+ const encoder = new TextEncoder();
129
+ /**
130
+ * Reject an imported hash whose derived-key segment is implausibly short: a
131
+ * 0-byte checksum would make `deriveBits(0)` compare empty-to-empty and turn
132
+ * any password into a match on a runtime where that returns an empty buffer.
133
+ */
134
+ const MIN_HASH_BYTES = 16;
135
+ const MAX_PBKDF2_ITERATIONS = 1_000_000;
136
+ function parseDjango(phc) {
137
+ const parts = phc.split("$");
138
+ if (parts.length !== 4)
139
+ return null;
140
+ const [algorithm, iterationsRaw, salt, hashB64] = parts;
141
+ const digestName = algorithm.slice("pbkdf2_".length);
142
+ const digest = PBKDF2_DIGEST[digestName];
143
+ if (!digest)
144
+ return null;
145
+ const iterations = Number(iterationsRaw);
146
+ if (!Number.isInteger(iterations) || iterations < 1)
147
+ return null;
148
+ try {
149
+ return {
150
+ digest,
151
+ iterations,
152
+ salt: encoder.encode(salt),
153
+ expected: decodeBase64(hashB64),
154
+ };
155
+ }
156
+ catch {
157
+ return null;
158
+ }
159
+ }
160
+ function parsePhcPbkdf2(phc) {
161
+ const parsed = parsePhc(phc);
162
+ if (!parsed || !parsed.salt || !parsed.hash)
163
+ return null;
164
+ const digestName = parsed.id.slice("pbkdf2-".length);
165
+ const digest = PBKDF2_DIGEST[digestName];
166
+ if (!digest)
167
+ return null;
168
+ const iterations = Number(parsed.params.i ?? parsed.params.rounds);
169
+ if (!Number.isInteger(iterations) || iterations < 1)
170
+ return null;
171
+ return { digest, iterations, salt: parsed.salt, expected: parsed.hash };
172
+ }
173
+ function parsePbkdf2(phc) {
174
+ const parsed = phc.startsWith("pbkdf2_")
175
+ ? parseDjango(phc)
176
+ : phc.startsWith("$pbkdf2-")
177
+ ? parsePhcPbkdf2(phc)
178
+ : null;
179
+ if (!parsed)
180
+ return null;
181
+ if (parsed.iterations > MAX_PBKDF2_ITERATIONS)
182
+ return null;
183
+ if (parsed.expected.length < MIN_HASH_BYTES)
184
+ return null;
185
+ return parsed;
186
+ }
187
+ /**
188
+ * Built-in {@link LegacyPasswordVerifier} for PBKDF2 hashes — the only family
189
+ * this package can verify dep-free (PBKDF2 is a Web Crypto primitive). Handles
190
+ * two common encodings, auto-detected:
191
+ *
192
+ * - **Django** — `pbkdf2_<digest>$<iterations>$<salt>$<base64 hash>`, where the
193
+ * salt is used as raw UTF-8 bytes and the hash is standard base64. This is
194
+ * Django's `PBKDF2PasswordHasher` output.
195
+ * - **PHC** — `$pbkdf2-<digest>$i=<iterations>$<base64 salt>$<base64 hash>`,
196
+ * with base64 (padding optional) salt and hash.
197
+ *
198
+ * `<digest>` is one of `sha1`, `sha256`, `sha512`. The derived-key length is
199
+ * taken from the stored hash. Comparison is constant-time. On a match,
200
+ * {@link IdentityService} rehashes into its native PBKDF2 format and clears the
201
+ * imported hash.
202
+ *
203
+ * A hash outside the accepted bounds is declined rather than verified —
204
+ * `canVerify` returns `false` and `verify` resolves `false`, which reads to your
205
+ * app as a wrong password. The bounds are more than 1,000,000 iterations and a
206
+ * stored checksum shorter than 16 bytes.
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * import { IdentityService } from "@udibo/oauth2/identity";
211
+ * import { pbkdf2Verifier } from "@udibo/oauth2/identity/migration";
212
+ *
213
+ * const identity = new IdentityService({
214
+ * users: store, // implements getLegacyCredential/clearLegacyCredential
215
+ * legacyVerifiers: [pbkdf2Verifier()],
216
+ * });
217
+ * ```
218
+ */
219
+ export function pbkdf2Verifier(options = {}) {
220
+ const id = options.id ?? "pbkdf2";
221
+ return {
222
+ id,
223
+ canVerify(phc) {
224
+ return parsePbkdf2(phc) !== null;
225
+ },
226
+ async verify(password, phc) {
227
+ const parsed = parsePbkdf2(phc);
228
+ if (!parsed)
229
+ return false;
230
+ const key = await crypto.subtle.importKey("raw", encoder.encode(password), "PBKDF2", false, ["deriveBits"]);
231
+ const bits = await crypto.subtle.deriveBits({
232
+ name: "PBKDF2",
233
+ salt: new Uint8Array(parsed.salt),
234
+ iterations: parsed.iterations,
235
+ hash: parsed.digest,
236
+ }, key, parsed.expected.length * 8);
237
+ const derived = new Uint8Array(bits);
238
+ if (derived.length !== parsed.expected.length)
239
+ return false;
240
+ return timingSafeEqual(derived, parsed.expected);
241
+ },
242
+ };
243
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Identity self-service primitives for apps that own their login.
3
+ *
4
+ * The OAuth2/BFF machinery handles tokens and sessions; this layer covers the
5
+ * "own-auth" flows an app otherwise hand-rolls. It is **optional** and
6
+ * composable — own the flows and the token lifecycle, keep storage as thin,
7
+ * app-owned seams (the `SessionStore` philosophy), never a DB-adapter zoo.
8
+ *
9
+ * - {@link IdentityService} — orchestrates the primitives below into sign-up,
10
+ * sign-in, password-reset, and email-verification flows you call from your
11
+ * own routes (storage stays an app-owned {@link IdentityUserStore}).
12
+ * - {@link PasswordIdentityService} — PBKDF2 password hashing + constant-time
13
+ * verify (no more hand-rolled crypto); implement {@link PasswordHasherLike}
14
+ * instead to plug in argon2 or scrypt.
15
+ * - {@link TokenFlowService} — single-use, hashed-at-rest verify/reset/sign-in
16
+ * tokens over an app-owned {@link TokenFlowStore} (magic links reuse it via
17
+ * {@link IdentityService.requestSignInLink}).
18
+ * - {@link EmailOtpService} — short numeric one-time codes over an app-owned
19
+ * {@link OtpStore}: hashed at rest, attempt-capped (guessable-online threat
20
+ * model), purpose-generic so it doubles as a step-up factor. Wire it into
21
+ * {@link IdentityService} via the `otp` option for "email me a code"
22
+ * sign-in.
23
+ * - {@link createIdentifierResolver} / {@link classifyIdentifier} — accept
24
+ * "email or username (or phone)" on a sign-in form without the bespoke branch.
25
+ * - {@link DeliveryHooks} + URL builders — the seam your mailer/SMS plugs into.
26
+ * - {@link RevocableSessionService} / {@link ListableSessionService} /
27
+ * {@link isRecentlyAuthenticated} — session revocation on credential change,
28
+ * the optional listing half ({@link SessionSummary} rows for a "where you're
29
+ * signed in" screen), and step-up checks.
30
+ * - {@link RateLimiter} — opt-in throttling (wire it into
31
+ * {@link IdentityService}); {@link checkPasswordPolicy} — the password rules
32
+ * {@link IdentityService} already applies to `signUp`/`resetPassword`, at
33
+ * their 8–256 character defaults unless you pass a tighter `passwordPolicy`.
34
+ * - {@link CaptchaProvider} + {@link verifyCaptcha} — the bring-your-own
35
+ * bot-challenge seam (Turnstile/hCaptcha/reCAPTCHA), with fail-open/closed
36
+ * handling; no provider means no challenge.
37
+ * - {@link AccountLockout} — failed-attempt lockout with a self-service unlock
38
+ * flow ({@link IdentityService.unlockAccount}) and a log-only rollout mode
39
+ * via `protectionMode`.
40
+ * - {@link IdentityEvent} + {@link IdentityEventHook} — auth-outcome events for
41
+ * audit capture (wire via {@link IdentityService}'s `onEvent`).
42
+ * - {@link IdentityError} — a stable error contract a front-end can switch on.
43
+ * - Importing users from another auth system without a password reset
44
+ * (upgrade-on-login) lives on its own subpath,
45
+ * `@udibo/oauth2/identity/migration`: the `LegacyPasswordVerifier` seam, the
46
+ * built-in `pbkdf2Verifier`, and `parsePhc`. Wire them through this module's
47
+ * {@link IdentityServiceOptions.legacyVerifiers}.
48
+ *
49
+ * @module
50
+ */
51
+ import "../_dnt.polyfills.js";
52
+ export { DEFAULT_PBKDF2_ITERATIONS, generateSalt, hashPassword, LEGACY_PBKDF2_ITERATIONS, type PasswordCredential, type PasswordHasherLike, type PasswordHashParams, PasswordIdentityService, type PasswordIdentityServiceOptions, PBKDF2_SHA256, verifyPassword, } from "./password.js";
53
+ export { classifyIdentifier, createIdentifierResolver, type IdentifierKind, type IdentifierLookups, } from "./identifier.js";
54
+ export { type CreatedToken, type CreateTokenOptions, MemoryTokenFlowStore, type ResolvedToken, type TokenFlowRecord, TokenFlowService, type TokenFlowStore, TokenPurpose, type TokenStatus, } from "./token-flow.js";
55
+ export { buildResetUrl, buildSignInUrl, buildTokenUrl, buildUnlockUrl, buildVerificationUrl, type CodeDeliveryHook, type CodeDeliveryMessage, type DeliveryHook, type DeliveryHooks, type DeliveryMessage, } from "./delivery.js";
56
+ export { isRecentlyAuthenticated, type ListableSessionService, type RevocableSessionService, type SessionSummary, supportsSessionListing, } from "./session.js";
57
+ export { type ConsumeSignInLinkResult, type IdentityRateLimiters, IdentityService, type IdentityServiceOptions, type IdentityUser, type IdentityUserStore, type UnlockAccountResult, type VerifyEmailResult, type VerifySignInCodeResult, } from "./service.js";
58
+ export { EmailOtpService, type EmailOtpServiceOptions, MemoryOtpStore, type OtpRecord, type OtpStore, type RequestedOtp, type RequestOtpOptions, type VerifyOtpOptions, type VerifyOtpResult, } from "./otp.js";
59
+ export { AccountLockout, type AccountLockoutLike, type AccountLockoutOptions, type LockoutFailureResult, type LockoutRecord, type LockoutStatus, type LockoutStore, MemoryLockoutStore, } from "./lockout.js";
60
+ export type { IdentityEvent, IdentityEventHook, SignInCodeFailureReason, SignInFailureReason, } from "./events.js";
61
+ export { IdentityError, type IdentityErrorCode, type IdentityErrorOptions, type IdentityErrorStatus, identityErrorStatus, isIdentityError, } from "./errors.js";
62
+ export { MemoryRateLimitStore, RateLimiter, type RateLimiterLike, type RateLimiterOptions, type RateLimitResult, type RateLimitStore, } from "./rate-limit.js";
63
+ export { type CaptchaOutcome, type CaptchaProvider, type CaptchaVerifyContext, type CaptchaVerifyResult, verifyCaptcha, type VerifyCaptchaOptions, } from "./captcha.js";
64
+ export { assertPasswordPolicy, checkPasswordPolicy, type PasswordPolicy, type PasswordPolicyResult, } from "./password-policy.js";
65
+ export { type BreachedPasswordOptions, breachedPasswordValidator, } from "./hibp.js";