@teleporthq/teleport-plugin-next-workflows 0.44.11 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__tests__/_helpers/fake-fetch.ts +75 -0
- package/__tests__/_helpers/gui-place-order-statements.ts +17 -0
- package/__tests__/_helpers/inserted-lines.ts +22 -0
- package/__tests__/_helpers/invoice-store.ts +10 -1
- package/__tests__/_helpers/load-security-runtime.ts +45 -0
- package/__tests__/_helpers/load-server-runtime.ts +26 -3
- package/__tests__/_helpers/run-account-delete-route.ts +3 -1
- package/__tests__/_helpers/run-generated-middleware.ts +7 -2
- package/__tests__/account-delete-owned-rows.test.ts +3 -1
- package/__tests__/adopted-element-trigger-code.test.ts +128 -0
- package/__tests__/analytics-track-outcome.test.ts +145 -0
- package/__tests__/auth-password-change-ends-sessions.test.ts +8 -1
- package/__tests__/auth-password-security.test.ts +2275 -0
- package/__tests__/auth-session-cost.test.ts +31 -0
- package/__tests__/auth-signup-route.test.ts +960 -0
- package/__tests__/authored-code-every-path.test.ts +247 -0
- package/__tests__/automations-bus.test.ts +3820 -0
- package/__tests__/automations-catalog-drift.test.ts +97 -0
- package/__tests__/booking-calendar-feed-route.test.ts +20 -3
- package/__tests__/cart-price-order.test.ts +336 -1
- package/__tests__/code-lookup-rate-limit-route.test.ts +189 -0
- package/__tests__/cost-bearing-node-types.test.ts +119 -0
- package/__tests__/credential-columns-reply.test.ts +9 -0
- package/__tests__/credential-result-reply.test.ts +19 -5
- package/__tests__/cron-route-loop-templateparams.test.ts +6 -3
- package/__tests__/custom-js-authored-code.test.ts +304 -0
- package/__tests__/custom-node-custom-code-gate.test.ts +136 -0
- package/__tests__/custom-node-step-handlers.test.ts +251 -0
- package/__tests__/custom-node-step-reach.test.ts +63 -0
- package/__tests__/data-api-anon-user-id-fallback.test.ts +7 -2
- package/__tests__/data-api-browser-reads.test.ts +7 -0
- package/__tests__/data-api-business-events.test.ts +403 -0
- package/__tests__/data-api-invalid-body.test.ts +7 -0
- package/__tests__/data-api-low-stock-autofire.test.ts +30 -4
- package/__tests__/data-api-optional-feature-columns.test.ts +44 -0
- package/__tests__/data-api-owner-scope-credentials.test.ts +1324 -0
- package/__tests__/data-api-protected-tables.test.ts +48 -15
- package/__tests__/data-api-retired-table-and-account-fields.test.ts +208 -0
- package/__tests__/email-locale-senders.test.ts +5 -3
- package/__tests__/expression-injection.test.ts +343 -0
- package/__tests__/fixtures/admin-file-workflows.json +242 -0
- package/__tests__/form-confirmation-email-template.test.ts +44 -0
- package/__tests__/internal-request-headers.test.ts +25 -0
- package/__tests__/invoice-pdf-service-project-header.test.ts +120 -0
- package/__tests__/invoice-self-call-origin.test.ts +57 -1
- package/__tests__/legacy-webhook-self-calls.test.ts +86 -1
- package/__tests__/middleware-home-route-protection.test.ts +3 -1
- package/__tests__/middleware-subscriber-access.test.ts +1 -1
- package/__tests__/newsletter-admin-route.test.ts +649 -0
- package/__tests__/newsletter-plugin.test.ts +1248 -0
- package/__tests__/outbound-nodes-security.test.ts +1929 -0
- package/__tests__/page-access-guard.test.ts +217 -0
- package/__tests__/page-access-route-table.test.ts +231 -0
- package/__tests__/payment-drivers-module.test.ts +23 -0
- package/__tests__/payment-legacy-webhook-routes.test.ts +100 -5
- package/__tests__/payment-webhook-route.test.ts +4 -1
- package/__tests__/paypal-webhook-signature-verification.test.ts +44 -0
- package/__tests__/place-order-guards.test.ts +506 -0
- package/__tests__/public-step-guards.test.ts +310 -0
- package/__tests__/publish-refusals.test.ts +505 -0
- package/__tests__/realtime-identity-routes.test.ts +492 -0
- package/__tests__/record-event-writer-server-only.test.ts +975 -0
- package/__tests__/resolve-config-template-params.test.ts +3 -3
- package/__tests__/runtime-storage-upload-access.test.ts +280 -17
- package/__tests__/security-integration-env-names.test.ts +74 -0
- package/__tests__/security-lists-parity.test.ts +245 -0
- package/__tests__/security-runtime-shadow.test.ts +224 -1
- package/__tests__/security-runtime.test.ts +893 -0
- package/__tests__/segment-rate-limit-error-branch.test.ts +284 -0
- package/__tests__/segment-route-abuse-gate.test.ts +309 -0
- package/__tests__/server-job-hops-real-data-api.test.ts +669 -0
- package/__tests__/server-session-identity.test.ts +522 -0
- package/__tests__/server-step-chain.test.ts +222 -0
- package/__tests__/server-step-signing.test.ts +107 -2
- package/__tests__/session-cookie-resolver.test.ts +62 -106
- package/__tests__/signup-event.test.ts +649 -0
- package/__tests__/signup-reply-identical.test.ts +144 -0
- package/__tests__/slack-message-escaping.test.ts +123 -0
- package/__tests__/visitor-step-limits.test.ts +318 -0
- package/__tests__/webhook-delivery-lease.test.ts +4 -1
- package/__tests__/webpack-safe-handlers.test.ts +57 -8
- package/__tests__/workflow-auth-guard.test.ts +9 -1
- package/dist/cjs/account-delete-route-generator.d.ts +1 -0
- package/dist/cjs/account-delete-route-generator.d.ts.map +1 -1
- package/dist/cjs/account-delete-route-generator.js +18 -3
- package/dist/cjs/account-delete-route-generator.js.map +1 -1
- package/dist/cjs/api-route-generator.d.ts +52 -3
- package/dist/cjs/api-route-generator.d.ts.map +1 -1
- package/dist/cjs/api-route-generator.js +600 -145
- package/dist/cjs/api-route-generator.js.map +1 -1
- package/dist/cjs/auth-generator.d.ts +126 -3
- package/dist/cjs/auth-generator.d.ts.map +1 -1
- package/dist/cjs/auth-generator.js +1353 -139
- package/dist/cjs/auth-generator.js.map +1 -1
- package/dist/cjs/automations/activity-kicks.d.ts +12 -0
- package/dist/cjs/automations/activity-kicks.d.ts.map +1 -0
- package/dist/cjs/automations/activity-kicks.js +123 -0
- package/dist/cjs/automations/activity-kicks.js.map +1 -0
- package/dist/cjs/automations/app-events-source.d.ts +33 -0
- package/dist/cjs/automations/app-events-source.d.ts.map +1 -0
- package/dist/cjs/automations/app-events-source.js +109 -0
- package/dist/cjs/automations/app-events-source.js.map +1 -0
- package/dist/cjs/automations/automations-project-plugin.d.ts +21 -0
- package/dist/cjs/automations/automations-project-plugin.d.ts.map +1 -0
- package/dist/cjs/automations/automations-project-plugin.js +102 -0
- package/dist/cjs/automations/automations-project-plugin.js.map +1 -0
- package/dist/cjs/automations/automations-scope.d.ts +104 -0
- package/dist/cjs/automations/automations-scope.d.ts.map +1 -0
- package/dist/cjs/automations/automations-scope.js +176 -0
- package/dist/cjs/automations/automations-scope.js.map +1 -0
- package/dist/cjs/automations/dispatcher-source.d.ts +72 -0
- package/dist/cjs/automations/dispatcher-source.d.ts.map +1 -0
- package/dist/cjs/automations/dispatcher-source.js +1008 -0
- package/dist/cjs/automations/dispatcher-source.js.map +1 -0
- package/dist/cjs/automations/event-bus-source.d.ts +44 -0
- package/dist/cjs/automations/event-bus-source.d.ts.map +1 -0
- package/dist/cjs/automations/event-bus-source.js +427 -0
- package/dist/cjs/automations/event-bus-source.js.map +1 -0
- package/dist/cjs/automations/handler-module-generator.d.ts +25 -0
- package/dist/cjs/automations/handler-module-generator.d.ts.map +1 -0
- package/dist/cjs/automations/handler-module-generator.js +78 -0
- package/dist/cjs/automations/handler-module-generator.js.map +1 -0
- package/dist/cjs/automations/hydrators-source.d.ts +33 -0
- package/dist/cjs/automations/hydrators-source.d.ts.map +1 -0
- package/dist/cjs/automations/hydrators-source.js +571 -0
- package/dist/cjs/automations/hydrators-source.js.map +1 -0
- package/dist/cjs/booking-calendar-feed-route-generator.d.ts.map +1 -1
- package/dist/cjs/booking-calendar-feed-route-generator.js +8 -0
- package/dist/cjs/booking-calendar-feed-route-generator.js.map +1 -1
- package/dist/cjs/configured-nextauth-url.d.ts +17 -0
- package/dist/cjs/configured-nextauth-url.d.ts.map +1 -0
- package/dist/cjs/configured-nextauth-url.js +20 -0
- package/dist/cjs/configured-nextauth-url.js.map +1 -0
- package/dist/cjs/cron-request-verification.d.ts +22 -1
- package/dist/cjs/cron-request-verification.d.ts.map +1 -1
- package/dist/cjs/cron-request-verification.js +50 -6
- package/dist/cjs/cron-request-verification.js.map +1 -1
- package/dist/cjs/data-api-route-generator.d.ts +19 -0
- package/dist/cjs/data-api-route-generator.d.ts.map +1 -1
- package/dist/cjs/data-api-route-generator.js +711 -43
- package/dist/cjs/data-api-route-generator.js.map +1 -1
- package/dist/cjs/ecommerce/parameterized-stock-chain.d.ts +3 -1
- package/dist/cjs/ecommerce/parameterized-stock-chain.d.ts.map +1 -1
- package/dist/cjs/ecommerce/parameterized-stock-chain.js +45 -5
- package/dist/cjs/ecommerce/parameterized-stock-chain.js.map +1 -1
- package/dist/cjs/ecommerce/place-order-guards.d.ts +126 -0
- package/dist/cjs/ecommerce/place-order-guards.d.ts.map +1 -0
- package/dist/cjs/ecommerce/place-order-guards.js +670 -0
- package/dist/cjs/ecommerce/place-order-guards.js.map +1 -0
- package/dist/cjs/ecommerce-customhandler-rewriter.d.ts +2 -0
- package/dist/cjs/ecommerce-customhandler-rewriter.d.ts.map +1 -1
- package/dist/cjs/ecommerce-customhandler-rewriter.js +38 -2
- package/dist/cjs/ecommerce-customhandler-rewriter.js.map +1 -1
- package/dist/cjs/executor-generator.d.ts.map +1 -1
- package/dist/cjs/executor-generator.js +368 -60
- package/dist/cjs/executor-generator.js.map +1 -1
- package/dist/cjs/graph-utils.d.ts +9 -0
- package/dist/cjs/graph-utils.d.ts.map +1 -1
- package/dist/cjs/graph-utils.js +32 -1
- package/dist/cjs/graph-utils.js.map +1 -1
- package/dist/cjs/index.d.ts +20 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +72 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/invoice/api-routes-code.d.ts +9 -3
- package/dist/cjs/invoice/api-routes-code.d.ts.map +1 -1
- package/dist/cjs/invoice/api-routes-code.js +9 -7
- package/dist/cjs/invoice/api-routes-code.js.map +1 -1
- package/dist/cjs/invoice/index.d.ts.map +1 -1
- package/dist/cjs/invoice/index.js +12 -7
- package/dist/cjs/invoice/index.js.map +1 -1
- package/dist/cjs/invoice/invoice-assembly-code.d.ts.map +1 -1
- package/dist/cjs/invoice/invoice-assembly-code.js +8 -3
- package/dist/cjs/invoice/invoice-assembly-code.js.map +1 -1
- package/dist/cjs/invoice/pdf-generator-code.d.ts.map +1 -1
- package/dist/cjs/invoice/pdf-generator-code.js +2 -1
- package/dist/cjs/invoice/pdf-generator-code.js.map +1 -1
- package/dist/cjs/invoice/pdf-service-client-code.d.ts.map +1 -1
- package/dist/cjs/invoice/pdf-service-client-code.js +15 -7
- package/dist/cjs/invoice/pdf-service-client-code.js.map +1 -1
- package/dist/cjs/newsletter/admin-route-code.d.ts +60 -0
- package/dist/cjs/newsletter/admin-route-code.d.ts.map +1 -0
- package/dist/cjs/newsletter/admin-route-code.js +300 -0
- package/dist/cjs/newsletter/admin-route-code.js.map +1 -0
- package/dist/cjs/newsletter/helper-code.d.ts +109 -0
- package/dist/cjs/newsletter/helper-code.d.ts.map +1 -0
- package/dist/cjs/newsletter/helper-code.js +662 -0
- package/dist/cjs/newsletter/helper-code.js.map +1 -0
- package/dist/cjs/newsletter/newsletter-scope.d.ts +65 -0
- package/dist/cjs/newsletter/newsletter-scope.d.ts.map +1 -0
- package/dist/cjs/newsletter/newsletter-scope.js +103 -0
- package/dist/cjs/newsletter/newsletter-scope.js.map +1 -0
- package/dist/cjs/newsletter/project-plugin.d.ts +18 -0
- package/dist/cjs/newsletter/project-plugin.d.ts.map +1 -0
- package/dist/cjs/newsletter/project-plugin.js +97 -0
- package/dist/cjs/newsletter/project-plugin.js.map +1 -0
- package/dist/cjs/newsletter/routes-code.d.ts +6 -0
- package/dist/cjs/newsletter/routes-code.d.ts.map +1 -0
- package/dist/cjs/newsletter/routes-code.js +181 -0
- package/dist/cjs/newsletter/routes-code.js.map +1 -0
- package/dist/cjs/nodes/account/account-compare-passwords.d.ts.map +1 -1
- package/dist/cjs/nodes/account/account-compare-passwords.js +16 -6
- package/dist/cjs/nodes/account/account-compare-passwords.js.map +1 -1
- package/dist/cjs/nodes/account/account-delete-current.js +1 -1
- package/dist/cjs/nodes/account/account-get-current.js +1 -1
- package/dist/cjs/nodes/account/account-hash-password.d.ts.map +1 -1
- package/dist/cjs/nodes/account/account-hash-password.js +15 -3
- package/dist/cjs/nodes/account/account-hash-password.js.map +1 -1
- package/dist/cjs/nodes/account/account-login.d.ts.map +1 -1
- package/dist/cjs/nodes/account/account-login.js +23 -6
- package/dist/cjs/nodes/account/account-login.js.map +1 -1
- package/dist/cjs/nodes/account/account-logout.js +1 -1
- package/dist/cjs/nodes/account/account-refresh-session.js +1 -1
- package/dist/cjs/nodes/account/account-request-password-reset.d.ts +3 -0
- package/dist/cjs/nodes/account/account-request-password-reset.d.ts.map +1 -0
- package/dist/cjs/nodes/account/account-request-password-reset.js +46 -0
- package/dist/cjs/nodes/account/account-request-password-reset.js.map +1 -0
- package/dist/cjs/nodes/account/account-reset-password.d.ts +3 -0
- package/dist/cjs/nodes/account/account-reset-password.d.ts.map +1 -0
- package/dist/cjs/nodes/account/account-reset-password.js +44 -0
- package/dist/cjs/nodes/account/account-reset-password.js.map +1 -0
- package/dist/cjs/nodes/account/account-signup.d.ts.map +1 -1
- package/dist/cjs/nodes/account/account-signup.js +82 -19
- package/dist/cjs/nodes/account/account-signup.js.map +1 -1
- package/dist/cjs/nodes/account/account-social-login.js +1 -1
- package/dist/cjs/nodes/ai/ai-custom-prompt.d.ts.map +1 -1
- package/dist/cjs/nodes/ai/ai-custom-prompt.js +20 -5
- package/dist/cjs/nodes/ai/ai-custom-prompt.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-detect-language.d.ts.map +1 -1
- package/dist/cjs/nodes/ai/ai-detect-language.js +20 -5
- package/dist/cjs/nodes/ai/ai-detect-language.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-generate-text-embedding.d.ts.map +1 -1
- package/dist/cjs/nodes/ai/ai-generate-text-embedding.js +10 -2
- package/dist/cjs/nodes/ai/ai-generate-text-embedding.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-provider-utils-sources.generated.js +2 -2
- package/dist/cjs/nodes/ai/ai-provider-utils-sources.generated.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-provider-utils.d.ts +31 -2
- package/dist/cjs/nodes/ai/ai-provider-utils.d.ts.map +1 -1
- package/dist/cjs/nodes/ai/ai-provider-utils.js +125 -11
- package/dist/cjs/nodes/ai/ai-provider-utils.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-select-database-data.d.ts.map +1 -1
- package/dist/cjs/nodes/ai/ai-select-database-data.js +6 -2
- package/dist/cjs/nodes/ai/ai-select-database-data.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-sentiment-analysis.d.ts.map +1 -1
- package/dist/cjs/nodes/ai/ai-sentiment-analysis.js +20 -5
- package/dist/cjs/nodes/ai/ai-sentiment-analysis.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-summarization.d.ts.map +1 -1
- package/dist/cjs/nodes/ai/ai-summarization.js +20 -5
- package/dist/cjs/nodes/ai/ai-summarization.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-text-classifier.d.ts.map +1 -1
- package/dist/cjs/nodes/ai/ai-text-classifier.js +20 -5
- package/dist/cjs/nodes/ai/ai-text-classifier.js.map +1 -1
- package/dist/cjs/nodes/ai/ai-text-transform.d.ts.map +1 -1
- package/dist/cjs/nodes/ai/ai-text-transform.js +20 -5
- package/dist/cjs/nodes/ai/ai-text-transform.js.map +1 -1
- package/dist/cjs/nodes/analytics/analytics-track-outcome.d.ts +3 -0
- package/dist/cjs/nodes/analytics/analytics-track-outcome.d.ts.map +1 -0
- package/dist/cjs/nodes/analytics/analytics-track-outcome.js +110 -0
- package/dist/cjs/nodes/analytics/analytics-track-outcome.js.map +1 -0
- package/dist/cjs/nodes/cart/cart-price-order.d.ts.map +1 -1
- package/dist/cjs/nodes/cart/cart-price-order.js +161 -2
- package/dist/cjs/nodes/cart/cart-price-order.js.map +1 -1
- package/dist/cjs/nodes/cost-bearing-node-types.d.ts +4 -0
- package/dist/cjs/nodes/cost-bearing-node-types.d.ts.map +1 -0
- package/dist/cjs/nodes/cost-bearing-node-types.js +43 -0
- package/dist/cjs/nodes/cost-bearing-node-types.js.map +1 -0
- package/dist/cjs/nodes/data/data-select.d.ts.map +1 -1
- package/dist/cjs/nodes/data/data-select.js +12 -2
- package/dist/cjs/nodes/data/data-select.js.map +1 -1
- package/dist/cjs/nodes/email/email-mailersend.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-mailersend.js +15 -11
- package/dist/cjs/nodes/email/email-mailersend.js.map +1 -1
- package/dist/cjs/nodes/email/email-mailgun.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-mailgun.js +15 -15
- package/dist/cjs/nodes/email/email-mailgun.js.map +1 -1
- package/dist/cjs/nodes/email/email-postmark.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-postmark.js +15 -15
- package/dist/cjs/nodes/email/email-postmark.js.map +1 -1
- package/dist/cjs/nodes/email/email-resend.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-resend.js +15 -15
- package/dist/cjs/nodes/email/email-resend.js.map +1 -1
- package/dist/cjs/nodes/email/email-sendgrid.d.ts.map +1 -1
- package/dist/cjs/nodes/email/email-sendgrid.js +15 -15
- package/dist/cjs/nodes/email/email-sendgrid.js.map +1 -1
- package/dist/cjs/nodes/email/html-escape-code.d.ts +3 -0
- package/dist/cjs/nodes/email/html-escape-code.d.ts.map +1 -0
- package/dist/cjs/nodes/email/html-escape-code.js +23 -0
- package/dist/cjs/nodes/email/html-escape-code.js.map +1 -0
- package/dist/cjs/nodes/email/message-trust.d.ts +40 -0
- package/dist/cjs/nodes/email/message-trust.d.ts.map +1 -0
- package/dist/cjs/nodes/email/message-trust.js +167 -0
- package/dist/cjs/nodes/email/message-trust.js.map +1 -0
- package/dist/cjs/nodes/email/outbound-message-guard.d.ts +113 -0
- package/dist/cjs/nodes/email/outbound-message-guard.d.ts.map +1 -0
- package/dist/cjs/nodes/email/outbound-message-guard.js +460 -0
- package/dist/cjs/nodes/email/outbound-message-guard.js.map +1 -0
- package/dist/cjs/nodes/email/trusted-html-code.d.ts +22 -0
- package/dist/cjs/nodes/email/trusted-html-code.d.ts.map +1 -0
- package/dist/cjs/nodes/email/trusted-html-code.js +52 -0
- package/dist/cjs/nodes/email/trusted-html-code.js.map +1 -0
- package/dist/cjs/nodes/general/general-custom-js.d.ts.map +1 -1
- package/dist/cjs/nodes/general/general-custom-js.js +44 -7
- package/dist/cjs/nodes/general/general-custom-js.js.map +1 -1
- package/dist/cjs/nodes/general/general-http-request.d.ts.map +1 -1
- package/dist/cjs/nodes/general/general-http-request.js +431 -45
- package/dist/cjs/nodes/general/general-http-request.js.map +1 -1
- package/dist/cjs/nodes/general/general-rate-limiter.d.ts.map +1 -1
- package/dist/cjs/nodes/general/general-rate-limiter.js +46 -78
- package/dist/cjs/nodes/general/general-rate-limiter.js.map +1 -1
- package/dist/cjs/nodes/general/general-wait.d.ts +3 -0
- package/dist/cjs/nodes/general/general-wait.d.ts.map +1 -0
- package/dist/cjs/nodes/general/general-wait.js +81 -0
- package/dist/cjs/nodes/general/general-wait.js.map +1 -0
- package/dist/cjs/nodes/general/visitor-request.d.ts +44 -0
- package/dist/cjs/nodes/general/visitor-request.d.ts.map +1 -0
- package/dist/cjs/nodes/general/visitor-request.js +72 -0
- package/dist/cjs/nodes/general/visitor-request.js.map +1 -0
- package/dist/cjs/nodes/index.d.ts.map +1 -1
- package/dist/cjs/nodes/index.js +7 -1
- package/dist/cjs/nodes/index.js.map +1 -1
- package/dist/cjs/nodes/integrations/integration-slack.d.ts.map +1 -1
- package/dist/cjs/nodes/integrations/integration-slack.js +17 -6
- package/dist/cjs/nodes/integrations/integration-slack.js.map +1 -1
- package/dist/cjs/nodes/newsletter/newsletter-confirm.d.ts +3 -0
- package/dist/cjs/nodes/newsletter/newsletter-confirm.d.ts.map +1 -0
- package/dist/cjs/nodes/newsletter/newsletter-confirm.js +41 -0
- package/dist/cjs/nodes/newsletter/newsletter-confirm.js.map +1 -0
- package/dist/cjs/nodes/newsletter/newsletter-subscribe.d.ts +7 -0
- package/dist/cjs/nodes/newsletter/newsletter-subscribe.d.ts.map +1 -0
- package/dist/cjs/nodes/newsletter/newsletter-subscribe.js +69 -0
- package/dist/cjs/nodes/newsletter/newsletter-subscribe.js.map +1 -0
- package/dist/cjs/nodes/newsletter/newsletter-unsubscribe.d.ts +3 -0
- package/dist/cjs/nodes/newsletter/newsletter-unsubscribe.d.ts.map +1 -0
- package/dist/cjs/nodes/newsletter/newsletter-unsubscribe.js +42 -0
- package/dist/cjs/nodes/newsletter/newsletter-unsubscribe.js.map +1 -0
- package/dist/cjs/nodes/realtime/realtime-join-channel.d.ts.map +1 -1
- package/dist/cjs/nodes/realtime/realtime-join-channel.js +5 -4
- package/dist/cjs/nodes/realtime/realtime-join-channel.js.map +1 -1
- package/dist/cjs/nodes/realtime/realtime-send-channel-event.d.ts.map +1 -1
- package/dist/cjs/nodes/realtime/realtime-send-channel-event.js +11 -2
- package/dist/cjs/nodes/realtime/realtime-send-channel-event.js.map +1 -1
- package/dist/cjs/nodes/realtime/realtime-send-channel-message.d.ts.map +1 -1
- package/dist/cjs/nodes/realtime/realtime-send-channel-message.js +11 -2
- package/dist/cjs/nodes/realtime/realtime-send-channel-message.js.map +1 -1
- package/dist/cjs/nodes/sms/sms-infobip.d.ts.map +1 -1
- package/dist/cjs/nodes/sms/sms-infobip.js +11 -8
- package/dist/cjs/nodes/sms/sms-infobip.js.map +1 -1
- package/dist/cjs/nodes/sms/sms-smsapi.d.ts.map +1 -1
- package/dist/cjs/nodes/sms/sms-smsapi.js +11 -8
- package/dist/cjs/nodes/sms/sms-smsapi.js.map +1 -1
- package/dist/cjs/nodes/sms/sms-textmagic.d.ts.map +1 -1
- package/dist/cjs/nodes/sms/sms-textmagic.js +11 -8
- package/dist/cjs/nodes/sms/sms-textmagic.js.map +1 -1
- package/dist/cjs/nodes/sms/sms-twilio.d.ts.map +1 -1
- package/dist/cjs/nodes/sms/sms-twilio.js +12 -10
- package/dist/cjs/nodes/sms/sms-twilio.js.map +1 -1
- package/dist/cjs/nodes/types.d.ts +8 -1
- package/dist/cjs/nodes/types.d.ts.map +1 -1
- package/dist/cjs/nodes/utility/utility-similarity-scoring.d.ts.map +1 -1
- package/dist/cjs/nodes/utility/utility-similarity-scoring.js +29 -10
- package/dist/cjs/nodes/utility/utility-similarity-scoring.js.map +1 -1
- package/dist/cjs/page-access-guard-generator.d.ts +23 -0
- package/dist/cjs/page-access-guard-generator.d.ts.map +1 -0
- package/dist/cjs/page-access-guard-generator.js +135 -0
- package/dist/cjs/page-access-guard-generator.js.map +1 -0
- package/dist/cjs/password-reset-route-generator.d.ts +119 -0
- package/dist/cjs/password-reset-route-generator.d.ts.map +1 -0
- package/dist/cjs/password-reset-route-generator.js +460 -0
- package/dist/cjs/password-reset-route-generator.js.map +1 -0
- package/dist/cjs/payments/payment-core-code.d.ts.map +1 -1
- package/dist/cjs/payments/payment-core-code.js +12 -7
- package/dist/cjs/payments/payment-core-code.js.map +1 -1
- package/dist/cjs/project-code-scanner.d.ts +11 -0
- package/dist/cjs/project-code-scanner.d.ts.map +1 -0
- package/dist/cjs/project-code-scanner.js +80 -0
- package/dist/cjs/project-code-scanner.js.map +1 -0
- package/dist/cjs/public-step-guards.d.ts +122 -0
- package/dist/cjs/public-step-guards.d.ts.map +1 -0
- package/dist/cjs/public-step-guards.js +288 -0
- package/dist/cjs/public-step-guards.js.map +1 -0
- package/dist/cjs/publish-refusals.d.ts +66 -0
- package/dist/cjs/publish-refusals.d.ts.map +1 -0
- package/dist/cjs/publish-refusals.js +278 -0
- package/dist/cjs/publish-refusals.js.map +1 -0
- package/dist/cjs/realtime-generator.d.ts +63 -1
- package/dist/cjs/realtime-generator.d.ts.map +1 -1
- package/dist/cjs/realtime-generator.js +404 -230
- package/dist/cjs/realtime-generator.js.map +1 -1
- package/dist/cjs/runtime-storage-generator.d.ts +32 -1
- package/dist/cjs/runtime-storage-generator.d.ts.map +1 -1
- package/dist/cjs/runtime-storage-generator.js +265 -12
- package/dist/cjs/runtime-storage-generator.js.map +1 -1
- package/dist/cjs/security-runtime/index.d.ts +14 -0
- package/dist/cjs/security-runtime/index.d.ts.map +1 -0
- package/dist/cjs/security-runtime/index.js +48 -0
- package/dist/cjs/security-runtime/index.js.map +1 -0
- package/dist/cjs/security-runtime/security-runtime-code.d.ts +53 -0
- package/dist/cjs/security-runtime/security-runtime-code.d.ts.map +1 -0
- package/dist/cjs/security-runtime/security-runtime-code.js +580 -0
- package/dist/cjs/security-runtime/security-runtime-code.js.map +1 -0
- package/dist/cjs/security-scanner.d.ts +72 -3
- package/dist/cjs/security-scanner.d.ts.map +1 -1
- package/dist/cjs/security-scanner.js +220 -8
- package/dist/cjs/security-scanner.js.map +1 -1
- package/dist/cjs/segment-splitter.d.ts.map +1 -1
- package/dist/cjs/segment-splitter.js +10 -2
- package/dist/cjs/segment-splitter.js.map +1 -1
- package/dist/cjs/segment-trust.d.ts +36 -0
- package/dist/cjs/segment-trust.d.ts.map +1 -1
- package/dist/cjs/segment-trust.js +147 -3
- package/dist/cjs/segment-trust.js.map +1 -1
- package/dist/cjs/self-call-headers.d.ts +31 -0
- package/dist/cjs/self-call-headers.d.ts.map +1 -0
- package/dist/cjs/self-call-headers.js +48 -0
- package/dist/cjs/self-call-headers.js.map +1 -0
- package/dist/cjs/sent-email-log/index.d.ts +7 -0
- package/dist/cjs/sent-email-log/index.d.ts.map +1 -1
- package/dist/cjs/sent-email-log/index.js +17 -1
- package/dist/cjs/sent-email-log/index.js.map +1 -1
- package/dist/cjs/sent-email-log/sent-email-log-code.d.ts +7 -0
- package/dist/cjs/sent-email-log/sent-email-log-code.d.ts.map +1 -1
- package/dist/cjs/sent-email-log/sent-email-log-code.js +17 -3
- package/dist/cjs/sent-email-log/sent-email-log-code.js.map +1 -1
- package/dist/cjs/server-custom-node-call.d.ts +79 -0
- package/dist/cjs/server-custom-node-call.d.ts.map +1 -0
- package/dist/cjs/server-custom-node-call.js +285 -0
- package/dist/cjs/server-custom-node-call.js.map +1 -0
- package/dist/cjs/server-runtime-code.d.ts +39 -12
- package/dist/cjs/server-runtime-code.d.ts.map +1 -1
- package/dist/cjs/server-runtime-code.js +484 -41
- package/dist/cjs/server-runtime-code.js.map +1 -1
- package/dist/cjs/session-cookie-resolver.d.ts +3 -1
- package/dist/cjs/session-cookie-resolver.d.ts.map +1 -1
- package/dist/cjs/session-revocation-generator.d.ts +39 -0
- package/dist/cjs/session-revocation-generator.d.ts.map +1 -0
- package/dist/cjs/session-revocation-generator.js +177 -0
- package/dist/cjs/session-revocation-generator.js.map +1 -0
- package/dist/cjs/slack-message-values.d.ts +6 -0
- package/dist/cjs/slack-message-values.d.ts.map +1 -0
- package/dist/cjs/slack-message-values.js +73 -0
- package/dist/cjs/slack-message-values.js.map +1 -0
- package/dist/cjs/transactional-email-code.d.ts +11 -0
- package/dist/cjs/transactional-email-code.d.ts.map +1 -1
- package/dist/cjs/transactional-email-code.js +29 -5
- package/dist/cjs/transactional-email-code.js.map +1 -1
- package/dist/cjs/trigger-generator.d.ts +2 -0
- package/dist/cjs/trigger-generator.d.ts.map +1 -1
- package/dist/cjs/trigger-generator.js +17 -4
- package/dist/cjs/trigger-generator.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/visitor-step-limits.d.ts +66 -0
- package/dist/cjs/visitor-step-limits.d.ts.map +1 -0
- package/dist/cjs/visitor-step-limits.js +187 -0
- package/dist/cjs/visitor-step-limits.js.map +1 -0
- package/dist/cjs/webhook-generator.d.ts.map +1 -1
- package/dist/cjs/webhook-generator.js +93 -15
- package/dist/cjs/webhook-generator.js.map +1 -1
- package/dist/cjs/webhook-signature-verification.d.ts.map +1 -1
- package/dist/cjs/webhook-signature-verification.js +13 -3
- package/dist/cjs/webhook-signature-verification.js.map +1 -1
- package/dist/cjs/workflow-auth-generator.d.ts +4 -1
- package/dist/cjs/workflow-auth-generator.d.ts.map +1 -1
- package/dist/cjs/workflow-auth-generator.js +8 -3
- package/dist/cjs/workflow-auth-generator.js.map +1 -1
- package/dist/cjs/workflow-component-plugin.js +11 -3
- package/dist/cjs/workflow-component-plugin.js.map +1 -1
- package/dist/cjs/workflow-project-plugin.d.ts.map +1 -1
- package/dist/cjs/workflow-project-plugin.js +302 -56
- package/dist/cjs/workflow-project-plugin.js.map +1 -1
- package/dist/cjs/workflow-scope.d.ts.map +1 -1
- package/dist/cjs/workflow-scope.js +5 -0
- package/dist/cjs/workflow-scope.js.map +1 -1
- package/dist/esm/account-delete-route-generator.d.ts +1 -0
- package/dist/esm/account-delete-route-generator.d.ts.map +1 -1
- package/dist/esm/account-delete-route-generator.js +18 -3
- package/dist/esm/account-delete-route-generator.js.map +1 -1
- package/dist/esm/api-route-generator.d.ts +52 -3
- package/dist/esm/api-route-generator.d.ts.map +1 -1
- package/dist/esm/api-route-generator.js +595 -142
- package/dist/esm/api-route-generator.js.map +1 -1
- package/dist/esm/auth-generator.d.ts +126 -3
- package/dist/esm/auth-generator.d.ts.map +1 -1
- package/dist/esm/auth-generator.js +1350 -140
- package/dist/esm/auth-generator.js.map +1 -1
- package/dist/esm/automations/activity-kicks.d.ts +12 -0
- package/dist/esm/automations/activity-kicks.d.ts.map +1 -0
- package/dist/esm/automations/activity-kicks.js +118 -0
- package/dist/esm/automations/activity-kicks.js.map +1 -0
- package/dist/esm/automations/app-events-source.d.ts +33 -0
- package/dist/esm/automations/app-events-source.d.ts.map +1 -0
- package/dist/esm/automations/app-events-source.js +104 -0
- package/dist/esm/automations/app-events-source.js.map +1 -0
- package/dist/esm/automations/automations-project-plugin.d.ts +21 -0
- package/dist/esm/automations/automations-project-plugin.d.ts.map +1 -0
- package/dist/esm/automations/automations-project-plugin.js +98 -0
- package/dist/esm/automations/automations-project-plugin.js.map +1 -0
- package/dist/esm/automations/automations-scope.d.ts +104 -0
- package/dist/esm/automations/automations-scope.d.ts.map +1 -0
- package/dist/esm/automations/automations-scope.js +165 -0
- package/dist/esm/automations/automations-scope.js.map +1 -0
- package/dist/esm/automations/dispatcher-source.d.ts +72 -0
- package/dist/esm/automations/dispatcher-source.d.ts.map +1 -0
- package/dist/esm/automations/dispatcher-source.js +999 -0
- package/dist/esm/automations/dispatcher-source.js.map +1 -0
- package/dist/esm/automations/event-bus-source.d.ts +44 -0
- package/dist/esm/automations/event-bus-source.d.ts.map +1 -0
- package/dist/esm/automations/event-bus-source.js +421 -0
- package/dist/esm/automations/event-bus-source.js.map +1 -0
- package/dist/esm/automations/handler-module-generator.d.ts +25 -0
- package/dist/esm/automations/handler-module-generator.d.ts.map +1 -0
- package/dist/esm/automations/handler-module-generator.js +71 -0
- package/dist/esm/automations/handler-module-generator.js.map +1 -0
- package/dist/esm/automations/hydrators-source.d.ts +33 -0
- package/dist/esm/automations/hydrators-source.d.ts.map +1 -0
- package/dist/esm/automations/hydrators-source.js +567 -0
- package/dist/esm/automations/hydrators-source.js.map +1 -0
- package/dist/esm/booking-calendar-feed-route-generator.d.ts.map +1 -1
- package/dist/esm/booking-calendar-feed-route-generator.js +8 -0
- package/dist/esm/booking-calendar-feed-route-generator.js.map +1 -1
- package/dist/esm/configured-nextauth-url.d.ts +17 -0
- package/dist/esm/configured-nextauth-url.d.ts.map +1 -0
- package/dist/esm/configured-nextauth-url.js +17 -0
- package/dist/esm/configured-nextauth-url.js.map +1 -0
- package/dist/esm/cron-request-verification.d.ts +22 -1
- package/dist/esm/cron-request-verification.d.ts.map +1 -1
- package/dist/esm/cron-request-verification.js +48 -5
- package/dist/esm/cron-request-verification.js.map +1 -1
- package/dist/esm/data-api-route-generator.d.ts +19 -0
- package/dist/esm/data-api-route-generator.d.ts.map +1 -1
- package/dist/esm/data-api-route-generator.js +710 -43
- package/dist/esm/data-api-route-generator.js.map +1 -1
- package/dist/esm/ecommerce/parameterized-stock-chain.d.ts +3 -1
- package/dist/esm/ecommerce/parameterized-stock-chain.d.ts.map +1 -1
- package/dist/esm/ecommerce/parameterized-stock-chain.js +43 -4
- package/dist/esm/ecommerce/parameterized-stock-chain.js.map +1 -1
- package/dist/esm/ecommerce/place-order-guards.d.ts +126 -0
- package/dist/esm/ecommerce/place-order-guards.d.ts.map +1 -0
- package/dist/esm/ecommerce/place-order-guards.js +655 -0
- package/dist/esm/ecommerce/place-order-guards.js.map +1 -0
- package/dist/esm/ecommerce-customhandler-rewriter.d.ts +2 -0
- package/dist/esm/ecommerce-customhandler-rewriter.d.ts.map +1 -1
- package/dist/esm/ecommerce-customhandler-rewriter.js +38 -2
- package/dist/esm/ecommerce-customhandler-rewriter.js.map +1 -1
- package/dist/esm/executor-generator.d.ts.map +1 -1
- package/dist/esm/executor-generator.js +368 -60
- package/dist/esm/executor-generator.js.map +1 -1
- package/dist/esm/graph-utils.d.ts +9 -0
- package/dist/esm/graph-utils.d.ts.map +1 -1
- package/dist/esm/graph-utils.js +30 -0
- package/dist/esm/graph-utils.js.map +1 -1
- package/dist/esm/index.d.ts +20 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +27 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/invoice/api-routes-code.d.ts +9 -3
- package/dist/esm/invoice/api-routes-code.d.ts.map +1 -1
- package/dist/esm/invoice/api-routes-code.js +9 -7
- package/dist/esm/invoice/api-routes-code.js.map +1 -1
- package/dist/esm/invoice/index.d.ts.map +1 -1
- package/dist/esm/invoice/index.js +12 -7
- package/dist/esm/invoice/index.js.map +1 -1
- package/dist/esm/invoice/invoice-assembly-code.d.ts.map +1 -1
- package/dist/esm/invoice/invoice-assembly-code.js +8 -3
- package/dist/esm/invoice/invoice-assembly-code.js.map +1 -1
- package/dist/esm/invoice/pdf-generator-code.d.ts.map +1 -1
- package/dist/esm/invoice/pdf-generator-code.js +2 -1
- package/dist/esm/invoice/pdf-generator-code.js.map +1 -1
- package/dist/esm/invoice/pdf-service-client-code.d.ts.map +1 -1
- package/dist/esm/invoice/pdf-service-client-code.js +15 -7
- package/dist/esm/invoice/pdf-service-client-code.js.map +1 -1
- package/dist/esm/newsletter/admin-route-code.d.ts +60 -0
- package/dist/esm/newsletter/admin-route-code.d.ts.map +1 -0
- package/dist/esm/newsletter/admin-route-code.js +295 -0
- package/dist/esm/newsletter/admin-route-code.js.map +1 -0
- package/dist/esm/newsletter/helper-code.d.ts +109 -0
- package/dist/esm/newsletter/helper-code.d.ts.map +1 -0
- package/dist/esm/newsletter/helper-code.js +656 -0
- package/dist/esm/newsletter/helper-code.js.map +1 -0
- package/dist/esm/newsletter/newsletter-scope.d.ts +65 -0
- package/dist/esm/newsletter/newsletter-scope.d.ts.map +1 -0
- package/dist/esm/newsletter/newsletter-scope.js +97 -0
- package/dist/esm/newsletter/newsletter-scope.js.map +1 -0
- package/dist/esm/newsletter/project-plugin.d.ts +18 -0
- package/dist/esm/newsletter/project-plugin.d.ts.map +1 -0
- package/dist/esm/newsletter/project-plugin.js +93 -0
- package/dist/esm/newsletter/project-plugin.js.map +1 -0
- package/dist/esm/newsletter/routes-code.d.ts +6 -0
- package/dist/esm/newsletter/routes-code.d.ts.map +1 -0
- package/dist/esm/newsletter/routes-code.js +175 -0
- package/dist/esm/newsletter/routes-code.js.map +1 -0
- package/dist/esm/nodes/account/account-compare-passwords.d.ts.map +1 -1
- package/dist/esm/nodes/account/account-compare-passwords.js +16 -6
- package/dist/esm/nodes/account/account-compare-passwords.js.map +1 -1
- package/dist/esm/nodes/account/account-delete-current.js +1 -1
- package/dist/esm/nodes/account/account-get-current.js +1 -1
- package/dist/esm/nodes/account/account-hash-password.d.ts.map +1 -1
- package/dist/esm/nodes/account/account-hash-password.js +15 -3
- package/dist/esm/nodes/account/account-hash-password.js.map +1 -1
- package/dist/esm/nodes/account/account-login.d.ts.map +1 -1
- package/dist/esm/nodes/account/account-login.js +23 -6
- package/dist/esm/nodes/account/account-login.js.map +1 -1
- package/dist/esm/nodes/account/account-logout.js +1 -1
- package/dist/esm/nodes/account/account-refresh-session.js +1 -1
- package/dist/esm/nodes/account/account-request-password-reset.d.ts +3 -0
- package/dist/esm/nodes/account/account-request-password-reset.d.ts.map +1 -0
- package/dist/esm/nodes/account/account-request-password-reset.js +43 -0
- package/dist/esm/nodes/account/account-request-password-reset.js.map +1 -0
- package/dist/esm/nodes/account/account-reset-password.d.ts +3 -0
- package/dist/esm/nodes/account/account-reset-password.d.ts.map +1 -0
- package/dist/esm/nodes/account/account-reset-password.js +41 -0
- package/dist/esm/nodes/account/account-reset-password.js.map +1 -0
- package/dist/esm/nodes/account/account-signup.d.ts.map +1 -1
- package/dist/esm/nodes/account/account-signup.js +82 -19
- package/dist/esm/nodes/account/account-signup.js.map +1 -1
- package/dist/esm/nodes/account/account-social-login.js +1 -1
- package/dist/esm/nodes/ai/ai-custom-prompt.d.ts.map +1 -1
- package/dist/esm/nodes/ai/ai-custom-prompt.js +20 -5
- package/dist/esm/nodes/ai/ai-custom-prompt.js.map +1 -1
- package/dist/esm/nodes/ai/ai-detect-language.d.ts.map +1 -1
- package/dist/esm/nodes/ai/ai-detect-language.js +20 -5
- package/dist/esm/nodes/ai/ai-detect-language.js.map +1 -1
- package/dist/esm/nodes/ai/ai-generate-text-embedding.d.ts.map +1 -1
- package/dist/esm/nodes/ai/ai-generate-text-embedding.js +10 -2
- package/dist/esm/nodes/ai/ai-generate-text-embedding.js.map +1 -1
- package/dist/esm/nodes/ai/ai-provider-utils-sources.generated.js +2 -2
- package/dist/esm/nodes/ai/ai-provider-utils-sources.generated.js.map +1 -1
- package/dist/esm/nodes/ai/ai-provider-utils.d.ts +31 -2
- package/dist/esm/nodes/ai/ai-provider-utils.d.ts.map +1 -1
- package/dist/esm/nodes/ai/ai-provider-utils.js +124 -10
- package/dist/esm/nodes/ai/ai-provider-utils.js.map +1 -1
- package/dist/esm/nodes/ai/ai-select-database-data.d.ts.map +1 -1
- package/dist/esm/nodes/ai/ai-select-database-data.js +6 -2
- package/dist/esm/nodes/ai/ai-select-database-data.js.map +1 -1
- package/dist/esm/nodes/ai/ai-sentiment-analysis.d.ts.map +1 -1
- package/dist/esm/nodes/ai/ai-sentiment-analysis.js +20 -5
- package/dist/esm/nodes/ai/ai-sentiment-analysis.js.map +1 -1
- package/dist/esm/nodes/ai/ai-summarization.d.ts.map +1 -1
- package/dist/esm/nodes/ai/ai-summarization.js +20 -5
- package/dist/esm/nodes/ai/ai-summarization.js.map +1 -1
- package/dist/esm/nodes/ai/ai-text-classifier.d.ts.map +1 -1
- package/dist/esm/nodes/ai/ai-text-classifier.js +20 -5
- package/dist/esm/nodes/ai/ai-text-classifier.js.map +1 -1
- package/dist/esm/nodes/ai/ai-text-transform.d.ts.map +1 -1
- package/dist/esm/nodes/ai/ai-text-transform.js +20 -5
- package/dist/esm/nodes/ai/ai-text-transform.js.map +1 -1
- package/dist/esm/nodes/analytics/analytics-track-outcome.d.ts +3 -0
- package/dist/esm/nodes/analytics/analytics-track-outcome.d.ts.map +1 -0
- package/dist/esm/nodes/analytics/analytics-track-outcome.js +107 -0
- package/dist/esm/nodes/analytics/analytics-track-outcome.js.map +1 -0
- package/dist/esm/nodes/cart/cart-price-order.d.ts.map +1 -1
- package/dist/esm/nodes/cart/cart-price-order.js +161 -2
- package/dist/esm/nodes/cart/cart-price-order.js.map +1 -1
- package/dist/esm/nodes/cost-bearing-node-types.d.ts +4 -0
- package/dist/esm/nodes/cost-bearing-node-types.d.ts.map +1 -0
- package/dist/esm/nodes/cost-bearing-node-types.js +39 -0
- package/dist/esm/nodes/cost-bearing-node-types.js.map +1 -0
- package/dist/esm/nodes/data/data-select.d.ts.map +1 -1
- package/dist/esm/nodes/data/data-select.js +12 -2
- package/dist/esm/nodes/data/data-select.js.map +1 -1
- package/dist/esm/nodes/email/email-mailersend.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-mailersend.js +15 -11
- package/dist/esm/nodes/email/email-mailersend.js.map +1 -1
- package/dist/esm/nodes/email/email-mailgun.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-mailgun.js +15 -15
- package/dist/esm/nodes/email/email-mailgun.js.map +1 -1
- package/dist/esm/nodes/email/email-postmark.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-postmark.js +15 -15
- package/dist/esm/nodes/email/email-postmark.js.map +1 -1
- package/dist/esm/nodes/email/email-resend.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-resend.js +15 -15
- package/dist/esm/nodes/email/email-resend.js.map +1 -1
- package/dist/esm/nodes/email/email-sendgrid.d.ts.map +1 -1
- package/dist/esm/nodes/email/email-sendgrid.js +15 -15
- package/dist/esm/nodes/email/email-sendgrid.js.map +1 -1
- package/dist/esm/nodes/email/html-escape-code.d.ts +3 -0
- package/dist/esm/nodes/email/html-escape-code.d.ts.map +1 -0
- package/dist/esm/nodes/email/html-escape-code.js +18 -0
- package/dist/esm/nodes/email/html-escape-code.js.map +1 -0
- package/dist/esm/nodes/email/message-trust.d.ts +40 -0
- package/dist/esm/nodes/email/message-trust.d.ts.map +1 -0
- package/dist/esm/nodes/email/message-trust.js +161 -0
- package/dist/esm/nodes/email/message-trust.js.map +1 -0
- package/dist/esm/nodes/email/outbound-message-guard.d.ts +113 -0
- package/dist/esm/nodes/email/outbound-message-guard.d.ts.map +1 -0
- package/dist/esm/nodes/email/outbound-message-guard.js +452 -0
- package/dist/esm/nodes/email/outbound-message-guard.js.map +1 -0
- package/dist/esm/nodes/email/trusted-html-code.d.ts +22 -0
- package/dist/esm/nodes/email/trusted-html-code.d.ts.map +1 -0
- package/dist/esm/nodes/email/trusted-html-code.js +49 -0
- package/dist/esm/nodes/email/trusted-html-code.js.map +1 -0
- package/dist/esm/nodes/general/general-custom-js.d.ts.map +1 -1
- package/dist/esm/nodes/general/general-custom-js.js +44 -7
- package/dist/esm/nodes/general/general-custom-js.js.map +1 -1
- package/dist/esm/nodes/general/general-http-request.d.ts.map +1 -1
- package/dist/esm/nodes/general/general-http-request.js +431 -45
- package/dist/esm/nodes/general/general-http-request.js.map +1 -1
- package/dist/esm/nodes/general/general-rate-limiter.d.ts.map +1 -1
- package/dist/esm/nodes/general/general-rate-limiter.js +46 -78
- package/dist/esm/nodes/general/general-rate-limiter.js.map +1 -1
- package/dist/esm/nodes/general/general-wait.d.ts +3 -0
- package/dist/esm/nodes/general/general-wait.d.ts.map +1 -0
- package/dist/esm/nodes/general/general-wait.js +78 -0
- package/dist/esm/nodes/general/general-wait.js.map +1 -0
- package/dist/esm/nodes/general/visitor-request.d.ts +44 -0
- package/dist/esm/nodes/general/visitor-request.d.ts.map +1 -0
- package/dist/esm/nodes/general/visitor-request.js +65 -0
- package/dist/esm/nodes/general/visitor-request.js.map +1 -0
- package/dist/esm/nodes/index.d.ts.map +1 -1
- package/dist/esm/nodes/index.js +7 -1
- package/dist/esm/nodes/index.js.map +1 -1
- package/dist/esm/nodes/integrations/integration-slack.d.ts.map +1 -1
- package/dist/esm/nodes/integrations/integration-slack.js +17 -6
- package/dist/esm/nodes/integrations/integration-slack.js.map +1 -1
- package/dist/esm/nodes/newsletter/newsletter-confirm.d.ts +3 -0
- package/dist/esm/nodes/newsletter/newsletter-confirm.d.ts.map +1 -0
- package/dist/esm/nodes/newsletter/newsletter-confirm.js +38 -0
- package/dist/esm/nodes/newsletter/newsletter-confirm.js.map +1 -0
- package/dist/esm/nodes/newsletter/newsletter-subscribe.d.ts +7 -0
- package/dist/esm/nodes/newsletter/newsletter-subscribe.d.ts.map +1 -0
- package/dist/esm/nodes/newsletter/newsletter-subscribe.js +66 -0
- package/dist/esm/nodes/newsletter/newsletter-subscribe.js.map +1 -0
- package/dist/esm/nodes/newsletter/newsletter-unsubscribe.d.ts +3 -0
- package/dist/esm/nodes/newsletter/newsletter-unsubscribe.d.ts.map +1 -0
- package/dist/esm/nodes/newsletter/newsletter-unsubscribe.js +39 -0
- package/dist/esm/nodes/newsletter/newsletter-unsubscribe.js.map +1 -0
- package/dist/esm/nodes/realtime/realtime-join-channel.d.ts.map +1 -1
- package/dist/esm/nodes/realtime/realtime-join-channel.js +5 -4
- package/dist/esm/nodes/realtime/realtime-join-channel.js.map +1 -1
- package/dist/esm/nodes/realtime/realtime-send-channel-event.d.ts.map +1 -1
- package/dist/esm/nodes/realtime/realtime-send-channel-event.js +11 -2
- package/dist/esm/nodes/realtime/realtime-send-channel-event.js.map +1 -1
- package/dist/esm/nodes/realtime/realtime-send-channel-message.d.ts.map +1 -1
- package/dist/esm/nodes/realtime/realtime-send-channel-message.js +11 -2
- package/dist/esm/nodes/realtime/realtime-send-channel-message.js.map +1 -1
- package/dist/esm/nodes/sms/sms-infobip.d.ts.map +1 -1
- package/dist/esm/nodes/sms/sms-infobip.js +11 -8
- package/dist/esm/nodes/sms/sms-infobip.js.map +1 -1
- package/dist/esm/nodes/sms/sms-smsapi.d.ts.map +1 -1
- package/dist/esm/nodes/sms/sms-smsapi.js +11 -8
- package/dist/esm/nodes/sms/sms-smsapi.js.map +1 -1
- package/dist/esm/nodes/sms/sms-textmagic.d.ts.map +1 -1
- package/dist/esm/nodes/sms/sms-textmagic.js +11 -8
- package/dist/esm/nodes/sms/sms-textmagic.js.map +1 -1
- package/dist/esm/nodes/sms/sms-twilio.d.ts.map +1 -1
- package/dist/esm/nodes/sms/sms-twilio.js +12 -10
- package/dist/esm/nodes/sms/sms-twilio.js.map +1 -1
- package/dist/esm/nodes/types.d.ts +8 -1
- package/dist/esm/nodes/types.d.ts.map +1 -1
- package/dist/esm/nodes/utility/utility-similarity-scoring.d.ts.map +1 -1
- package/dist/esm/nodes/utility/utility-similarity-scoring.js +29 -10
- package/dist/esm/nodes/utility/utility-similarity-scoring.js.map +1 -1
- package/dist/esm/page-access-guard-generator.d.ts +23 -0
- package/dist/esm/page-access-guard-generator.d.ts.map +1 -0
- package/dist/esm/page-access-guard-generator.js +131 -0
- package/dist/esm/page-access-guard-generator.js.map +1 -0
- package/dist/esm/password-reset-route-generator.d.ts +119 -0
- package/dist/esm/password-reset-route-generator.d.ts.map +1 -0
- package/dist/esm/password-reset-route-generator.js +448 -0
- package/dist/esm/password-reset-route-generator.js.map +1 -0
- package/dist/esm/payments/payment-core-code.d.ts.map +1 -1
- package/dist/esm/payments/payment-core-code.js +12 -7
- package/dist/esm/payments/payment-core-code.js.map +1 -1
- package/dist/esm/project-code-scanner.d.ts +11 -0
- package/dist/esm/project-code-scanner.d.ts.map +1 -0
- package/dist/esm/project-code-scanner.js +75 -0
- package/dist/esm/project-code-scanner.js.map +1 -0
- package/dist/esm/public-step-guards.d.ts +122 -0
- package/dist/esm/public-step-guards.d.ts.map +1 -0
- package/dist/esm/public-step-guards.js +280 -0
- package/dist/esm/public-step-guards.js.map +1 -0
- package/dist/esm/publish-refusals.d.ts +66 -0
- package/dist/esm/publish-refusals.d.ts.map +1 -0
- package/dist/esm/publish-refusals.js +271 -0
- package/dist/esm/publish-refusals.js.map +1 -0
- package/dist/esm/realtime-generator.d.ts +63 -1
- package/dist/esm/realtime-generator.d.ts.map +1 -1
- package/dist/esm/realtime-generator.js +401 -229
- package/dist/esm/realtime-generator.js.map +1 -1
- package/dist/esm/runtime-storage-generator.d.ts +32 -1
- package/dist/esm/runtime-storage-generator.d.ts.map +1 -1
- package/dist/esm/runtime-storage-generator.js +264 -11
- package/dist/esm/runtime-storage-generator.js.map +1 -1
- package/dist/esm/security-runtime/index.d.ts +14 -0
- package/dist/esm/security-runtime/index.d.ts.map +1 -0
- package/dist/esm/security-runtime/index.js +37 -0
- package/dist/esm/security-runtime/index.js.map +1 -0
- package/dist/esm/security-runtime/security-runtime-code.d.ts +53 -0
- package/dist/esm/security-runtime/security-runtime-code.d.ts.map +1 -0
- package/dist/esm/security-runtime/security-runtime-code.js +575 -0
- package/dist/esm/security-runtime/security-runtime-code.js.map +1 -0
- package/dist/esm/security-scanner.d.ts +72 -3
- package/dist/esm/security-scanner.d.ts.map +1 -1
- package/dist/esm/security-scanner.js +217 -8
- package/dist/esm/security-scanner.js.map +1 -1
- package/dist/esm/segment-splitter.d.ts.map +1 -1
- package/dist/esm/segment-splitter.js +10 -2
- package/dist/esm/segment-splitter.js.map +1 -1
- package/dist/esm/segment-trust.d.ts +36 -0
- package/dist/esm/segment-trust.d.ts.map +1 -1
- package/dist/esm/segment-trust.js +146 -2
- package/dist/esm/segment-trust.js.map +1 -1
- package/dist/esm/self-call-headers.d.ts +31 -0
- package/dist/esm/self-call-headers.d.ts.map +1 -0
- package/dist/esm/self-call-headers.js +43 -0
- package/dist/esm/self-call-headers.js.map +1 -0
- package/dist/esm/sent-email-log/index.d.ts +7 -0
- package/dist/esm/sent-email-log/index.d.ts.map +1 -1
- package/dist/esm/sent-email-log/index.js +15 -0
- package/dist/esm/sent-email-log/index.js.map +1 -1
- package/dist/esm/sent-email-log/sent-email-log-code.d.ts +7 -0
- package/dist/esm/sent-email-log/sent-email-log-code.d.ts.map +1 -1
- package/dist/esm/sent-email-log/sent-email-log-code.js +17 -3
- package/dist/esm/sent-email-log/sent-email-log-code.js.map +1 -1
- package/dist/esm/server-custom-node-call.d.ts +79 -0
- package/dist/esm/server-custom-node-call.d.ts.map +1 -0
- package/dist/esm/server-custom-node-call.js +278 -0
- package/dist/esm/server-custom-node-call.js.map +1 -0
- package/dist/esm/server-runtime-code.d.ts +39 -12
- package/dist/esm/server-runtime-code.d.ts.map +1 -1
- package/dist/esm/server-runtime-code.js +483 -40
- package/dist/esm/server-runtime-code.js.map +1 -1
- package/dist/esm/session-cookie-resolver.d.ts +3 -1
- package/dist/esm/session-cookie-resolver.d.ts.map +1 -1
- package/dist/esm/session-revocation-generator.d.ts +39 -0
- package/dist/esm/session-revocation-generator.d.ts.map +1 -0
- package/dist/esm/session-revocation-generator.js +172 -0
- package/dist/esm/session-revocation-generator.js.map +1 -0
- package/dist/esm/slack-message-values.d.ts +6 -0
- package/dist/esm/slack-message-values.d.ts.map +1 -0
- package/dist/esm/slack-message-values.js +68 -0
- package/dist/esm/slack-message-values.js.map +1 -0
- package/dist/esm/transactional-email-code.d.ts +11 -0
- package/dist/esm/transactional-email-code.d.ts.map +1 -1
- package/dist/esm/transactional-email-code.js +28 -4
- package/dist/esm/transactional-email-code.js.map +1 -1
- package/dist/esm/trigger-generator.d.ts +2 -0
- package/dist/esm/trigger-generator.d.ts.map +1 -1
- package/dist/esm/trigger-generator.js +15 -3
- package/dist/esm/trigger-generator.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/visitor-step-limits.d.ts +66 -0
- package/dist/esm/visitor-step-limits.d.ts.map +1 -0
- package/dist/esm/visitor-step-limits.js +181 -0
- package/dist/esm/visitor-step-limits.js.map +1 -0
- package/dist/esm/webhook-generator.d.ts.map +1 -1
- package/dist/esm/webhook-generator.js +93 -15
- package/dist/esm/webhook-generator.js.map +1 -1
- package/dist/esm/webhook-signature-verification.d.ts.map +1 -1
- package/dist/esm/webhook-signature-verification.js +13 -3
- package/dist/esm/webhook-signature-verification.js.map +1 -1
- package/dist/esm/workflow-auth-generator.d.ts +4 -1
- package/dist/esm/workflow-auth-generator.d.ts.map +1 -1
- package/dist/esm/workflow-auth-generator.js +8 -3
- package/dist/esm/workflow-auth-generator.js.map +1 -1
- package/dist/esm/workflow-component-plugin.js +12 -4
- package/dist/esm/workflow-component-plugin.js.map +1 -1
- package/dist/esm/workflow-project-plugin.d.ts.map +1 -1
- package/dist/esm/workflow-project-plugin.js +306 -60
- package/dist/esm/workflow-project-plugin.js.map +1 -1
- package/dist/esm/workflow-scope.d.ts.map +1 -1
- package/dist/esm/workflow-scope.js +5 -0
- package/dist/esm/workflow-scope.js.map +1 -1
- package/package.json +4 -4
- package/src/account-delete-route-generator.ts +28 -4
- package/src/api-route-generator.ts +712 -134
- package/src/auth-generator.ts +1471 -142
- package/src/automations/activity-kicks.ts +139 -0
- package/src/automations/app-events-source.ts +117 -0
- package/src/automations/automations-project-plugin.ts +202 -0
- package/src/automations/automations-scope.ts +196 -0
- package/src/automations/dispatcher-source.ts +1072 -0
- package/src/automations/event-bus-source.ts +428 -0
- package/src/automations/handler-module-generator.ts +107 -0
- package/src/automations/hydrators-source.ts +647 -0
- package/src/booking-calendar-feed-route-generator.ts +8 -0
- package/src/configured-nextauth-url.ts +17 -0
- package/src/cron-request-verification.ts +55 -5
- package/src/data-api-route-generator.ts +780 -43
- package/src/ecommerce/parameterized-stock-chain.ts +52 -5
- package/src/ecommerce/place-order-guards.ts +785 -0
- package/src/ecommerce-customhandler-rewriter.ts +55 -2
- package/src/executor-generator.ts +372 -60
- package/src/graph-utils.ts +33 -0
- package/src/index.ts +70 -0
- package/src/invoice/api-routes-code.ts +20 -7
- package/src/invoice/index.ts +11 -6
- package/src/invoice/invoice-assembly-code.ts +8 -3
- package/src/invoice/pdf-generator-code.ts +2 -1
- package/src/invoice/pdf-service-client-code.ts +15 -7
- package/src/newsletter/admin-route-code.ts +319 -0
- package/src/newsletter/helper-code.ts +748 -0
- package/src/newsletter/newsletter-scope.ts +116 -0
- package/src/newsletter/project-plugin.ts +119 -0
- package/src/newsletter/routes-code.ts +186 -0
- package/src/nodes/account/account-compare-passwords.ts +17 -6
- package/src/nodes/account/account-delete-current.ts +1 -1
- package/src/nodes/account/account-get-current.ts +1 -1
- package/src/nodes/account/account-hash-password.ts +19 -3
- package/src/nodes/account/account-login.ts +24 -7
- package/src/nodes/account/account-logout.ts +1 -1
- package/src/nodes/account/account-refresh-session.ts +1 -1
- package/src/nodes/account/account-request-password-reset.ts +47 -0
- package/src/nodes/account/account-reset-password.ts +45 -0
- package/src/nodes/account/account-signup.ts +86 -20
- package/src/nodes/account/account-social-login.ts +1 -1
- package/src/nodes/ai/ai-custom-prompt.ts +25 -6
- package/src/nodes/ai/ai-detect-language.ts +25 -6
- package/src/nodes/ai/ai-generate-text-embedding.ts +16 -3
- package/src/nodes/ai/ai-provider-utils-sources.generated.ts +2 -2
- package/src/nodes/ai/ai-provider-utils.ts +135 -10
- package/src/nodes/ai/ai-select-database-data.ts +8 -3
- package/src/nodes/ai/ai-sentiment-analysis.ts +25 -6
- package/src/nodes/ai/ai-summarization.ts +25 -6
- package/src/nodes/ai/ai-text-classifier.ts +25 -6
- package/src/nodes/ai/ai-text-transform.ts +25 -6
- package/src/nodes/analytics/analytics-track-outcome.ts +111 -0
- package/src/nodes/cart/cart-price-order.ts +200 -2
- package/src/nodes/cost-bearing-node-types.ts +45 -0
- package/src/nodes/data/data-select.ts +11 -2
- package/src/nodes/email/email-mailersend.ts +16 -8
- package/src/nodes/email/email-mailgun.ts +17 -12
- package/src/nodes/email/email-postmark.ts +17 -12
- package/src/nodes/email/email-resend.ts +17 -12
- package/src/nodes/email/email-sendgrid.ts +17 -12
- package/src/nodes/email/html-escape-code.ts +21 -0
- package/src/nodes/email/message-trust.ts +198 -0
- package/src/nodes/email/outbound-message-guard.ts +572 -0
- package/src/nodes/email/trusted-html-code.ts +49 -0
- package/src/nodes/general/general-custom-js.ts +46 -7
- package/src/nodes/general/general-http-request.ts +449 -46
- package/src/nodes/general/general-rate-limiter.ts +54 -81
- package/src/nodes/general/general-wait.ts +80 -0
- package/src/nodes/general/visitor-request.ts +79 -0
- package/src/nodes/index.ts +12 -0
- package/src/nodes/integrations/integration-slack.ts +21 -6
- package/src/nodes/newsletter/newsletter-confirm.ts +44 -0
- package/src/nodes/newsletter/newsletter-subscribe.ts +78 -0
- package/src/nodes/newsletter/newsletter-unsubscribe.ts +43 -0
- package/src/nodes/realtime/realtime-join-channel.ts +5 -4
- package/src/nodes/realtime/realtime-send-channel-event.ts +13 -2
- package/src/nodes/realtime/realtime-send-channel-message.ts +13 -2
- package/src/nodes/sms/sms-infobip.ts +14 -4
- package/src/nodes/sms/sms-smsapi.ts +14 -4
- package/src/nodes/sms/sms-textmagic.ts +14 -4
- package/src/nodes/sms/sms-twilio.ts +14 -4
- package/src/nodes/types.ts +9 -1
- package/src/nodes/utility/utility-similarity-scoring.ts +33 -12
- package/src/page-access-guard-generator.ts +154 -0
- package/src/password-reset-route-generator.ts +531 -0
- package/src/payments/payment-core-code.ts +12 -7
- package/src/project-code-scanner.ts +98 -0
- package/src/public-step-guards.ts +448 -0
- package/src/publish-refusals.ts +459 -0
- package/src/realtime-generator.ts +457 -234
- package/src/runtime-storage-generator.ts +270 -11
- package/src/security-runtime/index.ts +53 -0
- package/src/security-runtime/security-runtime-code.ts +579 -0
- package/src/security-scanner.ts +253 -10
- package/src/segment-splitter.ts +10 -2
- package/src/segment-trust.ts +205 -2
- package/src/self-call-headers.ts +46 -0
- package/src/sent-email-log/index.ts +16 -0
- package/src/sent-email-log/sent-email-log-code.ts +30 -4
- package/src/server-custom-node-call.ts +331 -0
- package/src/server-runtime-code.ts +488 -40
- package/src/session-revocation-generator.ts +177 -0
- package/src/slack-message-values.ts +84 -0
- package/src/transactional-email-code.ts +30 -4
- package/src/trigger-generator.ts +19 -3
- package/src/visitor-step-limits.ts +228 -0
- package/src/webhook-generator.ts +94 -14
- package/src/webhook-signature-verification.ts +13 -3
- package/src/workflow-auth-generator.ts +9 -4
- package/src/workflow-component-plugin.ts +14 -4
- package/src/workflow-project-plugin.ts +374 -61
- package/src/workflow-scope.ts +6 -0
package/src/auth-generator.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type {
|
|
2
2
|
UIDLAuthentication,
|
|
3
3
|
UIDLAuthTableColumn,
|
|
4
4
|
UIDLCustomUserProperty,
|
|
@@ -11,10 +11,39 @@ import {
|
|
|
11
11
|
generateFillTemplateFn,
|
|
12
12
|
generateEmailLocaleRequire,
|
|
13
13
|
generateLocalizedEmailCopyFn,
|
|
14
|
+
EXISTING_ACCOUNT_EMAIL,
|
|
15
|
+
SET_PASSWORD_EMAIL,
|
|
14
16
|
WELCOME_EMAIL_CONFIG_KEYS,
|
|
15
17
|
} from './transactional-email-code'
|
|
16
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
PASSWORD_RESET_TOKEN_TTL_SECONDS,
|
|
20
|
+
TRUSTED_ORIGIN_CODE,
|
|
21
|
+
resolveResetPagePath,
|
|
22
|
+
} from './password-reset-route-generator'
|
|
23
|
+
import { AccountFields, SessionCookieResolver, TableAccess } from '@teleporthq/teleport-shared'
|
|
17
24
|
import { generateSessionTokenResolverCode } from './session-cookie-resolver'
|
|
25
|
+
import { AUTOMATIONS_PATH } from './automations/automations-scope'
|
|
26
|
+
import { SESSION_REVOCATION_FILE_NAME, SESSION_STAMP_KEY } from './session-revocation-generator'
|
|
27
|
+
import { securityRuntimeSpecifier } from './security-runtime/security-runtime-code'
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Credentials sign-in throttle: a visitor's next sign-in for an email is
|
|
31
|
+
* refused (429) once this many failures for it are in the window, or once it
|
|
32
|
+
* was tried SIGN_IN_ATTEMPT_LIMIT times in the window whatever the outcome.
|
|
33
|
+
*/
|
|
34
|
+
export const SIGN_IN_FAILURE_LIMIT = 10
|
|
35
|
+
export const SIGN_IN_ATTEMPT_LIMIT = 30
|
|
36
|
+
export const SIGN_IN_WINDOW_SECONDS = 900
|
|
37
|
+
/** Failed sign-ins one visitor may make across every email in the window. */
|
|
38
|
+
export const SIGN_IN_VISITOR_FAILURE_LIMIT = 50
|
|
39
|
+
/** Sign-ups per visitor per hour. */
|
|
40
|
+
export const SIGN_UP_LIMIT = 10
|
|
41
|
+
/**
|
|
42
|
+
* "You already have an account" (or, for an address without a password,
|
|
43
|
+
* "Set your password") emails one address can be sent per hour.
|
|
44
|
+
*/
|
|
45
|
+
export const EXISTING_ACCOUNT_EMAIL_LIMIT = 3
|
|
46
|
+
export const SIGN_UP_WINDOW_SECONDS = 3600
|
|
18
47
|
|
|
19
48
|
// GUI provider id → the actual next-auth v4 provider MODULE name, for the few
|
|
20
49
|
// cases where they differ. Everything else uses the id as the module name.
|
|
@@ -218,9 +247,6 @@ export const SESSION_SAFE_USER_FIELDS: readonly string[] = [
|
|
|
218
247
|
'roles',
|
|
219
248
|
]
|
|
220
249
|
|
|
221
|
-
/** A `users` key the session derives the visitor's role from, in any case. */
|
|
222
|
-
const ROLE_BEARING_USER_KEY = /^(roles?|role_?name)$/i
|
|
223
|
-
|
|
224
250
|
/**
|
|
225
251
|
* Credentials that must never leave the server, whatever the `users` table
|
|
226
252
|
* happens to declare: the OAuth single-table adapter's provider tokens plus the
|
|
@@ -257,6 +283,13 @@ export const SENSITIVE_USER_FIELDS: readonly string[] = [
|
|
|
257
283
|
*/
|
|
258
284
|
const ID_ALIAS_FIELD = '_id'
|
|
259
285
|
|
|
286
|
+
/**
|
|
287
|
+
* The Newsletter's own `users` columns (`newsletter_status`, …). A sign-in
|
|
288
|
+
* snapshot of them goes stale the moment the person confirms or unsubscribes,
|
|
289
|
+
* so they never reach the session: a page reads the row.
|
|
290
|
+
*/
|
|
291
|
+
const NEWSLETTER_COLUMN_PREFIX = 'newsletter_'
|
|
292
|
+
|
|
260
293
|
/**
|
|
261
294
|
* The exact set of `users` columns this project allows into the session.
|
|
262
295
|
*
|
|
@@ -272,7 +305,9 @@ export const buildSessionUserFields = (
|
|
|
272
305
|
tables: Record<string, UIDLAuthTableColumn[]> | undefined,
|
|
273
306
|
customProps: UIDLCustomUserProperty[]
|
|
274
307
|
): string[] => {
|
|
275
|
-
const declared = (tables?.users || [])
|
|
308
|
+
const declared = (tables?.users || [])
|
|
309
|
+
.map((column) => column.name)
|
|
310
|
+
.filter((name) => !name.startsWith(NEWSLETTER_COLUMN_PREFIX))
|
|
276
311
|
const custom = customProps.map((prop) => prop.key)
|
|
277
312
|
const denied = new Set<string>(SENSITIVE_USER_FIELDS)
|
|
278
313
|
|
|
@@ -373,6 +408,47 @@ const generateCustomPropAirtableEntries = (props: UIDLCustomUserProperty[]): str
|
|
|
373
408
|
)
|
|
374
409
|
}
|
|
375
410
|
|
|
411
|
+
/**
|
|
412
|
+
* The `users` columns a sign-up writes itself, never from its form: where the
|
|
413
|
+
* person came from (`source = 'signup'`) and their consent as given
|
|
414
|
+
* (`marketing_consent`, from the "Email me news and offers" box). On the
|
|
415
|
+
* Postgres family `createUser` writes each one it is given, and creates the
|
|
416
|
+
* account without them on a users table made before they existed.
|
|
417
|
+
*/
|
|
418
|
+
export const SIGNUP_OWN_COLUMNS: readonly string[] = ['source', 'marketing_consent']
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* `findSignupUserByEmail(email)`: the sign-up's own existence check, blind to
|
|
422
|
+
* letter case, so `Ava@site.test` signing up reaches the `ava@site.test` row
|
|
423
|
+
* (a subscriber, a guest, an account) instead of making a second one. The
|
|
424
|
+
* exact address first; else the one row equal to it in any case, none when
|
|
425
|
+
* that is ambiguous. Sign-in keeps its exact `findUserByEmail`. On the other
|
|
426
|
+
* stores it is that exact lookup.
|
|
427
|
+
*/
|
|
428
|
+
const generateSignupLookupFunction = (dataSourceType: DataSourceType | null): string => {
|
|
429
|
+
switch (dataSourceType) {
|
|
430
|
+
case 'teleport':
|
|
431
|
+
case 'postgresql':
|
|
432
|
+
case 'cockroachdb':
|
|
433
|
+
return `async function findSignupUserByEmail(email) {
|
|
434
|
+
const client = getClient();
|
|
435
|
+
try {
|
|
436
|
+
await client.connect();
|
|
437
|
+
const exact = await client.query('SELECT * FROM users WHERE email = $1 LIMIT 1', [email]);
|
|
438
|
+
if (exact.rows && exact.rows.length > 0) return exact.rows[0];
|
|
439
|
+
const anyCase = await client.query('SELECT * FROM users WHERE LOWER(email) = LOWER($1) LIMIT 2', [email]);
|
|
440
|
+
return anyCase.rows && anyCase.rows.length === 1 ? anyCase.rows[0] : null;
|
|
441
|
+
} finally {
|
|
442
|
+
try { await client.end(); } catch (_e) {}
|
|
443
|
+
}
|
|
444
|
+
}`
|
|
445
|
+
default:
|
|
446
|
+
return `async function findSignupUserByEmail(email) {
|
|
447
|
+
return findUserByEmail(email);
|
|
448
|
+
}`
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
376
452
|
const generateFindUserFunction = (
|
|
377
453
|
dataSourceType: DataSourceType | null,
|
|
378
454
|
customProps: UIDLCustomUserProperty[]
|
|
@@ -390,6 +466,9 @@ async function userExistsByEmail() { return false; }`
|
|
|
390
466
|
const customValues = generateCustomPropValues(customProps)
|
|
391
467
|
const docEntries = generateCustomPropDocEntries(customProps)
|
|
392
468
|
const airtableEntries = generateCustomPropAirtableEntries(customProps)
|
|
469
|
+
// A declared account property of the same name is already one of the columns.
|
|
470
|
+
const customKeys = new Set(customProps.map((prop) => prop.key))
|
|
471
|
+
const signupOwnColumns = SIGNUP_OWN_COLUMNS.filter((column) => !customKeys.has(column))
|
|
393
472
|
|
|
394
473
|
switch (dataSourceType) {
|
|
395
474
|
case 'teleport':
|
|
@@ -406,14 +485,44 @@ async function userExistsByEmail() { return false; }`
|
|
|
406
485
|
}
|
|
407
486
|
}
|
|
408
487
|
|
|
488
|
+
// What a sign-up writes besides its form (where the person came from, their
|
|
489
|
+
// consent as given), each when it is given. A users table made before one of
|
|
490
|
+
// them lacks it (the column is added when a feature that reads it is switched
|
|
491
|
+
// on): the account is then created without them.
|
|
492
|
+
const SIGNUP_OWN_COLUMNS = ${JSON.stringify(signupOwnColumns)};
|
|
493
|
+
|
|
409
494
|
async function createUser(userData) {
|
|
410
495
|
const client = getClient();
|
|
411
496
|
try {
|
|
412
497
|
await client.connect();
|
|
413
|
-
const
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
498
|
+
const values = [userData.name || null, userData.email, userData.password, userData.role || 'user', userData.image || null${customValues}];
|
|
499
|
+
const insert = function (extra) {
|
|
500
|
+
let columns = '';
|
|
501
|
+
let placeholders = '';
|
|
502
|
+
for (let i = 0; i < extra.length; i++) {
|
|
503
|
+
columns += ', ' + extra[i];
|
|
504
|
+
placeholders += ', $' + (values.length + i + 1);
|
|
505
|
+
}
|
|
506
|
+
return client.query(
|
|
507
|
+
'INSERT INTO users (name, email, password, role, image${sqlCols}' + columns + ') VALUES ($1, $2, $3, $4, $5${pgPlaceholders}' + placeholders + ') RETURNING *',
|
|
508
|
+
values.concat(extra.map(function (column) { return userData[column]; }))
|
|
509
|
+
);
|
|
510
|
+
};
|
|
511
|
+
let extra = SIGNUP_OWN_COLUMNS.filter(function (column) { return userData[column] !== undefined; });
|
|
512
|
+
let result = null;
|
|
513
|
+
while (!result) {
|
|
514
|
+
try {
|
|
515
|
+
result = await insert(extra);
|
|
516
|
+
} catch (err) {
|
|
517
|
+
// 42703 names a column the table does not have. Only that one is left
|
|
518
|
+
// out, and only when it is one of these: the others are still written.
|
|
519
|
+
const named = err && err.code === '42703' ? /column "([^"]+)"/.exec(String(err.message || '')) : null;
|
|
520
|
+
const missing = named ? named[1] : '';
|
|
521
|
+
if (!missing || extra.indexOf(missing) === -1) { throw err; }
|
|
522
|
+
console.warn('[auth] the users table has no ' + missing + ' column yet: the account is created without it');
|
|
523
|
+
extra = extra.filter(function (column) { return column !== missing; });
|
|
524
|
+
}
|
|
525
|
+
}
|
|
417
526
|
return result.rows && result.rows.length > 0 ? result.rows[0] : null;
|
|
418
527
|
} finally {
|
|
419
528
|
try { await client.end(); } catch (_e) {}
|
|
@@ -615,6 +724,279 @@ async function userExistsByEmail() { return false; }`
|
|
|
615
724
|
}
|
|
616
725
|
}
|
|
617
726
|
|
|
727
|
+
/** Where the password-reset links live: hashed, single-use, per account id. */
|
|
728
|
+
export const PASSWORD_RESETS_TABLE = 'teleport_password_resets'
|
|
729
|
+
|
|
730
|
+
const PG_PASSWORD_RESET_FUNCTIONS = `
|
|
731
|
+
// Password-reset links (pages/api/auth/forgot-password + reset-password). Only
|
|
732
|
+
// the SHA-256 of a link's token is stored, bound to the account id, with its
|
|
733
|
+
// expiry; the table is created on first use. The earlier password_reset_tokens
|
|
734
|
+
// table and its rows are never read.
|
|
735
|
+
var PASSWORD_RESETS_TABLE = '${PASSWORD_RESETS_TABLE}';
|
|
736
|
+
var __passwordResetsTableReady = false;
|
|
737
|
+
|
|
738
|
+
async function ensurePasswordResetsTable(client) {
|
|
739
|
+
if (__passwordResetsTableReady) return;
|
|
740
|
+
await client.query('BEGIN');
|
|
741
|
+
try {
|
|
742
|
+
await client.query('SELECT pg_advisory_xact_lock(hashtext($1))', [PASSWORD_RESETS_TABLE]);
|
|
743
|
+
await client.query(
|
|
744
|
+
'CREATE TABLE IF NOT EXISTS ' + PASSWORD_RESETS_TABLE + ' (token_hash TEXT PRIMARY KEY, user_id TEXT NOT NULL, ' +
|
|
745
|
+
'expires_at TIMESTAMPTZ NOT NULL, used_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW())'
|
|
746
|
+
);
|
|
747
|
+
await client.query('CREATE INDEX IF NOT EXISTS ' + PASSWORD_RESETS_TABLE + '_user_id_idx ON ' + PASSWORD_RESETS_TABLE + ' (user_id)');
|
|
748
|
+
await client.query('COMMIT');
|
|
749
|
+
} catch (err) {
|
|
750
|
+
try { await client.query('ROLLBACK'); } catch (_e) {}
|
|
751
|
+
throw err;
|
|
752
|
+
}
|
|
753
|
+
__passwordResetsTableReady = true;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// One live link per account: a new one ends the ones sent before it.
|
|
757
|
+
async function storePasswordReset(userId, tokenHash, ttlSeconds) {
|
|
758
|
+
const client = getClient();
|
|
759
|
+
try {
|
|
760
|
+
await client.connect();
|
|
761
|
+
await ensurePasswordResetsTable(client);
|
|
762
|
+
await client.query(
|
|
763
|
+
'UPDATE ' + PASSWORD_RESETS_TABLE + ' SET used_at = NOW() WHERE user_id = $1 AND used_at IS NULL',
|
|
764
|
+
[String(userId)]
|
|
765
|
+
);
|
|
766
|
+
await client.query(
|
|
767
|
+
'INSERT INTO ' + PASSWORD_RESETS_TABLE + " (token_hash, user_id, expires_at) VALUES ($1, $2, NOW() + ($3::int * INTERVAL '1 second'))",
|
|
768
|
+
[tokenHash, String(userId), ttlSeconds]
|
|
769
|
+
);
|
|
770
|
+
await client.query(
|
|
771
|
+
'DELETE FROM ' + PASSWORD_RESETS_TABLE + " WHERE expires_at < NOW() - INTERVAL '1 day'"
|
|
772
|
+
);
|
|
773
|
+
} finally {
|
|
774
|
+
try { await client.end(); } catch (_e) {}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Spends the link and sets the password in ONE statement: the link must be
|
|
779
|
+
// unspent and unexpired, and is spent by this call alone (a second call with the
|
|
780
|
+
// same token finds it used). Returns the account it was for, or null.
|
|
781
|
+
async function consumePasswordReset(tokenHash, passwordHash) {
|
|
782
|
+
const client = getClient();
|
|
783
|
+
try {
|
|
784
|
+
await client.connect();
|
|
785
|
+
const result = await client.query(
|
|
786
|
+
'WITH consumed AS (' +
|
|
787
|
+
' UPDATE ' + PASSWORD_RESETS_TABLE + ' SET used_at = NOW()' +
|
|
788
|
+
' WHERE token_hash = $1 AND used_at IS NULL AND expires_at > NOW()' +
|
|
789
|
+
' RETURNING user_id' +
|
|
790
|
+
'), changed AS (' +
|
|
791
|
+
' UPDATE users SET password = $2 FROM consumed WHERE users.id::text = consumed.user_id' +
|
|
792
|
+
' RETURNING users.id, users.email' +
|
|
793
|
+
') SELECT id, email FROM changed',
|
|
794
|
+
[tokenHash, passwordHash]
|
|
795
|
+
);
|
|
796
|
+
return result.rows && result.rows.length > 0 ? result.rows[0] : null;
|
|
797
|
+
} catch (err) {
|
|
798
|
+
// 42P01: no link was ever sent, so the table is not there yet.
|
|
799
|
+
if (err && err.code === '42P01') return null;
|
|
800
|
+
throw err;
|
|
801
|
+
} finally {
|
|
802
|
+
try { await client.end(); } catch (_e) {}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
async function passwordResetIsLive(tokenHash) {
|
|
807
|
+
const client = getClient();
|
|
808
|
+
try {
|
|
809
|
+
await client.connect();
|
|
810
|
+
const result = await client.query(
|
|
811
|
+
'SELECT 1 FROM ' + PASSWORD_RESETS_TABLE + ' WHERE token_hash = $1 AND used_at IS NULL AND expires_at > NOW() LIMIT 1',
|
|
812
|
+
[tokenHash]
|
|
813
|
+
);
|
|
814
|
+
return !!(result.rows && result.rows.length > 0);
|
|
815
|
+
} catch (err) {
|
|
816
|
+
if (err && err.code === '42P01') return false;
|
|
817
|
+
throw err;
|
|
818
|
+
} finally {
|
|
819
|
+
try { await client.end(); } catch (_e) {}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
const PASSWORD_RESET_SUPPORTED = true;`
|
|
824
|
+
|
|
825
|
+
const UNSUPPORTED_PASSWORD_RESET_FUNCTIONS = `
|
|
826
|
+
// Password-reset links need a Postgres database; this project's user store is
|
|
827
|
+
// not one, so the reset routes answer without ever sending a link.
|
|
828
|
+
async function storePasswordReset() { throw new Error('Password reset needs a Postgres database'); }
|
|
829
|
+
async function consumePasswordReset() { return null; }
|
|
830
|
+
async function passwordResetIsLive() { return false; }
|
|
831
|
+
const PASSWORD_RESET_SUPPORTED = false;`
|
|
832
|
+
|
|
833
|
+
/**
|
|
834
|
+
* `updateUserPasswordHash(user, newHash, oldHash)` — the sign-in moving a hash
|
|
835
|
+
* onto the project's own pepper. It writes only over the hash it verified, so a
|
|
836
|
+
* reset that landed in between is never undone. Plus the password-reset link
|
|
837
|
+
* store (Postgres family only).
|
|
838
|
+
*/
|
|
839
|
+
const generatePasswordStoreFunctions = (dataSourceType: DataSourceType | null): string => {
|
|
840
|
+
switch (dataSourceType) {
|
|
841
|
+
case 'teleport':
|
|
842
|
+
case 'postgresql':
|
|
843
|
+
case 'cockroachdb':
|
|
844
|
+
return `// The account a stamped session was issued for (session-revocation). A value
|
|
845
|
+
// that is not an id of this table's type (22P02) is no account; any other
|
|
846
|
+
// error is thrown, so the session check fails closed.
|
|
847
|
+
async function findUserById(id) {
|
|
848
|
+
const client = getClient();
|
|
849
|
+
try {
|
|
850
|
+
await client.connect();
|
|
851
|
+
const result = await client.query('SELECT * FROM users WHERE id = $1 LIMIT 1', [id]);
|
|
852
|
+
return result.rows && result.rows.length > 0 ? result.rows[0] : null;
|
|
853
|
+
} catch (err) {
|
|
854
|
+
if (err && err.code === '22P02') return null;
|
|
855
|
+
throw err;
|
|
856
|
+
} finally {
|
|
857
|
+
try { await client.end(); } catch (_e) {}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
862
|
+
const client = getClient();
|
|
863
|
+
try {
|
|
864
|
+
await client.connect();
|
|
865
|
+
const result = await client.query('UPDATE users SET password = $1 WHERE id = $2 AND password = $3', [newHash, user.id, oldHash]);
|
|
866
|
+
return !!(result && result.rowCount > 0);
|
|
867
|
+
} finally {
|
|
868
|
+
try { await client.end(); } catch (_e) {}
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
${PG_PASSWORD_RESET_FUNCTIONS}`
|
|
872
|
+
case 'supabase':
|
|
873
|
+
return `async function findUserById(id) {
|
|
874
|
+
const client = getSupabaseClient();
|
|
875
|
+
const result = await client.from('users').select('*').eq('id', id).maybeSingle();
|
|
876
|
+
if (result.error) throw new Error(result.error.message || 'users lookup failed');
|
|
877
|
+
return result.data || null;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
881
|
+
const client = getSupabaseClient();
|
|
882
|
+
const result = await client.from('users').update({ password: newHash }).eq('id', user.id).eq('password', oldHash).select('id');
|
|
883
|
+
return !!(result.data && result.data.length > 0);
|
|
884
|
+
}
|
|
885
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`
|
|
886
|
+
case 'mysql':
|
|
887
|
+
case 'mariadb':
|
|
888
|
+
case 'tidb':
|
|
889
|
+
return `async function findUserById(id) {
|
|
890
|
+
const conn = await getConnection();
|
|
891
|
+
try {
|
|
892
|
+
const [rows] = await conn.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [id]);
|
|
893
|
+
return rows && rows.length > 0 ? rows[0] : null;
|
|
894
|
+
} finally {
|
|
895
|
+
try { await conn.end(); } catch (_e) {}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
900
|
+
const conn = await getConnection();
|
|
901
|
+
try {
|
|
902
|
+
const [result] = await conn.execute('UPDATE users SET password = ? WHERE id = ? AND password = ?', [newHash, user.id, oldHash]);
|
|
903
|
+
return !!(result && result.affectedRows > 0);
|
|
904
|
+
} finally {
|
|
905
|
+
try { await conn.end(); } catch (_e) {}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`
|
|
909
|
+
case 'mongodb':
|
|
910
|
+
return `async function findUserById(id) {
|
|
911
|
+
const client = await _mongoClientPromise;
|
|
912
|
+
const db = client.db();
|
|
913
|
+
const ObjectId = require('mongodb').ObjectId;
|
|
914
|
+
if (!ObjectId.isValid(String(id))) return null;
|
|
915
|
+
return await db.collection('users').findOne({ _id: new ObjectId(String(id)) });
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
919
|
+
const client = await _mongoClientPromise;
|
|
920
|
+
const db = client.db();
|
|
921
|
+
const result = await db.collection('users').updateOne({ _id: user._id, password: oldHash }, { $set: { password: newHash } });
|
|
922
|
+
return !!(result && result.modifiedCount > 0);
|
|
923
|
+
}
|
|
924
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`
|
|
925
|
+
case 'firestore':
|
|
926
|
+
return `async function findUserById(id) {
|
|
927
|
+
const snap = await firestoreDb.collection('users').doc(String(id)).get();
|
|
928
|
+
return snap.exists ? Object.assign({ id: snap.id }, snap.data()) : null;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
932
|
+
const ref = firestoreDb.collection('users').doc(String(user.id));
|
|
933
|
+
return firestoreDb.runTransaction(async function (tx) {
|
|
934
|
+
const snap = await tx.get(ref);
|
|
935
|
+
if (!snap.exists || snap.data().password !== oldHash) return false;
|
|
936
|
+
tx.update(ref, { password: newHash });
|
|
937
|
+
return true;
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`
|
|
941
|
+
case 'turso':
|
|
942
|
+
return `async function findUserById(id) {
|
|
943
|
+
const result = await tursoClient.execute({ sql: 'SELECT * FROM users WHERE id = ? LIMIT 1', args: [id] });
|
|
944
|
+
return result.rows && result.rows.length > 0 ? result.rows[0] : null;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
948
|
+
const result = await tursoClient.execute({ sql: 'UPDATE users SET password = ? WHERE id = ? AND password = ?', args: [newHash, user.id, oldHash] });
|
|
949
|
+
return !!(result && result.rowsAffected > 0);
|
|
950
|
+
}
|
|
951
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`
|
|
952
|
+
case 'airtable':
|
|
953
|
+
return `async function findUserById(id) {
|
|
954
|
+
return new Promise(function(resolve, reject) {
|
|
955
|
+
airtableBase('users').find(String(id), function(err, rec) {
|
|
956
|
+
if (err && (err.statusCode === 404 || err.error === 'NOT_FOUND')) { resolve(null); return; }
|
|
957
|
+
if (err) { reject(err); return; }
|
|
958
|
+
resolve(rec ? Object.assign({ id: rec.id }, rec.fields) : null);
|
|
959
|
+
});
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
964
|
+
const current = await findUserByEmail(user.email);
|
|
965
|
+
if (!current || current.password !== oldHash) return false;
|
|
966
|
+
await new Promise(function(resolve, reject) {
|
|
967
|
+
airtableBase('users').update(String(user.id), { password: newHash }, function(err) { if (err) { reject(err); } else { resolve(); } });
|
|
968
|
+
});
|
|
969
|
+
return true;
|
|
970
|
+
}
|
|
971
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`
|
|
972
|
+
default:
|
|
973
|
+
return `async function findUserById() { return null; }
|
|
974
|
+
async function updateUserPasswordHash() { return false; }
|
|
975
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
/** Microsoft provider ids whose email claim is only trusted for one tenant. */
|
|
980
|
+
const MICROSOFT_PROVIDER_IDS = new Set(['azure-ad', 'microsoft-entra-id'])
|
|
981
|
+
|
|
982
|
+
/** The credential key naming a Microsoft provider's tenant (`*_TENANT_ID`, or Entra's `*_ISSUER`). */
|
|
983
|
+
const microsoftTenantKeyOf = (provider: { id: string; credentials: Record<string, string> }) =>
|
|
984
|
+
Object.keys(provider.credentials || {}).find(
|
|
985
|
+
(key) => key.endsWith('_TENANT_ID') || key.endsWith('_ISSUER')
|
|
986
|
+
)
|
|
987
|
+
|
|
988
|
+
/** provider id → env var of its configured tenant, for each Microsoft provider. */
|
|
989
|
+
const microsoftTenantEnvKeys = (auth: UIDLAuthentication): Record<string, string> => {
|
|
990
|
+
const keys: Record<string, string> = {}
|
|
991
|
+
for (const provider of auth.providers || []) {
|
|
992
|
+
const key = MICROSOFT_PROVIDER_IDS.has(provider.id) ? microsoftTenantKeyOf(provider) : undefined
|
|
993
|
+
if (key) {
|
|
994
|
+
keys[provider.id] = key
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
return keys
|
|
998
|
+
}
|
|
999
|
+
|
|
618
1000
|
// Only the CredentialsProvider is imported at module scope (it always exists).
|
|
619
1001
|
// OAuth providers are required lazily inside generateProvidersSetup.
|
|
620
1002
|
const generateProviderImports = (auth: UIDLAuthentication): string => {
|
|
@@ -654,11 +1036,11 @@ const generateProvidersSetup = (auth: UIDLAuthentication): string => {
|
|
|
654
1036
|
//
|
|
655
1037
|
// Unknown email and wrong password deliberately share one code: telling them
|
|
656
1038
|
// apart is a user-enumeration oracle.
|
|
657
|
-
async authorize(credentials) {
|
|
1039
|
+
async authorize(credentials, req) {
|
|
658
1040
|
if (!credentials || !credentials.email || !credentials.password) {
|
|
659
1041
|
throw new Error('MissingCredentials');
|
|
660
1042
|
}
|
|
661
|
-
const {
|
|
1043
|
+
const { checkPassword, hashPassword } = require('./hash-password');
|
|
662
1044
|
const { classifyAuthInfrastructureError } = require('./db-health');
|
|
663
1045
|
let user;
|
|
664
1046
|
try {
|
|
@@ -668,22 +1050,35 @@ const generateProvidersSetup = (auth: UIDLAuthentication): string => {
|
|
|
668
1050
|
console.error('[auth] Could not reach the user store (' + kind + '):', err && err.message ? err.message : err);
|
|
669
1051
|
throw new Error('ServiceUnavailable');
|
|
670
1052
|
}
|
|
1053
|
+
let verdict;
|
|
671
1054
|
try {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
const signedIn = sanitizeUser(user);
|
|
677
|
-
signedIn[TOKEN_PASSWORD_FINGERPRINT] = passwordFingerprint(user.password);
|
|
678
|
-
return signedIn;
|
|
1055
|
+
// No account, or one without a password, is checked too (against a
|
|
1056
|
+
// stand-in hash): every failure costs what a wrong password costs.
|
|
1057
|
+
verdict = checkPassword(String(credentials.password), user ? user.password : null);
|
|
679
1058
|
} catch (err) {
|
|
680
|
-
if (err && err.message === 'InvalidCredentials') throw err;
|
|
681
1059
|
// A hashing/serialisation fault is an infrastructure problem, not a
|
|
682
1060
|
// wrong password — saying "check your details" would send the user
|
|
683
1061
|
// round in circles.
|
|
684
1062
|
console.error('[auth] Credential check failed:', err && err.message ? err.message : err);
|
|
685
1063
|
throw new Error('ServiceUnavailable');
|
|
686
1064
|
}
|
|
1065
|
+
if (!verdict.ok) {
|
|
1066
|
+
await countSignInFailure(req, credentials.email);
|
|
1067
|
+
throw new Error('InvalidCredentials');
|
|
1068
|
+
}
|
|
1069
|
+
// A hash still made with the old pepper moves onto the project's own.
|
|
1070
|
+
let storedHash = user.password;
|
|
1071
|
+
if (verdict.needsRehash) {
|
|
1072
|
+
try {
|
|
1073
|
+
const rehashed = hashPassword(String(credentials.password));
|
|
1074
|
+
if (await updateUserPasswordHash(user, rehashed, storedHash)) {
|
|
1075
|
+
storedHash = rehashed;
|
|
1076
|
+
}
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
console.error('[auth] Could not move the password onto the current pepper:', err && err.message ? err.message : err);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
return withSessionStamp(sanitizeUser(user), Object.assign({}, user, { password: storedHash }));
|
|
687
1082
|
}
|
|
688
1083
|
}));`)
|
|
689
1084
|
}
|
|
@@ -702,7 +1097,11 @@ const generateProvidersSetup = (auth: UIDLAuthentication): string => {
|
|
|
702
1097
|
` clientId: process.env.${idKey}`,
|
|
703
1098
|
` clientSecret: process.env.${secretKey}`,
|
|
704
1099
|
]
|
|
705
|
-
if (issuerKey) {
|
|
1100
|
+
if (provider.id === 'azure-ad' && issuerKey && issuerKey.endsWith('_TENANT_ID')) {
|
|
1101
|
+
// next-auth's Azure AD provider takes the tenant as tenantId ("common",
|
|
1102
|
+
// every tenant, when absent).
|
|
1103
|
+
cfg.push(` tenantId: process.env.${issuerKey} || undefined`)
|
|
1104
|
+
} else if (issuerKey) {
|
|
706
1105
|
cfg.push(` issuer: process.env.${issuerKey}`)
|
|
707
1106
|
}
|
|
708
1107
|
|
|
@@ -730,17 +1129,35 @@ export const generateAuthOptionsFile = (
|
|
|
730
1129
|
const providerImports = generateProviderImports(auth)
|
|
731
1130
|
const providersSetup = generateProvidersSetup(auth)
|
|
732
1131
|
const findUserCode = auth.passwordAuthEnabled
|
|
733
|
-
? generateFindUserFunction(auth.dataSourceType, customProps)
|
|
1132
|
+
? `${generateFindUserFunction(auth.dataSourceType, customProps)}
|
|
1133
|
+
|
|
1134
|
+
${generateSignupLookupFunction(auth.dataSourceType)}`
|
|
734
1135
|
: ''
|
|
735
1136
|
const sanitizeUserCode = generateSanitizeUserFunction(auth.tables, customProps)
|
|
736
1137
|
const signInRoute = auth.authPages.signIn?.route || '/auth/sign-in'
|
|
1138
|
+
// Password sign-in brings the local account a session can be revoked from;
|
|
1139
|
+
// with an OAuth provider beside it, an OAuth sign-in may become that account.
|
|
1140
|
+
const withPassword = !!auth.passwordAuthEnabled
|
|
1141
|
+
// Every OAuth project checks whether the provider vouches for the email: an
|
|
1142
|
+
// unvouched email never is the visitor's identity, whether or not a local
|
|
1143
|
+
// account could be adopted (R15).
|
|
1144
|
+
const withOAuthBinding = (auth.providers || []).length > 0
|
|
1145
|
+
const passwordSessionCode = withPassword
|
|
1146
|
+
? `${generatePasswordStoreFunctions(auth.dataSourceType)}
|
|
1147
|
+
|
|
1148
|
+
${CREDENTIAL_SESSION_HELPERS}
|
|
1149
|
+
`
|
|
1150
|
+
: ''
|
|
1151
|
+
const oauthBindingCode = withOAuthBinding
|
|
1152
|
+
? `${generateOAuthEmailVerificationHelpers(auth)}\n`
|
|
1153
|
+
: ''
|
|
737
1154
|
|
|
738
1155
|
return `${providerImports}
|
|
739
1156
|
${dbSetupCode}
|
|
740
1157
|
${sanitizeUserCode}
|
|
741
1158
|
|
|
742
1159
|
${findUserCode}
|
|
743
|
-
|
|
1160
|
+
${passwordSessionCode}${oauthBindingCode}
|
|
744
1161
|
${providersSetup}
|
|
745
1162
|
|
|
746
1163
|
// How long a JWT may serve profile fields before the next /api/auth/session
|
|
@@ -791,7 +1208,29 @@ const authOptions = {
|
|
|
791
1208
|
for (let i = 0; i < keys.length; i++) {
|
|
792
1209
|
token[keys[i]] = user[keys[i]];
|
|
793
1210
|
}
|
|
794
|
-
|
|
1211
|
+
${
|
|
1212
|
+
withOAuthBinding
|
|
1213
|
+
? ` // An OAuth sign-in becomes the local account with its email only when
|
|
1214
|
+
// the provider vouches for that email; otherwise it stays a session of
|
|
1215
|
+
// its own, without the account's id or role.
|
|
1216
|
+
if (params.account && params.account.type !== 'credentials') {
|
|
1217
|
+
if (!(await oauthEmailIsVerified(params.account, params.profile, token))) {
|
|
1218
|
+
asDistinctOAuthIdentity(token, params.account);
|
|
1219
|
+
}${
|
|
1220
|
+
withPassword
|
|
1221
|
+
? ` else {
|
|
1222
|
+
// Vouched for: the next refresh adopts the local account with that
|
|
1223
|
+
// email (its id and role). Until then — and for good when there is
|
|
1224
|
+
// none — the id is the provider's, namespaced where it could equal
|
|
1225
|
+
// a local id.
|
|
1226
|
+
namespaceOAuthId(token, params.account);
|
|
1227
|
+
}`
|
|
1228
|
+
: ''
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
`
|
|
1232
|
+
: ''
|
|
1233
|
+
} // Deliberately NOT stamped. An OAuth \`user\` is the PROVIDER's profile
|
|
795
1234
|
// (id/name/email/image) — it carries no \`role\`, which only exists on
|
|
796
1235
|
// the \`users\` row. Stamping here would make the first session request
|
|
797
1236
|
// after an OAuth sign-in skip the database and leave the visitor
|
|
@@ -800,7 +1239,15 @@ const authOptions = {
|
|
|
800
1239
|
// exactly as it does today, and the interval applies from there on.
|
|
801
1240
|
return token;
|
|
802
1241
|
}
|
|
803
|
-
|
|
1242
|
+
${
|
|
1243
|
+
withOAuthBinding
|
|
1244
|
+
? ` // A session the provider did not vouch for never takes a local account.
|
|
1245
|
+
if (token && token[OAUTH_UNVERIFIED]) {
|
|
1246
|
+
return token;
|
|
1247
|
+
}
|
|
1248
|
+
`
|
|
1249
|
+
: ''
|
|
1250
|
+
} // Subsequent calls (every /api/auth/session): re-read the user from the
|
|
804
1251
|
// database so profile edits — name, image, role, etc. — propagate into the
|
|
805
1252
|
// session. The JWT is otherwise a snapshot captured at login, which is why
|
|
806
1253
|
// the navbar avatar/name reverted to the old value after a refresh: the
|
|
@@ -821,35 +1268,19 @@ const authOptions = {
|
|
|
821
1268
|
if (params.trigger !== 'update' && isFresh) {
|
|
822
1269
|
return token;
|
|
823
1270
|
}
|
|
824
|
-
|
|
825
|
-
try {
|
|
826
|
-
if (token && token.email && typeof findUserByEmail === 'function') {
|
|
827
|
-
const fresh = await findUserByEmail(String(token.email));
|
|
828
|
-
const safe = fresh && typeof sanitizeUser === 'function' ? sanitizeUser(fresh) : null;
|
|
829
|
-
if (safe && typeof safe === 'object') {
|
|
830
|
-
const fk = Object.keys(safe);
|
|
831
|
-
for (let i = 0; i < fk.length; i++) {
|
|
832
|
-
token[fk[i]] = safe[fk[i]];
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if (fresh) {
|
|
836
|
-
const currentFingerprint = passwordFingerprint(fresh.password);
|
|
837
|
-
if (typeof token[TOKEN_PASSWORD_FINGERPRINT] !== 'string') {
|
|
838
|
-
token[TOKEN_PASSWORD_FINGERPRINT] = currentFingerprint;
|
|
839
|
-
} else if (token[TOKEN_PASSWORD_FINGERPRINT] !== currentFingerprint) {
|
|
840
|
-
passwordChanged = true;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
} catch (e) {
|
|
1271
|
+
${withPassword ? PASSWORD_REFRESH_CODE : PLAIN_REFRESH_CODE} } catch (e) {
|
|
845
1272
|
// Keep the existing token on any DB hiccup — never sign the user out.
|
|
846
1273
|
}
|
|
847
|
-
|
|
1274
|
+
${
|
|
1275
|
+
withPassword
|
|
1276
|
+
? ` if (revoked) {
|
|
848
1277
|
// NextAuth answers a jwt callback that throws by clearing the session
|
|
849
|
-
// cookie
|
|
850
|
-
throw new Error('
|
|
1278
|
+
// cookie: the visitor is signed out.
|
|
1279
|
+
throw new Error('SessionRevoked');
|
|
851
1280
|
}
|
|
852
|
-
|
|
1281
|
+
`
|
|
1282
|
+
: ''
|
|
1283
|
+
} // Stamped even when the read threw, so an unreachable database costs one
|
|
853
1284
|
// attempt per interval instead of one per request.
|
|
854
1285
|
if (token) {
|
|
855
1286
|
token[TOKEN_REFRESH_STAMP] = now;
|
|
@@ -863,7 +1294,9 @@ const authOptions = {
|
|
|
863
1294
|
const skip = { iat: 1, exp: 1, jti: 1, sub: 1 };
|
|
864
1295
|
skip[TOKEN_REFRESH_STAMP] = 1;
|
|
865
1296
|
skip[TOKEN_PASSWORD_FINGERPRINT] = 1;
|
|
866
|
-
|
|
1297
|
+
${withPassword ? ` skip['${SESSION_STAMP_KEY}'] = 1;\n` : ''}${
|
|
1298
|
+
withOAuthBinding ? ' skip[OAUTH_UNVERIFIED] = 1;\n' : ''
|
|
1299
|
+
} const keys = Object.keys(token);
|
|
867
1300
|
for (let i = 0; i < keys.length; i++) {
|
|
868
1301
|
if (!skip[keys[i]]) {
|
|
869
1302
|
session.user[keys[i]] = token[keys[i]];
|
|
@@ -879,14 +1312,407 @@ const authOptions = {
|
|
|
879
1312
|
secret: process.env.NEXTAUTH_SECRET,
|
|
880
1313
|
};
|
|
881
1314
|
|
|
1315
|
+
// On https the session token and the callback-url cookie are "__Host-" cookies
|
|
1316
|
+
// (Path=/, Secure, HttpOnly, SameSite=Lax, no Domain): only this site can set
|
|
1317
|
+
// them. Published sites share a parent domain that is not a public suffix, so a
|
|
1318
|
+
// sibling site could otherwise plant a session cookie this site reads (cookie
|
|
1319
|
+
// tossing: the visitor silently signed in to someone else's account). Every
|
|
1320
|
+
// reader in the app reads this one name (session-cookie-resolver). NextAuth
|
|
1321
|
+
// reads the option on every request, after the auth route has set
|
|
1322
|
+
// NEXTAUTH_URL to the request's origin; on http (a local server) its defaults stay.
|
|
1323
|
+
var SECURE_SESSION_COOKIES = {
|
|
1324
|
+
sessionToken: {
|
|
1325
|
+
name: '${SessionCookieResolver.SECURE_SESSION_COOKIE_NAME}',
|
|
1326
|
+
options: { httpOnly: true, sameSite: 'lax', path: '/', secure: true },
|
|
1327
|
+
},
|
|
1328
|
+
callbackUrl: {
|
|
1329
|
+
name: '${SessionCookieResolver.SECURE_CALLBACK_URL_COOKIE_NAME}',
|
|
1330
|
+
options: { httpOnly: true, sameSite: 'lax', path: '/', secure: true },
|
|
1331
|
+
},
|
|
1332
|
+
};
|
|
1333
|
+
Object.defineProperty(authOptions, 'cookies', {
|
|
1334
|
+
enumerable: true,
|
|
1335
|
+
get: function () {
|
|
1336
|
+
return String(process.env.NEXTAUTH_URL || '').indexOf('https://') === 0 ? SECURE_SESSION_COOKIES : undefined;
|
|
1337
|
+
},
|
|
1338
|
+
});
|
|
1339
|
+
|
|
882
1340
|
module.exports = authOptions;
|
|
883
1341
|
module.exports.sanitizeUser = sanitizeUser;
|
|
884
1342
|
${
|
|
885
1343
|
auth.passwordAuthEnabled
|
|
886
|
-
? `module.exports.findUserByEmail = findUserByEmail;\nmodule.exports.createUser = createUser;\nmodule.exports.userExistsByEmail = userExistsByEmail;\n`
|
|
1344
|
+
? `module.exports.findUserByEmail = findUserByEmail;\nmodule.exports.findSignupUserByEmail = findSignupUserByEmail;\nmodule.exports.createUser = createUser;\nmodule.exports.userExistsByEmail = userExistsByEmail;\n`
|
|
887
1345
|
: ''
|
|
1346
|
+
}${
|
|
1347
|
+
withPassword
|
|
1348
|
+
? `module.exports.findUserById = findUserById;
|
|
1349
|
+
module.exports.checkSignInAllowed = checkSignInAllowed;
|
|
1350
|
+
module.exports.countSignInFailure = countSignInFailure;
|
|
1351
|
+
module.exports.SIGN_IN_VISITOR_HEADER = SIGN_IN_VISITOR_HEADER;
|
|
1352
|
+
module.exports.storePasswordReset = storePasswordReset;
|
|
1353
|
+
module.exports.consumePasswordReset = consumePasswordReset;
|
|
1354
|
+
module.exports.passwordResetIsLive = passwordResetIsLive;
|
|
1355
|
+
module.exports.PASSWORD_RESET_SUPPORTED = PASSWORD_RESET_SUPPORTED;
|
|
1356
|
+
`
|
|
1357
|
+
: ''
|
|
1358
|
+
}
|
|
1359
|
+
`
|
|
888
1360
|
}
|
|
1361
|
+
|
|
1362
|
+
// The jwt callback's refresh without password sign-in (no local lookup exists).
|
|
1363
|
+
const PLAIN_REFRESH_CODE = ` try {
|
|
1364
|
+
if (token && token.email && typeof findUserByEmail === 'function') {
|
|
1365
|
+
const fresh = await findUserByEmail(String(token.email));
|
|
1366
|
+
const safe = fresh && typeof sanitizeUser === 'function' ? sanitizeUser(fresh) : null;
|
|
1367
|
+
if (safe && typeof safe === 'object') {
|
|
1368
|
+
const fk = Object.keys(safe);
|
|
1369
|
+
for (let i = 0; i < fk.length; i++) {
|
|
1370
|
+
token[fk[i]] = safe[fk[i]];
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
`
|
|
1375
|
+
|
|
1376
|
+
// With password sign-in: a stamped session follows its account BY ID (the key
|
|
1377
|
+
// every consumer acts on), so a deleted account or a changed password ends it
|
|
1378
|
+
// even after an email change; an unstamped one (an OAuth sign-in, a token from
|
|
1379
|
+
// before stamps) adopts the local account by its email.
|
|
1380
|
+
const PASSWORD_REFRESH_CODE = ` let revoked = false;
|
|
1381
|
+
try {
|
|
1382
|
+
const stamped = !!token && typeof token['${SESSION_STAMP_KEY}'] === 'string';
|
|
1383
|
+
const accountId = token && (token.id != null ? token.id : token.sub);
|
|
1384
|
+
let fresh = null;
|
|
1385
|
+
let looked = false;
|
|
1386
|
+
if (stamped && typeof findUserById === 'function') {
|
|
1387
|
+
fresh = accountId != null ? await findUserById(String(accountId)) : null;
|
|
1388
|
+
looked = true;
|
|
1389
|
+
} else if (token && token.email && typeof findUserByEmail === 'function') {
|
|
1390
|
+
fresh = await findUserByEmail(String(token.email));
|
|
1391
|
+
looked = true;
|
|
1392
|
+
}
|
|
1393
|
+
if (looked) {
|
|
1394
|
+
// The account's password (or session_version) changed since this
|
|
1395
|
+
// token was stamped, or the account is gone: the session is over.
|
|
1396
|
+
// Otherwise the token takes the stamp of the account as it is now.
|
|
1397
|
+
revoked = sessionStampCheck(token, fresh) === 'revoked';
|
|
1398
|
+
// The same end, for a token issued before stamps: a short fingerprint
|
|
1399
|
+
// of the stored hash, never the hash. A change (a reset included)
|
|
1400
|
+
// signs that session out at its next refresh.
|
|
1401
|
+
if (!revoked && fresh) {
|
|
1402
|
+
const currentFingerprint = passwordFingerprint(fresh.password);
|
|
1403
|
+
if (typeof token[TOKEN_PASSWORD_FINGERPRINT] !== 'string') {
|
|
1404
|
+
token[TOKEN_PASSWORD_FINGERPRINT] = currentFingerprint;
|
|
1405
|
+
} else if (token[TOKEN_PASSWORD_FINGERPRINT] !== currentFingerprint) {
|
|
1406
|
+
revoked = true;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
const safe = !revoked && fresh && typeof sanitizeUser === 'function' ? sanitizeUser(fresh) : null;
|
|
1410
|
+
if (safe && typeof safe === 'object') {
|
|
1411
|
+
const fk = Object.keys(safe);
|
|
1412
|
+
for (let i = 0; i < fk.length; i++) {
|
|
1413
|
+
token[fk[i]] = safe[fk[i]];
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
889
1417
|
`
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* auth-options helpers for password sign-in: the sign-in throttle (failures
|
|
1421
|
+
* per visitor and email, counted after a wrong password, checked by the
|
|
1422
|
+
* NextAuth route before the next attempt) and the session stamp
|
|
1423
|
+
* (`utils/auth/session-revocation.js`).
|
|
1424
|
+
*/
|
|
1425
|
+
const CREDENTIAL_SESSION_HELPERS = `// Sign-in throttle. The NextAuth route refuses a visitor's next sign-in for an
|
|
1426
|
+
// email once SIGN_IN_FAILURE_LIMIT failures for it are in the window, once the
|
|
1427
|
+
// visitor has SIGN_IN_VISITOR_FAILURE_LIMIT failures across all emails, or once
|
|
1428
|
+
// they tried it SIGN_IN_ATTEMPT_LIMIT times (so a burst of parallel guesses
|
|
1429
|
+
// cannot all start before the first failure is counted). The route writes who
|
|
1430
|
+
// the visitor is on the request (SIGN_IN_VISITOR_HEADER, replacing whatever the
|
|
1431
|
+
// request carried), because NextAuth hands authorize() the headers only.
|
|
1432
|
+
var SIGN_IN_VISITOR_HEADER = 'x-teleport-signin-visitor';
|
|
1433
|
+
var SIGN_IN_FAILURE_SCOPE = 'auth-signin-failures';
|
|
1434
|
+
var SIGN_IN_FAILURE_LIMIT = ${SIGN_IN_FAILURE_LIMIT};
|
|
1435
|
+
var SIGN_IN_ATTEMPT_SCOPE = 'auth-signin-attempts';
|
|
1436
|
+
var SIGN_IN_ATTEMPT_LIMIT = ${SIGN_IN_ATTEMPT_LIMIT};
|
|
1437
|
+
var SIGN_IN_WINDOW_SECONDS = ${SIGN_IN_WINDOW_SECONDS};
|
|
1438
|
+
// Failures one visitor may make across every email (credential stuffing tries
|
|
1439
|
+
// many addresses once each), counted the same way: after a failure only.
|
|
1440
|
+
var SIGN_IN_VISITOR_FAILURE_SCOPE = 'auth-signin-visitor-failures';
|
|
1441
|
+
var SIGN_IN_VISITOR_FAILURE_LIMIT = ${SIGN_IN_VISITOR_FAILURE_LIMIT};
|
|
1442
|
+
|
|
1443
|
+
function signInVisitorRequest(req) {
|
|
1444
|
+
var headers = (req && req.headers) || {};
|
|
1445
|
+
var visitor = headers[SIGN_IN_VISITOR_HEADER];
|
|
1446
|
+
return { headers: {}, socket: { remoteAddress: typeof visitor === 'string' ? visitor : '' } };
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
function signInEmailKey(email) {
|
|
1450
|
+
return String(email == null ? '' : email).trim().toLowerCase();
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// { allowed, retryAfter } for the sign-in the request is about to make.
|
|
1454
|
+
async function checkSignInAllowed(req) {
|
|
1455
|
+
var security = require('../security-runtime');
|
|
1456
|
+
var visitor = signInVisitorRequest(req);
|
|
1457
|
+
var email = signInEmailKey(req && req.body ? req.body.email : '');
|
|
1458
|
+
var failures = await security.peekRateLimitRequest(visitor, {
|
|
1459
|
+
scope: SIGN_IN_FAILURE_SCOPE,
|
|
1460
|
+
limit: SIGN_IN_FAILURE_LIMIT,
|
|
1461
|
+
windowSeconds: SIGN_IN_WINDOW_SECONDS,
|
|
1462
|
+
keyParts: [email],
|
|
1463
|
+
});
|
|
1464
|
+
if (!failures.allowed) {
|
|
1465
|
+
return failures;
|
|
1466
|
+
}
|
|
1467
|
+
var visitorFailures = await security.peekRateLimitRequest(visitor, {
|
|
1468
|
+
scope: SIGN_IN_VISITOR_FAILURE_SCOPE,
|
|
1469
|
+
limit: SIGN_IN_VISITOR_FAILURE_LIMIT,
|
|
1470
|
+
windowSeconds: SIGN_IN_WINDOW_SECONDS,
|
|
1471
|
+
});
|
|
1472
|
+
if (!visitorFailures.allowed) {
|
|
1473
|
+
return visitorFailures;
|
|
1474
|
+
}
|
|
1475
|
+
var attemptAllowed = await security.rateLimitRequest(visitor, null, {
|
|
1476
|
+
scope: SIGN_IN_ATTEMPT_SCOPE,
|
|
1477
|
+
limit: SIGN_IN_ATTEMPT_LIMIT,
|
|
1478
|
+
windowSeconds: SIGN_IN_WINDOW_SECONDS,
|
|
1479
|
+
keyParts: [email],
|
|
1480
|
+
});
|
|
1481
|
+
if (!attemptAllowed) {
|
|
1482
|
+
var nowSec = Math.floor(Date.now() / 1000);
|
|
1483
|
+
return { allowed: false, retryAfter: Math.max(1, SIGN_IN_WINDOW_SECONDS - (nowSec % SIGN_IN_WINDOW_SECONDS)) };
|
|
1484
|
+
}
|
|
1485
|
+
return { allowed: true, retryAfter: 0 };
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
async function countSignInFailure(req, email) {
|
|
1489
|
+
try {
|
|
1490
|
+
var security = require('../security-runtime');
|
|
1491
|
+
var visitor = signInVisitorRequest(req);
|
|
1492
|
+
await security.rateLimitRequest(visitor, null, {
|
|
1493
|
+
scope: SIGN_IN_FAILURE_SCOPE,
|
|
1494
|
+
limit: SIGN_IN_FAILURE_LIMIT,
|
|
1495
|
+
windowSeconds: SIGN_IN_WINDOW_SECONDS,
|
|
1496
|
+
keyParts: [signInEmailKey(email)],
|
|
1497
|
+
});
|
|
1498
|
+
await security.rateLimitRequest(visitor, null, {
|
|
1499
|
+
scope: SIGN_IN_VISITOR_FAILURE_SCOPE,
|
|
1500
|
+
limit: SIGN_IN_VISITOR_FAILURE_LIMIT,
|
|
1501
|
+
windowSeconds: SIGN_IN_WINDOW_SECONDS,
|
|
1502
|
+
});
|
|
1503
|
+
} catch (err) {
|
|
1504
|
+
console.error('[auth] Could not count a failed sign-in:', err && err.message ? err.message : err);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
// The shared session-stamp check (utils/auth/session-revocation.js).
|
|
1509
|
+
function sessionRevocationModule() {
|
|
1510
|
+
try {
|
|
1511
|
+
var revocation = require('./${SESSION_REVOCATION_FILE_NAME}');
|
|
1512
|
+
return revocation && typeof revocation.stampVerdict === 'function' ? revocation : null;
|
|
1513
|
+
} catch (e) {
|
|
1514
|
+
return null;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
// A signed-in user's session carries the stamp of the account it was issued for.
|
|
1519
|
+
function withSessionStamp(safeUser, row) {
|
|
1520
|
+
var revocation = sessionRevocationModule();
|
|
1521
|
+
var stamp = revocation ? revocation.credentialStamp(row) : null;
|
|
1522
|
+
if (safeUser && stamp) {
|
|
1523
|
+
safeUser[revocation.SESSION_STAMP_KEY] = stamp;
|
|
1524
|
+
}
|
|
1525
|
+
if (safeUser) {
|
|
1526
|
+
safeUser[TOKEN_PASSWORD_FINGERPRINT] = passwordFingerprint(row ? row.password : null);
|
|
1527
|
+
}
|
|
1528
|
+
return safeUser;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// 'revoked' when the stored account no longer matches the token's stamp;
|
|
1532
|
+
// otherwise the token takes the account's current stamp.
|
|
1533
|
+
function sessionStampCheck(token, row) {
|
|
1534
|
+
var revocation = sessionRevocationModule();
|
|
1535
|
+
if (!revocation) {
|
|
1536
|
+
return 'unchecked';
|
|
1537
|
+
}
|
|
1538
|
+
var verdict = revocation.stampVerdict(token, row || null);
|
|
1539
|
+
if (verdict !== 'revoked' && row) {
|
|
1540
|
+
var stamp = revocation.credentialStamp(row);
|
|
1541
|
+
if (stamp) {
|
|
1542
|
+
token[revocation.SESSION_STAMP_KEY] = stamp;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
return verdict;
|
|
1546
|
+
}`
|
|
1547
|
+
|
|
1548
|
+
/**
|
|
1549
|
+
* The user stores whose own account ids can never equal an OAuth provider's id:
|
|
1550
|
+
* MongoDB (`_id`, a 24-hex ObjectId), Airtable (`rec…` record ids) and
|
|
1551
|
+
* Firestore (20-character auto ids). Read from `auth.dataSourceType`, the same
|
|
1552
|
+
* value that picks the auth driver.
|
|
1553
|
+
*/
|
|
1554
|
+
const STORES_WITH_DISJOINT_IDS = new Set(['mongodb', 'airtable', 'firestore'])
|
|
1555
|
+
|
|
1556
|
+
/**
|
|
1557
|
+
* Whether an OAuth session's id must be namespaced (`oauth:<provider>:<id>`)
|
|
1558
|
+
* because the provider's raw account id could equal another account's id:
|
|
1559
|
+
* - the project has more than one OAuth provider: two providers' id spaces
|
|
1560
|
+
* can collide, whatever the store;
|
|
1561
|
+
* - otherwise a store whose ids are its own (STORES_WITH_DISJOINT_IDS) keeps
|
|
1562
|
+
* the raw id;
|
|
1563
|
+
* - any other store (SQL or not) is decided by the declared `users` id type:
|
|
1564
|
+
* a UUID keeps the raw id; any other type (a provider's integer id can equal
|
|
1565
|
+
* a local integer id on every owner rule), or none declared, namespaces.
|
|
1566
|
+
* The editor declares the type the database reported for its `users` table
|
|
1567
|
+
* (teleport-gui `liveUsersIdType`), and no type when none was recorded.
|
|
1568
|
+
* Where the raw id stays it never equals a local id, and every record a
|
|
1569
|
+
* published site wrote under it (entitlements, cart, favourites, chats,
|
|
1570
|
+
* downloads) stays the visitor's. Wherever a collision cannot be ruled out the
|
|
1571
|
+
* id is namespaced: a collision hands one person another's records, which no
|
|
1572
|
+
* later fix undoes; lost continuity leaves the records in place under the old
|
|
1573
|
+
* id.
|
|
1574
|
+
*/
|
|
1575
|
+
export const oauthIdsNeedNamespace = (auth: UIDLAuthentication): boolean => {
|
|
1576
|
+
if ((auth.providers || []).length > 1) {
|
|
1577
|
+
return true
|
|
1578
|
+
}
|
|
1579
|
+
if (auth.dataSourceType && STORES_WITH_DISJOINT_IDS.has(auth.dataSourceType)) {
|
|
1580
|
+
return false
|
|
1581
|
+
}
|
|
1582
|
+
const idColumn = ((auth.tables && auth.tables.users) || []).find(
|
|
1583
|
+
(column) => !!column && String(column.name || '').toLowerCase() === 'id'
|
|
1584
|
+
)
|
|
1585
|
+
return !(idColumn && /^\s*uuid\b/i.test(String(idColumn.type || '')))
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
/**
|
|
1589
|
+
* auth-options helpers for a project with password sign-in AND an OAuth
|
|
1590
|
+
* provider: whether the provider vouches for the email an OAuth sign-in
|
|
1591
|
+
* presents. Only then may that sign-in become the local account with the same
|
|
1592
|
+
* email (its id and role); otherwise it is a session of its own.
|
|
1593
|
+
*/
|
|
1594
|
+
const generateOAuthEmailVerificationHelpers = (auth: UIDLAuthentication): string => {
|
|
1595
|
+
const tenants = microsoftTenantEnvKeys(auth)
|
|
1596
|
+
const tenantEntries = Object.keys(tenants)
|
|
1597
|
+
.map((id) => `${JSON.stringify(id)}: process.env.${tenants[id]}`)
|
|
1598
|
+
.join(', ')
|
|
1599
|
+
return `// Providers that only ever hand out an email its owner proved. Any other
|
|
1600
|
+
// provider must say so in the profile (email_verified); an explicit "not
|
|
1601
|
+
// verified" outweighs the list.
|
|
1602
|
+
var VERIFIED_EMAIL_PROVIDERS = { google: true, apple: true };
|
|
1603
|
+
var OAUTH_UNVERIFIED = '__oauthUnverified';
|
|
1604
|
+
// A provider's raw account id could equal another account's id here (see
|
|
1605
|
+
// oauthIdsNeedNamespace in the code generator): sessions without a local
|
|
1606
|
+
// account take the provider's id as "oauth:<provider>:<id>".
|
|
1607
|
+
var OAUTH_IDS_NAMESPACED = ${JSON.stringify(oauthIdsNeedNamespace(auth))};
|
|
1608
|
+
|
|
1609
|
+
// Microsoft (Azure AD / Entra ID) tokens carry no email_verified, and on the
|
|
1610
|
+
// multi-tenant endpoint the email claim is whatever the signing-in tenant's
|
|
1611
|
+
// administrator set (the "nOAuth" class). An email counts only when the token
|
|
1612
|
+
// says its domain is verified (xms_edov), or when the provider is configured
|
|
1613
|
+
// for one specific tenant (an id or a domain, never common / organizations /
|
|
1614
|
+
// consumers): the site owner's own directory.
|
|
1615
|
+
var MICROSOFT_PROVIDERS = { 'azure-ad': true, 'microsoft-entra-id': true };
|
|
1616
|
+
var MICROSOFT_TENANTS = { ${tenantEntries} };
|
|
1617
|
+
var MICROSOFT_TENANT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
1618
|
+
|
|
1619
|
+
// The one tenant a Microsoft provider is configured for, or '' (multi-tenant).
|
|
1620
|
+
function microsoftTenantOf(value) {
|
|
1621
|
+
var raw = String(value == null ? '' : value).trim().toLowerCase();
|
|
1622
|
+
if (!raw || raw.indexOf('teleporthq.secrets.') === 0) {
|
|
1623
|
+
return '';
|
|
1624
|
+
}
|
|
1625
|
+
var fromIssuer = /^https:\\/\\/login\\.microsoftonline\\.com\\/([^\\/?#]+)/.exec(raw);
|
|
1626
|
+
var tenant = fromIssuer ? fromIssuer[1] : raw;
|
|
1627
|
+
if (tenant === 'common' || tenant === 'organizations' || tenant === 'consumers') {
|
|
1628
|
+
return '';
|
|
1629
|
+
}
|
|
1630
|
+
return MICROSOFT_TENANT_ID.test(tenant) || /^[a-z0-9-]+(\\.[a-z0-9-]+)+$/.test(tenant) ? tenant : '';
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
function microsoftEmailIsVerified(provider, claims) {
|
|
1634
|
+
if (claims.xms_edov === true || claims.xms_edov === 'true' || claims.xms_edov === 1) {
|
|
1635
|
+
return true;
|
|
1636
|
+
}
|
|
1637
|
+
var tenant = microsoftTenantOf(MICROSOFT_TENANTS[provider]);
|
|
1638
|
+
if (!tenant) {
|
|
1639
|
+
return false;
|
|
1640
|
+
}
|
|
1641
|
+
// A tenant given by id must be the one the token was issued in.
|
|
1642
|
+
if (MICROSOFT_TENANT_ID.test(tenant) && claims.tid != null && String(claims.tid).toLowerCase() !== tenant) {
|
|
1643
|
+
return false;
|
|
1644
|
+
}
|
|
1645
|
+
return true;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// GitHub's profile email is not always a verified one: ask GitHub (the
|
|
1649
|
+
// provider requests the user:email scope) whether this address is.
|
|
1650
|
+
async function githubEmailIsVerified(account, email) {
|
|
1651
|
+
if (!account || !account.access_token || !email) {
|
|
1652
|
+
return false;
|
|
1653
|
+
}
|
|
1654
|
+
try {
|
|
1655
|
+
var res = await fetch('https://api.github.com/user/emails', {
|
|
1656
|
+
headers: {
|
|
1657
|
+
Authorization: 'Bearer ' + account.access_token,
|
|
1658
|
+
Accept: 'application/vnd.github+json',
|
|
1659
|
+
'User-Agent': 'teleporthq-auth',
|
|
1660
|
+
},
|
|
1661
|
+
});
|
|
1662
|
+
if (!res.ok) {
|
|
1663
|
+
return false;
|
|
1664
|
+
}
|
|
1665
|
+
var list = await res.json();
|
|
1666
|
+
var wanted = String(email).toLowerCase();
|
|
1667
|
+
return Array.isArray(list) && list.some(function (entry) {
|
|
1668
|
+
return !!entry && entry.verified === true && String(entry.email || '').toLowerCase() === wanted;
|
|
1669
|
+
});
|
|
1670
|
+
} catch (e) {
|
|
1671
|
+
return false;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
async function oauthEmailIsVerified(account, profile, token) {
|
|
1676
|
+
var claims = profile || {};
|
|
1677
|
+
if (claims.email_verified === false || claims.email_verified === 'false') {
|
|
1678
|
+
return false;
|
|
1679
|
+
}
|
|
1680
|
+
if (claims.email_verified === true || claims.email_verified === 'true') {
|
|
1681
|
+
return true;
|
|
1682
|
+
}
|
|
1683
|
+
var provider = String((account && account.provider) || '');
|
|
1684
|
+
if (provider === 'github') {
|
|
1685
|
+
return githubEmailIsVerified(account, token && token.email);
|
|
1686
|
+
}
|
|
1687
|
+
if (MICROSOFT_PROVIDERS[provider] === true) {
|
|
1688
|
+
return microsoftEmailIsVerified(provider, claims);
|
|
1689
|
+
}
|
|
1690
|
+
return VERIFIED_EMAIL_PROVIDERS[provider] === true;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// The provider's own account id as "oauth:<provider>:<id>": a namespace no
|
|
1694
|
+
// local account id can share (a provider's integer ids would otherwise match
|
|
1695
|
+
// local integer ids on owner rules). Where no id can collide, the raw id stays.
|
|
1696
|
+
function namespaceOAuthId(token, account) {
|
|
1697
|
+
if (!OAUTH_IDS_NAMESPACED) {
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
var accountId = String((account && account.providerAccountId) || token.sub || token.id || '');
|
|
1701
|
+
var id = 'oauth:' + String((account && account.provider) || 'provider') + ':' + accountId;
|
|
1702
|
+
token.id = id;
|
|
1703
|
+
token.sub = id;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
// Not vouched for: the visitor keeps a session under an id no local account can
|
|
1707
|
+
// have (namespaced where one could), with no email to be matched by and no role.
|
|
1708
|
+
function asDistinctOAuthIdentity(token, account) {
|
|
1709
|
+
namespaceOAuthId(token, account);
|
|
1710
|
+
delete token.email;
|
|
1711
|
+
delete token.role;
|
|
1712
|
+
delete token.roleName;
|
|
1713
|
+
delete token.roles;
|
|
1714
|
+
token[OAUTH_UNVERIFIED] = true;
|
|
1715
|
+
}`
|
|
890
1716
|
}
|
|
891
1717
|
|
|
892
1718
|
/**
|
|
@@ -909,6 +1735,7 @@ const AUTH_ERROR_MESSAGES = {
|
|
|
909
1735
|
InvalidCredentials: 'That email and password do not match an account.',
|
|
910
1736
|
ServiceUnavailable:
|
|
911
1737
|
'We could not reach the account service. Please try again in a moment — if it keeps happening, the site owner needs to check its database configuration.',
|
|
1738
|
+
TooManyAttempts: 'Too many sign-in attempts. Please wait a few minutes and try again.',
|
|
912
1739
|
|
|
913
1740
|
// NextAuth's own.
|
|
914
1741
|
CredentialsSignin: 'That email and password do not match an account.',
|
|
@@ -1008,37 +1835,163 @@ module.exports = {
|
|
|
1008
1835
|
`
|
|
1009
1836
|
}
|
|
1010
1837
|
|
|
1838
|
+
/** The password a site accepts when one is set (sign-up, reset, a change). */
|
|
1839
|
+
export const MIN_PASSWORD_LENGTH = 8
|
|
1840
|
+
export const MAX_PASSWORD_BYTES = 128
|
|
1841
|
+
|
|
1011
1842
|
export const generateHashPasswordFile = (): string => {
|
|
1012
1843
|
return `const bcrypt = require('bcryptjs');
|
|
1013
1844
|
const crypto = require('crypto');
|
|
1014
1845
|
|
|
1015
|
-
const PEPPER = process.env.AUTH_PEPPER || '';
|
|
1016
1846
|
const COST_FACTOR = 12;
|
|
1847
|
+
const MIN_PASSWORD_LENGTH = ${MIN_PASSWORD_LENGTH};
|
|
1848
|
+
const MAX_PASSWORD_BYTES = ${MAX_PASSWORD_BYTES};
|
|
1849
|
+
|
|
1850
|
+
// Two peppers. AUTH_PEPPER_SECRET is the project's own random secret, which the
|
|
1851
|
+
// deploy step provisions; every new hash uses it. AUTH_PEPPER (the project id,
|
|
1852
|
+
// no secret) is the one every hash was made with before it existed: such a hash
|
|
1853
|
+
// still verifies, and the sign-in replaces it. Without the secret, hashing and
|
|
1854
|
+
// verifying stay on the old pepper — never on none.
|
|
1855
|
+
function pepperSecret() {
|
|
1856
|
+
var value = process.env.AUTH_PEPPER_SECRET;
|
|
1857
|
+
return typeof value === 'string' && value && value.indexOf('teleporthq.secrets.') !== 0 ? value : '';
|
|
1858
|
+
}
|
|
1017
1859
|
|
|
1018
|
-
function
|
|
1860
|
+
function legacyPepper() {
|
|
1861
|
+
return process.env.AUTH_PEPPER || '';
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// SHA-256 first, so bcrypt (which reads at most 72 bytes) sees all of it.
|
|
1865
|
+
function prepare(password, pepper) {
|
|
1019
1866
|
const str = String(password);
|
|
1020
1867
|
if (str.length > 1000) {
|
|
1021
1868
|
throw new Error('Password exceeds maximum length');
|
|
1022
1869
|
}
|
|
1023
|
-
|
|
1024
|
-
return sha;
|
|
1870
|
+
return crypto.createHash('sha256').update(str + pepper).digest('base64');
|
|
1025
1871
|
}
|
|
1026
1872
|
|
|
1027
1873
|
function hashPassword(password) {
|
|
1028
|
-
return bcrypt.hashSync(prepare(password), COST_FACTOR);
|
|
1874
|
+
return bcrypt.hashSync(prepare(password, pepperSecret() || legacyPepper()), COST_FACTOR);
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// A hash no password matches, made once per process with the project pepper
|
|
1878
|
+
// at the same cost. An address without an account, or a row without a
|
|
1879
|
+
// password (a subscriber, a guest), is checked against it, so every failed
|
|
1880
|
+
// sign-in does the bcrypt work a wrong password does: the time never tells
|
|
1881
|
+
// them apart.
|
|
1882
|
+
var __standInHash = null;
|
|
1883
|
+
function standInHash() {
|
|
1884
|
+
if (!__standInHash) {
|
|
1885
|
+
__standInHash = bcrypt.hashSync(prepare(crypto.randomBytes(32).toString('hex'), pepperSecret() || legacyPepper()), COST_FACTOR);
|
|
1886
|
+
}
|
|
1887
|
+
return __standInHash;
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// { ok, needsRehash }: ok when the password matches; needsRehash when it
|
|
1891
|
+
// matched through the old pepper while the project has its own.
|
|
1892
|
+
function checkPassword(password, hash) {
|
|
1893
|
+
var known = typeof hash === 'string' && hash !== '';
|
|
1894
|
+
var against = known ? hash : standInHash();
|
|
1895
|
+
var secret = pepperSecret();
|
|
1896
|
+
if (secret && bcrypt.compareSync(prepare(password, secret), against) && known) {
|
|
1897
|
+
return { ok: true, needsRehash: false };
|
|
1898
|
+
}
|
|
1899
|
+
// Run on every miss, whatever the hash was.
|
|
1900
|
+
if (bcrypt.compareSync(prepare(password, legacyPepper()), against) && known) {
|
|
1901
|
+
return { ok: true, needsRehash: !!secret };
|
|
1902
|
+
}
|
|
1903
|
+
return { ok: false, needsRehash: false };
|
|
1029
1904
|
}
|
|
1030
1905
|
|
|
1031
1906
|
function verifyPassword(password, hash) {
|
|
1032
|
-
return
|
|
1907
|
+
return checkPassword(password, hash).ok;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// Why a password may not be set, or '' when it may.
|
|
1911
|
+
function passwordPolicyError(password) {
|
|
1912
|
+
if (typeof password !== 'string' || password.length < MIN_PASSWORD_LENGTH) {
|
|
1913
|
+
return 'Password must be at least ' + MIN_PASSWORD_LENGTH + ' characters';
|
|
1914
|
+
}
|
|
1915
|
+
if (Buffer.byteLength(password, 'utf8') > MAX_PASSWORD_BYTES) {
|
|
1916
|
+
return 'Password is too long (at most ' + MAX_PASSWORD_BYTES + ' bytes)';
|
|
1917
|
+
}
|
|
1918
|
+
return '';
|
|
1033
1919
|
}
|
|
1034
1920
|
|
|
1035
1921
|
module.exports = hashPassword;
|
|
1036
1922
|
module.exports.hashPassword = hashPassword;
|
|
1037
1923
|
module.exports.verifyPassword = verifyPassword;
|
|
1924
|
+
module.exports.checkPassword = checkPassword;
|
|
1925
|
+
module.exports.passwordPolicyError = passwordPolicyError;
|
|
1038
1926
|
`
|
|
1039
1927
|
}
|
|
1040
1928
|
|
|
1041
|
-
export
|
|
1929
|
+
export interface NextAuthRouteOptions {
|
|
1930
|
+
/** Credentials sign-in is on: the route throttles it (see checkSignInAllowed). */
|
|
1931
|
+
passwordAuthEnabled?: boolean
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// Answered to a credentials sign-in the throttle refuses, in the shape
|
|
1935
|
+
// next-auth/react's signIn() reads (it parses \`url\` for the error code, which
|
|
1936
|
+
// describeAuthError turns into copy).
|
|
1937
|
+
const SIGN_IN_THROTTLE_CODE = `var __security = require('${securityRuntimeSpecifier('../../..')}');
|
|
1938
|
+
|
|
1939
|
+
function isCredentialsSignIn(req) {
|
|
1940
|
+
var parts = req && req.query ? req.query.nextauth : null;
|
|
1941
|
+
return (
|
|
1942
|
+
req && req.method === 'POST' && Array.isArray(parts) && parts[0] === 'callback' && parts[1] === 'credentials'
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
function refuseSignIn(res, status, code, message, retryAfter) {
|
|
1947
|
+
var origin = process.env.NEXTAUTH_URL || 'http://localhost:3000';
|
|
1948
|
+
var url;
|
|
1949
|
+
try {
|
|
1950
|
+
url = new URL('/api/auth/error?error=' + code, origin).toString();
|
|
1951
|
+
} catch (e) {
|
|
1952
|
+
url = 'http://localhost:3000/api/auth/error?error=' + code;
|
|
1953
|
+
}
|
|
1954
|
+
if (retryAfter) {
|
|
1955
|
+
res.setHeader('Retry-After', String(retryAfter));
|
|
1956
|
+
}
|
|
1957
|
+
res.status(status).json({ url: url, error: message });
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// Too many failed (or attempted) sign-ins for this email from this visitor:
|
|
1961
|
+
// refused before the password is even looked at.
|
|
1962
|
+
async function throttleCredentialsSignIn(req, res) {
|
|
1963
|
+
if (req && req.headers) {
|
|
1964
|
+
delete req.headers[authOptions.SIGN_IN_VISITOR_HEADER];
|
|
1965
|
+
}
|
|
1966
|
+
if (!isCredentialsSignIn(req)) {
|
|
1967
|
+
return false;
|
|
1968
|
+
}
|
|
1969
|
+
req.headers[authOptions.SIGN_IN_VISITOR_HEADER] = __security.clientIp(req);
|
|
1970
|
+
var verdict;
|
|
1971
|
+
try {
|
|
1972
|
+
verdict = await authOptions.checkSignInAllowed(req);
|
|
1973
|
+
} catch (err) {
|
|
1974
|
+
console.error('[auth] The sign-in throttle failed:', err && err.message ? err.message : err);
|
|
1975
|
+
refuseSignIn(res, 503, 'ServiceUnavailable', 'Sign-in is unavailable right now. Please try again in a moment.', 0);
|
|
1976
|
+
return true;
|
|
1977
|
+
}
|
|
1978
|
+
if (verdict && verdict.allowed) {
|
|
1979
|
+
return false;
|
|
1980
|
+
}
|
|
1981
|
+
refuseSignIn(
|
|
1982
|
+
res,
|
|
1983
|
+
429,
|
|
1984
|
+
'TooManyAttempts',
|
|
1985
|
+
'Too many sign-in attempts. Try again later.',
|
|
1986
|
+
verdict && verdict.retryAfter ? verdict.retryAfter : ${SIGN_IN_WINDOW_SECONDS}
|
|
1987
|
+
);
|
|
1988
|
+
return true;
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1991
|
+
`
|
|
1992
|
+
|
|
1993
|
+
export const generateNextAuthRouteFile = (options: NextAuthRouteOptions = {}): string => {
|
|
1994
|
+
const throttled = options.passwordAuthEnabled === true
|
|
1042
1995
|
return `let NextAuth = require('next-auth');
|
|
1043
1996
|
if (NextAuth.default) NextAuth = NextAuth.default;
|
|
1044
1997
|
const authOptions = require('../../../utils/auth/auth-options');
|
|
@@ -1133,13 +2086,22 @@ function resolveRequestOrigin(req) {
|
|
|
1133
2086
|
return proto + '://' + host;
|
|
1134
2087
|
}
|
|
1135
2088
|
|
|
1136
|
-
module.exports =
|
|
2089
|
+
${throttled ? SIGN_IN_THROTTLE_CODE : ''}module.exports = ${
|
|
2090
|
+
throttled ? 'async ' : ''
|
|
2091
|
+
}function nextAuthRoute(req, res) {
|
|
1137
2092
|
try {
|
|
1138
2093
|
process.env.NEXTAUTH_URL = resolveRequestOrigin(req);
|
|
1139
2094
|
} catch (e) {
|
|
1140
2095
|
/* fall back to whatever NEXTAUTH_URL holds */
|
|
1141
2096
|
}
|
|
1142
|
-
|
|
2097
|
+
${
|
|
2098
|
+
throttled
|
|
2099
|
+
? ` if (await throttleCredentialsSignIn(req, res)) {
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
`
|
|
2103
|
+
: ''
|
|
2104
|
+
} return authHandler(req, res);
|
|
1143
2105
|
};
|
|
1144
2106
|
`
|
|
1145
2107
|
}
|
|
@@ -1161,31 +2123,333 @@ export interface WelcomeEmailOptions {
|
|
|
1161
2123
|
siteName?: string
|
|
1162
2124
|
}
|
|
1163
2125
|
|
|
2126
|
+
/** The business events an account route records (spec §5.1). */
|
|
2127
|
+
export interface AccountEventOptions {
|
|
2128
|
+
/** The project has the in-app event bus (`hasAutomationsBus`). */
|
|
2129
|
+
withAutomations?: boolean
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
/**
|
|
2133
|
+
* `recordAccountEvent(type, user, source, req)`, for the account routes of a
|
|
2134
|
+
* project with automations: records `user-signed-up` / `user-deleted` on the
|
|
2135
|
+
* in-app event bus, which writes the `teleport_events` row and wakes the
|
|
2136
|
+
* automations off the request path, on the app's own origin as `req` reached
|
|
2137
|
+
* it. Only the profile fields go into the event, never a credential. It never
|
|
2138
|
+
* throws, so the event cannot fail the request it records. `relativePrefix`
|
|
2139
|
+
* leads from the route to the project root.
|
|
2140
|
+
*/
|
|
2141
|
+
export const generateAccountEventRecorder = (options: {
|
|
2142
|
+
relativePrefix: string
|
|
2143
|
+
logTag: string
|
|
2144
|
+
}): string => `
|
|
2145
|
+
// The project has automations: the account event wakes them. Best effort,
|
|
2146
|
+
// like the emails: recording it can never fail the request.
|
|
2147
|
+
function accountEventTime(value) {
|
|
2148
|
+
var date = value ? new Date(value) : new Date();
|
|
2149
|
+
return isNaN(date.getTime()) ? new Date().toISOString() : date.toISOString();
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
async function recordAccountEvent(type, user, source, req) {
|
|
2153
|
+
try {
|
|
2154
|
+
var userId = user ? user.id || user._id : null;
|
|
2155
|
+
if (!userId) { return; }
|
|
2156
|
+
var id = String(userId);
|
|
2157
|
+
var email = user.email ? String(user.email) : '';
|
|
2158
|
+
var bus = require('${options.relativePrefix}/${AUTOMATIONS_PATH.join('/')}/event-bus');
|
|
2159
|
+
await bus.emitBusinessEvent({
|
|
2160
|
+
type: type,
|
|
2161
|
+
entityType: 'user',
|
|
2162
|
+
entityId: id,
|
|
2163
|
+
payload: {
|
|
2164
|
+
user: {
|
|
2165
|
+
id: id,
|
|
2166
|
+
email: email || null,
|
|
2167
|
+
name: user.name || null,
|
|
2168
|
+
role: user.role || null,
|
|
2169
|
+
source: source || user.source || null,
|
|
2170
|
+
createdAt: accountEventTime(user.created_at || user.createdAt),
|
|
2171
|
+
},
|
|
2172
|
+
},
|
|
2173
|
+
actor: email,
|
|
2174
|
+
actorType: 'customer',
|
|
2175
|
+
dedupeKey: type + ':' + id,
|
|
2176
|
+
}, req);
|
|
2177
|
+
} catch (eventErr) {
|
|
2178
|
+
var reason = eventErr && eventErr.message ? eventErr.message : eventErr;
|
|
2179
|
+
console.error('${options.logTag} could not record the ' + type + ' event:', reason);
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
`
|
|
2183
|
+
|
|
2184
|
+
/**
|
|
2185
|
+
* A user column that grants something — a role, an admin or staff flag, a
|
|
2186
|
+
* verified email, credit, a plan or an entitlement — in any spelling
|
|
2187
|
+
* (`is_admin`, `isAdmin`, `Email-Verified`). Only the server sets these; a
|
|
2188
|
+
* sign-up never takes them from the visitor. The one shared rule
|
|
2189
|
+
* (`AccountFields.isPrivilegedAccountField`).
|
|
2190
|
+
*/
|
|
2191
|
+
export const isPrivilegedUserField = (key: string): boolean =>
|
|
2192
|
+
AccountFields.isPrivilegedAccountField(key)
|
|
2193
|
+
|
|
2194
|
+
/** Config keys of an `account-signup` node that are not user fields. */
|
|
2195
|
+
const SIGNUP_NODE_NON_FIELD_KEYS = new Set<string>([
|
|
2196
|
+
'email',
|
|
2197
|
+
'password',
|
|
2198
|
+
'name',
|
|
2199
|
+
'image',
|
|
2200
|
+
'role',
|
|
2201
|
+
...WELCOME_EMAIL_CONFIG_KEYS,
|
|
2202
|
+
])
|
|
2203
|
+
|
|
2204
|
+
/**
|
|
2205
|
+
* The fields a sign-up may write besides email, password and name: the avatar
|
|
2206
|
+
* (`image`) and each declared custom account property that a sign-up form of
|
|
2207
|
+
* the project collects (a key of an `account-signup` node), never a
|
|
2208
|
+
* privileged one.
|
|
2209
|
+
*/
|
|
2210
|
+
export const resolveSignupFields = (
|
|
2211
|
+
auth: UIDLAuthentication,
|
|
2212
|
+
signupNodeConfigs: Array<Record<string, unknown>>
|
|
2213
|
+
): string[] => {
|
|
2214
|
+
const declared = new Set((auth.customUserProperties || []).map((prop) => prop.key))
|
|
2215
|
+
const collected = new Set<string>()
|
|
2216
|
+
for (const config of signupNodeConfigs) {
|
|
2217
|
+
for (const key of Object.keys(config || {})) {
|
|
2218
|
+
if (
|
|
2219
|
+
!SIGNUP_NODE_NON_FIELD_KEYS.has(key) &&
|
|
2220
|
+
declared.has(key) &&
|
|
2221
|
+
!isPrivilegedUserField(key)
|
|
2222
|
+
) {
|
|
2223
|
+
collected.add(key)
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2227
|
+
return ['image', ...Array.from(collected).sort()]
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
export interface SignupRouteOptions {
|
|
2231
|
+
/** `resolveSignupFields` — what a sign-up may write besides email, password, name. */
|
|
2232
|
+
fields?: string[]
|
|
2233
|
+
/**
|
|
2234
|
+
* The page the set-password link opens and where it carries its token: the
|
|
2235
|
+
* project's reset link (`resetLinkOf`). Default: `/reset-password`, `?token=`.
|
|
2236
|
+
*/
|
|
2237
|
+
resetPagePath?: string
|
|
2238
|
+
tokenInFragment?: boolean
|
|
2239
|
+
/**
|
|
2240
|
+
* The forgot-password node's sender (`resetEmailSenderOf`): the emails to an
|
|
2241
|
+
* address the site already knows go through it when the sign-up node has no
|
|
2242
|
+
* email provider.
|
|
2243
|
+
*/
|
|
2244
|
+
noticeEmail?: KnownAddressEmailSender
|
|
2245
|
+
/**
|
|
2246
|
+
* The project has the newsletter, and the sign-up form's box joins it: a
|
|
2247
|
+
* ticked box (consent) also subscribes the new account through the
|
|
2248
|
+
* newsletter helper (`subscribeNewsletterPerson`, sign-up point `signup`).
|
|
2249
|
+
*/
|
|
2250
|
+
newsletter?: boolean
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
/**
|
|
2254
|
+
* After a new account with a ticked consent box: the newsletter helper adds it
|
|
2255
|
+
* (double opt-in, the `newsletter-subscribed` event, the newsletter's welcome
|
|
2256
|
+
* unless the account's own went out). Best effort, like the welcome email.
|
|
2257
|
+
*/
|
|
2258
|
+
const SIGNUP_NEWSLETTER_CODE = ` // The ticked box also subscribes the person to the newsletter.
|
|
2259
|
+
if (userData.marketing_consent) {
|
|
2260
|
+
try {
|
|
2261
|
+
const __newsletter = require('../../../utils/newsletter/newsletter');
|
|
2262
|
+
await __newsletter.withNewsletterDb(function (db) {
|
|
2263
|
+
return __newsletter.subscribeNewsletterPerson({ db: db, email: email, signUpPoint: 'signup', req: req });
|
|
2264
|
+
});
|
|
2265
|
+
} catch (newsletterErr) {
|
|
2266
|
+
console.error('[account-signup] newsletter sign-up failed:', newsletterErr && newsletterErr.message ? newsletterErr.message : newsletterErr);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
`
|
|
2270
|
+
|
|
2271
|
+
/** An email sender baked into a route: provider id without `email-`, sender, credential env var. */
|
|
2272
|
+
export interface KnownAddressEmailSender {
|
|
2273
|
+
emailProvider?: string | null
|
|
2274
|
+
fromEmail?: string
|
|
2275
|
+
emailSecretEnvName?: string | null
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
/**
|
|
2279
|
+
* The answer to a sign-up for an address the site already knows (an account,
|
|
2280
|
+
* or a row without a password) takes at least this long, as the
|
|
2281
|
+
* forgot-password answer does: the two are answered alike, and the time must
|
|
2282
|
+
* not tell them apart either.
|
|
2283
|
+
*/
|
|
2284
|
+
export const KNOWN_ADDRESS_MIN_RESPONSE_MS = 1200
|
|
2285
|
+
|
|
2286
|
+
/**
|
|
2287
|
+
* The sign-up route's emails to an address the site already knows, sent
|
|
2288
|
+
* through `sender` (the sign-up node's, else the forgot-password node's):
|
|
2289
|
+
* `sendAccountNotice(emailType, msg, logged)`, which sends and records the
|
|
2290
|
+
* email under its own purpose (with what the log may keep), and
|
|
2291
|
+
* `sendSetPasswordEmail(user, siteName, req)`, the link for an address without
|
|
2292
|
+
* a password, built on the forgot-password token flow (a CSPRNG token whose
|
|
2293
|
+
* SHA-256 alone is stored against the account, the same lifetime, origin rule
|
|
2294
|
+
* and reset page).
|
|
2295
|
+
*/
|
|
2296
|
+
const generateKnownAddressEmailCode = (options: {
|
|
2297
|
+
sender: { provider: string | null; from: string; secretEnv: string }
|
|
2298
|
+
pagePath: string
|
|
2299
|
+
tokenInFragment: boolean
|
|
2300
|
+
}): string => {
|
|
2301
|
+
const { sender } = options
|
|
2302
|
+
const senderConstants = `
|
|
2303
|
+
// Who sends the emails to an address the site already knows: the sign-up
|
|
2304
|
+
// node's email settings, else the forgot-password node's.
|
|
2305
|
+
const NOTICE_EMAIL_PROVIDER = ${JSON.stringify(sender.provider)};
|
|
2306
|
+
const NOTICE_EMAIL_FROM = ${JSON.stringify(sender.from)};
|
|
2307
|
+
const NOTICE_EMAIL_SECRET_ENV_NAME = ${JSON.stringify(sender.secretEnv)};`
|
|
2308
|
+
if (!sender.provider) {
|
|
2309
|
+
return `${senderConstants}
|
|
2310
|
+
|
|
2311
|
+
// No email provider: nothing is sent to an address the site already knows.
|
|
2312
|
+
async function sendAccountNotice() {}
|
|
2313
|
+
|
|
2314
|
+
// A sign-up for an address the site knows without a password mails its owner
|
|
2315
|
+
// a link to set one; this project has no email provider, so none is sent.
|
|
2316
|
+
async function sendSetPasswordEmail() {
|
|
2317
|
+
console.warn('[account-signup] set-password link not sent: no email provider is configured');
|
|
2318
|
+
}`
|
|
2319
|
+
}
|
|
2320
|
+
const ttlMinutes = Math.round(PASSWORD_RESET_TOKEN_TTL_SECONDS / 60)
|
|
2321
|
+
// Where the link carries the token, as the forgot-password link does.
|
|
2322
|
+
const tokenLinkPrefix = options.tokenInFragment ? '#token=' : '?token='
|
|
2323
|
+
const noticeSend = generateProviderSendFunction(sender.provider).replace(
|
|
2324
|
+
'async function __sendProviderEmail(',
|
|
2325
|
+
'async function __sendNoticeEmail('
|
|
2326
|
+
)
|
|
2327
|
+
return `${senderConstants}
|
|
2328
|
+
|
|
2329
|
+
${noticeSend}
|
|
2330
|
+
|
|
2331
|
+
var __noticeEmailLog = require('../../../utils/email/sent-email-log');
|
|
2332
|
+
|
|
2333
|
+
// Sends one of these emails and records it under its own purpose, with what
|
|
2334
|
+
// the log may keep of it (\`logged\`: a link's token hidden).
|
|
2335
|
+
async function sendAccountNotice(emailType, msg, logged) {
|
|
2336
|
+
var failure = null;
|
|
2337
|
+
try {
|
|
2338
|
+
await __sendNoticeEmail(msg);
|
|
2339
|
+
} catch (err) {
|
|
2340
|
+
failure = err;
|
|
2341
|
+
}
|
|
2342
|
+
__noticeEmailLog.recordSentEmail({
|
|
2343
|
+
emailType: emailType,
|
|
2344
|
+
audience: 'customer',
|
|
2345
|
+
to: msg.to,
|
|
2346
|
+
from: msg.from,
|
|
2347
|
+
subject: logged.subject,
|
|
2348
|
+
html: logged.html,
|
|
2349
|
+
payload: logged.payload,
|
|
2350
|
+
provider: NOTICE_EMAIL_PROVIDER,
|
|
2351
|
+
status: failure ? 'failed' : 'sent',
|
|
2352
|
+
error: failure ? (failure.message || String(failure)) : null,
|
|
2353
|
+
source: 'signup',
|
|
2354
|
+
sourceRef: 'api/auth/signup',
|
|
2355
|
+
userId: msg.userId,
|
|
2356
|
+
});
|
|
2357
|
+
if (failure) { throw failure; }
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
// A sign-up for an address the site knows without a password (a newsletter
|
|
2361
|
+
// subscriber, a checkout or booking guest, a contact the owner added) never
|
|
2362
|
+
// takes that row: whoever typed the address may not own it. Its owner is mailed
|
|
2363
|
+
// a link to set a password instead, a password-reset link minted and stored as
|
|
2364
|
+
// the forgot-password route mints it, at most ${EXISTING_ACCOUNT_EMAIL_LIMIT} an hour per address
|
|
2365
|
+
// (counted with the "already have an account" email).
|
|
2366
|
+
var SET_PASSWORD_SUBJECT = 'Set your password to finish creating your account';
|
|
2367
|
+
var SET_PASSWORD_BODY_HTML =
|
|
2368
|
+
'<p>Someone, hopefully you, tried to create an account{{siteNameSuffix}} with this email address.</p>' +
|
|
2369
|
+
'<p>This address is already known here (from a newsletter sign-up or an order, for example), but it has no password yet. Set one to finish creating your account:</p>' +
|
|
2370
|
+
'<p><a href="{{resetUrl}}">Set your password</a></p>' +
|
|
2371
|
+
'<p>The link works for ${ttlMinutes} minutes. If this was not you, you can ignore this email: nothing has changed.</p>';
|
|
2372
|
+
var SET_PASSWORD_PAGE_PATH = ${JSON.stringify(options.pagePath)};
|
|
2373
|
+
var SET_PASSWORD_TOKEN_LINK_PREFIX = ${JSON.stringify(tokenLinkPrefix)};
|
|
2374
|
+
var SET_PASSWORD_TOKEN_TTL_SECONDS = ${PASSWORD_RESET_TOKEN_TTL_SECONDS};
|
|
2375
|
+
|
|
2376
|
+
${TRUSTED_ORIGIN_CODE}
|
|
2377
|
+
|
|
2378
|
+
// The token, wherever it appears, for what the sent-email log keeps: whoever
|
|
2379
|
+
// can read the log must not be able to set the password with it.
|
|
2380
|
+
function hideToken(text, token) {
|
|
2381
|
+
return String(text == null ? '' : text).split(token).join('[hidden]');
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
async function sendSetPasswordEmail(user, siteName, req) {
|
|
2385
|
+
var toEmail = String(user.email || '');
|
|
2386
|
+
var userId = user.id != null ? user.id : user._id;
|
|
2387
|
+
var apiKey = NOTICE_EMAIL_SECRET_ENV_NAME ? process.env[NOTICE_EMAIL_SECRET_ENV_NAME] : '';
|
|
2388
|
+
if (apiKey && String(apiKey).indexOf('teleporthq.secrets.') === 0) { apiKey = ''; }
|
|
2389
|
+
var from = NOTICE_EMAIL_FROM || process.env.EMAIL_FROM || '';
|
|
2390
|
+
if (!apiKey || !from) { console.warn('[account-signup] set-password link not sent: the email credential or sender is not set'); return; }
|
|
2391
|
+
if (!toEmail || userId == null) { return; }
|
|
2392
|
+
var allowed = await __security.rateLimit({
|
|
2393
|
+
key: 'auth-signup-existing:' + toEmail.trim().toLowerCase(),
|
|
2394
|
+
limit: ${EXISTING_ACCOUNT_EMAIL_LIMIT},
|
|
2395
|
+
windowSeconds: 3600,
|
|
2396
|
+
});
|
|
2397
|
+
if (!allowed.allowed) { return; }
|
|
2398
|
+
var crypto = require('crypto');
|
|
2399
|
+
var token = crypto.randomBytes(32).toString('hex');
|
|
2400
|
+
await authUtils.storePasswordReset(userId, crypto.createHash('sha256').update(token).digest('hex'), SET_PASSWORD_TOKEN_TTL_SECONDS);
|
|
2401
|
+
var resetUrl = trustedOrigin(req) + SET_PASSWORD_PAGE_PATH + SET_PASSWORD_TOKEN_LINK_PREFIX + token;
|
|
2402
|
+
var html = fillTemplate(SET_PASSWORD_BODY_HTML, { siteNameSuffix: siteName ? ' on ' + siteName : '', resetUrl: resetUrl });
|
|
2403
|
+
await sendAccountNotice(
|
|
2404
|
+
${JSON.stringify(SET_PASSWORD_EMAIL)},
|
|
2405
|
+
{ from: from, to: toEmail, subject: SET_PASSWORD_SUBJECT, html: html, apiKey: apiKey, userId: userId },
|
|
2406
|
+
{
|
|
2407
|
+
subject: SET_PASSWORD_SUBJECT,
|
|
2408
|
+
html: hideToken(html, token),
|
|
2409
|
+
payload: { resetUrl: hideToken(resetUrl, token), recipientEmail: toEmail },
|
|
2410
|
+
}
|
|
2411
|
+
);
|
|
2412
|
+
}`
|
|
2413
|
+
}
|
|
2414
|
+
|
|
1164
2415
|
export const generateSignupRouteFile = (
|
|
1165
2416
|
auth: UIDLAuthentication,
|
|
1166
|
-
welcome: WelcomeEmailOptions = {}
|
|
2417
|
+
welcome: WelcomeEmailOptions = {},
|
|
2418
|
+
events: AccountEventOptions = {},
|
|
2419
|
+
signup: SignupRouteOptions = {}
|
|
1167
2420
|
): string => {
|
|
1168
2421
|
const needsDb = !!auth.dataSourceType
|
|
2422
|
+
const recordsSignup = needsDb && events.withAutomations === true
|
|
1169
2423
|
const welcomeProvider =
|
|
1170
2424
|
welcome.emailProvider && SUPPORTED_EMAIL_PROVIDERS.has(welcome.emailProvider)
|
|
1171
2425
|
? welcome.emailProvider
|
|
1172
2426
|
: null
|
|
1173
|
-
//
|
|
1174
|
-
//
|
|
1175
|
-
//
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
//
|
|
1182
|
-
//
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
2427
|
+
// An ALLOW-list: the only body keys copied onto the new user row. Whatever
|
|
2428
|
+
// else the body carries (a role, `is_admin`, `email_verified`, credits, a
|
|
2429
|
+
// plan, a welcome-email config key) never reaches createUser. The columns
|
|
2430
|
+
// the route sets itself (source, consent) are never copied from it either.
|
|
2431
|
+
const signupFields = (signup.fields || ['image']).filter(
|
|
2432
|
+
(field) => field && !isPrivilegedUserField(field) && SIGNUP_OWN_COLUMNS.indexOf(field) === -1
|
|
2433
|
+
)
|
|
2434
|
+
const setPasswordPagePath = resolveResetPagePath(signup.resetPagePath)
|
|
2435
|
+
// The emails to an address the site already knows go through the sign-up
|
|
2436
|
+
// node's sender, else the forgot-password node's.
|
|
2437
|
+
const fallbackSender = signup.noticeEmail || {}
|
|
2438
|
+
const noticeSender = welcomeProvider
|
|
2439
|
+
? {
|
|
2440
|
+
provider: welcomeProvider,
|
|
2441
|
+
from: welcome.fromEmail || '',
|
|
2442
|
+
secretEnv: welcome.emailSecretEnvName || '',
|
|
2443
|
+
}
|
|
2444
|
+
: {
|
|
2445
|
+
provider:
|
|
2446
|
+
fallbackSender.emailProvider &&
|
|
2447
|
+
SUPPORTED_EMAIL_PROVIDERS.has(fallbackSender.emailProvider)
|
|
2448
|
+
? fallbackSender.emailProvider
|
|
2449
|
+
: null,
|
|
2450
|
+
from: fallbackSender.fromEmail || '',
|
|
2451
|
+
secretEnv: fallbackSender.emailSecretEnvName || '',
|
|
2452
|
+
}
|
|
1189
2453
|
let dbImport = ''
|
|
1190
2454
|
let createUserCall = ''
|
|
1191
2455
|
let welcomeHelpers = ''
|
|
@@ -1193,8 +2457,7 @@ export const generateSignupRouteFile = (
|
|
|
1193
2457
|
if (needsDb) {
|
|
1194
2458
|
dbImport = `const authUtils = require('../../../utils/auth/auth-options');
|
|
1195
2459
|
const createUser = authUtils.createUser;
|
|
1196
|
-
const
|
|
1197
|
-
const sanitizeUser = authUtils.sanitizeUser;`
|
|
2460
|
+
const findSignupUserByEmail = authUtils.findSignupUserByEmail;`
|
|
1198
2461
|
|
|
1199
2462
|
// Baked welcome-email helpers + config (no-op send when no provider set).
|
|
1200
2463
|
welcomeHelpers = `
|
|
@@ -1234,19 +2497,95 @@ async function sendWelcomeEmail(toEmail, tokenValues, userId, locale) {
|
|
|
1234
2497
|
var from = WELCOME_EMAIL_FROM || process.env.EMAIL_FROM || '';
|
|
1235
2498
|
if (!from) { console.warn('[account-signup] welcome email skipped: sender not configured'); return; }
|
|
1236
2499
|
var copy = resolveWelcomeEmailCopy(locale);
|
|
1237
|
-
var subject =
|
|
2500
|
+
var subject = fillSubjectTemplate(copy.subject, tokenValues);
|
|
1238
2501
|
var html = fillTemplate(copy.body, tokenValues);
|
|
1239
2502
|
await __sendProviderEmail({ from: from, fromName: WELCOME_EMAIL_FROM_NAME, to: toEmail, subject: subject, html: html, apiKey: apiKey, tokenValues: tokenValues, userId: userId });
|
|
1240
2503
|
}
|
|
1241
|
-
`
|
|
1242
2504
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
2505
|
+
// A sign-up for an address that already has an account gets the same status,
|
|
2506
|
+
// body and headers as a new one; its time differs from a new address's, which
|
|
2507
|
+
// the account a new sign-up makes reveals anyway. The address's owner is told
|
|
2508
|
+
// by email instead (only the mailbox owner reads it), at most
|
|
2509
|
+
// ${EXISTING_ACCOUNT_EMAIL_LIMIT} times an hour per address, through the known-address sender.
|
|
2510
|
+
var EXISTING_ACCOUNT_SUBJECT = 'You already have an account';
|
|
2511
|
+
var EXISTING_ACCOUNT_BODY_HTML =
|
|
2512
|
+
'<p>Someone, hopefully you, tried to create an account{{siteNameSuffix}} with this email address.</p>' +
|
|
2513
|
+
'<p>You already have one. Sign in with your password, or use "Forgot password" on the sign-in page to choose a new one.</p>' +
|
|
2514
|
+
'<p>If this was not you, you can ignore this email: nothing has changed.</p>';
|
|
2515
|
+
|
|
2516
|
+
async function sendExistingAccountEmail(toEmail, siteName, userId) {
|
|
2517
|
+
if (!NOTICE_EMAIL_PROVIDER || !toEmail) { return; }
|
|
2518
|
+
var apiKey = NOTICE_EMAIL_SECRET_ENV_NAME ? process.env[NOTICE_EMAIL_SECRET_ENV_NAME] : '';
|
|
2519
|
+
if (apiKey && String(apiKey).indexOf('teleporthq.secrets.') === 0) { apiKey = ''; }
|
|
2520
|
+
var from = NOTICE_EMAIL_FROM || process.env.EMAIL_FROM || '';
|
|
2521
|
+
if (!apiKey || !from) { return; }
|
|
2522
|
+
var allowed = await __security.rateLimit({
|
|
2523
|
+
key: 'auth-signup-existing:' + String(toEmail).trim().toLowerCase(),
|
|
2524
|
+
limit: ${EXISTING_ACCOUNT_EMAIL_LIMIT},
|
|
2525
|
+
windowSeconds: 3600,
|
|
2526
|
+
});
|
|
2527
|
+
if (!allowed.allowed) { return; }
|
|
2528
|
+
var tokenValues = { siteNameSuffix: siteName ? ' on ' + siteName : '' };
|
|
2529
|
+
var html = fillTemplate(EXISTING_ACCOUNT_BODY_HTML, tokenValues);
|
|
2530
|
+
await sendAccountNotice(
|
|
2531
|
+
${JSON.stringify(EXISTING_ACCOUNT_EMAIL)},
|
|
2532
|
+
{ from: from, to: toEmail, subject: EXISTING_ACCOUNT_SUBJECT, html: html, apiKey: apiKey, userId: userId },
|
|
2533
|
+
{ subject: EXISTING_ACCOUNT_SUBJECT, html: html, payload: tokenValues }
|
|
2534
|
+
);
|
|
2535
|
+
}
|
|
2536
|
+
${generateKnownAddressEmailCode({
|
|
2537
|
+
sender: noticeSender,
|
|
2538
|
+
pagePath: setPasswordPagePath,
|
|
2539
|
+
tokenInFragment: signup.tokenInFragment === true,
|
|
2540
|
+
})}
|
|
2541
|
+
|
|
2542
|
+
function waitUntilElapsed(startedAt, ms) {
|
|
2543
|
+
var left = ms - (Date.now() - startedAt);
|
|
2544
|
+
return left > 0 ? new Promise(function (resolve) { setTimeout(resolve, left); }) : Promise.resolve();
|
|
2545
|
+
}
|
|
2546
|
+
${
|
|
2547
|
+
recordsSignup
|
|
2548
|
+
? generateAccountEventRecorder({ relativePrefix: '../../..', logTag: '[account-signup]' })
|
|
2549
|
+
: ''
|
|
2550
|
+
}`
|
|
2551
|
+
|
|
2552
|
+
createUserCall = ` // The answer is the same for a new account and for an address that already
|
|
2553
|
+
// has one (see sendExistingAccountEmail): built from what the visitor sent.
|
|
2554
|
+
const answer = { user: { email: email, name: name || null } };
|
|
2555
|
+
const __siteName = WELCOME_EMAIL_SITE_NAME ||
|
|
2556
|
+
(req.headers && req.headers.host ? String(req.headers.host).replace(/^www\\./, '').split(':')[0] : '');
|
|
2557
|
+
// In any letter case: \`Ava@site.test\` is the \`ava@site.test\` row.
|
|
2558
|
+
const existing = await findSignupUserByEmail(email);
|
|
2559
|
+
if (existing) {
|
|
2560
|
+
// A newsletter subscriber, a checkout or booking guest or a contact the
|
|
2561
|
+
// owner added has a row but no password (and no OAuth sign-in). Signing
|
|
2562
|
+
// up must not hand that row — and what the site knows about the person —
|
|
2563
|
+
// to whoever types the address first: its owner is mailed a link to set
|
|
2564
|
+
// a password, which proves the mailbox is theirs. An account's owner is
|
|
2565
|
+
// told it already has one. Either way the page is answered alike, so it
|
|
2566
|
+
// never tells a subscriber or a guest from an account.
|
|
2567
|
+
const knownEmail = String(existing.email || email);
|
|
2568
|
+
const existingId = existing.id != null ? existing.id : existing._id;
|
|
2569
|
+
try {
|
|
2570
|
+
if (!existing.password && !existing.provider && authUtils.PASSWORD_RESET_SUPPORTED) {
|
|
2571
|
+
await sendSetPasswordEmail(existing, __siteName, req);
|
|
2572
|
+
} else {
|
|
2573
|
+
// An account; or a row without a password where the store cannot
|
|
2574
|
+
// keep reset links (not Postgres): told it is already known here.
|
|
2575
|
+
await sendExistingAccountEmail(knownEmail, __siteName, existingId);
|
|
2576
|
+
}
|
|
2577
|
+
} catch (existingErr) {
|
|
2578
|
+
console.error('[account-signup] existing-account email failed:', existingErr && existingErr.message ? existingErr.message : existingErr);
|
|
2579
|
+
}
|
|
2580
|
+
${
|
|
2581
|
+
noticeSender.provider
|
|
2582
|
+
? ` await __noticeEmailLog.settleSentEmailLog();
|
|
2583
|
+
`
|
|
2584
|
+
: ''
|
|
2585
|
+
} // At least ${KNOWN_ADDRESS_MIN_RESPONSE_MS} ms, like the forgot-password answer: the time tells
|
|
2586
|
+
// neither which email went out nor whether one did.
|
|
2587
|
+
await waitUntilElapsed(startedAt, ${KNOWN_ADDRESS_MIN_RESPONSE_MS});
|
|
2588
|
+
res.status(201).json(answer);
|
|
1250
2589
|
return;
|
|
1251
2590
|
}
|
|
1252
2591
|
|
|
@@ -1255,12 +2594,16 @@ async function sendWelcomeEmail(toEmail, tokenValues, userId, locale) {
|
|
|
1255
2594
|
email: email,
|
|
1256
2595
|
password: hashedPassword,
|
|
1257
2596
|
role: 'user',
|
|
2597
|
+
// Where the person came from, and their consent as they gave it: the
|
|
2598
|
+
// "Email me news and offers" box ticked ('true') or not. Never assumed.
|
|
2599
|
+
source: 'signup',
|
|
2600
|
+
marketing_consent: body.marketing_consent === 'true',
|
|
1258
2601
|
};
|
|
1259
|
-
|
|
1260
|
-
const
|
|
1261
|
-
for (let i = 0; i <
|
|
1262
|
-
if (
|
|
1263
|
-
userData[
|
|
2602
|
+
// Only the fields this project's sign-up forms collect.
|
|
2603
|
+
const signupFields = ${JSON.stringify(signupFields)};
|
|
2604
|
+
for (let i = 0; i < signupFields.length; i++) {
|
|
2605
|
+
if (Object.prototype.hasOwnProperty.call(body, signupFields[i])) {
|
|
2606
|
+
userData[signupFields[i]] = body[signupFields[i]];
|
|
1264
2607
|
}
|
|
1265
2608
|
}
|
|
1266
2609
|
|
|
@@ -1273,12 +2616,10 @@ async function sendWelcomeEmail(toEmail, tokenValues, userId, locale) {
|
|
|
1273
2616
|
|
|
1274
2617
|
// Best-effort welcome email — a failed send must never fail the signup.
|
|
1275
2618
|
try {
|
|
1276
|
-
const __welcomeSiteName = WELCOME_EMAIL_SITE_NAME ||
|
|
1277
|
-
(req.headers && req.headers.host ? String(req.headers.host).replace(/^www\\./, '').split(':')[0] : '');
|
|
1278
2619
|
await sendWelcomeEmail(email, {
|
|
1279
2620
|
userName: name || (newUser && newUser.name) || 'there',
|
|
1280
2621
|
userEmail: email,
|
|
1281
|
-
siteName:
|
|
2622
|
+
siteName: __siteName,
|
|
1282
2623
|
}, newUser && newUser.id, __emailLocale.resolveRequestLocale(req));
|
|
1283
2624
|
} catch (welcomeErr) {
|
|
1284
2625
|
console.error('[account-signup] welcome email failed:', welcomeErr && welcomeErr.message ? welcomeErr.message : welcomeErr);
|
|
@@ -1289,13 +2630,20 @@ ${
|
|
|
1289
2630
|
if (typeof __sentEmailLog !== 'undefined') { await __sentEmailLog.settleSentEmailLog(); }
|
|
1290
2631
|
`
|
|
1291
2632
|
: ''
|
|
1292
|
-
}
|
|
1293
|
-
|
|
2633
|
+
}${
|
|
2634
|
+
recordsSignup
|
|
2635
|
+
? ` // The account exists: the last thing the signup does is record it.
|
|
2636
|
+
await recordAccountEvent('user-signed-up', newUser, 'signup', req);
|
|
2637
|
+
`
|
|
2638
|
+
: ''
|
|
2639
|
+
}${signup.newsletter === true ? SIGNUP_NEWSLETTER_CODE : ''}
|
|
2640
|
+
res.status(201).json(answer);`
|
|
1294
2641
|
} else {
|
|
1295
2642
|
createUserCall = ` res.status(501).json({ error: 'No data source configured for user storage' });`
|
|
1296
2643
|
}
|
|
1297
2644
|
|
|
1298
2645
|
return `const hashPassword = require('../../../utils/auth/hash-password');
|
|
2646
|
+
const __security = require('${securityRuntimeSpecifier('../../..')}');
|
|
1299
2647
|
${dbImport}
|
|
1300
2648
|
${welcomeHelpers}
|
|
1301
2649
|
|
|
@@ -1305,11 +2653,16 @@ module.exports = async function handler(req, res) {
|
|
|
1305
2653
|
return;
|
|
1306
2654
|
}
|
|
1307
2655
|
|
|
2656
|
+
// ${SIGN_UP_LIMIT} sign-ups per visitor per hour.
|
|
2657
|
+
if (!(await __security.rateLimitRequest(req, res, { scope: 'auth-signup', limit: ${SIGN_UP_LIMIT}, windowSeconds: ${SIGN_UP_WINDOW_SECONDS} }))) {
|
|
2658
|
+
return;
|
|
2659
|
+
}
|
|
2660
|
+
${needsDb ? ' const startedAt = Date.now();\n' : ''}
|
|
1308
2661
|
try {
|
|
1309
|
-
const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
|
|
1310
|
-
const email = body.email;
|
|
1311
|
-
const password = body.password;
|
|
1312
|
-
const name = body.name;
|
|
2662
|
+
const body = (typeof req.body === 'string' ? JSON.parse(req.body) : req.body) || {};
|
|
2663
|
+
const email = typeof body.email === 'string' ? body.email : '';
|
|
2664
|
+
const password = typeof body.password === 'string' ? body.password : '';
|
|
2665
|
+
const name = typeof body.name === 'string' ? body.name : '';
|
|
1313
2666
|
|
|
1314
2667
|
if (!email || !password) {
|
|
1315
2668
|
res.status(400).json({ error: 'Email and password are required' });
|
|
@@ -1322,13 +2675,9 @@ module.exports = async function handler(req, res) {
|
|
|
1322
2675
|
return;
|
|
1323
2676
|
}
|
|
1324
2677
|
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
if (password.length > 128) {
|
|
1331
|
-
res.status(400).json({ error: 'Password must not exceed 128 characters' });
|
|
2678
|
+
const policyError = hashPassword.passwordPolicyError(password);
|
|
2679
|
+
if (policyError) {
|
|
2680
|
+
res.status(400).json({ error: policyError });
|
|
1332
2681
|
return;
|
|
1333
2682
|
}
|
|
1334
2683
|
|
|
@@ -1336,8 +2685,7 @@ module.exports = async function handler(req, res) {
|
|
|
1336
2685
|
|
|
1337
2686
|
${createUserCall}
|
|
1338
2687
|
} catch (error) {
|
|
1339
|
-
|
|
1340
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
2688
|
+
__security.sendClientError(res, 500, 'Sign-up failed. Please try again.', error);
|
|
1341
2689
|
}
|
|
1342
2690
|
};
|
|
1343
2691
|
`
|
|
@@ -1659,40 +3007,19 @@ function getUserRoleFromToken(token) {
|
|
|
1659
3007
|
return null;
|
|
1660
3008
|
}
|
|
1661
3009
|
|
|
1662
|
-
//
|
|
1663
|
-
//
|
|
1664
|
-
function readCookie(request, name) {
|
|
1665
|
-
try {
|
|
1666
|
-
var c = request.cookies.get(name);
|
|
1667
|
-
if (!c) return null;
|
|
1668
|
-
return typeof c === 'string' ? c : (c.value || null);
|
|
1669
|
-
} catch (e) {
|
|
1670
|
-
return null;
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
// next-auth stores the session token under a secure-prefixed cookie on https
|
|
1675
|
-
// and a plain one on http, and splits large JWTs into ".0"/".1" chunks. The
|
|
3010
|
+
// The session token lives in the "__Host-" cookie on https and the plain one on
|
|
3011
|
+
// http (see the auth options), split into ".0"/".1" chunks when large. The
|
|
1676
3012
|
// Edge runtime's getToken() can return null for a session the Node runtime
|
|
1677
3013
|
// considers valid (e.g. NEXTAUTH_SECRET not identical in the Edge runtime, or
|
|
1678
3014
|
// the JWE simply failing to decode at the edge), which previously redirected
|
|
1679
3015
|
// logged-in users to sign-in. So cookie PRESENCE is the source of truth for
|
|
1680
3016
|
// "is there a session"; getToken is only trusted for the optional role check.
|
|
1681
|
-
//
|
|
1682
|
-
//
|
|
3017
|
+
// Only the cookie the request's protocol allows counts — the same one
|
|
3018
|
+
// __tqSessionTokenCandidates names: a cookie a sibling site planted under
|
|
3019
|
+
// another name is never a session. Server-side (getServerSideProps / API
|
|
3020
|
+
// routes) remain the authoritative validators of the session and role.
|
|
1683
3021
|
function hasSessionCookie(request) {
|
|
1684
|
-
|
|
1685
|
-
'__Secure-next-auth.session-token',
|
|
1686
|
-
'next-auth.session-token',
|
|
1687
|
-
'__Secure-authjs.session-token',
|
|
1688
|
-
'authjs.session-token'
|
|
1689
|
-
];
|
|
1690
|
-
for (var i = 0; i < names.length; i++) {
|
|
1691
|
-
if (readCookie(request, names[i]) || readCookie(request, names[i] + '.0')) {
|
|
1692
|
-
return true;
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
return false;
|
|
3022
|
+
return __tqSessionTokenCandidates(request).length > 0;
|
|
1696
3023
|
}
|
|
1697
3024
|
|
|
1698
3025
|
// Turns a Next.js route pattern into an anchored matcher: "[param]" matches a
|
|
@@ -1955,8 +3282,10 @@ export const config = {
|
|
|
1955
3282
|
// Next.js does NOT run the second (negative-lookahead) matcher for the root
|
|
1956
3283
|
// path, which would leave a page published at "/" (e.g. a protected dashboard)
|
|
1957
3284
|
// publicly reachable. '/' compiles to ^/$ and covers exactly the home route;
|
|
1958
|
-
// the second entry covers every deeper path.
|
|
1959
|
-
|
|
3285
|
+
// the second entry covers every deeper path. Only the API routes are left
|
|
3286
|
+
// out — "/api" and "/api/..." — never a page whose path merely starts with
|
|
3287
|
+
// "api" (a protected "/api-keys" page).
|
|
3288
|
+
matcher: ['/', '/((?!api(?:/|$)|_next/static|_next/image|favicon\\\\.ico).*)'],
|
|
1960
3289
|
};
|
|
1961
3290
|
`
|
|
1962
3291
|
}
|