@baseplate-dev/plugin-auth 0.6.17 → 0.6.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +54 -0
- package/dist/better-auth/admin/generators/admin-crud-manage-roles-action/admin-crud-manage-roles-action.generator.d.ts +74 -0
- package/dist/better-auth/admin/generators/admin-crud-manage-roles-action/generated/index.d.ts +74 -0
- package/dist/better-auth/admin/generators/admin-crud-manage-roles-action/generated/template-renderers.d.ts +37 -0
- package/dist/better-auth/admin/generators/admin-crud-manage-roles-action/generated/typed-templates.d.ts +37 -0
- package/dist/better-auth/admin/generators/admin-crud-manage-roles-action/templates/routes/-components/role-manager-dialog.js +1 -2
- package/dist/better-auth/admin/generators/admin-crud-manage-roles-action/templates/routes/-components/role-manager-dialog.tsx +1 -1
- package/dist/better-auth/admin/generators/admin-crud-reset-password-action/admin-crud-reset-password-action.generator.d.ts +74 -0
- package/dist/better-auth/admin/generators/admin-crud-reset-password-action/generated/index.d.ts +74 -0
- package/dist/better-auth/admin/generators/admin-crud-reset-password-action/generated/template-renderers.d.ts +37 -0
- package/dist/better-auth/admin/generators/admin-crud-reset-password-action/generated/typed-templates.d.ts +74 -0
- package/dist/better-auth/admin/generators/admin-crud-reset-password-action/templates/routes/-components/password-reset-dialog.js +1 -2
- package/dist/better-auth/admin/generators/admin-crud-reset-password-action/templates/routes/-components/password-reset-dialog.tsx +1 -1
- package/dist/better-auth/admin/generators/admin-crud-roles-column/admin-crud-roles-column.generator.d.ts +37 -0
- package/dist/better-auth/core/node.js +7 -8
- package/dist/better-auth/generators/fastify/better-auth-admin-module/generated/index.d.ts +1 -0
- package/dist/better-auth/generators/fastify/better-auth-admin-module/generated/ts-import-providers.d.ts +1 -0
- package/dist/better-auth/generators/fastify/better-auth-admin-module/generated/ts-import-providers.js +1 -0
- package/dist/better-auth/generators/fastify/better-auth-module/better-auth-module.generator.d.ts +20 -3
- package/dist/better-auth/generators/fastify/better-auth-module/better-auth-module.generator.js +5 -24
- package/dist/better-auth/generators/fastify/better-auth-module/generated/index.d.ts +40 -2
- package/dist/better-auth/generators/fastify/better-auth-module/generated/template-renderers.d.ts +19 -1
- package/dist/better-auth/generators/fastify/better-auth-module/generated/template-renderers.js +6 -2
- package/dist/better-auth/generators/fastify/better-auth-module/generated/ts-import-providers.d.ts +1 -0
- package/dist/better-auth/generators/fastify/better-auth-module/generated/ts-import-providers.js +1 -0
- package/dist/better-auth/generators/fastify/better-auth-module/generated/typed-templates.d.ts +20 -1
- package/dist/better-auth/generators/fastify/better-auth-module/generated/typed-templates.js +4 -1
- package/dist/better-auth/generators/fastify/better-auth-module/templates/module/services/auth.ts +16 -8
- package/dist/better-auth/generators/react/better-auth-hooks/better-auth-hooks.generator.d.ts +1 -1
- package/dist/better-auth/generators/react/better-auth-hooks/generated/index.d.ts +5 -4
- package/dist/better-auth/generators/react/better-auth-hooks/generated/template-paths.d.ts +1 -1
- package/dist/better-auth/generators/react/better-auth-hooks/generated/template-paths.js +1 -1
- package/dist/better-auth/generators/react/better-auth-hooks/generated/ts-import-providers.d.ts +2 -1
- package/dist/better-auth/generators/react/better-auth-hooks/generated/ts-import-providers.js +2 -1
- package/dist/better-auth/generators/react/better-auth-hooks/generated/typed-templates.d.ts +6 -6
- package/dist/better-auth/generators/react/better-auth-hooks/generated/typed-templates.js +13 -13
- package/dist/better-auth/generators/react/better-auth-hooks/templates/src/hooks/{use-required-user-id.ts → use-user-id-or-throw.ts} +1 -1
- package/dist/better-auth/generators/react/better-auth-pages/better-auth-pages.generator.d.ts +37 -0
- package/dist/better-auth/generators/react/better-auth-pages/generated/index.d.ts +222 -0
- package/dist/better-auth/generators/react/better-auth-pages/generated/template-renderers.d.ts +37 -0
- package/dist/better-auth/generators/react/better-auth-pages/generated/typed-templates.d.ts +370 -0
- package/dist/better-auth/generators/react/better-auth-pages/templates/routes/forgot-password.tsx +1 -4
- package/dist/better-auth/generators/react/react-better-auth/generated/index.d.ts +77 -2
- package/dist/better-auth/generators/react/react-better-auth/generated/template-renderers.d.ts +38 -1
- package/dist/better-auth/generators/react/react-better-auth/generated/ts-import-providers.d.ts +1 -0
- package/dist/better-auth/generators/react/react-better-auth/generated/ts-import-providers.js +1 -0
- package/dist/better-auth/generators/react/react-better-auth/generated/typed-templates.d.ts +38 -1
- package/dist/better-auth/generators/react/react-better-auth/react-better-auth.generator.d.ts +38 -1
- package/dist/better-auth/generators/react/react-better-auth/templates/src/app/auth-loaded-gate.tsx +12 -3
- package/dist/local-auth/admin/common.js +6 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/admin-crud-invite-user-action.generator.d.ts +593 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/admin-crud-invite-user-action.generator.js +119 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/index.d.ts +602 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/index.js +11 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/template-paths.d.ts +12 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/template-paths.js +24 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/template-renderers.d.ts +307 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/template-renderers.js +45 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/typed-templates.d.ts +294 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/generated/typed-templates.js +21 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/index.d.ts +2 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/index.js +2 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/templates/routes/-components/invite-user-dialog.d.ts +21 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/templates/routes/-components/invite-user-dialog.js +68 -0
- package/dist/local-auth/admin/generators/admin-crud-invite-user-action/templates/routes/-components/invite-user-dialog.tsx +122 -0
- package/dist/local-auth/admin/generators/admin-crud-manage-roles-action/admin-crud-manage-roles-action.generator.d.ts +74 -0
- package/dist/local-auth/admin/generators/admin-crud-manage-roles-action/generated/index.d.ts +74 -0
- package/dist/local-auth/admin/generators/admin-crud-manage-roles-action/generated/template-renderers.d.ts +37 -0
- package/dist/local-auth/admin/generators/admin-crud-manage-roles-action/generated/typed-templates.d.ts +37 -0
- package/dist/local-auth/admin/generators/admin-crud-manage-roles-action/templates/routes/-components/role-manager-dialog.js +1 -2
- package/dist/local-auth/admin/generators/admin-crud-manage-roles-action/templates/routes/-components/role-manager-dialog.tsx +1 -1
- package/dist/local-auth/admin/generators/admin-crud-reset-password-action/admin-crud-reset-password-action.generator.d.ts +74 -0
- package/dist/local-auth/admin/generators/admin-crud-reset-password-action/generated/index.d.ts +74 -0
- package/dist/local-auth/admin/generators/admin-crud-reset-password-action/generated/template-renderers.d.ts +37 -0
- package/dist/local-auth/admin/generators/admin-crud-reset-password-action/generated/typed-templates.d.ts +74 -0
- package/dist/local-auth/admin/generators/admin-crud-reset-password-action/templates/routes/-components/password-reset-dialog.js +1 -2
- package/dist/local-auth/admin/generators/admin-crud-reset-password-action/templates/routes/-components/password-reset-dialog.tsx +1 -1
- package/dist/local-auth/admin/generators/admin-crud-roles-column/admin-crud-roles-column.generator.d.ts +37 -0
- package/dist/local-auth/admin/node.js +13 -0
- package/dist/local-auth/admin/schema/invite-user-action.d.ts +12 -0
- package/dist/local-auth/admin/schema/invite-user-action.js +6 -0
- package/dist/local-auth/admin/web.js +8 -0
- package/dist/local-auth/core/common.js +7 -2
- package/dist/local-auth/core/components/local-auth-definition-editor.js +2 -1
- package/dist/local-auth/core/components/local-auth-web-app-settings-form.d.ts +9 -0
- package/dist/local-auth/core/components/local-auth-web-app-settings-form.js +11 -0
- package/dist/local-auth/core/generators/auth-email-password/auth-email-password.generator.d.ts +16 -7
- package/dist/local-auth/core/generators/auth-email-password/auth-email-password.generator.js +73 -16
- package/dist/local-auth/core/generators/auth-email-password/generated/index.d.ts +222 -7
- package/dist/local-auth/core/generators/auth-email-password/generated/template-paths.d.ts +5 -0
- package/dist/local-auth/core/generators/auth-email-password/generated/template-paths.js +5 -0
- package/dist/local-auth/core/generators/auth-email-password/generated/template-renderers.d.ts +27 -0
- package/dist/local-auth/core/generators/auth-email-password/generated/template-renderers.js +71 -4
- package/dist/local-auth/core/generators/auth-email-password/generated/ts-import-providers.d.ts +1 -3
- package/dist/local-auth/core/generators/auth-email-password/generated/ts-import-providers.js +1 -2
- package/dist/local-auth/core/generators/auth-email-password/generated/typed-templates.d.ts +226 -10
- package/dist/local-auth/core/generators/auth-email-password/generated/typed-templates.js +91 -7
- package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/otp.constants.d.ts +19 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/otp.constants.js +20 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/otp.constants.ts +22 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/password.constants.d.ts +12 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/password.constants.js +12 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/constants/password.constants.ts +14 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/email-otp.mutations.d.ts +2 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/email-otp.mutations.js +30 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/email-otp.mutations.ts +38 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/invite.mutations.d.ts +2 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/invite.mutations.js +42 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/invite.mutations.ts +54 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/user-password.mutations.js +2 -19
- package/dist/local-auth/core/generators/auth-email-password/templates/module/schema/user-password.mutations.ts +1 -22
- package/dist/local-auth/core/generators/auth-email-password/templates/module/services/email-otp.service.d.ts +37 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/services/email-otp.service.js +225 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/services/email-otp.service.ts +333 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/services/email-verification.service.js +2 -3
- package/dist/local-auth/core/generators/auth-email-password/templates/module/services/email-verification.service.ts +5 -3
- package/dist/local-auth/core/generators/auth-email-password/templates/module/services/invite.service.d.ts +39 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/services/invite.service.js +142 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/services/invite.service.ts +221 -0
- package/dist/local-auth/core/generators/auth-email-password/templates/module/services/password-reset.service.js +5 -6
- package/dist/local-auth/core/generators/auth-email-password/templates/module/services/password-reset.service.ts +8 -5
- package/dist/local-auth/core/generators/auth-email-templates/auth-email-templates.generator.d.ts +3 -1
- package/dist/local-auth/core/generators/auth-email-templates/auth-email-templates.generator.js +18 -3
- package/dist/local-auth/core/generators/auth-email-templates/generated/index.d.ts +40 -0
- package/dist/local-auth/core/generators/auth-email-templates/generated/template-paths.d.ts +2 -0
- package/dist/local-auth/core/generators/auth-email-templates/generated/template-paths.js +2 -0
- package/dist/local-auth/core/generators/auth-email-templates/generated/template-renderers.d.ts +6 -0
- package/dist/local-auth/core/generators/auth-email-templates/generated/template-renderers.js +20 -0
- package/dist/local-auth/core/generators/auth-email-templates/generated/typed-templates.d.ts +40 -0
- package/dist/local-auth/core/generators/auth-email-templates/generated/typed-templates.js +24 -0
- package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/email-otp.email.d.ts +3 -0
- package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/email-otp.email.js +18 -0
- package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/email-otp.email.tsx +57 -0
- package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/invite.email.d.ts +3 -0
- package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/invite.email.js +14 -0
- package/dist/local-auth/core/generators/auth-email-templates/templates/src/emails/auth/invite.email.tsx +47 -0
- package/dist/local-auth/core/generators/auth-hooks/auth-hooks.generator.d.ts +272 -1
- package/dist/local-auth/core/generators/auth-hooks/generated/index.d.ts +547 -4
- package/dist/local-auth/core/generators/auth-hooks/generated/template-paths.d.ts +1 -1
- package/dist/local-auth/core/generators/auth-hooks/generated/template-paths.js +1 -1
- package/dist/local-auth/core/generators/auth-hooks/generated/template-renderers.d.ts +271 -0
- package/dist/local-auth/core/generators/auth-hooks/generated/template-renderers.js +4 -2
- package/dist/local-auth/core/generators/auth-hooks/generated/ts-import-providers.d.ts +2 -1
- package/dist/local-auth/core/generators/auth-hooks/generated/ts-import-providers.js +2 -1
- package/dist/local-auth/core/generators/auth-hooks/generated/typed-templates.d.ts +548 -6
- package/dist/local-auth/core/generators/auth-hooks/generated/typed-templates.js +15 -14
- package/dist/local-auth/core/generators/auth-hooks/templates/src/hooks/use-log-out.js +1 -1
- package/dist/local-auth/core/generators/auth-hooks/templates/src/hooks/use-log-out.ts +1 -1
- package/dist/local-auth/core/generators/auth-hooks/templates/src/hooks/use-session.d.ts +7 -0
- package/dist/local-auth/core/generators/auth-hooks/templates/src/hooks/use-session.ts +7 -0
- package/dist/local-auth/core/generators/auth-module/auth-module.generator.d.ts +23 -7
- package/dist/local-auth/core/generators/auth-module/auth-module.generator.js +7 -26
- package/dist/local-auth/core/generators/auth-module/generated/index.d.ts +65 -4
- package/dist/local-auth/core/generators/auth-module/generated/template-paths.d.ts +1 -1
- package/dist/local-auth/core/generators/auth-module/generated/template-paths.js +1 -1
- package/dist/local-auth/core/generators/auth-module/generated/template-renderers.d.ts +22 -1
- package/dist/local-auth/core/generators/auth-module/generated/template-renderers.js +19 -2
- package/dist/local-auth/core/generators/auth-module/generated/ts-import-providers.d.ts +10 -0
- package/dist/local-auth/core/generators/auth-module/generated/ts-import-providers.js +7 -0
- package/dist/local-auth/core/generators/auth-module/generated/typed-templates.d.ts +42 -4
- package/dist/local-auth/core/generators/auth-module/generated/typed-templates.js +24 -13
- package/dist/local-auth/core/generators/auth-module/templates/module/services/auth-verification.service.d.ts +4 -0
- package/dist/local-auth/core/generators/auth-module/templates/module/services/auth-verification.service.js +1 -1
- package/dist/local-auth/core/generators/auth-module/templates/module/services/auth-verification.service.ts +1 -1
- package/dist/local-auth/core/generators/auth-module/templates/module/services/code-verification.service.d.ts +67 -0
- package/dist/local-auth/core/generators/auth-module/templates/module/services/code-verification.service.js +140 -0
- package/dist/local-auth/core/generators/auth-module/templates/module/services/code-verification.service.ts +191 -0
- package/dist/local-auth/core/generators/auth-module/templates/module/services/user-session.service.js +8 -9
- package/dist/local-auth/core/generators/auth-module/templates/module/services/user-session.service.ts +10 -11
- package/dist/local-auth/core/generators/auth-routes/auth-routes.generator.d.ts +39 -0
- package/dist/local-auth/core/generators/auth-routes/auth-routes.generator.js +38 -5
- package/dist/local-auth/core/generators/auth-routes/generated/index.d.ts +1089 -268
- package/dist/local-auth/core/generators/auth-routes/generated/template-paths.d.ts +3 -0
- package/dist/local-auth/core/generators/auth-routes/generated/template-paths.js +3 -0
- package/dist/local-auth/core/generators/auth-routes/generated/template-renderers.d.ts +49 -0
- package/dist/local-auth/core/generators/auth-routes/generated/template-renderers.js +51 -0
- package/dist/local-auth/core/generators/auth-routes/generated/ts-import-providers.d.ts +1 -0
- package/dist/local-auth/core/generators/auth-routes/generated/ts-import-providers.js +1 -0
- package/dist/local-auth/core/generators/auth-routes/generated/typed-templates.d.ts +918 -283
- package/dist/local-auth/core/generators/auth-routes/generated/typed-templates.js +69 -23
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/-otp-constants.d.ts +9 -0
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/-otp-constants.js +10 -0
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/-otp-constants.ts +11 -0
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/accept-invite.d.ts +7 -0
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/accept-invite.js +155 -0
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/accept-invite.tsx +274 -0
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/forgot-password.js +1 -2
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/forgot-password.tsx +1 -1
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/login-otp.d.ts +7 -0
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/login-otp.js +184 -0
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/login-otp.tsx +312 -0
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/login.js +5 -5
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/login.tsx +6 -12
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/register.js +4 -4
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/register.tsx +4 -3
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/reset-password.js +1 -2
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/reset-password.tsx +1 -1
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/verify-email.js +1 -2
- package/dist/local-auth/core/generators/auth-routes/templates/routes/auth_/verify-email.tsx +1 -1
- package/dist/local-auth/core/generators/react-session/generated/index.d.ts +90 -7
- package/dist/local-auth/core/generators/react-session/generated/template-renderers.d.ts +38 -1
- package/dist/local-auth/core/generators/react-session/generated/ts-import-providers.d.ts +1 -0
- package/dist/local-auth/core/generators/react-session/generated/ts-import-providers.js +1 -0
- package/dist/local-auth/core/generators/react-session/generated/typed-templates.d.ts +102 -12
- package/dist/local-auth/core/generators/react-session/generated/typed-templates.js +5 -2
- package/dist/local-auth/core/generators/react-session/react-session.generator.d.ts +54 -1
- package/dist/local-auth/core/generators/react-session/react-session.generator.js +35 -1
- package/dist/local-auth/core/generators/react-session/templates/src/app/user-session-provider.js +17 -44
- package/dist/local-auth/core/generators/react-session/templates/src/app/user-session-provider.tsx +21 -49
- package/dist/local-auth/core/generators/react-session/templates/src/services/user-session-client.d.ts +43 -34
- package/dist/local-auth/core/generators/react-session/templates/src/services/user-session-client.js +96 -80
- package/dist/local-auth/core/generators/react-session/templates/src/services/user-session-client.ts +148 -82
- package/dist/local-auth/core/generators/seed-initial-user/generated/index.d.ts +0 -2
- package/dist/local-auth/core/generators/seed-initial-user/generated/template-renderers.d.ts +0 -1
- package/dist/local-auth/core/generators/seed-initial-user/generated/typed-templates.d.ts +0 -1
- package/dist/local-auth/core/generators/seed-initial-user/seed-initial-user.generator.d.ts +0 -1
- package/dist/local-auth/core/node.js +30 -12
- package/dist/local-auth/core/schema/migrations.d.ts +3 -0
- package/dist/local-auth/core/schema/migrations.js +36 -0
- package/dist/local-auth/core/schema/models.js +8 -0
- package/dist/local-auth/core/schema/plugin-definition.d.ts +1 -0
- package/dist/local-auth/core/schema/plugin-definition.js +1 -0
- package/dist/local-auth/core/schema/web-app-schema.d.ts +15 -0
- package/dist/local-auth/core/schema/web-app-schema.js +16 -0
- package/dist/local-auth/core/web.js +8 -1
- package/dist/placeholder-auth/core/generators/placeholder-auth-hooks/generated/index.d.ts +4 -3
- package/dist/placeholder-auth/core/generators/placeholder-auth-hooks/generated/template-paths.d.ts +1 -1
- package/dist/placeholder-auth/core/generators/placeholder-auth-hooks/generated/template-paths.js +1 -1
- package/dist/placeholder-auth/core/generators/placeholder-auth-hooks/generated/ts-import-providers.d.ts +2 -1
- package/dist/placeholder-auth/core/generators/placeholder-auth-hooks/generated/ts-import-providers.js +2 -1
- package/dist/placeholder-auth/core/generators/placeholder-auth-hooks/generated/typed-templates.d.ts +4 -4
- package/dist/placeholder-auth/core/generators/placeholder-auth-hooks/generated/typed-templates.js +13 -13
- package/dist/placeholder-auth/core/generators/placeholder-auth-hooks/placeholder-auth-hooks.generator.d.ts +1 -1
- package/dist/placeholder-auth/core/generators/placeholder-auth-hooks/templates/src/hooks/use-session.d.ts +1 -0
- package/dist/placeholder-auth/core/generators/placeholder-auth-hooks/templates/src/hooks/use-session.ts +2 -0
- package/dist/placeholder-auth/core/generators/placeholder-auth-module/generated/index.d.ts +1 -2
- package/dist/placeholder-auth/core/generators/placeholder-auth-module/generated/template-renderers.d.ts +0 -1
- package/dist/placeholder-auth/core/generators/placeholder-auth-module/generated/ts-import-providers.d.ts +1 -0
- package/dist/placeholder-auth/core/generators/placeholder-auth-module/generated/ts-import-providers.js +1 -0
- package/dist/placeholder-auth/core/generators/placeholder-auth-module/generated/typed-templates.d.ts +0 -1
- package/dist/placeholder-auth/core/generators/placeholder-auth-module/placeholder-auth-module.generator.d.ts +0 -1
- 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-DUupKqD4.js} +42 -34
- 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-DE4rTqEA.js} +16857 -381
- 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-mszeXpf-.js} +90 -38
- 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-CtIleM_e.js} +4095 -2399
- 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-BckLhSjF.js} +212 -57
- package/dist/web/assets/auth-DR6QxrAW.js +1 -0
- package/dist/web/assets/{common-iHaemino.js → common-BsyRiQi3.js} +6 -6
- package/dist/web/assets/{common-TDf_PrjX.js → common-CRwNlCMn.js} +10 -10
- package/dist/web/assets/{common-ByxS61u8.js → common-CvqlRZC9.js} +12 -12
- package/dist/web/assets/common-D3atSs7O.js +70 -0
- package/dist/web/assets/common-DuFyXa6l.js +36 -0
- package/dist/web/assets/common-islduJTj.js +43 -0
- package/dist/web/assets/{dist-CrLSslv6.js → dist-DQd1TAJX.js} +273 -132
- package/dist/web/assets/{get-auth-plugin-definition-kDCtXq_6.js → get-auth-plugin-definition-BgWWHl_Y.js} +3 -3
- package/dist/web/assets/{hostInit-Cruek6c6.js → hostInit-DNA4kLRY.js} +32 -5
- package/dist/web/assets/{mf-entry-bootstrap-0-dfed0275.js → mf-entry-bootstrap-0-ea77c09b.js} +2 -1
- package/dist/web/assets/{mf-entry-bootstrap-1-5275674a.js → mf-entry-bootstrap-1-9dee3d8d.js} +3 -2
- package/dist/web/assets/{mf-entry-bootstrap-2-39fb9255.js → mf-entry-bootstrap-2-3179040b.js} +3 -2
- package/dist/web/assets/{mf-entry-bootstrap-3-49e64336.js → mf-entry-bootstrap-3-370d200d.js} +3 -2
- package/dist/web/assets/{models-DSvs_3bA.js → models-BOeSYfRn.js} +4 -5
- package/dist/web/assets/{models-BZmCbizK.js → models-DF6DoXt6.js} +11 -6
- package/dist/web/assets/{plugin-definition-CYj29S3q.js → plugin-definition--k0VAeK3.js} +9 -10
- package/dist/web/assets/plugin-definition-CMwSlxsJ.js +10 -0
- package/dist/web/assets/{styles-yjbHBTlw.js → styles-BQEcQHoR.js} +9 -10
- package/dist/web/assets/{virtualExposes-CbPBmicF.js → virtualExposes-C7fDiAmS.js} +1 -1
- package/dist/web/assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__plugin-auth__remoteEntry_js-BZBRyml2.js +747 -0
- package/dist/web/assets/{virtual_mf-exposes___mfe_internal__plugin-auth__remoteEntry_js-B6BXIeF1.js → virtual_mf-exposes___mfe_internal__plugin-auth__remoteEntry_js-6SjVUCs8.js} +13 -13
- package/dist/web/assets/web-8wz0ad-V.js +59 -0
- package/dist/web/assets/{web-CXAHKjIY.js → web-BGixH_gT.js} +27 -27
- package/dist/web/assets/{web-1eTcxMhf.js → web-BxIwkN3O.js} +6 -6
- package/dist/web/assets/{web-VqBA3jxZ.js → web-CQMfABjr.js} +24 -24
- package/dist/web/assets/{web-CDAKPPK9.js → web-CcNpItSu.js} +22 -22
- package/dist/web/assets/web-CypPeiVv.js +148 -0
- package/dist/web/assets/{web-D0MSKfyv.js → web-CzK_la2_.js} +53 -53
- package/dist/web/index.html +5 -5
- package/dist/web/remoteEntry.js +1 -1
- package/package.json +14 -14
- package/dist/local-auth/core/generators/auth-module/templates/module/utils/cookie-signer.d.ts +0 -38
- package/dist/local-auth/core/generators/auth-module/templates/module/utils/cookie-signer.js +0 -71
- package/dist/local-auth/core/generators/auth-module/templates/module/utils/cookie-signer.ts +0 -77
- 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
- package/dist/web/assets/_virtual_mf___mfe_internal__plugin_mf_2_auth__loadShare__zod__loadShare__.js-BEZ7A-cz.js +0 -13566
- package/dist/web/assets/auth-BxAE0rgh.js +0 -1
- package/dist/web/assets/common-CKl_XlvH.js +0 -37
- package/dist/web/assets/common-UpVCKZeW.js +0 -41
- package/dist/web/assets/common-crSwCD36.js +0 -37
- package/dist/web/assets/model-utils-C0WDb_Ec.js +0 -1599
- package/dist/web/assets/plugin-definition-DOMmPL2Z.js +0 -11
- package/dist/web/assets/virtual_mf-REMOTE_ENTRY_ID___mfe_internal__plugin-auth__remoteEntry_js-XM6iYVxf.js +0 -259
- package/dist/web/assets/virtual_mf-exposes-ssr___mfe_internal__plugin-auth__remoteEntry_js-DGjg7Brm.js +0 -129
- package/dist/web/assets/web-DbeAaDTV.js +0 -118
- package/dist/web/assets/web-miDovYk_.js +0 -46
- package/dist/web/remoteEntry.ssr.js +0 -43
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { EMAIL_OTP_EXPIRY_SEC, EMAIL_OTP_LENGTH, EMAIL_OTP_MAX_ATTEMPTS, } from '$constantsOtp';
|
|
3
|
+
import { PASSWORD_MAX_LENGTH } from '$constantsPassword';
|
|
4
|
+
import { consumeCodeVerification, createCodeVerification, validateCodeVerification, } from '%authModuleImports';
|
|
5
|
+
import { isDevelopment } from '%configServiceImports';
|
|
6
|
+
import { BadRequestError, handleZodRequestValidationError, } from '%errorHandlerServiceImports';
|
|
7
|
+
import { prisma } from '%prismaImports';
|
|
8
|
+
import { memoizeRateLimiter } from '%rateLimitImports';
|
|
9
|
+
import * as crypto from 'node:crypto';
|
|
10
|
+
import z from 'zod';
|
|
11
|
+
const PROVIDER_ID = 'email-otp';
|
|
12
|
+
const EMAIL_OTP_TYPE = 'email-otp';
|
|
13
|
+
/**
|
|
14
|
+
* Rate limiters for emailed sign-in codes.
|
|
15
|
+
*/
|
|
16
|
+
// Per-email burst limit: 3 codes / 15 minutes
|
|
17
|
+
const getEmailOtpRequestEmailLimiter = memoizeRateLimiter('email-otp-email', {
|
|
18
|
+
points: 3,
|
|
19
|
+
duration: 60 * 15, // 15 minutes
|
|
20
|
+
});
|
|
21
|
+
// Per-email daily cap. Each new code resets that code's attempt budget, so
|
|
22
|
+
// this is what actually bounds guesses per day: 10 codes x 5 attempts.
|
|
23
|
+
const getEmailOtpRequestEmailDailyLimiter = memoizeRateLimiter('email-otp-email-daily', {
|
|
24
|
+
points: 10,
|
|
25
|
+
duration: 60 * 60 * 24, // 1 day
|
|
26
|
+
});
|
|
27
|
+
// Per-IP rate limit: 10 codes/hour (prevents email scanning)
|
|
28
|
+
const getEmailOtpRequestIpLimiter = memoizeRateLimiter('email-otp-ip', {
|
|
29
|
+
points: 10,
|
|
30
|
+
duration: 60 * 60, // 1 hour
|
|
31
|
+
});
|
|
32
|
+
// Deliberately no global limiter here. A shared counter would let anyone who
|
|
33
|
+
// exhausts it lock every user out of signing in, and accounts created through
|
|
34
|
+
// this flow have no password to fall back on. Distributed abuse is bounded by
|
|
35
|
+
// the per-IP limit above; protecting the email provider from a cost or
|
|
36
|
+
// reputation spike belongs in its own quotas and alerting, not in the login
|
|
37
|
+
// path.
|
|
38
|
+
// Per-address verification limit. Unlike the per-code attempt budget this
|
|
39
|
+
// survives the code being reissued, and unlike the per-IP limit it survives
|
|
40
|
+
// the attacker rotating IPs.
|
|
41
|
+
const getEmailOtpVerifyEmailLimiter = memoizeRateLimiter('email-otp-verify-email', {
|
|
42
|
+
points: 10,
|
|
43
|
+
duration: 60 * 60, // 1 hour
|
|
44
|
+
blockDuration: 60 * 60, // Block for 1 hour if exceeded
|
|
45
|
+
});
|
|
46
|
+
// Per-IP verification limit, on top of each code's own attempt budget
|
|
47
|
+
const getEmailOtpVerifyIpLimiter = memoizeRateLimiter('email-otp-verify-ip', {
|
|
48
|
+
points: 20,
|
|
49
|
+
duration: 60 * 60, // 1 hour
|
|
50
|
+
blockDuration: 60 * 60, // Block for 1 hour if exceeded
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* Cookie holding the id of the pending verification.
|
|
54
|
+
*
|
|
55
|
+
* Requiring it at verification binds a code to the browser that asked for it,
|
|
56
|
+
* so a code read over someone's shoulder or talked out of them over the phone
|
|
57
|
+
* cannot be redeemed elsewhere.
|
|
58
|
+
*/
|
|
59
|
+
const EMAIL_OTP_COOKIE_NAME = 'email-otp-verification';
|
|
60
|
+
function getEmailOtpCookieOptions() {
|
|
61
|
+
return {
|
|
62
|
+
httpOnly: true,
|
|
63
|
+
sameSite: 'lax',
|
|
64
|
+
secure: !isDevelopment(),
|
|
65
|
+
maxAge: EMAIL_OTP_EXPIRY_SEC,
|
|
66
|
+
path: '/',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const emailSchema = z.object({
|
|
70
|
+
email: z
|
|
71
|
+
.email()
|
|
72
|
+
.max(PASSWORD_MAX_LENGTH)
|
|
73
|
+
.transform((value) => value.toLowerCase()),
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Generates a numeric code using rejection sampling so every digit is equally
|
|
77
|
+
* likely. 250 is the largest multiple of 10 within a byte, so values above it
|
|
78
|
+
* are discarded rather than folded back and over-weighting digits 0-5.
|
|
79
|
+
*/
|
|
80
|
+
function generateNumericCode(length) {
|
|
81
|
+
const digits = Array.from({ length }, () => {
|
|
82
|
+
let value = 256;
|
|
83
|
+
while (value >= 250) {
|
|
84
|
+
value = crypto.randomBytes(1).readUInt8(0);
|
|
85
|
+
}
|
|
86
|
+
return String(value % 10);
|
|
87
|
+
});
|
|
88
|
+
return digits.join('');
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Emails a single-use sign-in code to the given address.
|
|
92
|
+
*
|
|
93
|
+
* A code is sent whether or not an account exists — verifying one signs the
|
|
94
|
+
* user in, creating the account on first use — so this endpoint reveals
|
|
95
|
+
* nothing about which addresses are registered.
|
|
96
|
+
*/
|
|
97
|
+
export async function requestEmailOtp({ email: rawEmail, context, }) {
|
|
98
|
+
const { services } = context;
|
|
99
|
+
const { email } = await emailSchema
|
|
100
|
+
.parseAsync({ email: rawEmail })
|
|
101
|
+
.catch(handleZodRequestValidationError);
|
|
102
|
+
await Promise.all([
|
|
103
|
+
getEmailOtpRequestIpLimiter().consumeOrThrow(context.reqInfo.ip, 'Too many sign-in code requests. Please try again later.', 'too-many-requests'),
|
|
104
|
+
getEmailOtpRequestEmailLimiter().consumeOrThrow(email, 'Too many sign-in code requests. Please try again later.', 'too-many-requests'),
|
|
105
|
+
getEmailOtpRequestEmailDailyLimiter().consumeOrThrow(email, 'Too many sign-in code requests. Please try again later.', 'too-many-requests'),
|
|
106
|
+
]);
|
|
107
|
+
const code = generateNumericCode(EMAIL_OTP_LENGTH);
|
|
108
|
+
const { id: verificationId } = await createCodeVerification({
|
|
109
|
+
type: EMAIL_OTP_TYPE,
|
|
110
|
+
identifier: email,
|
|
111
|
+
code,
|
|
112
|
+
expiresInSec: EMAIL_OTP_EXPIRY_SEC,
|
|
113
|
+
});
|
|
114
|
+
context.cookieStore.set(EMAIL_OTP_COOKIE_NAME, verificationId, getEmailOtpCookieOptions());
|
|
115
|
+
await services.email.send(TPL_EMAIL_OTP_EMAIL, {
|
|
116
|
+
to: email,
|
|
117
|
+
data: { code, expiryMinutes: Math.floor(EMAIL_OTP_EXPIRY_SEC / 60) },
|
|
118
|
+
});
|
|
119
|
+
return { success: true };
|
|
120
|
+
}
|
|
121
|
+
const verifyEmailOtpSchema = z.object({
|
|
122
|
+
email: z
|
|
123
|
+
.email()
|
|
124
|
+
.max(PASSWORD_MAX_LENGTH)
|
|
125
|
+
.transform((value) => value.toLowerCase()),
|
|
126
|
+
code: z.string().length(EMAIL_OTP_LENGTH),
|
|
127
|
+
// Blank and whitespace-only names are normalized to absent rather than
|
|
128
|
+
// rejected, so a client that always sends the field still gets the
|
|
129
|
+
// `name-required` response rather than a generic validation failure.
|
|
130
|
+
name: z
|
|
131
|
+
.string()
|
|
132
|
+
.max(100)
|
|
133
|
+
.optional()
|
|
134
|
+
.transform((value) => {
|
|
135
|
+
const trimmed = value?.trim();
|
|
136
|
+
return trimmed ? trimmed : undefined;
|
|
137
|
+
}),
|
|
138
|
+
});
|
|
139
|
+
/**
|
|
140
|
+
* Signs a user in with an emailed code, creating the account on first use.
|
|
141
|
+
*
|
|
142
|
+
* A verified code proves control of the inbox, so the account it creates (or
|
|
143
|
+
* signs in to) is marked as having a verified email. Accounts created this way
|
|
144
|
+
* have no password until the user goes through the password reset flow.
|
|
145
|
+
*
|
|
146
|
+
* @throws BadRequestError `name-required` when the code is valid but the
|
|
147
|
+
* address has no account yet and no name was supplied. The code is left intact
|
|
148
|
+
* so the client can collect a name and retry with it.
|
|
149
|
+
*/
|
|
150
|
+
export async function signInWithEmailOtp({ input, context, }) {
|
|
151
|
+
const { email, code, name } = await verifyEmailOtpSchema
|
|
152
|
+
.parseAsync(input)
|
|
153
|
+
.catch(handleZodRequestValidationError);
|
|
154
|
+
await getEmailOtpVerifyIpLimiter().consumeOrThrow(context.reqInfo.ip, 'Too many sign-in attempts. Please try again later.', 'too-many-requests');
|
|
155
|
+
// A code is only redeemable in the browser that requested it.
|
|
156
|
+
const verificationId = context.cookieStore.get(EMAIL_OTP_COOKIE_NAME);
|
|
157
|
+
if (!verificationId) {
|
|
158
|
+
throw new BadRequestError('This code must be entered in the browser that requested it', 'verification-context-missing');
|
|
159
|
+
}
|
|
160
|
+
// Establish that this browser owns the outstanding code before spending any
|
|
161
|
+
// of the address's budget. Charging first would let anyone lock a chosen
|
|
162
|
+
// address out of signing in just by posting its email with a junk code —
|
|
163
|
+
// which is worse than it sounds, because an account created through this
|
|
164
|
+
// flow has no password to fall back on.
|
|
165
|
+
const challenge = await prisma.authVerification.findUnique({
|
|
166
|
+
where: { type_identifier: { type: EMAIL_OTP_TYPE, identifier: email } },
|
|
167
|
+
select: { id: true },
|
|
168
|
+
});
|
|
169
|
+
if (challenge?.id !== verificationId) {
|
|
170
|
+
throw new BadRequestError('Invalid or expired code', 'invalid-code');
|
|
171
|
+
}
|
|
172
|
+
await getEmailOtpVerifyEmailLimiter().consumeOrThrow(email, 'Too many sign-in attempts. Please try again later.', 'too-many-requests');
|
|
173
|
+
const record = await validateCodeVerification({
|
|
174
|
+
type: EMAIL_OTP_TYPE,
|
|
175
|
+
identifier: email,
|
|
176
|
+
code,
|
|
177
|
+
maxAttempts: EMAIL_OTP_MAX_ATTEMPTS,
|
|
178
|
+
});
|
|
179
|
+
if (!record) {
|
|
180
|
+
throw new BadRequestError('Invalid or expired code', 'invalid-code');
|
|
181
|
+
}
|
|
182
|
+
// Re-checked because a fresh code may have been issued since the lookup
|
|
183
|
+
// above, which would have replaced the id this browser is holding.
|
|
184
|
+
if (record.id !== verificationId) {
|
|
185
|
+
throw new BadRequestError('Invalid or expired code', 'invalid-code');
|
|
186
|
+
}
|
|
187
|
+
// Asking for a name only when the address is new would normally leak whether
|
|
188
|
+
// an account exists. Gating it behind a valid code keeps that safe: only
|
|
189
|
+
// someone who controls the mailbox reaches this point.
|
|
190
|
+
TPL_NAME_REQUIRED_CHECK;
|
|
191
|
+
// Spending the code and settling the account commit together, so a failure
|
|
192
|
+
// partway through leaves the code still usable rather than burning it and
|
|
193
|
+
// forcing the user to request another.
|
|
194
|
+
const userId = await prisma.$transaction(async (tx) => {
|
|
195
|
+
if (!(await consumeCodeVerification({ id: record.id, client: tx }))) {
|
|
196
|
+
throw new BadRequestError('Invalid or expired code', 'invalid-code');
|
|
197
|
+
}
|
|
198
|
+
// Receiving the code proves control of the address, so the account is
|
|
199
|
+
// created (or marked) as verified. An existing user's name is left alone.
|
|
200
|
+
const { id } = await tx.user.upsert({
|
|
201
|
+
where: { email },
|
|
202
|
+
create: {
|
|
203
|
+
email,
|
|
204
|
+
name,
|
|
205
|
+
emailVerified: true,
|
|
206
|
+
accounts: {
|
|
207
|
+
create: {
|
|
208
|
+
accountId: email,
|
|
209
|
+
providerId: PROVIDER_ID,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
update: { emailVerified: true },
|
|
214
|
+
select: { id: true },
|
|
215
|
+
});
|
|
216
|
+
return id;
|
|
217
|
+
});
|
|
218
|
+
context.cookieStore.clear(EMAIL_OTP_COOKIE_NAME, getEmailOtpCookieOptions());
|
|
219
|
+
// Outside the transaction: session creation manages its own storage through
|
|
220
|
+
// the request context. If it fails the code is spent, but the account now
|
|
221
|
+
// exists, so retrying signs the user in without asking for a name again.
|
|
222
|
+
const session = await context.services.userSession.createSession(userId, context);
|
|
223
|
+
return { session };
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=email-otp.service.js.map
|
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { EMAIL_VERIFICATION_TOKEN_EXPIRY_SEC } from '$constantsPassword';
|
|
3
|
+
import { getWebAppForUrl, getWebUrl } from '%appUrlsImports';
|
|
3
4
|
import { createAuthVerification, validateAuthVerification, } from '%authModuleImports';
|
|
4
|
-
import { getConfig } from '%configServiceImports';
|
|
5
5
|
import { BadRequestError } from '%errorHandlerServiceImports';
|
|
6
6
|
import { prisma } from '%prismaImports';
|
|
7
7
|
import { memoizeRateLimiter } from '%rateLimitImports';
|
|
@@ -49,8 +49,7 @@ export async function requestEmailVerification({ userId, context, }) {
|
|
|
49
49
|
userId: user.id,
|
|
50
50
|
expiresInSec: EMAIL_VERIFICATION_TOKEN_EXPIRY_SEC,
|
|
51
51
|
});
|
|
52
|
-
|
|
53
|
-
const verifyLink = `${getConfig().AUTH_FRONTEND_URL}/auth/verify-email?token=${encodeURIComponent(token)}`;
|
|
52
|
+
const verifyLink = getWebUrl(getWebAppForUrl(context.reqInfo.headers.origin) ?? TPL_AUTH_WEB_APP, `/auth/verify-email?token=${encodeURIComponent(token)}`);
|
|
54
53
|
await services.email.send(TPL_ACCOUNT_VERIFICATION_EMAIL, {
|
|
55
54
|
to: user.email,
|
|
56
55
|
data: { verifyLink },
|
|
@@ -6,11 +6,11 @@ import type {
|
|
|
6
6
|
} from '%requestServiceContextImports';
|
|
7
7
|
|
|
8
8
|
import { EMAIL_VERIFICATION_TOKEN_EXPIRY_SEC } from '$constantsPassword';
|
|
9
|
+
import { getWebAppForUrl, getWebUrl } from '%appUrlsImports';
|
|
9
10
|
import {
|
|
10
11
|
createAuthVerification,
|
|
11
12
|
validateAuthVerification,
|
|
12
13
|
} from '%authModuleImports';
|
|
13
|
-
import { getConfig } from '%configServiceImports';
|
|
14
14
|
import { BadRequestError } from '%errorHandlerServiceImports';
|
|
15
15
|
import { prisma } from '%prismaImports';
|
|
16
16
|
import { memoizeRateLimiter } from '%rateLimitImports';
|
|
@@ -90,8 +90,10 @@ export async function requestEmailVerification({
|
|
|
90
90
|
expiresInSec: EMAIL_VERIFICATION_TOKEN_EXPIRY_SEC,
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
const verifyLink = getWebUrl(
|
|
94
|
+
getWebAppForUrl(context.reqInfo.headers.origin) ?? TPL_AUTH_WEB_APP,
|
|
95
|
+
`/auth/verify-email?token=${encodeURIComponent(token)}`,
|
|
96
|
+
);
|
|
95
97
|
|
|
96
98
|
await services.email.send(TPL_ACCOUNT_VERIFICATION_EMAIL, {
|
|
97
99
|
to: user.email,
|
|
@@ -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
|