@baseplate-dev/plugin-auth 0.6.17 → 0.6.18

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 (197) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/better-auth/admin/generators/admin-crud-manage-roles-action/admin-crud-manage-roles-action.generator.d.ts +12 -0
  3. package/dist/better-auth/admin/generators/admin-crud-manage-roles-action/generated/index.d.ts +12 -0
  4. package/dist/better-auth/admin/generators/admin-crud-manage-roles-action/generated/template-renderers.d.ts +6 -0
  5. package/dist/better-auth/admin/generators/admin-crud-manage-roles-action/generated/typed-templates.d.ts +6 -0
  6. package/dist/better-auth/admin/generators/admin-crud-reset-password-action/admin-crud-reset-password-action.generator.d.ts +12 -0
  7. package/dist/better-auth/admin/generators/admin-crud-reset-password-action/generated/index.d.ts +12 -0
  8. package/dist/better-auth/admin/generators/admin-crud-reset-password-action/generated/template-renderers.d.ts +6 -0
  9. package/dist/better-auth/admin/generators/admin-crud-reset-password-action/generated/typed-templates.d.ts +12 -0
  10. package/dist/better-auth/admin/generators/admin-crud-roles-column/admin-crud-roles-column.generator.d.ts +6 -0
  11. package/dist/better-auth/generators/react/better-auth-pages/better-auth-pages.generator.d.ts +6 -0
  12. package/dist/better-auth/generators/react/better-auth-pages/generated/index.d.ts +36 -0
  13. package/dist/better-auth/generators/react/better-auth-pages/generated/template-renderers.d.ts +6 -0
  14. package/dist/better-auth/generators/react/better-auth-pages/generated/typed-templates.d.ts +60 -0
  15. package/dist/better-auth/generators/react/react-better-auth/generated/index.d.ts +12 -0
  16. package/dist/better-auth/generators/react/react-better-auth/generated/template-renderers.d.ts +6 -0
  17. package/dist/better-auth/generators/react/react-better-auth/generated/typed-templates.d.ts +6 -0
  18. package/dist/better-auth/generators/react/react-better-auth/react-better-auth.generator.d.ts +6 -0
  19. package/dist/local-auth/admin/common.js +6 -0
  20. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/admin-crud-invite-user-action.generator.d.ts +531 -0
  21. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/admin-crud-invite-user-action.generator.js +119 -0
  22. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/index.d.ts +540 -0
  23. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/index.js +11 -0
  24. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/template-paths.d.ts +12 -0
  25. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/template-paths.js +24 -0
  26. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/template-renderers.d.ts +276 -0
  27. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/template-renderers.js +45 -0
  28. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/typed-templates.d.ts +263 -0
  29. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/typed-templates.js +21 -0
  30. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/index.d.ts +2 -0
  31. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/index.js +2 -0
  32. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/templates/routes/-components/invite-user-dialog.d.ts +21 -0
  33. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/templates/routes/-components/invite-user-dialog.js +69 -0
  34. package/dist/local-auth/admin/generators/admin-crud-invite-user-action/templates/routes/-components/invite-user-dialog.tsx +122 -0
  35. package/dist/local-auth/admin/generators/admin-crud-manage-roles-action/admin-crud-manage-roles-action.generator.d.ts +12 -0
  36. package/dist/local-auth/admin/generators/admin-crud-manage-roles-action/generated/index.d.ts +12 -0
  37. package/dist/local-auth/admin/generators/admin-crud-manage-roles-action/generated/template-renderers.d.ts +6 -0
  38. package/dist/local-auth/admin/generators/admin-crud-manage-roles-action/generated/typed-templates.d.ts +6 -0
  39. package/dist/local-auth/admin/generators/admin-crud-reset-password-action/admin-crud-reset-password-action.generator.d.ts +12 -0
  40. package/dist/local-auth/admin/generators/admin-crud-reset-password-action/generated/index.d.ts +12 -0
  41. package/dist/local-auth/admin/generators/admin-crud-reset-password-action/generated/template-renderers.d.ts +6 -0
  42. package/dist/local-auth/admin/generators/admin-crud-reset-password-action/generated/typed-templates.d.ts +12 -0
  43. package/dist/local-auth/admin/generators/admin-crud-roles-column/admin-crud-roles-column.generator.d.ts +6 -0
  44. package/dist/local-auth/admin/node.js +13 -0
  45. package/dist/local-auth/admin/schema/invite-user-action.d.ts +12 -0
  46. package/dist/local-auth/admin/schema/invite-user-action.js +6 -0
  47. package/dist/local-auth/admin/web.js +8 -0
  48. package/dist/local-auth/core/common.js +7 -2
  49. package/dist/local-auth/core/components/local-auth-definition-editor.js +2 -1
  50. package/dist/local-auth/core/components/local-auth-web-app-settings-form.d.ts +9 -0
  51. package/dist/local-auth/core/components/local-auth-web-app-settings-form.js +11 -0
  52. package/dist/local-auth/core/generators/auth-email-password/auth-email-password.generator.d.ts +6 -0
  53. package/dist/local-auth/core/generators/auth-email-password/auth-email-password.generator.js +66 -6
  54. package/dist/local-auth/core/generators/auth-email-password/generated/index.d.ts +188 -0
  55. package/dist/local-auth/core/generators/auth-email-password/generated/template-paths.d.ts +5 -0
  56. package/dist/local-auth/core/generators/auth-email-password/generated/template-paths.js +5 -0
  57. package/dist/local-auth/core/generators/auth-email-password/generated/template-renderers.d.ts +18 -0
  58. package/dist/local-auth/core/generators/auth-email-password/generated/template-renderers.js +66 -0
  59. package/dist/local-auth/core/generators/auth-email-password/generated/typed-templates.d.ts +192 -0
  60. package/dist/local-auth/core/generators/auth-email-password/generated/typed-templates.js +82 -1
  61. package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/otp.constants.d.ts +19 -0
  62. package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/otp.constants.js +20 -0
  63. package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/otp.constants.ts +22 -0
  64. package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/password.constants.d.ts +12 -0
  65. package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/password.constants.js +12 -0
  66. package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/password.constants.ts +14 -0
  67. package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/email-otp.mutations.d.ts +2 -0
  68. package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/email-otp.mutations.js +30 -0
  69. package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/email-otp.mutations.ts +38 -0
  70. package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/invite.mutations.d.ts +2 -0
  71. package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/invite.mutations.js +42 -0
  72. package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/invite.mutations.ts +54 -0
  73. package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/user-password.mutations.js +2 -19
  74. package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/user-password.mutations.ts +1 -22
  75. package/dist/local-auth/core/generators/auth-email-password/templates/module/services/email-otp.service.d.ts +37 -0
  76. package/dist/local-auth/core/generators/auth-email-password/templates/module/services/email-otp.service.js +225 -0
  77. package/dist/local-auth/core/generators/auth-email-password/templates/module/services/email-otp.service.ts +333 -0
  78. package/dist/local-auth/core/generators/auth-email-password/templates/module/services/invite.service.d.ts +39 -0
  79. package/dist/local-auth/core/generators/auth-email-password/templates/module/services/invite.service.js +138 -0
  80. package/dist/local-auth/core/generators/auth-email-password/templates/module/services/invite.service.ts +209 -0
  81. package/dist/local-auth/core/generators/auth-email-password/templates/module/services/password-reset.service.js +3 -3
  82. package/dist/local-auth/core/generators/auth-email-password/templates/module/services/password-reset.service.ts +3 -2
  83. package/dist/local-auth/core/generators/auth-email-templates/auth-email-templates.generator.d.ts +3 -1
  84. package/dist/local-auth/core/generators/auth-email-templates/auth-email-templates.generator.js +18 -3
  85. package/dist/local-auth/core/generators/auth-email-templates/generated/index.d.ts +40 -0
  86. package/dist/local-auth/core/generators/auth-email-templates/generated/template-paths.d.ts +2 -0
  87. package/dist/local-auth/core/generators/auth-email-templates/generated/template-paths.js +2 -0
  88. package/dist/local-auth/core/generators/auth-email-templates/generated/template-renderers.d.ts +6 -0
  89. package/dist/local-auth/core/generators/auth-email-templates/generated/template-renderers.js +20 -0
  90. package/dist/local-auth/core/generators/auth-email-templates/generated/typed-templates.d.ts +40 -0
  91. package/dist/local-auth/core/generators/auth-email-templates/generated/typed-templates.js +24 -0
  92. package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/email-otp.email.d.ts +3 -0
  93. package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/email-otp.email.js +18 -0
  94. package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/email-otp.email.tsx +57 -0
  95. package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/invite.email.d.ts +3 -0
  96. package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/invite.email.js +14 -0
  97. package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/invite.email.tsx +47 -0
  98. package/dist/local-auth/core/generators/auth-module/auth-module.generator.d.ts +4 -0
  99. package/dist/local-auth/core/generators/auth-module/auth-module.generator.js +5 -1
  100. package/dist/local-auth/core/generators/auth-module/generated/index.d.ts +20 -0
  101. package/dist/local-auth/core/generators/auth-module/generated/template-paths.d.ts +1 -0
  102. package/dist/local-auth/core/generators/auth-module/generated/template-paths.js +1 -0
  103. package/dist/local-auth/core/generators/auth-module/generated/template-renderers.d.ts +3 -0
  104. package/dist/local-auth/core/generators/auth-module/generated/template-renderers.js +13 -0
  105. package/dist/local-auth/core/generators/auth-module/generated/ts-import-providers.d.ts +9 -0
  106. package/dist/local-auth/core/generators/auth-module/generated/ts-import-providers.js +6 -0
  107. package/dist/local-auth/core/generators/auth-module/generated/typed-templates.d.ts +17 -0
  108. package/dist/local-auth/core/generators/auth-module/generated/typed-templates.js +20 -0
  109. package/dist/local-auth/core/generators/auth-module/templates/module/services/auth-verification.service.d.ts +4 -0
  110. package/dist/local-auth/core/generators/auth-module/templates/module/services/auth-verification.service.js +1 -1
  111. package/dist/local-auth/core/generators/auth-module/templates/module/services/auth-verification.service.ts +1 -1
  112. package/dist/local-auth/core/generators/auth-module/templates/module/services/code-verification.service.d.ts +66 -0
  113. package/dist/local-auth/core/generators/auth-module/templates/module/services/code-verification.service.js +134 -0
  114. package/dist/local-auth/core/generators/auth-module/templates/module/services/code-verification.service.ts +184 -0
  115. package/dist/local-auth/core/generators/auth-routes/auth-routes.generator.d.ts +8 -0
  116. package/dist/local-auth/core/generators/auth-routes/auth-routes.generator.js +38 -5
  117. package/dist/local-auth/core/generators/auth-routes/generated/index.d.ts +840 -268
  118. package/dist/local-auth/core/generators/auth-routes/generated/template-paths.d.ts +3 -0
  119. package/dist/local-auth/core/generators/auth-routes/generated/template-paths.js +3 -0
  120. package/dist/local-auth/core/generators/auth-routes/generated/template-renderers.d.ts +18 -0
  121. package/dist/local-auth/core/generators/auth-routes/generated/template-renderers.js +51 -0
  122. package/dist/local-auth/core/generators/auth-routes/generated/typed-templates.d.ts +608 -283
  123. package/dist/local-auth/core/generators/auth-routes/generated/typed-templates.js +69 -23
  124. package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/-otp-constants.d.ts +9 -0
  125. package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/-otp-constants.js +10 -0
  126. package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/-otp-constants.ts +11 -0
  127. package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/accept-invite.d.ts +7 -0
  128. package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/accept-invite.js +148 -0
  129. package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/accept-invite.tsx +264 -0
  130. package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/login-otp.d.ts +7 -0
  131. package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/login-otp.js +184 -0
  132. package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/login-otp.tsx +311 -0
  133. package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/login.js +1 -1
  134. package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/login.tsx +2 -9
  135. package/dist/local-auth/core/generators/react-session/generated/index.d.ts +12 -0
  136. package/dist/local-auth/core/generators/react-session/generated/template-renderers.d.ts +6 -0
  137. package/dist/local-auth/core/generators/react-session/generated/typed-templates.d.ts +12 -0
  138. package/dist/local-auth/core/generators/react-session/react-session.generator.d.ts +6 -0
  139. package/dist/local-auth/core/node.js +24 -6
  140. package/dist/local-auth/core/schema/migrations.d.ts +3 -0
  141. package/dist/local-auth/core/schema/migrations.js +36 -0
  142. package/dist/local-auth/core/schema/models.js +8 -0
  143. package/dist/local-auth/core/schema/plugin-definition.d.ts +1 -0
  144. package/dist/local-auth/core/schema/plugin-definition.js +1 -0
  145. package/dist/local-auth/core/schema/web-app-schema.d.ts +15 -0
  146. package/dist/local-auth/core/schema/web-app-schema.js +16 -0
  147. package/dist/local-auth/core/web.js +8 -1
  148. package/dist/web/assets/{_virtual_mf-localSharedImportMap___mfe_internal__plugin_mf_2_auth-ChkXxpXe.js → _virtual_mf-localSharedImportMap___mfe_internal__plugin_mf_2_auth__mf_owner__1-CKKmnLep.js} +42 -34
  149. package/dist/web/assets/{dist-DY7ZXvq4.js → _virtual_mf___mfe_internal__plugin_mf_2_auth__mf_owner__1__loadShare___mf_0_baseplate_mf_2_dev_mf_1_project_mf_2_builder_mf_2_lib__loadShare__.js-Cx7TvZ_H.js} +16717 -373
  150. package/dist/web/assets/{_virtual_mf___mfe_internal__plugin_mf_2_auth__loadShare___mf_0_baseplate_mf_2_dev_mf_1_project_mf_2_builder_mf_2_lib_mf_1_web__loadShare__.js-37vlYRxk.js → _virtual_mf___mfe_internal__plugin_mf_2_auth__mf_owner__1__loadShare___mf_0_baseplate_mf_2_dev_mf_1_project_mf_2_builder_mf_2_lib_mf_1_web__loadShare__.js-D3Nvasea.js} +90 -38
  151. package/dist/web/assets/{_virtual_mf___mfe_internal__plugin_mf_2_auth__loadShare___mf_0_baseplate_mf_2_dev_mf_1_ui_mf_2_components__loadShare__.js-DRSNXPPo.js → _virtual_mf___mfe_internal__plugin_mf_2_auth__mf_owner__1__loadShare___mf_0_baseplate_mf_2_dev_mf_1_ui_mf_2_components__loadShare__.js-C2GDE8Py.js} +1645 -921
  152. package/dist/web/assets/{_virtual_mf___mfe_internal__plugin_mf_2_auth__loadShare___mf_0_tanstack_mf_1_react_mf_2_router__loadShare__.js-DfiQqvVC.js → _virtual_mf___mfe_internal__plugin_mf_2_auth__mf_owner__1__loadShare___mf_0_tanstack_mf_1_react_mf_2_router__loadShare__.js-CiccpWjh.js} +212 -57
  153. package/dist/web/assets/auth-CsHp2bQi.js +1 -0
  154. package/dist/web/assets/common-BGTuTgx7.js +43 -0
  155. package/dist/web/assets/common-C8_sC5pp.js +70 -0
  156. package/dist/web/assets/{common-ByxS61u8.js → common-Co5lW_TK.js} +10 -10
  157. package/dist/web/assets/{common-iHaemino.js → common-CoLuMS7U.js} +6 -6
  158. package/dist/web/assets/common-Dueu_1NG.js +36 -0
  159. package/dist/web/assets/{common-TDf_PrjX.js → common-N3skPbzk.js} +10 -10
  160. package/dist/web/assets/{dist-CrLSslv6.js → dist-DQd1TAJX.js} +273 -132
  161. package/dist/web/assets/{get-auth-plugin-definition-kDCtXq_6.js → get-auth-plugin-definition-DyokUVjX.js} +3 -3
  162. package/dist/web/assets/{hostInit-Cruek6c6.js → hostInit-BCYmw0H5.js} +32 -5
  163. package/dist/web/assets/{mf-entry-bootstrap-0-dfed0275.js → mf-entry-bootstrap-0-3ede9334.js} +2 -1
  164. package/dist/web/assets/{mf-entry-bootstrap-1-5275674a.js → mf-entry-bootstrap-1-3da85eea.js} +3 -2
  165. package/dist/web/assets/{mf-entry-bootstrap-2-39fb9255.js → mf-entry-bootstrap-2-32e24e11.js} +3 -2
  166. package/dist/web/assets/{mf-entry-bootstrap-3-49e64336.js → mf-entry-bootstrap-3-a3cfea9d.js} +3 -2
  167. package/dist/web/assets/{models-DSvs_3bA.js → models-BcPOQC5p.js} +4 -5
  168. package/dist/web/assets/{models-BZmCbizK.js → models-BcUgOGGn.js} +11 -6
  169. package/dist/web/assets/{plugin-definition-CYj29S3q.js → plugin-definition-D1EImXIf.js} +9 -10
  170. package/dist/web/assets/plugin-definition-lw-gB4hU.js +10 -0
  171. package/dist/web/assets/{styles-yjbHBTlw.js → styles-EOyEbGA4.js} +9 -10
  172. package/dist/web/assets/{virtualExposes-CbPBmicF.js → virtualExposes-DWORnjyA.js} +1 -1
  173. package/dist/web/assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__plugin-auth__remoteEntry_js-t-KnBiUh.js +747 -0
  174. package/dist/web/assets/{virtual_mf-exposes___mfe_internal__plugin-auth__remoteEntry_js-B6BXIeF1.js → virtual_mf-exposes___mfe_internal__plugin-auth__remoteEntry_js-DZfE3QCR.js} +13 -13
  175. package/dist/web/assets/{web-VqBA3jxZ.js → web-CBVepebK.js} +22 -22
  176. package/dist/web/assets/{web-D0MSKfyv.js → web-Dgb85jhs.js} +47 -47
  177. package/dist/web/assets/{web-CXAHKjIY.js → web-EDeSquSP.js} +28 -28
  178. package/dist/web/assets/{web-1eTcxMhf.js → web-PqAcfBSy.js} +9 -9
  179. package/dist/web/assets/{web-CDAKPPK9.js → web-RhChfBw_.js} +21 -21
  180. package/dist/web/assets/web-kaD9AzNS.js +59 -0
  181. package/dist/web/assets/web-xJ-GnSkC.js +148 -0
  182. package/dist/web/index.html +5 -5
  183. package/dist/web/remoteEntry.js +1 -1
  184. package/package.json +14 -14
  185. package/dist/web/assets/_virtual_mf___mfe_internal__plugin_mf_2_auth__loadShare___mf_0_baseplate_mf_2_dev_mf_1_project_mf_2_builder_mf_2_lib__loadShare__.js-CO5Pvo0y.js +0 -384
  186. package/dist/web/assets/_virtual_mf___mfe_internal__plugin_mf_2_auth__loadShare__zod__loadShare__.js-BEZ7A-cz.js +0 -13566
  187. package/dist/web/assets/auth-BxAE0rgh.js +0 -1
  188. package/dist/web/assets/common-CKl_XlvH.js +0 -37
  189. package/dist/web/assets/common-UpVCKZeW.js +0 -41
  190. package/dist/web/assets/common-crSwCD36.js +0 -37
  191. package/dist/web/assets/model-utils-C0WDb_Ec.js +0 -1599
  192. package/dist/web/assets/plugin-definition-DOMmPL2Z.js +0 -11
  193. package/dist/web/assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__plugin-auth__remoteEntry_js-XM6iYVxf.js +0 -259
  194. package/dist/web/assets/virtual_mf-exposes-ssr___mfe_internal__plugin-auth__remoteEntry_js-DGjg7Brm.js +0 -129
  195. package/dist/web/assets/web-DbeAaDTV.js +0 -118
  196. package/dist/web/assets/web-miDovYk_.js +0 -46
  197. package/dist/web/remoteEntry.ssr.js +0 -43
