@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,42 @@
1
+ /**
2
+ * MFA primitives for apps that own their login: TOTP (RFC 6238) and single-use
3
+ * recovery codes, plus a small orchestrator over an app-owned store — the same
4
+ * thin-seam philosophy as the rest of the identity layer. Built entirely on
5
+ * Web Crypto and `@std`; no other dependencies.
6
+ *
7
+ * - {@link generateTotpSecret} / {@link buildOtpauthUri} /
8
+ * {@link generateTotpCode} / {@link verifyTotpCode} — the TOTP math, with a
9
+ * constant-time verify that reports the matched time step for replay
10
+ * rejection.
11
+ * - {@link generateRecoveryCodes} / {@link hashRecoveryCode} /
12
+ * {@link verifyRecoveryCode} — readable single-use fallback codes, hashed at
13
+ * rest.
14
+ * - {@link MfaService} over an app-implemented {@link MfaStore} — enrollment
15
+ * (pending → confirmed), verification with atomic replay and burn handling,
16
+ * regeneration, disable, plus the layer's usual seams (`rateLimiter`,
17
+ * `onEvent` for `mfa.*` audit events). {@link MemoryMfaStore} ships for
18
+ * dev/tests.
19
+ *
20
+ * The library stops at the primitives: whether MFA is optional or required,
21
+ * where challenges happen, and sessions are the app's policy and belong in
22
+ * its routes.
23
+ *
24
+ * @example Wire MFA into an app
25
+ * ```ts
26
+ * import { MemoryMfaStore, MfaService } from "@udibo/oauth2/identity/mfa";
27
+ *
28
+ * const mfa = new MfaService({ store: new MemoryMfaStore() });
29
+ * const { otpauthUri } = await mfa.startEnrollment(user.id, {
30
+ * issuer: "Example",
31
+ * accountName: user.email,
32
+ * });
33
+ * // render otpauthUri as a QR, then:
34
+ * const confirmation = await mfa.confirmEnrollment(user.id, submittedCode);
35
+ * ```
36
+ *
37
+ * @module
38
+ */
39
+ import "../../_dnt.polyfills.js";
40
+ export { buildOtpauthUri, decodeTotpSecret, type GeneratedTotpSecret, generateTotpCode, type GenerateTotpCodeOptions, generateTotpSecret, type OtpauthUriOptions, type TotpAlgorithm, type TotpVerification, verifyTotpCode, type VerifyTotpCodeOptions, } from "./totp.js";
41
+ export { type GeneratedRecoveryCodes, generateRecoveryCodes, type GenerateRecoveryCodesOptions, hashRecoveryCode, normalizeRecoveryCode, RECOVERY_CODE_LENGTH, type RecoveryCodeVerification, verifyRecoveryCode, type VerifyRecoveryCodeOptions, } from "./recovery-codes.js";
42
+ export { MemoryMfaStore, type MfaEnrollmentConfirmation, type MfaEnrollmentStart, type MfaEnrollmentStatus, MfaService, type MfaServiceOptions, type MfaStore, type MfaTotpOptions, type MfaTotpRecord, type MfaVerification, type MfaVerifyFailureReason, type MfaVerifyOptions, type StartEnrollmentOptions, } from "./service.js";
@@ -0,0 +1,42 @@
1
+ /**
2
+ * MFA primitives for apps that own their login: TOTP (RFC 6238) and single-use
3
+ * recovery codes, plus a small orchestrator over an app-owned store — the same
4
+ * thin-seam philosophy as the rest of the identity layer. Built entirely on
5
+ * Web Crypto and `@std`; no other dependencies.
6
+ *
7
+ * - {@link generateTotpSecret} / {@link buildOtpauthUri} /
8
+ * {@link generateTotpCode} / {@link verifyTotpCode} — the TOTP math, with a
9
+ * constant-time verify that reports the matched time step for replay
10
+ * rejection.
11
+ * - {@link generateRecoveryCodes} / {@link hashRecoveryCode} /
12
+ * {@link verifyRecoveryCode} — readable single-use fallback codes, hashed at
13
+ * rest.
14
+ * - {@link MfaService} over an app-implemented {@link MfaStore} — enrollment
15
+ * (pending → confirmed), verification with atomic replay and burn handling,
16
+ * regeneration, disable, plus the layer's usual seams (`rateLimiter`,
17
+ * `onEvent` for `mfa.*` audit events). {@link MemoryMfaStore} ships for
18
+ * dev/tests.
19
+ *
20
+ * The library stops at the primitives: whether MFA is optional or required,
21
+ * where challenges happen, and sessions are the app's policy and belong in
22
+ * its routes.
23
+ *
24
+ * @example Wire MFA into an app
25
+ * ```ts
26
+ * import { MemoryMfaStore, MfaService } from "@udibo/oauth2/identity/mfa";
27
+ *
28
+ * const mfa = new MfaService({ store: new MemoryMfaStore() });
29
+ * const { otpauthUri } = await mfa.startEnrollment(user.id, {
30
+ * issuer: "Example",
31
+ * accountName: user.email,
32
+ * });
33
+ * // render otpauthUri as a QR, then:
34
+ * const confirmation = await mfa.confirmEnrollment(user.id, submittedCode);
35
+ * ```
36
+ *
37
+ * @module
38
+ */
39
+ import "../../_dnt.polyfills.js";
40
+ export { buildOtpauthUri, decodeTotpSecret, generateTotpCode, generateTotpSecret, verifyTotpCode, } from "./totp.js";
41
+ export { generateRecoveryCodes, hashRecoveryCode, normalizeRecoveryCode, RECOVERY_CODE_LENGTH, verifyRecoveryCode, } from "./recovery-codes.js";
42
+ export { MemoryMfaStore, MfaService, } from "./service.js";
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Recovery-code primitives for MFA — the single-use fallback a user redeems
3
+ * when their authenticator is unavailable.
4
+ *
5
+ * Codes are generated from a Crockford-style alphabet (no `I`, `L`, `O`, or
6
+ * `U`, so nothing reads ambiguously off paper) as two 8-character groups —
7
+ * 80 bits of entropy per code. Only SHA-256 hashes are meant to be stored:
8
+ * at 80 bits an unsalted fast hash is sufficient — even a full offline
9
+ * keyspace sweep is infeasible, so a leaked hash table stays unbroken and the
10
+ * deterministic hash doubles as an exact-match storage key for atomic
11
+ * burn-on-use. Online guessing should be rate-limited like any credential.
12
+ * Verification is constant-time against every stored hash and reports which
13
+ * one matched so the app can delete ("burn") it — **single use is the app's
14
+ * storage responsibility**.
15
+ *
16
+ * @module
17
+ */
18
+ /**
19
+ * The length of a normalized recovery code (separators stripped). A submitted
20
+ * value of any other length cannot be a recovery code, so callers can skip
21
+ * hashing and storage lookups for it.
22
+ */
23
+ export declare const RECOVERY_CODE_LENGTH = 16;
24
+ /**
25
+ * Normalize a recovery code to its canonical stored form: uppercase with
26
+ * separators (anything non-alphanumeric) stripped. Hashing and verification
27
+ * apply this automatically, so users can submit codes in any case and with or
28
+ * without the display hyphens.
29
+ */
30
+ export declare function normalizeRecoveryCode(code: string): string;
31
+ /**
32
+ * Hash a recovery code for storage: hex-encoded SHA-256 of the normalized
33
+ * code, the same stored-hash format the package's other high-entropy
34
+ * credentials use. Fast on purpose — see the module doc for why the code's
35
+ * entropy makes a slow hash unnecessary. Store only the hash; the plaintext
36
+ * code is shown to the user once at generation time.
37
+ */
38
+ export declare function hashRecoveryCode(code: string): Promise<string>;
39
+ /** Options for {@link generateRecoveryCodes}. */
40
+ export interface GenerateRecoveryCodesOptions {
41
+ /** How many codes to generate. Defaults to `10`. */
42
+ count?: number;
43
+ }
44
+ /** A freshly generated recovery-code set (see {@link generateRecoveryCodes}). */
45
+ export interface GeneratedRecoveryCodes {
46
+ /**
47
+ * The plaintext codes, e.g. `"4Q7MB2XW-9ZHT3KVR"`. Show them to the user
48
+ * exactly once; they are not recoverable afterwards.
49
+ */
50
+ codes: string[];
51
+ /**
52
+ * The hash of each code, index-aligned with `codes` — persist these (see
53
+ * {@link hashRecoveryCode}).
54
+ */
55
+ hashes: string[];
56
+ }
57
+ /**
58
+ * Generate a set of single-use recovery codes plus the hashes to store.
59
+ * Persist `hashes`, display `codes` once, and delete a hash when
60
+ * {@link verifyRecoveryCode} reports its index matched.
61
+ *
62
+ * @example Issue codes on MFA enrollment
63
+ * ```ts
64
+ * const { codes, hashes } = await generateRecoveryCodes();
65
+ * await store.setRecoveryHashes(user.id, hashes);
66
+ * // display `codes` to the user exactly once
67
+ * ```
68
+ */
69
+ export declare function generateRecoveryCodes(options?: GenerateRecoveryCodesOptions): Promise<GeneratedRecoveryCodes>;
70
+ /** Options for {@link verifyRecoveryCode}. */
71
+ export interface VerifyRecoveryCodeOptions {
72
+ /** The code the user submitted (any case, separators optional). */
73
+ code: string;
74
+ /** The stored hashes for the user's unused codes. */
75
+ hashes: string[];
76
+ }
77
+ /** The outcome of {@link verifyRecoveryCode}. */
78
+ export type RecoveryCodeVerification = {
79
+ valid: false;
80
+ } | {
81
+ valid: true;
82
+ /**
83
+ * The index of the matching hash. Burn it with an atomic delete-by-value
84
+ * (a conditional `DELETE` on the hash, never read-filter-write) so the
85
+ * code cannot be redeemed again — the library does not do this for you at
86
+ * this level ({@link MfaService} does, atomically).
87
+ */
88
+ index: number;
89
+ };
90
+ /**
91
+ * Check a submitted recovery code against the stored hashes. Comparison is
92
+ * constant-time and every hash is checked even after a match, so timing does
93
+ * not reveal whether or where a match occurred. On success the caller must
94
+ * burn the matched hash (see {@link RecoveryCodeVerification}).
95
+ */
96
+ export declare function verifyRecoveryCode(options: VerifyRecoveryCodeOptions): Promise<RecoveryCodeVerification>;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Recovery-code primitives for MFA — the single-use fallback a user redeems
3
+ * when their authenticator is unavailable.
4
+ *
5
+ * Codes are generated from a Crockford-style alphabet (no `I`, `L`, `O`, or
6
+ * `U`, so nothing reads ambiguously off paper) as two 8-character groups —
7
+ * 80 bits of entropy per code. Only SHA-256 hashes are meant to be stored:
8
+ * at 80 bits an unsalted fast hash is sufficient — even a full offline
9
+ * keyspace sweep is infeasible, so a leaked hash table stays unbroken and the
10
+ * deterministic hash doubles as an exact-match storage key for atomic
11
+ * burn-on-use. Online guessing should be rate-limited like any credential.
12
+ * Verification is constant-time against every stored hash and reports which
13
+ * one matched so the app can delete ("burn") it — **single use is the app's
14
+ * storage responsibility**.
15
+ *
16
+ * @module
17
+ */
18
+ import { sha256Hash } from "../../server/utils/hash.js";
19
+ import { timingSafeMatchIndex } from "../../utils/_timing-safe.js";
20
+ const RECOVERY_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
21
+ const DEFAULT_COUNT = 10;
22
+ /**
23
+ * The length of a normalized recovery code (separators stripped). A submitted
24
+ * value of any other length cannot be a recovery code, so callers can skip
25
+ * hashing and storage lookups for it.
26
+ */
27
+ export const RECOVERY_CODE_LENGTH = 16;
28
+ /**
29
+ * Normalize a recovery code to its canonical stored form: uppercase with
30
+ * separators (anything non-alphanumeric) stripped. Hashing and verification
31
+ * apply this automatically, so users can submit codes in any case and with or
32
+ * without the display hyphens.
33
+ */
34
+ export function normalizeRecoveryCode(code) {
35
+ return code.toUpperCase().replaceAll(/[^0-9A-Z]/g, "");
36
+ }
37
+ /**
38
+ * Hash a recovery code for storage: hex-encoded SHA-256 of the normalized
39
+ * code, the same stored-hash format the package's other high-entropy
40
+ * credentials use. Fast on purpose — see the module doc for why the code's
41
+ * entropy makes a slow hash unnecessary. Store only the hash; the plaintext
42
+ * code is shown to the user once at generation time.
43
+ */
44
+ export function hashRecoveryCode(code) {
45
+ return sha256Hash(normalizeRecoveryCode(code));
46
+ }
47
+ /**
48
+ * Generate a set of single-use recovery codes plus the hashes to store.
49
+ * Persist `hashes`, display `codes` once, and delete a hash when
50
+ * {@link verifyRecoveryCode} reports its index matched.
51
+ *
52
+ * @example Issue codes on MFA enrollment
53
+ * ```ts
54
+ * const { codes, hashes } = await generateRecoveryCodes();
55
+ * await store.setRecoveryHashes(user.id, hashes);
56
+ * // display `codes` to the user exactly once
57
+ * ```
58
+ */
59
+ export async function generateRecoveryCodes(options) {
60
+ const count = options?.count ?? DEFAULT_COUNT;
61
+ const bytes = crypto.getRandomValues(new Uint8Array(count * RECOVERY_CODE_LENGTH));
62
+ const codes = [];
63
+ for (let i = 0; i < count; i++) {
64
+ const raw = Array.from(bytes.subarray(i * RECOVERY_CODE_LENGTH, (i + 1) * RECOVERY_CODE_LENGTH), (byte) => RECOVERY_ALPHABET[byte % RECOVERY_ALPHABET.length]).join("");
65
+ codes.push(`${raw.slice(0, 8)}-${raw.slice(8)}`);
66
+ }
67
+ const hashes = await Promise.all(codes.map(hashRecoveryCode));
68
+ return { codes, hashes };
69
+ }
70
+ /**
71
+ * Check a submitted recovery code against the stored hashes. Comparison is
72
+ * constant-time and every hash is checked even after a match, so timing does
73
+ * not reveal whether or where a match occurred. On success the caller must
74
+ * burn the matched hash (see {@link RecoveryCodeVerification}).
75
+ */
76
+ export async function verifyRecoveryCode(options) {
77
+ const submitted = await hashRecoveryCode(options.code);
78
+ const index = timingSafeMatchIndex(options.hashes, submitted);
79
+ return index === undefined ? { valid: false } : { valid: true, index };
80
+ }
@@ -0,0 +1,338 @@
1
+ /**
2
+ * The MFA orchestrator: a small service that wires the TOTP and recovery-code
3
+ * primitives to an app-owned {@link MfaStore} — the same thin storage seam as
4
+ * every other layer here. It owns the lifecycle rules (pending vs. active
5
+ * secrets, TOTP replay rejection, recovery-code burning); the app owns the
6
+ * rows, the routes, and the policy for *when* MFA is required.
7
+ *
8
+ * @module
9
+ */
10
+ import { type IdentityEventHook } from "../events.js";
11
+ import { type RateLimiterLike } from "../rate-limit.js";
12
+ import { type TotpAlgorithm } from "./totp.js";
13
+ /**
14
+ * A user's TOTP state as the store persists it. `secretBase32` is a symmetric
15
+ * key — encrypt it at rest. A record may hold only a pending secret (enrollment
16
+ * started but unconfirmed) or only an active one.
17
+ */
18
+ export interface MfaTotpRecord {
19
+ /** The active, confirmed secret. Absent until enrollment is confirmed. */
20
+ secretBase32?: string;
21
+ /**
22
+ * The highest time-step counter a code has been accepted for — the replay
23
+ * guard. Present whenever `secretBase32` is.
24
+ */
25
+ lastStep?: number;
26
+ /** A generated-but-unconfirmed secret awaiting {@link MfaService.confirmEnrollment}. */
27
+ pendingSecretBase32?: string;
28
+ }
29
+ /**
30
+ * App-owned persistence for {@link MfaService}, keyed by user id. Implement it
31
+ * over your database; {@link MemoryMfaStore} ships for dev and tests. The
32
+ * service never reads or writes anything else — encrypt `secretBase32` /
33
+ * `pendingSecretBase32` at rest however your app seals secrets (they are
34
+ * symmetric keys, not hashes).
35
+ *
36
+ * Three methods are the concurrency guards of the whole design and **must be
37
+ * atomic** in a real implementation (a conditional `UPDATE`/`DELETE`, not a
38
+ * read-then-write): {@link activateTotp}, {@link advanceLastStep}, and
39
+ * {@link consumeRecoveryHash}.
40
+ */
41
+ export interface MfaStore {
42
+ /** Return the user's TOTP record, or `undefined` when none exists. */
43
+ getTotp(userId: string): Promise<MfaTotpRecord | undefined>;
44
+ /**
45
+ * Save a pending (unconfirmed) secret, preserving any active credential.
46
+ * A repeat call overwrites the previous pending secret.
47
+ */
48
+ setPendingTotp(userId: string, secretBase32: string): Promise<void>;
49
+ /**
50
+ * Atomically promote the pending secret to active — but only when the
51
+ * stored pending secret still equals `secretBase32` (the secret the
52
+ * confirmation code was verified against): set `secretBase32`, set
53
+ * `lastStep`, clear the pending value, and return `true`. Return `false`
54
+ * without changing anything when the pending secret is missing or has been
55
+ * replaced by a newer enrollment.
56
+ */
57
+ activateTotp(userId: string, secretBase32: string, lastStep: number): Promise<boolean>;
58
+ /** Delete the user's TOTP record entirely (active and pending). */
59
+ clearTotp(userId: string): Promise<void>;
60
+ /**
61
+ * Atomically advance the replay guard: persist `step` and return `true`
62
+ * only when it is greater than the stored `lastStep` (e.g.
63
+ * `UPDATE … SET last_step = $step WHERE last_step < $step`). Return `false`
64
+ * otherwise — a concurrent request already spent this step, so the caller
65
+ * treats the code as replayed.
66
+ */
67
+ advanceLastStep(userId: string, step: number): Promise<boolean>;
68
+ /** Return the user's unused recovery-code hashes (empty when none). */
69
+ getRecoveryHashes(userId: string): Promise<string[]>;
70
+ /**
71
+ * Replace the user's recovery-code hashes with exactly this set — used for
72
+ * initial issue, regeneration, and disable (`[]`).
73
+ */
74
+ setRecoveryHashes(userId: string, hashes: string[]): Promise<void>;
75
+ /**
76
+ * Atomically delete one stored hash and return whether it existed (e.g.
77
+ * `DELETE … WHERE user_id = $user AND hash = $hash` checking the row
78
+ * count). This is what makes a recovery code single-use under concurrent
79
+ * redemption — never implement it as read-filter-write.
80
+ */
81
+ consumeRecoveryHash(userId: string, hash: string): Promise<boolean>;
82
+ }
83
+ /** TOTP parameters an {@link MfaService} applies to every credential it manages. */
84
+ export interface MfaTotpOptions {
85
+ /** Code length. Defaults to `6`. */
86
+ digits?: number;
87
+ /** Time-step length in seconds. Defaults to `30`. */
88
+ periodSeconds?: number;
89
+ /** HMAC hash. Defaults to `"SHA-1"` (see {@link TotpAlgorithm}). */
90
+ algorithm?: TotpAlgorithm;
91
+ /**
92
+ * Clock-skew windows accepted on each side of the current step during
93
+ * {@link MfaService.verify} and {@link MfaService.confirmEnrollment}.
94
+ * Defaults to `1`.
95
+ */
96
+ windows?: number;
97
+ }
98
+ /** Options for {@link MfaService}. */
99
+ export interface MfaServiceOptions {
100
+ /** The app-owned persistence seam. */
101
+ store: MfaStore;
102
+ /**
103
+ * TOTP parameters. Set them once at construction — changing them later
104
+ * invalidates codes for already-enrolled authenticators.
105
+ */
106
+ totp?: MfaTotpOptions;
107
+ /** How many recovery codes to issue on confirm/regenerate. Defaults to `10`. */
108
+ recoveryCodeCount?: number;
109
+ /**
110
+ * Throttles {@link verify} per user id (a 6-digit code is brute-forceable
111
+ * without one). When the limit is hit the service emits
112
+ * `mfa.verify.rate_limited` and — under `protectionMode: "enforce"` —
113
+ * throws an `IdentityError` with code `rate_limited`. A successful
114
+ * verification resets the user's window, so legitimate sign-ins never
115
+ * accumulate toward a lockout.
116
+ */
117
+ rateLimiter?: RateLimiterLike;
118
+ /**
119
+ * `"enforce"` (default) applies the rate limit; `"log-only"` emits the
120
+ * event but lets the attempt through, for observing limits before turning
121
+ * them on.
122
+ */
123
+ protectionMode?: "enforce" | "log-only";
124
+ /** Receives `mfa.*` {@link IdentityEvent}s for audit capture. */
125
+ onEvent?: IdentityEventHook;
126
+ }
127
+ /** Options for {@link MfaService.startEnrollment}. */
128
+ export interface StartEnrollmentOptions {
129
+ /** The service name shown in the authenticator app (e.g. `"Udibo"`). */
130
+ issuer: string;
131
+ /** The account label shown under the issuer (typically the user's email). */
132
+ accountName: string;
133
+ }
134
+ /** The result of {@link MfaService.startEnrollment}. */
135
+ export interface MfaEnrollmentStart {
136
+ /** The pending secret in base32, for manual entry into an authenticator. */
137
+ base32: string;
138
+ /** The `otpauth://` URI to render as a QR code. */
139
+ otpauthUri: string;
140
+ }
141
+ /** The result of {@link MfaService.confirmEnrollment}. */
142
+ export type MfaEnrollmentConfirmation = {
143
+ confirmed: false;
144
+ } | {
145
+ confirmed: true;
146
+ /**
147
+ * The plaintext recovery codes. Display them to the user exactly once;
148
+ * only their hashes are stored.
149
+ */
150
+ recoveryCodes: string[];
151
+ };
152
+ /** Per-call options for {@link MfaService.verify} and {@link MfaService.confirmEnrollment}. */
153
+ export interface MfaVerifyOptions {
154
+ /**
155
+ * Restrict which factor the code is checked against. Defaults to trying
156
+ * TOTP first, then recovery — pass `"totp"` when your UI has a dedicated
157
+ * recovery-code screen and the authenticator field must never silently burn
158
+ * a recovery code.
159
+ */
160
+ method?: "totp" | "recovery";
161
+ /** Epoch milliseconds to verify against. Defaults to `Date.now()`. */
162
+ timestamp?: number;
163
+ }
164
+ /**
165
+ * Why {@link MfaService.verify} rejected a code, on the invalid branch of
166
+ * {@link MfaVerification}. Mirrors the `reason` on the `mfa.verify.failed`
167
+ * event — same source of truth — so a caller need not subscribe to `onEvent`
168
+ * to tell the two apart:
169
+ *
170
+ * - `"replayed"` — a *valid* TOTP code whose time step was already spent. A
171
+ * UI can say "that code was already used, wait for the next one"; it is also
172
+ * the classic signal of an intercepted or reused code.
173
+ * - `"invalid"` — a wrong code, a non-recovery string, or a code submitted by
174
+ * a user with no active credential (indistinguishable on purpose).
175
+ */
176
+ export type MfaVerifyFailureReason = "invalid" | "replayed";
177
+ /** The result of {@link MfaService.verify}. */
178
+ export type MfaVerification = {
179
+ valid: false;
180
+ /** Why the code was rejected — see {@link MfaVerifyFailureReason}. */
181
+ reason: MfaVerifyFailureReason;
182
+ } | {
183
+ valid: true;
184
+ method: "totp";
185
+ } | {
186
+ valid: true;
187
+ method: "recovery";
188
+ /**
189
+ * How many unused recovery codes remain — surface it so the UI can
190
+ * prompt regeneration when low.
191
+ */
192
+ remainingRecoveryCodes: number;
193
+ };
194
+ /**
195
+ * A user's TOTP enrollment stage, as {@link MfaService.enrollmentStatus}
196
+ * reports it:
197
+ *
198
+ * - `"none"` — no credential and no enrollment in progress.
199
+ * - `"pending"` — {@link MfaService.startEnrollment} ran but
200
+ * {@link MfaService.confirmEnrollment} has not; a secret is stored but does
201
+ * not yet authenticate. An enrollment screen can resume instead of minting a
202
+ * fresh secret (which would invalidate the one already in the authenticator).
203
+ * - `"active"` — a confirmed credential that {@link MfaService.verify} accepts.
204
+ */
205
+ export type MfaEnrollmentStatus = "none" | "pending" | "active";
206
+ /**
207
+ * Orchestrates TOTP enrollment, verification, and recovery codes over an
208
+ * app-owned {@link MfaStore}.
209
+ *
210
+ * Lifecycle: {@link startEnrollment} stores a *pending* secret (ignored by
211
+ * {@link verify}, so an unconfirmed enrollment can never lock a user out);
212
+ * {@link confirmEnrollment} proves the user's authenticator works, activates
213
+ * the secret, and issues recovery codes; {@link verify} accepts either a TOTP
214
+ * code (rejecting replays via the stored last step) or a recovery code
215
+ * (burning it on use). Replacing an active credential requires
216
+ * {@link disable} first — gate that route behind a fresh re-authentication,
217
+ * or a hijacked session could silently swap the user's authenticator.
218
+ * Deciding *when* to demand a code — sign-in policy, step-up, admin reset —
219
+ * is the app's job.
220
+ *
221
+ * @example Enroll, confirm, and verify
222
+ * ```ts
223
+ * const mfa = new MfaService({ store, rateLimiter });
224
+ * const { otpauthUri } = await mfa.startEnrollment(user.id, {
225
+ * issuer: "Udibo", accountName: user.email,
226
+ * });
227
+ * // …render QR, user scans and submits a code…
228
+ * const confirmation = await mfa.confirmEnrollment(user.id, code);
229
+ * // …later, at sign-in…
230
+ * const result = await mfa.verify(user.id, submittedCode);
231
+ * ```
232
+ */
233
+ export declare class MfaService {
234
+ #private;
235
+ /**
236
+ * Builds the service around an app-owned {@link MfaStore}.
237
+ *
238
+ * @throws {TypeError} on unusable configuration — non-positive-integer
239
+ * `digits`/`periodSeconds`/`recoveryCodeCount` or negative `windows` — so a
240
+ * miswired deploy fails at construction instead of silently rejecting every
241
+ * code an enrolled user submits.
242
+ */
243
+ constructor(options: MfaServiceOptions);
244
+ /**
245
+ * Begin TOTP enrollment: generates a fresh secret, stores it as *pending*,
246
+ * and returns what the enrollment screen needs. Repeating the call replaces
247
+ * the pending secret.
248
+ *
249
+ * @throws {IdentityError} `mfa_already_enrolled` when an active credential
250
+ * exists — replacing an authenticator requires {@link disable} first (behind
251
+ * re-authentication), so a stolen session cannot silently swap it.
252
+ */
253
+ startEnrollment(userId: string, options: StartEnrollmentOptions): Promise<MfaEnrollmentStart>;
254
+ /**
255
+ * Confirm enrollment by verifying a code against the *pending* secret. On
256
+ * success the secret becomes active, a fresh recovery-code set is generated
257
+ * and stored, and the plaintext codes are returned for one-time display.
258
+ * Returns `{ confirmed: false }` when the code is wrong, no enrollment is
259
+ * pending, or the pending secret changed under a concurrent enrollment. If
260
+ * storing the recovery codes fails after activation, the just-activated
261
+ * credential is rolled back and the error rethrown — a failure leaves the
262
+ * user *not* enrolled (free to retry), never enrolled without the recovery
263
+ * codes they were promised.
264
+ *
265
+ * @throws {IdentityError} `mfa_already_enrolled` when an active credential
266
+ * exists (see {@link startEnrollment}).
267
+ */
268
+ confirmEnrollment(userId: string, code: string, options?: Pick<MfaVerifyOptions, "timestamp">): Promise<MfaEnrollmentConfirmation>;
269
+ /**
270
+ * Verify a second-factor code: first as TOTP against the *active* secret
271
+ * (pending secrets are ignored — they need {@link confirmEnrollment}), then
272
+ * as a recovery code (restrict with {@link MfaVerifyOptions.method}). An
273
+ * accepted TOTP code atomically advances the stored last step, so the same
274
+ * code is rejected on replay even under concurrent submission; an accepted
275
+ * recovery code is atomically burned, so it is single-use. Without an
276
+ * active credential every code is rejected — recovery codes are a fallback
277
+ * *for* the enrolled factor, so stale hashes can never authenticate a user
278
+ * whose MFA was disabled. A successful verification resets the per-user
279
+ * rate-limit window. On failure the result carries a
280
+ * {@link MfaVerifyFailureReason} — `"replayed"` for a valid-but-spent TOTP
281
+ * code, `"invalid"` otherwise — so a caller can distinguish a reused code
282
+ * from a wrong one without subscribing to `onEvent`.
283
+ *
284
+ * @throws {IdentityError} `rate_limited` when a `rateLimiter` is configured,
285
+ * the per-user limit is hit, and `protectionMode` is `"enforce"`.
286
+ */
287
+ verify(userId: string, code: string, options?: MfaVerifyOptions): Promise<MfaVerification>;
288
+ /** Whether the user has an *active* (confirmed) TOTP credential. */
289
+ isEnrolled(userId: string): Promise<boolean>;
290
+ /**
291
+ * The user's TOTP enrollment stage — `"none"`, `"pending"`, or `"active"`
292
+ * (see {@link MfaEnrollmentStatus}). Unlike {@link isEnrolled}, this also
293
+ * reveals a started-but-unconfirmed enrollment, so an enrollment screen can
294
+ * resume a pending secret the user already scanned rather than calling
295
+ * {@link startEnrollment} again and invalidating it.
296
+ */
297
+ enrollmentStatus(userId: string): Promise<MfaEnrollmentStatus>;
298
+ /**
299
+ * Remove the user's MFA entirely: recovery codes first, then active and
300
+ * pending secrets — so a partial failure can only leave MFA still *on*,
301
+ * never a "disabled" account whose old recovery codes still authenticate.
302
+ * Gate the route that calls this behind a fresh re-authentication —
303
+ * disabling MFA is what an account takeover wants most.
304
+ */
305
+ disable(userId: string): Promise<void>;
306
+ /**
307
+ * Replace the user's recovery codes with a fresh set and return the
308
+ * plaintext for one-time display. All previously issued codes stop working.
309
+ *
310
+ * @throws {IdentityError} `mfa_not_enrolled` when the user has no active
311
+ * TOTP enrollment.
312
+ */
313
+ regenerateRecoveryCodes(userId: string): Promise<string[]>;
314
+ }
315
+ /**
316
+ * In-memory {@link MfaStore} for development and tests. State is lost on
317
+ * restart; back the contract with a database (secrets encrypted at rest,
318
+ * conditional writes for the atomic methods) for production.
319
+ */
320
+ export declare class MemoryMfaStore implements MfaStore {
321
+ #private;
322
+ /** Return a copy of the user's TOTP record, or `undefined`. */
323
+ getTotp(userId: string): Promise<MfaTotpRecord | undefined>;
324
+ /** Store a pending secret, keeping any active credential. */
325
+ setPendingTotp(userId: string, secretBase32: string): Promise<void>;
326
+ /** Promote the matching pending secret to active; `false` when it changed. */
327
+ activateTotp(userId: string, secretBase32: string, lastStep: number): Promise<boolean>;
328
+ /** Delete the user's TOTP record. */
329
+ clearTotp(userId: string): Promise<void>;
330
+ /** Advance the replay guard only forward; `false` when the step was spent. */
331
+ advanceLastStep(userId: string, step: number): Promise<boolean>;
332
+ /** Return the user's stored recovery hashes (empty when none). */
333
+ getRecoveryHashes(userId: string): Promise<string[]>;
334
+ /** Replace the user's recovery hashes with exactly this set. */
335
+ setRecoveryHashes(userId: string, hashes: string[]): Promise<void>;
336
+ /** Delete one hash, reporting whether it was present. */
337
+ consumeRecoveryHash(userId: string, hash: string): Promise<boolean>;
338
+ }