@@ -0,0 +1,333 @@
1
+ // @ts-nocheck
2
+
3
+ import type {
4
+ RequestServiceContext,
5
+ RequestServiceContextWith,
6
+ } from '%requestServiceContextImports';
7
+ import type { UserSessionPayload } from '%userSessionTypesImports';
8
+ import type { CookieSerializeOptions } from '@fastify/cookie';
9
+
10
+ import {
11
+ EMAIL_OTP_EXPIRY_SEC,
12
+ EMAIL_OTP_LENGTH,
13
+ EMAIL_OTP_MAX_ATTEMPTS,
14
+ } from '$constantsOtp';
15
+ import { PASSWORD_MAX_LENGTH } from '$constantsPassword';
16
+ import {
17
+ consumeCodeVerification,
18
+ createCodeVerification,
19
+ validateCodeVerification,
20
+ } from '%authModuleImports';
21
+ import { isDevelopment } from '%configServiceImports';
22
+ import {
23
+ BadRequestError,
24
+ handleZodRequestValidationError,
25
+ } from '%errorHandlerServiceImports';
26
+ import { prisma } from '%prismaImports';
27
+ import { memoizeRateLimiter } from '%rateLimitImports';
28
+ import * as crypto from 'node:crypto';
29
+ import z from 'zod';
30
+
31
+ const PROVIDER_ID = 'email-otp';
32
+ const EMAIL_OTP_TYPE = 'email-otp';
33
+
34
+ /**
35
+ * Rate limiters for emailed sign-in codes.
36
+ */
37
+
38
+ // Per-email burst limit: 3 codes / 15 minutes
39
+ const getEmailOtpRequestEmailLimiter = memoizeRateLimiter('email-otp-email', {
40
+ points: 3,
41
+ duration: 60 * 15, // 15 minutes
42
+ });
43
+
44
+ // Per-email daily cap. Each new code resets that code's attempt budget, so
45
+ // this is what actually bounds guesses per day: 10 codes x 5 attempts.
46
+ const getEmailOtpRequestEmailDailyLimiter = memoizeRateLimiter(
47
+ 'email-otp-email-daily',
48
+ {
49
+ points: 10,
50
+ duration: 60 * 60 * 24, // 1 day
51
+ },
52
+ );
53
+
54
+ // Per-IP rate limit: 10 codes/hour (prevents email scanning)
55
+ const getEmailOtpRequestIpLimiter = memoizeRateLimiter('email-otp-ip', {
56
+ points: 10,
57
+ duration: 60 * 60, // 1 hour
58
+ });
59
+
60
+ // Deliberately no global limiter here. A shared counter would let anyone who
61
+ // exhausts it lock every user out of signing in, and accounts created through
62
+ // this flow have no password to fall back on. Distributed abuse is bounded by
63
+ // the per-IP limit above; protecting the email provider from a cost or
64
+ // reputation spike belongs in its own quotas and alerting, not in the login
65
+ // path.
66
+
67
+ // Per-address verification limit. Unlike the per-code attempt budget this
68
+ // survives the code being reissued, and unlike the per-IP limit it survives
69
+ // the attacker rotating IPs.
70
+ const getEmailOtpVerifyEmailLimiter = memoizeRateLimiter(
71
+ 'email-otp-verify-email',
72
+ {
73
+ points: 10,
74
+ duration: 60 * 60, // 1 hour
75
+ blockDuration: 60 * 60, // Block for 1 hour if exceeded
76
+ },
77
+ );
78
+
79
+ // Per-IP verification limit, on top of each code's own attempt budget
80
+ const getEmailOtpVerifyIpLimiter = memoizeRateLimiter('email-otp-verify-ip', {
81
+ points: 20,
82
+ duration: 60 * 60, // 1 hour
83
+ blockDuration: 60 * 60, // Block for 1 hour if exceeded
84
+ });
85
+
86
+ /**
87
+ * Cookie holding the id of the pending verification.
88
+ *
89
+ * Requiring it at verification binds a code to the browser that asked for it,
90
+ * so a code read over someone's shoulder or talked out of them over the phone
91
+ * cannot be redeemed elsewhere.
92
+ */
93
+ const EMAIL_OTP_COOKIE_NAME = 'email-otp-verification';
94
+
95
+ function getEmailOtpCookieOptions(): CookieSerializeOptions {
96
+ return {
97
+ httpOnly: true,
98
+ sameSite: 'lax',
99
+ secure: !isDevelopment(),
100
+ maxAge: EMAIL_OTP_EXPIRY_SEC,
101
+ path: '/',
102
+ };
103
+ }
104
+
105
+ const emailSchema = z.object({
106
+ email: z
107
+ .email()
108
+ .max(PASSWORD_MAX_LENGTH)
109
+ .transform((value) => value.toLowerCase()),
110
+ });
111
+
112
+ /**
113
+ * Generates a numeric code using rejection sampling so every digit is equally
114
+ * likely. 250 is the largest multiple of 10 within a byte, so values above it
115
+ * are discarded rather than folded back and over-weighting digits 0-5.
116
+ */
117
+ function generateNumericCode(length: number): string {
118
+ const digits = Array.from({ length }, () => {
119
+ let value = 256;
120
+ while (value >= 250) {
121
+ value = crypto.randomBytes(1).readUInt8(0);
122
+ }
123
+ return String(value % 10);
124
+ });
125
+ return digits.join('');
126
+ }
127
+
128
+ /**
129
+ * Emails a single-use sign-in code to the given address.
130
+ *
131
+ * A code is sent whether or not an account exists — verifying one signs the
132
+ * user in, creating the account on first use — so this endpoint reveals
133
+ * nothing about which addresses are registered.
134
+ */
135
+ export async function requestEmailOtp({
136
+ email: rawEmail,
137
+ context,
138
+ }: {
139
+ email: string;
140
+ context: RequestServiceContextWith<'email'>;
141
+ }): Promise<{ success: true }> {
142
+ const { services } = context;
143
+
144
+ const { email } = await emailSchema
145
+ .parseAsync({ email: rawEmail })
146
+ .catch(handleZodRequestValidationError);
147
+
148
+ await Promise.all([
149
+ getEmailOtpRequestIpLimiter().consumeOrThrow(
150
+ context.reqInfo.ip,
151
+ 'Too many sign-in code requests. Please try again later.',
152
+ 'too-many-requests',
153
+ ),
154
+ getEmailOtpRequestEmailLimiter().consumeOrThrow(
155
+ email,
156
+ 'Too many sign-in code requests. Please try again later.',
157
+ 'too-many-requests',
158
+ ),
159
+ getEmailOtpRequestEmailDailyLimiter().consumeOrThrow(
160
+ email,
161
+ 'Too many sign-in code requests. Please try again later.',
162
+ 'too-many-requests',
163
+ ),
164
+ ]);
165
+
166
+ const code = generateNumericCode(EMAIL_OTP_LENGTH);
167
+
168
+ const { id: verificationId } = await createCodeVerification({
169
+ type: EMAIL_OTP_TYPE,
170
+ identifier: email,
171
+ code,
172
+ expiresInSec: EMAIL_OTP_EXPIRY_SEC,
173
+ });
174
+
175
+ context.cookieStore.set(
176
+ EMAIL_OTP_COOKIE_NAME,
177
+ verificationId,
178
+ getEmailOtpCookieOptions(),
179
+ );
180
+
181
+ await services.email.send(TPL_EMAIL_OTP_EMAIL, {
182
+ to: email,
183
+ data: { code, expiryMinutes: Math.floor(EMAIL_OTP_EXPIRY_SEC / 60) },
184
+ });
185
+
186
+ return { success: true };
187
+ }
188
+
189
+ const verifyEmailOtpSchema = z.object({
190
+ email: z
191
+ .email()
192
+ .max(PASSWORD_MAX_LENGTH)
193
+ .transform((value) => value.toLowerCase()),
194
+ code: z.string().length(EMAIL_OTP_LENGTH),
195
+ // Blank and whitespace-only names are normalized to absent rather than
196
+ // rejected, so a client that always sends the field still gets the
197
+ // `name-required` response rather than a generic validation failure.
198
+ name: z
199
+ .string()
200
+ .max(100)
201
+ .optional()
202
+ .transform((value) => {
203
+ const trimmed = value?.trim();
204
+ return trimmed ? trimmed : undefined;
205
+ }),
206
+ });
207
+
208
+ /**
209
+ * Signs a user in with an emailed code, creating the account on first use.
210
+ *
211
+ * A verified code proves control of the inbox, so the account it creates (or
212
+ * signs in to) is marked as having a verified email. Accounts created this way
213
+ * have no password until the user goes through the password reset flow.
214
+ *
215
+ * @throws BadRequestError `name-required` when the code is valid but the
216
+ * address has no account yet and no name was supplied. The code is left intact
217
+ * so the client can collect a name and retry with it.
218
+ */
219
+ export async function signInWithEmailOtp({
220
+ input,
221
+ context,
222
+ }: {
223
+ input: {
224
+ email: string;
225
+ code: string;
226
+ name?: string;
227
+ };
228
+ context: RequestServiceContext;
229
+ }): Promise<{ session: UserSessionPayload }> {
230
+ const { email, code, name } = await verifyEmailOtpSchema
231
+ .parseAsync(input)
232
+ .catch(handleZodRequestValidationError);
233
+
234
+ await getEmailOtpVerifyIpLimiter().consumeOrThrow(
235
+ context.reqInfo.ip,
236
+ 'Too many sign-in attempts. Please try again later.',
237
+ 'too-many-requests',
238
+ );
239
+
240
+ // A code is only redeemable in the browser that requested it.
241
+ const verificationId = context.cookieStore.get(EMAIL_OTP_COOKIE_NAME);
242
+
243
+ if (!verificationId) {
244
+ throw new BadRequestError(
245
+ 'This code must be entered in the browser that requested it',
246
+ 'verification-context-missing',
247
+ );
248
+ }
249
+
250
+ // Establish that this browser owns the outstanding code before spending any
251
+ // of the address's budget. Charging first would let anyone lock a chosen
252
+ // address out of signing in just by posting its email with a junk code —
253
+ // which is worse than it sounds, because an account created through this
254
+ // flow has no password to fall back on.
255
+ const challenge = await prisma.authVerification.findUnique({
256
+ where: { type_identifier: { type: EMAIL_OTP_TYPE, identifier: email } },
257
+ select: { id: true },
258
+ });
259
+
260
+ if (challenge?.id !== verificationId) {
261
+ throw new BadRequestError('Invalid or expired code', 'invalid-code');
262
+ }
263
+
264
+ await getEmailOtpVerifyEmailLimiter().consumeOrThrow(
265
+ email,
266
+ 'Too many sign-in attempts. Please try again later.',
267
+ 'too-many-requests',
268
+ );
269
+
270
+ const record = await validateCodeVerification({
271
+ type: EMAIL_OTP_TYPE,
272
+ identifier: email,
273
+ code,
274
+ maxAttempts: EMAIL_OTP_MAX_ATTEMPTS,
275
+ });
276
+
277
+ if (!record) {
278
+ throw new BadRequestError('Invalid or expired code', 'invalid-code');
279
+ }
280
+
281
+ // Re-checked because a fresh code may have been issued since the lookup
282
+ // above, which would have replaced the id this browser is holding.
283
+ if (record.id !== verificationId) {
284
+ throw new BadRequestError('Invalid or expired code', 'invalid-code');
285
+ }
286
+
287
+ // Asking for a name only when the address is new would normally leak whether
288
+ // an account exists. Gating it behind a valid code keeps that safe: only
289
+ // someone who controls the mailbox reaches this point.
290
+ TPL_NAME_REQUIRED_CHECK;
291
+
292
+ // Spending the code and settling the account commit together, so a failure
293
+ // partway through leaves the code still usable rather than burning it and
294
+ // forcing the user to request another.
295
+ const userId = await prisma.$transaction(async (tx) => {
296
+ if (!(await consumeCodeVerification({ id: record.id, client: tx }))) {
297
+ throw new BadRequestError('Invalid or expired code', 'invalid-code');
298
+ }
299
+
300
+ // Receiving the code proves control of the address, so the account is
301
+ // created (or marked) as verified. An existing user's name is left alone.
302
+ const { id } = await tx.user.upsert({
303
+ where: { email },
304
+ create: {
305
+ email,
306
+ name,
307
+ emailVerified: true,
308
+ accounts: {
309
+ create: {
310
+ accountId: email,
311
+ providerId: PROVIDER_ID,
312
+ },
313
+ },
314
+ },
315
+ update: { emailVerified: true },
316
+ select: { id: true },
317
+ });
318
+
319
+ return id;
320
+ });
321
+
322
+ context.cookieStore.clear(EMAIL_OTP_COOKIE_NAME, getEmailOtpCookieOptions());
323
+
324
+ // Outside the transaction: session creation manages its own storage through
325
+ // the request context. If it fails the code is spent, but the account now
326
+ // exists, so retrying signs the user in without asking for a name again.
327
+ const session = await context.services.userSession.createSession(
328
+ userId,
329
+ context,
330
+ );
331
+
332
+ return { session };
333
+ }
@@ -0,0 +1,39 @@
1
+ import type { User } from '%prismaGeneratedImports';
2
+ import type { RequestServiceContext, RequestServiceContextWith } from '%requestServiceContextImports';
3
+ import type { UserSessionPayload } from '%userSessionTypesImports';
4
+ /**
5
+ * Sends an invite email so a user can set their password and sign in.
6
+ * Admin-triggered, so unlike password reset requests, it does not need
7
+ * anti-enumeration rate limiting.
8
+ */
9
+ export declare function inviteUser({ userId, context, }: {
10
+ userId: string;
11
+ context: RequestServiceContextWith<'email'>;
12
+ }): Promise<{
13
+ user: User;
14
+ }>;
15
+ /**
16
+ * Validates an invite token without consuming it.
17
+ * Used by the frontend to verify the token is valid and to show the
18
+ * invited email before displaying the set-password form.
19
+ *
20
+ * @throws BadRequestError with code 'invalid-token' if the token is invalid or expired
21
+ */
22
+ export declare function validateInviteToken({ token: rawToken, }: {
23
+ token: string;
24
+ }): Promise<{
25
+ email: string;
26
+ }>;
27
+ /**
28
+ * Completes the invite flow by setting the invited user's password and
29
+ * signing them in. Token is single-use (deleted after acceptance, along
30
+ * with any other pending invite tokens for the user).
31
+ */
32
+ export declare function acceptInvite({ token: rawToken, newPassword: rawNewPassword, context, }: {
33
+ token: string;
34
+ newPassword: string;
35
+ context: RequestServiceContext;
36
+ }): Promise<{
37
+ session: UserSessionPayload;
38
+ }>;
39
+ //# sourceMappingURL=invite.service.d.ts.map
@@ -0,0 +1,138 @@
1
+ // @ts-nocheck
2
+ import { AUTH_TOKEN_MAX_LENGTH, INVITE_TOKEN_EXPIRY_SEC, PASSWORD_MAX_LENGTH, PASSWORD_MIN_LENGTH, } from '$constantsPassword';
3
+ import { createAuthVerification, validateAuthVerification, } from '%authModuleImports';
4
+ import { getConfig } from '%configServiceImports';
5
+ import { BadRequestError, handleZodRequestValidationError, NotFoundError, } from '%errorHandlerServiceImports';
6
+ import { createPasswordHash } from '%passwordHasherServiceImports';
7
+ import { prisma } from '%prismaImports';
8
+ import z from 'zod';
9
+ const PROVIDER_ID = 'email-password';
10
+ const INVITE_TYPE = 'invite';
11
+ /**
12
+ * Sends an invite email so a user can set their password and sign in.
13
+ * Admin-triggered, so unlike password reset requests, it does not need
14
+ * anti-enumeration rate limiting.
15
+ */
16
+ export async function inviteUser({ userId, context, }) {
17
+ const { services } = context;
18
+ const user = await prisma.user.findUnique({ where: { id: userId } });
19
+ if (!user) {
20
+ throw new NotFoundError('User not found', 'user-not-found');
21
+ }
22
+ if (!user.email) {
23
+ throw new BadRequestError('User has no email', 'user-has-no-email');
24
+ }
25
+ const existingAccount = await prisma.userAccount.findUnique({
26
+ where: {
27
+ accountId_providerId: {
28
+ accountId: user.email,
29
+ providerId: PROVIDER_ID,
30
+ },
31
+ },
32
+ });
33
+ if (existingAccount) {
34
+ throw new BadRequestError('User already has a password set', 'user-already-has-account');
35
+ }
36
+ const { token } = await createAuthVerification({
37
+ type: INVITE_TYPE,
38
+ userId: user.id,
39
+ expiresInSec: INVITE_TOKEN_EXPIRY_SEC,
40
+ });
41
+ const acceptLink = `${getConfig().AUTH_FRONTEND_URL}/auth/accept-invite?token=${encodeURIComponent(token)}`;
42
+ await services.email.send(TPL_INVITE_EMAIL, {
43
+ to: user.email,
44
+ data: { acceptLink },
45
+ });
46
+ return { user };
47
+ }
48
+ const validateTokenSchema = z.object({
49
+ token: z.string().min(1).max(AUTH_TOKEN_MAX_LENGTH),
50
+ });
51
+ /**
52
+ * Validates an invite token without consuming it.
53
+ * Used by the frontend to verify the token is valid and to show the
54
+ * invited email before displaying the set-password form.
55
+ *
56
+ * @throws BadRequestError with code 'invalid-token' if the token is invalid or expired
57
+ */
58
+ export async function validateInviteToken({ token: rawToken, }) {
59
+ const { token } = await validateTokenSchema
60
+ .parseAsync({ token: rawToken })
61
+ .catch(handleZodRequestValidationError);
62
+ const record = await validateAuthVerification({
63
+ type: INVITE_TYPE,
64
+ token,
65
+ });
66
+ if (!record?.userId) {
67
+ throw new BadRequestError('Invalid or expired token', 'invalid-token');
68
+ }
69
+ const user = await prisma.user.findUniqueOrThrow({
70
+ where: { id: record.userId },
71
+ select: { email: true },
72
+ });
73
+ if (!user.email) {
74
+ throw new BadRequestError('User has no email', 'user-has-no-email');
75
+ }
76
+ return { email: user.email };
77
+ }
78
+ const acceptInviteSchema = z.object({
79
+ token: z.string().min(1).max(AUTH_TOKEN_MAX_LENGTH),
80
+ newPassword: z.string().min(PASSWORD_MIN_LENGTH).max(PASSWORD_MAX_LENGTH),
81
+ });
82
+ /**
83
+ * Completes the invite flow by setting the invited user's password and
84
+ * signing them in. Token is single-use (deleted after acceptance, along
85
+ * with any other pending invite tokens for the user).
86
+ */
87
+ export async function acceptInvite({ token: rawToken, newPassword: rawNewPassword, context, }) {
88
+ const { token, newPassword } = await acceptInviteSchema
89
+ .parseAsync({
90
+ token: rawToken,
91
+ newPassword: rawNewPassword,
92
+ })
93
+ .catch(handleZodRequestValidationError);
94
+ const record = await validateAuthVerification({
95
+ type: INVITE_TYPE,
96
+ token,
97
+ });
98
+ if (!record?.userId) {
99
+ throw new BadRequestError('Invalid or expired token', 'invalid-token');
100
+ }
101
+ const user = await prisma.user.findUniqueOrThrow({
102
+ where: { id: record.userId },
103
+ select: { id: true, email: true },
104
+ });
105
+ if (!user.email) {
106
+ throw new BadRequestError('User has no email', 'user-has-no-email');
107
+ }
108
+ const passwordHash = await createPasswordHash(newPassword);
109
+ await prisma.$transaction([
110
+ prisma.authVerification.deleteMany({
111
+ where: { type: INVITE_TYPE, userId: user.id },
112
+ }),
113
+ prisma.userAccount.upsert({
114
+ where: {
115
+ accountId_providerId: {
116
+ accountId: user.email,
117
+ providerId: PROVIDER_ID,
118
+ },
119
+ },
120
+ create: {
121
+ userId: user.id,
122
+ accountId: user.email,
123
+ providerId: PROVIDER_ID,
124
+ password: passwordHash,
125
+ },
126
+ update: {
127
+ password: passwordHash,
128
+ },
129
+ }),
130
+ prisma.user.update({
131
+ where: { id: user.id },
132
+ data: { emailVerified: true },
133
+ }),
134
+ ]);
135
+ const session = await context.services.userSession.createSession(user.id, context);
136
+ return { session };
137
+ }
138
+ //# sourceMappingURL=invite.service.js.map