@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
|
@@ -1,6 +1,28 @@
|
|
|
1
|
-
import { SUPPORTED_EMAIL_PROVIDERS, generateProviderSendFunction, generateFillTemplateFn, generateEmailLocaleRequire, generateLocalizedEmailCopyFn, WELCOME_EMAIL_CONFIG_KEYS, } from './transactional-email-code';
|
|
2
|
-
import {
|
|
1
|
+
import { SUPPORTED_EMAIL_PROVIDERS, generateProviderSendFunction, generateFillTemplateFn, generateEmailLocaleRequire, generateLocalizedEmailCopyFn, EXISTING_ACCOUNT_EMAIL, SET_PASSWORD_EMAIL, WELCOME_EMAIL_CONFIG_KEYS, } from './transactional-email-code';
|
|
2
|
+
import { PASSWORD_RESET_TOKEN_TTL_SECONDS, TRUSTED_ORIGIN_CODE, resolveResetPagePath, } from './password-reset-route-generator';
|
|
3
|
+
import { AccountFields, SessionCookieResolver, TableAccess } from '@teleporthq/teleport-shared';
|
|
3
4
|
import { generateSessionTokenResolverCode } from './session-cookie-resolver';
|
|
5
|
+
import { AUTOMATIONS_PATH } from './automations/automations-scope';
|
|
6
|
+
import { SESSION_REVOCATION_FILE_NAME, SESSION_STAMP_KEY } from './session-revocation-generator';
|
|
7
|
+
import { securityRuntimeSpecifier } from './security-runtime/security-runtime-code';
|
|
8
|
+
/**
|
|
9
|
+
* Credentials sign-in throttle: a visitor's next sign-in for an email is
|
|
10
|
+
* refused (429) once this many failures for it are in the window, or once it
|
|
11
|
+
* was tried SIGN_IN_ATTEMPT_LIMIT times in the window whatever the outcome.
|
|
12
|
+
*/
|
|
13
|
+
export const SIGN_IN_FAILURE_LIMIT = 10;
|
|
14
|
+
export const SIGN_IN_ATTEMPT_LIMIT = 30;
|
|
15
|
+
export const SIGN_IN_WINDOW_SECONDS = 900;
|
|
16
|
+
/** Failed sign-ins one visitor may make across every email in the window. */
|
|
17
|
+
export const SIGN_IN_VISITOR_FAILURE_LIMIT = 50;
|
|
18
|
+
/** Sign-ups per visitor per hour. */
|
|
19
|
+
export const SIGN_UP_LIMIT = 10;
|
|
20
|
+
/**
|
|
21
|
+
* "You already have an account" (or, for an address without a password,
|
|
22
|
+
* "Set your password") emails one address can be sent per hour.
|
|
23
|
+
*/
|
|
24
|
+
export const EXISTING_ACCOUNT_EMAIL_LIMIT = 3;
|
|
25
|
+
export const SIGN_UP_WINDOW_SECONDS = 3600;
|
|
4
26
|
// GUI provider id → the actual next-auth v4 provider MODULE name, for the few
|
|
5
27
|
// cases where they differ. Everything else uses the id as the module name.
|
|
6
28
|
// (Providers next-auth v4 cannot supply are not offered by the GUI; if one
|
|
@@ -191,8 +213,6 @@ export const SESSION_SAFE_USER_FIELDS = [
|
|
|
191
213
|
'roleName',
|
|
192
214
|
'roles',
|
|
193
215
|
];
|
|
194
|
-
/** A `users` key the session derives the visitor's role from, in any case. */
|
|
195
|
-
const ROLE_BEARING_USER_KEY = /^(roles?|role_?name)$/i;
|
|
196
216
|
/**
|
|
197
217
|
* Credentials that must never leave the server, whatever the `users` table
|
|
198
218
|
* happens to declare: the OAuth single-table adapter's provider tokens plus the
|
|
@@ -227,6 +247,12 @@ export const SENSITIVE_USER_FIELDS = [
|
|
|
227
247
|
* it is kept out of `SENSITIVE_USER_FIELDS`.
|
|
228
248
|
*/
|
|
229
249
|
const ID_ALIAS_FIELD = '_id';
|
|
250
|
+
/**
|
|
251
|
+
* The Newsletter's own `users` columns (`newsletter_status`, …). A sign-in
|
|
252
|
+
* snapshot of them goes stale the moment the person confirms or unsubscribes,
|
|
253
|
+
* so they never reach the session: a page reads the row.
|
|
254
|
+
*/
|
|
255
|
+
const NEWSLETTER_COLUMN_PREFIX = 'newsletter_';
|
|
230
256
|
/**
|
|
231
257
|
* The exact set of `users` columns this project allows into the session.
|
|
232
258
|
*
|
|
@@ -239,7 +265,9 @@ const ID_ALIAS_FIELD = '_id';
|
|
|
239
265
|
* properties without a matching `tables` entry.
|
|
240
266
|
*/
|
|
241
267
|
export const buildSessionUserFields = (tables, customProps) => {
|
|
242
|
-
const declared = ((tables === null || tables === void 0 ? void 0 : tables.users) || [])
|
|
268
|
+
const declared = ((tables === null || tables === void 0 ? void 0 : tables.users) || [])
|
|
269
|
+
.map((column) => column.name)
|
|
270
|
+
.filter((name) => !name.startsWith(NEWSLETTER_COLUMN_PREFIX));
|
|
243
271
|
const custom = customProps.map((prop) => prop.key);
|
|
244
272
|
const denied = new Set(SENSITIVE_USER_FIELDS);
|
|
245
273
|
return Array.from(new Set([...SESSION_SAFE_USER_FIELDS, ...declared, ...custom])).filter((field) => Boolean(field) && field !== ID_ALIAS_FIELD && !denied.has(field));
|
|
@@ -319,6 +347,45 @@ const generateCustomPropAirtableEntries = (props) => {
|
|
|
319
347
|
.map((p) => ` ${p.key}: userData.${p.key} != null ? userData.${p.key} : '',`)
|
|
320
348
|
.join('\n'));
|
|
321
349
|
};
|
|
350
|
+
/**
|
|
351
|
+
* The `users` columns a sign-up writes itself, never from its form: where the
|
|
352
|
+
* person came from (`source = 'signup'`) and their consent as given
|
|
353
|
+
* (`marketing_consent`, from the "Email me news and offers" box). On the
|
|
354
|
+
* Postgres family `createUser` writes each one it is given, and creates the
|
|
355
|
+
* account without them on a users table made before they existed.
|
|
356
|
+
*/
|
|
357
|
+
export const SIGNUP_OWN_COLUMNS = ['source', 'marketing_consent'];
|
|
358
|
+
/**
|
|
359
|
+
* `findSignupUserByEmail(email)`: the sign-up's own existence check, blind to
|
|
360
|
+
* letter case, so `Ava@site.test` signing up reaches the `ava@site.test` row
|
|
361
|
+
* (a subscriber, a guest, an account) instead of making a second one. The
|
|
362
|
+
* exact address first; else the one row equal to it in any case, none when
|
|
363
|
+
* that is ambiguous. Sign-in keeps its exact `findUserByEmail`. On the other
|
|
364
|
+
* stores it is that exact lookup.
|
|
365
|
+
*/
|
|
366
|
+
const generateSignupLookupFunction = (dataSourceType) => {
|
|
367
|
+
switch (dataSourceType) {
|
|
368
|
+
case 'teleport':
|
|
369
|
+
case 'postgresql':
|
|
370
|
+
case 'cockroachdb':
|
|
371
|
+
return `async function findSignupUserByEmail(email) {
|
|
372
|
+
const client = getClient();
|
|
373
|
+
try {
|
|
374
|
+
await client.connect();
|
|
375
|
+
const exact = await client.query('SELECT * FROM users WHERE email = $1 LIMIT 1', [email]);
|
|
376
|
+
if (exact.rows && exact.rows.length > 0) return exact.rows[0];
|
|
377
|
+
const anyCase = await client.query('SELECT * FROM users WHERE LOWER(email) = LOWER($1) LIMIT 2', [email]);
|
|
378
|
+
return anyCase.rows && anyCase.rows.length === 1 ? anyCase.rows[0] : null;
|
|
379
|
+
} finally {
|
|
380
|
+
try { await client.end(); } catch (_e) {}
|
|
381
|
+
}
|
|
382
|
+
}`;
|
|
383
|
+
default:
|
|
384
|
+
return `async function findSignupUserByEmail(email) {
|
|
385
|
+
return findUserByEmail(email);
|
|
386
|
+
}`;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
322
389
|
const generateFindUserFunction = (dataSourceType, customProps) => {
|
|
323
390
|
if (!dataSourceType) {
|
|
324
391
|
return `async function findUserByEmail() { return null; }
|
|
@@ -332,6 +399,9 @@ async function userExistsByEmail() { return false; }`;
|
|
|
332
399
|
const customValues = generateCustomPropValues(customProps);
|
|
333
400
|
const docEntries = generateCustomPropDocEntries(customProps);
|
|
334
401
|
const airtableEntries = generateCustomPropAirtableEntries(customProps);
|
|
402
|
+
// A declared account property of the same name is already one of the columns.
|
|
403
|
+
const customKeys = new Set(customProps.map((prop) => prop.key));
|
|
404
|
+
const signupOwnColumns = SIGNUP_OWN_COLUMNS.filter((column) => !customKeys.has(column));
|
|
335
405
|
switch (dataSourceType) {
|
|
336
406
|
case 'teleport':
|
|
337
407
|
case 'postgresql':
|
|
@@ -347,14 +417,44 @@ async function userExistsByEmail() { return false; }`;
|
|
|
347
417
|
}
|
|
348
418
|
}
|
|
349
419
|
|
|
420
|
+
// What a sign-up writes besides its form (where the person came from, their
|
|
421
|
+
// consent as given), each when it is given. A users table made before one of
|
|
422
|
+
// them lacks it (the column is added when a feature that reads it is switched
|
|
423
|
+
// on): the account is then created without them.
|
|
424
|
+
const SIGNUP_OWN_COLUMNS = ${JSON.stringify(signupOwnColumns)};
|
|
425
|
+
|
|
350
426
|
async function createUser(userData) {
|
|
351
427
|
const client = getClient();
|
|
352
428
|
try {
|
|
353
429
|
await client.connect();
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
430
|
+
const values = [userData.name || null, userData.email, userData.password, userData.role || 'user', userData.image || null${customValues}];
|
|
431
|
+
const insert = function (extra) {
|
|
432
|
+
let columns = '';
|
|
433
|
+
let placeholders = '';
|
|
434
|
+
for (let i = 0; i < extra.length; i++) {
|
|
435
|
+
columns += ', ' + extra[i];
|
|
436
|
+
placeholders += ', $' + (values.length + i + 1);
|
|
437
|
+
}
|
|
438
|
+
return client.query(
|
|
439
|
+
'INSERT INTO users (name, email, password, role, image${sqlCols}' + columns + ') VALUES ($1, $2, $3, $4, $5${pgPlaceholders}' + placeholders + ') RETURNING *',
|
|
440
|
+
values.concat(extra.map(function (column) { return userData[column]; }))
|
|
441
|
+
);
|
|
442
|
+
};
|
|
443
|
+
let extra = SIGNUP_OWN_COLUMNS.filter(function (column) { return userData[column] !== undefined; });
|
|
444
|
+
let result = null;
|
|
445
|
+
while (!result) {
|
|
446
|
+
try {
|
|
447
|
+
result = await insert(extra);
|
|
448
|
+
} catch (err) {
|
|
449
|
+
// 42703 names a column the table does not have. Only that one is left
|
|
450
|
+
// out, and only when it is one of these: the others are still written.
|
|
451
|
+
const named = err && err.code === '42703' ? /column "([^"]+)"/.exec(String(err.message || '')) : null;
|
|
452
|
+
const missing = named ? named[1] : '';
|
|
453
|
+
if (!missing || extra.indexOf(missing) === -1) { throw err; }
|
|
454
|
+
console.warn('[auth] the users table has no ' + missing + ' column yet: the account is created without it');
|
|
455
|
+
extra = extra.filter(function (column) { return column !== missing; });
|
|
456
|
+
}
|
|
457
|
+
}
|
|
358
458
|
return result.rows && result.rows.length > 0 ? result.rows[0] : null;
|
|
359
459
|
} finally {
|
|
360
460
|
try { await client.end(); } catch (_e) {}
|
|
@@ -555,6 +655,269 @@ async function createUser() { return null; }
|
|
|
555
655
|
async function userExistsByEmail() { return false; }`;
|
|
556
656
|
}
|
|
557
657
|
};
|
|
658
|
+
/** Where the password-reset links live: hashed, single-use, per account id. */
|
|
659
|
+
export const PASSWORD_RESETS_TABLE = 'teleport_password_resets';
|
|
660
|
+
const PG_PASSWORD_RESET_FUNCTIONS = `
|
|
661
|
+
// Password-reset links (pages/api/auth/forgot-password + reset-password). Only
|
|
662
|
+
// the SHA-256 of a link's token is stored, bound to the account id, with its
|
|
663
|
+
// expiry; the table is created on first use. The earlier password_reset_tokens
|
|
664
|
+
// table and its rows are never read.
|
|
665
|
+
var PASSWORD_RESETS_TABLE = '${PASSWORD_RESETS_TABLE}';
|
|
666
|
+
var __passwordResetsTableReady = false;
|
|
667
|
+
|
|
668
|
+
async function ensurePasswordResetsTable(client) {
|
|
669
|
+
if (__passwordResetsTableReady) return;
|
|
670
|
+
await client.query('BEGIN');
|
|
671
|
+
try {
|
|
672
|
+
await client.query('SELECT pg_advisory_xact_lock(hashtext($1))', [PASSWORD_RESETS_TABLE]);
|
|
673
|
+
await client.query(
|
|
674
|
+
'CREATE TABLE IF NOT EXISTS ' + PASSWORD_RESETS_TABLE + ' (token_hash TEXT PRIMARY KEY, user_id TEXT NOT NULL, ' +
|
|
675
|
+
'expires_at TIMESTAMPTZ NOT NULL, used_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW())'
|
|
676
|
+
);
|
|
677
|
+
await client.query('CREATE INDEX IF NOT EXISTS ' + PASSWORD_RESETS_TABLE + '_user_id_idx ON ' + PASSWORD_RESETS_TABLE + ' (user_id)');
|
|
678
|
+
await client.query('COMMIT');
|
|
679
|
+
} catch (err) {
|
|
680
|
+
try { await client.query('ROLLBACK'); } catch (_e) {}
|
|
681
|
+
throw err;
|
|
682
|
+
}
|
|
683
|
+
__passwordResetsTableReady = true;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// One live link per account: a new one ends the ones sent before it.
|
|
687
|
+
async function storePasswordReset(userId, tokenHash, ttlSeconds) {
|
|
688
|
+
const client = getClient();
|
|
689
|
+
try {
|
|
690
|
+
await client.connect();
|
|
691
|
+
await ensurePasswordResetsTable(client);
|
|
692
|
+
await client.query(
|
|
693
|
+
'UPDATE ' + PASSWORD_RESETS_TABLE + ' SET used_at = NOW() WHERE user_id = $1 AND used_at IS NULL',
|
|
694
|
+
[String(userId)]
|
|
695
|
+
);
|
|
696
|
+
await client.query(
|
|
697
|
+
'INSERT INTO ' + PASSWORD_RESETS_TABLE + " (token_hash, user_id, expires_at) VALUES ($1, $2, NOW() + ($3::int * INTERVAL '1 second'))",
|
|
698
|
+
[tokenHash, String(userId), ttlSeconds]
|
|
699
|
+
);
|
|
700
|
+
await client.query(
|
|
701
|
+
'DELETE FROM ' + PASSWORD_RESETS_TABLE + " WHERE expires_at < NOW() - INTERVAL '1 day'"
|
|
702
|
+
);
|
|
703
|
+
} finally {
|
|
704
|
+
try { await client.end(); } catch (_e) {}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Spends the link and sets the password in ONE statement: the link must be
|
|
709
|
+
// unspent and unexpired, and is spent by this call alone (a second call with the
|
|
710
|
+
// same token finds it used). Returns the account it was for, or null.
|
|
711
|
+
async function consumePasswordReset(tokenHash, passwordHash) {
|
|
712
|
+
const client = getClient();
|
|
713
|
+
try {
|
|
714
|
+
await client.connect();
|
|
715
|
+
const result = await client.query(
|
|
716
|
+
'WITH consumed AS (' +
|
|
717
|
+
' UPDATE ' + PASSWORD_RESETS_TABLE + ' SET used_at = NOW()' +
|
|
718
|
+
' WHERE token_hash = $1 AND used_at IS NULL AND expires_at > NOW()' +
|
|
719
|
+
' RETURNING user_id' +
|
|
720
|
+
'), changed AS (' +
|
|
721
|
+
' UPDATE users SET password = $2 FROM consumed WHERE users.id::text = consumed.user_id' +
|
|
722
|
+
' RETURNING users.id, users.email' +
|
|
723
|
+
') SELECT id, email FROM changed',
|
|
724
|
+
[tokenHash, passwordHash]
|
|
725
|
+
);
|
|
726
|
+
return result.rows && result.rows.length > 0 ? result.rows[0] : null;
|
|
727
|
+
} catch (err) {
|
|
728
|
+
// 42P01: no link was ever sent, so the table is not there yet.
|
|
729
|
+
if (err && err.code === '42P01') return null;
|
|
730
|
+
throw err;
|
|
731
|
+
} finally {
|
|
732
|
+
try { await client.end(); } catch (_e) {}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
async function passwordResetIsLive(tokenHash) {
|
|
737
|
+
const client = getClient();
|
|
738
|
+
try {
|
|
739
|
+
await client.connect();
|
|
740
|
+
const result = await client.query(
|
|
741
|
+
'SELECT 1 FROM ' + PASSWORD_RESETS_TABLE + ' WHERE token_hash = $1 AND used_at IS NULL AND expires_at > NOW() LIMIT 1',
|
|
742
|
+
[tokenHash]
|
|
743
|
+
);
|
|
744
|
+
return !!(result.rows && result.rows.length > 0);
|
|
745
|
+
} catch (err) {
|
|
746
|
+
if (err && err.code === '42P01') return false;
|
|
747
|
+
throw err;
|
|
748
|
+
} finally {
|
|
749
|
+
try { await client.end(); } catch (_e) {}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const PASSWORD_RESET_SUPPORTED = true;`;
|
|
754
|
+
const UNSUPPORTED_PASSWORD_RESET_FUNCTIONS = `
|
|
755
|
+
// Password-reset links need a Postgres database; this project's user store is
|
|
756
|
+
// not one, so the reset routes answer without ever sending a link.
|
|
757
|
+
async function storePasswordReset() { throw new Error('Password reset needs a Postgres database'); }
|
|
758
|
+
async function consumePasswordReset() { return null; }
|
|
759
|
+
async function passwordResetIsLive() { return false; }
|
|
760
|
+
const PASSWORD_RESET_SUPPORTED = false;`;
|
|
761
|
+
/**
|
|
762
|
+
* `updateUserPasswordHash(user, newHash, oldHash)` — the sign-in moving a hash
|
|
763
|
+
* onto the project's own pepper. It writes only over the hash it verified, so a
|
|
764
|
+
* reset that landed in between is never undone. Plus the password-reset link
|
|
765
|
+
* store (Postgres family only).
|
|
766
|
+
*/
|
|
767
|
+
const generatePasswordStoreFunctions = (dataSourceType) => {
|
|
768
|
+
switch (dataSourceType) {
|
|
769
|
+
case 'teleport':
|
|
770
|
+
case 'postgresql':
|
|
771
|
+
case 'cockroachdb':
|
|
772
|
+
return `// The account a stamped session was issued for (session-revocation). A value
|
|
773
|
+
// that is not an id of this table's type (22P02) is no account; any other
|
|
774
|
+
// error is thrown, so the session check fails closed.
|
|
775
|
+
async function findUserById(id) {
|
|
776
|
+
const client = getClient();
|
|
777
|
+
try {
|
|
778
|
+
await client.connect();
|
|
779
|
+
const result = await client.query('SELECT * FROM users WHERE id = $1 LIMIT 1', [id]);
|
|
780
|
+
return result.rows && result.rows.length > 0 ? result.rows[0] : null;
|
|
781
|
+
} catch (err) {
|
|
782
|
+
if (err && err.code === '22P02') return null;
|
|
783
|
+
throw err;
|
|
784
|
+
} finally {
|
|
785
|
+
try { await client.end(); } catch (_e) {}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
790
|
+
const client = getClient();
|
|
791
|
+
try {
|
|
792
|
+
await client.connect();
|
|
793
|
+
const result = await client.query('UPDATE users SET password = $1 WHERE id = $2 AND password = $3', [newHash, user.id, oldHash]);
|
|
794
|
+
return !!(result && result.rowCount > 0);
|
|
795
|
+
} finally {
|
|
796
|
+
try { await client.end(); } catch (_e) {}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
${PG_PASSWORD_RESET_FUNCTIONS}`;
|
|
800
|
+
case 'supabase':
|
|
801
|
+
return `async function findUserById(id) {
|
|
802
|
+
const client = getSupabaseClient();
|
|
803
|
+
const result = await client.from('users').select('*').eq('id', id).maybeSingle();
|
|
804
|
+
if (result.error) throw new Error(result.error.message || 'users lookup failed');
|
|
805
|
+
return result.data || null;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
809
|
+
const client = getSupabaseClient();
|
|
810
|
+
const result = await client.from('users').update({ password: newHash }).eq('id', user.id).eq('password', oldHash).select('id');
|
|
811
|
+
return !!(result.data && result.data.length > 0);
|
|
812
|
+
}
|
|
813
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`;
|
|
814
|
+
case 'mysql':
|
|
815
|
+
case 'mariadb':
|
|
816
|
+
case 'tidb':
|
|
817
|
+
return `async function findUserById(id) {
|
|
818
|
+
const conn = await getConnection();
|
|
819
|
+
try {
|
|
820
|
+
const [rows] = await conn.execute('SELECT * FROM users WHERE id = ? LIMIT 1', [id]);
|
|
821
|
+
return rows && rows.length > 0 ? rows[0] : null;
|
|
822
|
+
} finally {
|
|
823
|
+
try { await conn.end(); } catch (_e) {}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
828
|
+
const conn = await getConnection();
|
|
829
|
+
try {
|
|
830
|
+
const [result] = await conn.execute('UPDATE users SET password = ? WHERE id = ? AND password = ?', [newHash, user.id, oldHash]);
|
|
831
|
+
return !!(result && result.affectedRows > 0);
|
|
832
|
+
} finally {
|
|
833
|
+
try { await conn.end(); } catch (_e) {}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`;
|
|
837
|
+
case 'mongodb':
|
|
838
|
+
return `async function findUserById(id) {
|
|
839
|
+
const client = await _mongoClientPromise;
|
|
840
|
+
const db = client.db();
|
|
841
|
+
const ObjectId = require('mongodb').ObjectId;
|
|
842
|
+
if (!ObjectId.isValid(String(id))) return null;
|
|
843
|
+
return await db.collection('users').findOne({ _id: new ObjectId(String(id)) });
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
847
|
+
const client = await _mongoClientPromise;
|
|
848
|
+
const db = client.db();
|
|
849
|
+
const result = await db.collection('users').updateOne({ _id: user._id, password: oldHash }, { $set: { password: newHash } });
|
|
850
|
+
return !!(result && result.modifiedCount > 0);
|
|
851
|
+
}
|
|
852
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`;
|
|
853
|
+
case 'firestore':
|
|
854
|
+
return `async function findUserById(id) {
|
|
855
|
+
const snap = await firestoreDb.collection('users').doc(String(id)).get();
|
|
856
|
+
return snap.exists ? Object.assign({ id: snap.id }, snap.data()) : null;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
860
|
+
const ref = firestoreDb.collection('users').doc(String(user.id));
|
|
861
|
+
return firestoreDb.runTransaction(async function (tx) {
|
|
862
|
+
const snap = await tx.get(ref);
|
|
863
|
+
if (!snap.exists || snap.data().password !== oldHash) return false;
|
|
864
|
+
tx.update(ref, { password: newHash });
|
|
865
|
+
return true;
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`;
|
|
869
|
+
case 'turso':
|
|
870
|
+
return `async function findUserById(id) {
|
|
871
|
+
const result = await tursoClient.execute({ sql: 'SELECT * FROM users WHERE id = ? LIMIT 1', args: [id] });
|
|
872
|
+
return result.rows && result.rows.length > 0 ? result.rows[0] : null;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
876
|
+
const result = await tursoClient.execute({ sql: 'UPDATE users SET password = ? WHERE id = ? AND password = ?', args: [newHash, user.id, oldHash] });
|
|
877
|
+
return !!(result && result.rowsAffected > 0);
|
|
878
|
+
}
|
|
879
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`;
|
|
880
|
+
case 'airtable':
|
|
881
|
+
return `async function findUserById(id) {
|
|
882
|
+
return new Promise(function(resolve, reject) {
|
|
883
|
+
airtableBase('users').find(String(id), function(err, rec) {
|
|
884
|
+
if (err && (err.statusCode === 404 || err.error === 'NOT_FOUND')) { resolve(null); return; }
|
|
885
|
+
if (err) { reject(err); return; }
|
|
886
|
+
resolve(rec ? Object.assign({ id: rec.id }, rec.fields) : null);
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
async function updateUserPasswordHash(user, newHash, oldHash) {
|
|
892
|
+
const current = await findUserByEmail(user.email);
|
|
893
|
+
if (!current || current.password !== oldHash) return false;
|
|
894
|
+
await new Promise(function(resolve, reject) {
|
|
895
|
+
airtableBase('users').update(String(user.id), { password: newHash }, function(err) { if (err) { reject(err); } else { resolve(); } });
|
|
896
|
+
});
|
|
897
|
+
return true;
|
|
898
|
+
}
|
|
899
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`;
|
|
900
|
+
default:
|
|
901
|
+
return `async function findUserById() { return null; }
|
|
902
|
+
async function updateUserPasswordHash() { return false; }
|
|
903
|
+
${UNSUPPORTED_PASSWORD_RESET_FUNCTIONS}`;
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
/** Microsoft provider ids whose email claim is only trusted for one tenant. */
|
|
907
|
+
const MICROSOFT_PROVIDER_IDS = new Set(['azure-ad', 'microsoft-entra-id']);
|
|
908
|
+
/** The credential key naming a Microsoft provider's tenant (`*_TENANT_ID`, or Entra's `*_ISSUER`). */
|
|
909
|
+
const microsoftTenantKeyOf = (provider) => Object.keys(provider.credentials || {}).find((key) => key.endsWith('_TENANT_ID') || key.endsWith('_ISSUER'));
|
|
910
|
+
/** provider id → env var of its configured tenant, for each Microsoft provider. */
|
|
911
|
+
const microsoftTenantEnvKeys = (auth) => {
|
|
912
|
+
const keys = {};
|
|
913
|
+
for (const provider of auth.providers || []) {
|
|
914
|
+
const key = MICROSOFT_PROVIDER_IDS.has(provider.id) ? microsoftTenantKeyOf(provider) : undefined;
|
|
915
|
+
if (key) {
|
|
916
|
+
keys[provider.id] = key;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return keys;
|
|
920
|
+
};
|
|
558
921
|
// Only the CredentialsProvider is imported at module scope (it always exists).
|
|
559
922
|
// OAuth providers are required lazily inside generateProvidersSetup.
|
|
560
923
|
const generateProviderImports = (auth) => {
|
|
@@ -592,11 +955,11 @@ const generateProvidersSetup = (auth) => {
|
|
|
592
955
|
//
|
|
593
956
|
// Unknown email and wrong password deliberately share one code: telling them
|
|
594
957
|
// apart is a user-enumeration oracle.
|
|
595
|
-
async authorize(credentials) {
|
|
958
|
+
async authorize(credentials, req) {
|
|
596
959
|
if (!credentials || !credentials.email || !credentials.password) {
|
|
597
960
|
throw new Error('MissingCredentials');
|
|
598
961
|
}
|
|
599
|
-
const {
|
|
962
|
+
const { checkPassword, hashPassword } = require('./hash-password');
|
|
600
963
|
const { classifyAuthInfrastructureError } = require('./db-health');
|
|
601
964
|
let user;
|
|
602
965
|
try {
|
|
@@ -606,22 +969,35 @@ const generateProvidersSetup = (auth) => {
|
|
|
606
969
|
console.error('[auth] Could not reach the user store (' + kind + '):', err && err.message ? err.message : err);
|
|
607
970
|
throw new Error('ServiceUnavailable');
|
|
608
971
|
}
|
|
972
|
+
let verdict;
|
|
609
973
|
try {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
const signedIn = sanitizeUser(user);
|
|
615
|
-
signedIn[TOKEN_PASSWORD_FINGERPRINT] = passwordFingerprint(user.password);
|
|
616
|
-
return signedIn;
|
|
974
|
+
// No account, or one without a password, is checked too (against a
|
|
975
|
+
// stand-in hash): every failure costs what a wrong password costs.
|
|
976
|
+
verdict = checkPassword(String(credentials.password), user ? user.password : null);
|
|
617
977
|
} catch (err) {
|
|
618
|
-
if (err && err.message === 'InvalidCredentials') throw err;
|
|
619
978
|
// A hashing/serialisation fault is an infrastructure problem, not a
|
|
620
979
|
// wrong password — saying "check your details" would send the user
|
|
621
980
|
// round in circles.
|
|
622
981
|
console.error('[auth] Credential check failed:', err && err.message ? err.message : err);
|
|
623
982
|
throw new Error('ServiceUnavailable');
|
|
624
983
|
}
|
|
984
|
+
if (!verdict.ok) {
|
|
985
|
+
await countSignInFailure(req, credentials.email);
|
|
986
|
+
throw new Error('InvalidCredentials');
|
|
987
|
+
}
|
|
988
|
+
// A hash still made with the old pepper moves onto the project's own.
|
|
989
|
+
let storedHash = user.password;
|
|
990
|
+
if (verdict.needsRehash) {
|
|
991
|
+
try {
|
|
992
|
+
const rehashed = hashPassword(String(credentials.password));
|
|
993
|
+
if (await updateUserPasswordHash(user, rehashed, storedHash)) {
|
|
994
|
+
storedHash = rehashed;
|
|
995
|
+
}
|
|
996
|
+
} catch (err) {
|
|
997
|
+
console.error('[auth] Could not move the password onto the current pepper:', err && err.message ? err.message : err);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return withSessionStamp(sanitizeUser(user), Object.assign({}, user, { password: storedHash }));
|
|
625
1001
|
}
|
|
626
1002
|
}));`);
|
|
627
1003
|
}
|
|
@@ -635,7 +1011,12 @@ const generateProvidersSetup = (auth) => {
|
|
|
635
1011
|
` clientId: process.env.${idKey}`,
|
|
636
1012
|
` clientSecret: process.env.${secretKey}`,
|
|
637
1013
|
];
|
|
638
|
-
if (issuerKey) {
|
|
1014
|
+
if (provider.id === 'azure-ad' && issuerKey && issuerKey.endsWith('_TENANT_ID')) {
|
|
1015
|
+
// next-auth's Azure AD provider takes the tenant as tenantId ("common",
|
|
1016
|
+
// every tenant, when absent).
|
|
1017
|
+
cfg.push(` tenantId: process.env.${issuerKey} || undefined`);
|
|
1018
|
+
}
|
|
1019
|
+
else if (issuerKey) {
|
|
639
1020
|
cfg.push(` issuer: process.env.${issuerKey}`);
|
|
640
1021
|
}
|
|
641
1022
|
lines.push(`try {
|
|
@@ -656,16 +1037,34 @@ export const generateAuthOptionsFile = (auth, dataSourceConfig) => {
|
|
|
656
1037
|
const providerImports = generateProviderImports(auth);
|
|
657
1038
|
const providersSetup = generateProvidersSetup(auth);
|
|
658
1039
|
const findUserCode = auth.passwordAuthEnabled
|
|
659
|
-
? generateFindUserFunction(auth.dataSourceType, customProps)
|
|
1040
|
+
? `${generateFindUserFunction(auth.dataSourceType, customProps)}
|
|
1041
|
+
|
|
1042
|
+
${generateSignupLookupFunction(auth.dataSourceType)}`
|
|
660
1043
|
: '';
|
|
661
1044
|
const sanitizeUserCode = generateSanitizeUserFunction(auth.tables, customProps);
|
|
662
1045
|
const signInRoute = ((_a = auth.authPages.signIn) === null || _a === void 0 ? void 0 : _a.route) || '/auth/sign-in';
|
|
1046
|
+
// Password sign-in brings the local account a session can be revoked from;
|
|
1047
|
+
// with an OAuth provider beside it, an OAuth sign-in may become that account.
|
|
1048
|
+
const withPassword = !!auth.passwordAuthEnabled;
|
|
1049
|
+
// Every OAuth project checks whether the provider vouches for the email: an
|
|
1050
|
+
// unvouched email never is the visitor's identity, whether or not a local
|
|
1051
|
+
// account could be adopted (R15).
|
|
1052
|
+
const withOAuthBinding = (auth.providers || []).length > 0;
|
|
1053
|
+
const passwordSessionCode = withPassword
|
|
1054
|
+
? `${generatePasswordStoreFunctions(auth.dataSourceType)}
|
|
1055
|
+
|
|
1056
|
+
${CREDENTIAL_SESSION_HELPERS}
|
|
1057
|
+
`
|
|
1058
|
+
: '';
|
|
1059
|
+
const oauthBindingCode = withOAuthBinding
|
|
1060
|
+
? `${generateOAuthEmailVerificationHelpers(auth)}\n`
|
|
1061
|
+
: '';
|
|
663
1062
|
return `${providerImports}
|
|
664
1063
|
${dbSetupCode}
|
|
665
1064
|
${sanitizeUserCode}
|
|
666
1065
|
|
|
667
1066
|
${findUserCode}
|
|
668
|
-
|
|
1067
|
+
${passwordSessionCode}${oauthBindingCode}
|
|
669
1068
|
${providersSetup}
|
|
670
1069
|
|
|
671
1070
|
// How long a JWT may serve profile fields before the next /api/auth/session
|
|
@@ -716,7 +1115,25 @@ const authOptions = {
|
|
|
716
1115
|
for (let i = 0; i < keys.length; i++) {
|
|
717
1116
|
token[keys[i]] = user[keys[i]];
|
|
718
1117
|
}
|
|
719
|
-
|
|
1118
|
+
${withOAuthBinding
|
|
1119
|
+
? ` // An OAuth sign-in becomes the local account with its email only when
|
|
1120
|
+
// the provider vouches for that email; otherwise it stays a session of
|
|
1121
|
+
// its own, without the account's id or role.
|
|
1122
|
+
if (params.account && params.account.type !== 'credentials') {
|
|
1123
|
+
if (!(await oauthEmailIsVerified(params.account, params.profile, token))) {
|
|
1124
|
+
asDistinctOAuthIdentity(token, params.account);
|
|
1125
|
+
}${withPassword
|
|
1126
|
+
? ` else {
|
|
1127
|
+
// Vouched for: the next refresh adopts the local account with that
|
|
1128
|
+
// email (its id and role). Until then — and for good when there is
|
|
1129
|
+
// none — the id is the provider's, namespaced where it could equal
|
|
1130
|
+
// a local id.
|
|
1131
|
+
namespaceOAuthId(token, params.account);
|
|
1132
|
+
}`
|
|
1133
|
+
: ''}
|
|
1134
|
+
}
|
|
1135
|
+
`
|
|
1136
|
+
: ''} // Deliberately NOT stamped. An OAuth \`user\` is the PROVIDER's profile
|
|
720
1137
|
// (id/name/email/image) — it carries no \`role\`, which only exists on
|
|
721
1138
|
// the \`users\` row. Stamping here would make the first session request
|
|
722
1139
|
// after an OAuth sign-in skip the database and leave the visitor
|
|
@@ -725,7 +1142,13 @@ const authOptions = {
|
|
|
725
1142
|
// exactly as it does today, and the interval applies from there on.
|
|
726
1143
|
return token;
|
|
727
1144
|
}
|
|
728
|
-
|
|
1145
|
+
${withOAuthBinding
|
|
1146
|
+
? ` // A session the provider did not vouch for never takes a local account.
|
|
1147
|
+
if (token && token[OAUTH_UNVERIFIED]) {
|
|
1148
|
+
return token;
|
|
1149
|
+
}
|
|
1150
|
+
`
|
|
1151
|
+
: ''} // Subsequent calls (every /api/auth/session): re-read the user from the
|
|
729
1152
|
// database so profile edits — name, image, role, etc. — propagate into the
|
|
730
1153
|
// session. The JWT is otherwise a snapshot captured at login, which is why
|
|
731
1154
|
// the navbar avatar/name reverted to the old value after a refresh: the
|
|
@@ -746,35 +1169,17 @@ const authOptions = {
|
|
|
746
1169
|
if (params.trigger !== 'update' && isFresh) {
|
|
747
1170
|
return token;
|
|
748
1171
|
}
|
|
749
|
-
|
|
750
|
-
try {
|
|
751
|
-
if (token && token.email && typeof findUserByEmail === 'function') {
|
|
752
|
-
const fresh = await findUserByEmail(String(token.email));
|
|
753
|
-
const safe = fresh && typeof sanitizeUser === 'function' ? sanitizeUser(fresh) : null;
|
|
754
|
-
if (safe && typeof safe === 'object') {
|
|
755
|
-
const fk = Object.keys(safe);
|
|
756
|
-
for (let i = 0; i < fk.length; i++) {
|
|
757
|
-
token[fk[i]] = safe[fk[i]];
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
if (fresh) {
|
|
761
|
-
const currentFingerprint = passwordFingerprint(fresh.password);
|
|
762
|
-
if (typeof token[TOKEN_PASSWORD_FINGERPRINT] !== 'string') {
|
|
763
|
-
token[TOKEN_PASSWORD_FINGERPRINT] = currentFingerprint;
|
|
764
|
-
} else if (token[TOKEN_PASSWORD_FINGERPRINT] !== currentFingerprint) {
|
|
765
|
-
passwordChanged = true;
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
} catch (e) {
|
|
1172
|
+
${withPassword ? PASSWORD_REFRESH_CODE : PLAIN_REFRESH_CODE} } catch (e) {
|
|
770
1173
|
// Keep the existing token on any DB hiccup — never sign the user out.
|
|
771
1174
|
}
|
|
772
|
-
|
|
1175
|
+
${withPassword
|
|
1176
|
+
? ` if (revoked) {
|
|
773
1177
|
// NextAuth answers a jwt callback that throws by clearing the session
|
|
774
|
-
// cookie
|
|
775
|
-
throw new Error('
|
|
1178
|
+
// cookie: the visitor is signed out.
|
|
1179
|
+
throw new Error('SessionRevoked');
|
|
776
1180
|
}
|
|
777
|
-
|
|
1181
|
+
`
|
|
1182
|
+
: ''} // Stamped even when the read threw, so an unreachable database costs one
|
|
778
1183
|
// attempt per interval instead of one per request.
|
|
779
1184
|
if (token) {
|
|
780
1185
|
token[TOKEN_REFRESH_STAMP] = now;
|
|
@@ -788,7 +1193,7 @@ const authOptions = {
|
|
|
788
1193
|
const skip = { iat: 1, exp: 1, jti: 1, sub: 1 };
|
|
789
1194
|
skip[TOKEN_REFRESH_STAMP] = 1;
|
|
790
1195
|
skip[TOKEN_PASSWORD_FINGERPRINT] = 1;
|
|
791
|
-
const keys = Object.keys(token);
|
|
1196
|
+
${withPassword ? ` skip['${SESSION_STAMP_KEY}'] = 1;\n` : ''}${withOAuthBinding ? ' skip[OAUTH_UNVERIFIED] = 1;\n' : ''} const keys = Object.keys(token);
|
|
792
1197
|
for (let i = 0; i < keys.length; i++) {
|
|
793
1198
|
if (!skip[keys[i]]) {
|
|
794
1199
|
session.user[keys[i]] = token[keys[i]];
|
|
@@ -804,13 +1209,396 @@ const authOptions = {
|
|
|
804
1209
|
secret: process.env.NEXTAUTH_SECRET,
|
|
805
1210
|
};
|
|
806
1211
|
|
|
1212
|
+
// On https the session token and the callback-url cookie are "__Host-" cookies
|
|
1213
|
+
// (Path=/, Secure, HttpOnly, SameSite=Lax, no Domain): only this site can set
|
|
1214
|
+
// them. Published sites share a parent domain that is not a public suffix, so a
|
|
1215
|
+
// sibling site could otherwise plant a session cookie this site reads (cookie
|
|
1216
|
+
// tossing: the visitor silently signed in to someone else's account). Every
|
|
1217
|
+
// reader in the app reads this one name (session-cookie-resolver). NextAuth
|
|
1218
|
+
// reads the option on every request, after the auth route has set
|
|
1219
|
+
// NEXTAUTH_URL to the request's origin; on http (a local server) its defaults stay.
|
|
1220
|
+
var SECURE_SESSION_COOKIES = {
|
|
1221
|
+
sessionToken: {
|
|
1222
|
+
name: '${SessionCookieResolver.SECURE_SESSION_COOKIE_NAME}',
|
|
1223
|
+
options: { httpOnly: true, sameSite: 'lax', path: '/', secure: true },
|
|
1224
|
+
},
|
|
1225
|
+
callbackUrl: {
|
|
1226
|
+
name: '${SessionCookieResolver.SECURE_CALLBACK_URL_COOKIE_NAME}',
|
|
1227
|
+
options: { httpOnly: true, sameSite: 'lax', path: '/', secure: true },
|
|
1228
|
+
},
|
|
1229
|
+
};
|
|
1230
|
+
Object.defineProperty(authOptions, 'cookies', {
|
|
1231
|
+
enumerable: true,
|
|
1232
|
+
get: function () {
|
|
1233
|
+
return String(process.env.NEXTAUTH_URL || '').indexOf('https://') === 0 ? SECURE_SESSION_COOKIES : undefined;
|
|
1234
|
+
},
|
|
1235
|
+
});
|
|
1236
|
+
|
|
807
1237
|
module.exports = authOptions;
|
|
808
1238
|
module.exports.sanitizeUser = sanitizeUser;
|
|
809
1239
|
${auth.passwordAuthEnabled
|
|
810
|
-
? `module.exports.findUserByEmail = findUserByEmail;\nmodule.exports.createUser = createUser;\nmodule.exports.userExistsByEmail = userExistsByEmail;\n`
|
|
1240
|
+
? `module.exports.findUserByEmail = findUserByEmail;\nmodule.exports.findSignupUserByEmail = findSignupUserByEmail;\nmodule.exports.createUser = createUser;\nmodule.exports.userExistsByEmail = userExistsByEmail;\n`
|
|
1241
|
+
: ''}${withPassword
|
|
1242
|
+
? `module.exports.findUserById = findUserById;
|
|
1243
|
+
module.exports.checkSignInAllowed = checkSignInAllowed;
|
|
1244
|
+
module.exports.countSignInFailure = countSignInFailure;
|
|
1245
|
+
module.exports.SIGN_IN_VISITOR_HEADER = SIGN_IN_VISITOR_HEADER;
|
|
1246
|
+
module.exports.storePasswordReset = storePasswordReset;
|
|
1247
|
+
module.exports.consumePasswordReset = consumePasswordReset;
|
|
1248
|
+
module.exports.passwordResetIsLive = passwordResetIsLive;
|
|
1249
|
+
module.exports.PASSWORD_RESET_SUPPORTED = PASSWORD_RESET_SUPPORTED;
|
|
1250
|
+
`
|
|
811
1251
|
: ''}
|
|
812
1252
|
`;
|
|
813
1253
|
};
|
|
1254
|
+
// The jwt callback's refresh without password sign-in (no local lookup exists).
|
|
1255
|
+
const PLAIN_REFRESH_CODE = ` try {
|
|
1256
|
+
if (token && token.email && typeof findUserByEmail === 'function') {
|
|
1257
|
+
const fresh = await findUserByEmail(String(token.email));
|
|
1258
|
+
const safe = fresh && typeof sanitizeUser === 'function' ? sanitizeUser(fresh) : null;
|
|
1259
|
+
if (safe && typeof safe === 'object') {
|
|
1260
|
+
const fk = Object.keys(safe);
|
|
1261
|
+
for (let i = 0; i < fk.length; i++) {
|
|
1262
|
+
token[fk[i]] = safe[fk[i]];
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
`;
|
|
1267
|
+
// With password sign-in: a stamped session follows its account BY ID (the key
|
|
1268
|
+
// every consumer acts on), so a deleted account or a changed password ends it
|
|
1269
|
+
// even after an email change; an unstamped one (an OAuth sign-in, a token from
|
|
1270
|
+
// before stamps) adopts the local account by its email.
|
|
1271
|
+
const PASSWORD_REFRESH_CODE = ` let revoked = false;
|
|
1272
|
+
try {
|
|
1273
|
+
const stamped = !!token && typeof token['${SESSION_STAMP_KEY}'] === 'string';
|
|
1274
|
+
const accountId = token && (token.id != null ? token.id : token.sub);
|
|
1275
|
+
let fresh = null;
|
|
1276
|
+
let looked = false;
|
|
1277
|
+
if (stamped && typeof findUserById === 'function') {
|
|
1278
|
+
fresh = accountId != null ? await findUserById(String(accountId)) : null;
|
|
1279
|
+
looked = true;
|
|
1280
|
+
} else if (token && token.email && typeof findUserByEmail === 'function') {
|
|
1281
|
+
fresh = await findUserByEmail(String(token.email));
|
|
1282
|
+
looked = true;
|
|
1283
|
+
}
|
|
1284
|
+
if (looked) {
|
|
1285
|
+
// The account's password (or session_version) changed since this
|
|
1286
|
+
// token was stamped, or the account is gone: the session is over.
|
|
1287
|
+
// Otherwise the token takes the stamp of the account as it is now.
|
|
1288
|
+
revoked = sessionStampCheck(token, fresh) === 'revoked';
|
|
1289
|
+
// The same end, for a token issued before stamps: a short fingerprint
|
|
1290
|
+
// of the stored hash, never the hash. A change (a reset included)
|
|
1291
|
+
// signs that session out at its next refresh.
|
|
1292
|
+
if (!revoked && fresh) {
|
|
1293
|
+
const currentFingerprint = passwordFingerprint(fresh.password);
|
|
1294
|
+
if (typeof token[TOKEN_PASSWORD_FINGERPRINT] !== 'string') {
|
|
1295
|
+
token[TOKEN_PASSWORD_FINGERPRINT] = currentFingerprint;
|
|
1296
|
+
} else if (token[TOKEN_PASSWORD_FINGERPRINT] !== currentFingerprint) {
|
|
1297
|
+
revoked = true;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
const safe = !revoked && fresh && typeof sanitizeUser === 'function' ? sanitizeUser(fresh) : null;
|
|
1301
|
+
if (safe && typeof safe === 'object') {
|
|
1302
|
+
const fk = Object.keys(safe);
|
|
1303
|
+
for (let i = 0; i < fk.length; i++) {
|
|
1304
|
+
token[fk[i]] = safe[fk[i]];
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
`;
|
|
1309
|
+
/**
|
|
1310
|
+
* auth-options helpers for password sign-in: the sign-in throttle (failures
|
|
1311
|
+
* per visitor and email, counted after a wrong password, checked by the
|
|
1312
|
+
* NextAuth route before the next attempt) and the session stamp
|
|
1313
|
+
* (`utils/auth/session-revocation.js`).
|
|
1314
|
+
*/
|
|
1315
|
+
const CREDENTIAL_SESSION_HELPERS = `// Sign-in throttle. The NextAuth route refuses a visitor's next sign-in for an
|
|
1316
|
+
// email once SIGN_IN_FAILURE_LIMIT failures for it are in the window, once the
|
|
1317
|
+
// visitor has SIGN_IN_VISITOR_FAILURE_LIMIT failures across all emails, or once
|
|
1318
|
+
// they tried it SIGN_IN_ATTEMPT_LIMIT times (so a burst of parallel guesses
|
|
1319
|
+
// cannot all start before the first failure is counted). The route writes who
|
|
1320
|
+
// the visitor is on the request (SIGN_IN_VISITOR_HEADER, replacing whatever the
|
|
1321
|
+
// request carried), because NextAuth hands authorize() the headers only.
|
|
1322
|
+
var SIGN_IN_VISITOR_HEADER = 'x-teleport-signin-visitor';
|
|
1323
|
+
var SIGN_IN_FAILURE_SCOPE = 'auth-signin-failures';
|
|
1324
|
+
var SIGN_IN_FAILURE_LIMIT = ${SIGN_IN_FAILURE_LIMIT};
|
|
1325
|
+
var SIGN_IN_ATTEMPT_SCOPE = 'auth-signin-attempts';
|
|
1326
|
+
var SIGN_IN_ATTEMPT_LIMIT = ${SIGN_IN_ATTEMPT_LIMIT};
|
|
1327
|
+
var SIGN_IN_WINDOW_SECONDS = ${SIGN_IN_WINDOW_SECONDS};
|
|
1328
|
+
// Failures one visitor may make across every email (credential stuffing tries
|
|
1329
|
+
// many addresses once each), counted the same way: after a failure only.
|
|
1330
|
+
var SIGN_IN_VISITOR_FAILURE_SCOPE = 'auth-signin-visitor-failures';
|
|
1331
|
+
var SIGN_IN_VISITOR_FAILURE_LIMIT = ${SIGN_IN_VISITOR_FAILURE_LIMIT};
|
|
1332
|
+
|
|
1333
|
+
function signInVisitorRequest(req) {
|
|
1334
|
+
var headers = (req && req.headers) || {};
|
|
1335
|
+
var visitor = headers[SIGN_IN_VISITOR_HEADER];
|
|
1336
|
+
return { headers: {}, socket: { remoteAddress: typeof visitor === 'string' ? visitor : '' } };
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function signInEmailKey(email) {
|
|
1340
|
+
return String(email == null ? '' : email).trim().toLowerCase();
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// { allowed, retryAfter } for the sign-in the request is about to make.
|
|
1344
|
+
async function checkSignInAllowed(req) {
|
|
1345
|
+
var security = require('../security-runtime');
|
|
1346
|
+
var visitor = signInVisitorRequest(req);
|
|
1347
|
+
var email = signInEmailKey(req && req.body ? req.body.email : '');
|
|
1348
|
+
var failures = await security.peekRateLimitRequest(visitor, {
|
|
1349
|
+
scope: SIGN_IN_FAILURE_SCOPE,
|
|
1350
|
+
limit: SIGN_IN_FAILURE_LIMIT,
|
|
1351
|
+
windowSeconds: SIGN_IN_WINDOW_SECONDS,
|
|
1352
|
+
keyParts: [email],
|
|
1353
|
+
});
|
|
1354
|
+
if (!failures.allowed) {
|
|
1355
|
+
return failures;
|
|
1356
|
+
}
|
|
1357
|
+
var visitorFailures = await security.peekRateLimitRequest(visitor, {
|
|
1358
|
+
scope: SIGN_IN_VISITOR_FAILURE_SCOPE,
|
|
1359
|
+
limit: SIGN_IN_VISITOR_FAILURE_LIMIT,
|
|
1360
|
+
windowSeconds: SIGN_IN_WINDOW_SECONDS,
|
|
1361
|
+
});
|
|
1362
|
+
if (!visitorFailures.allowed) {
|
|
1363
|
+
return visitorFailures;
|
|
1364
|
+
}
|
|
1365
|
+
var attemptAllowed = await security.rateLimitRequest(visitor, null, {
|
|
1366
|
+
scope: SIGN_IN_ATTEMPT_SCOPE,
|
|
1367
|
+
limit: SIGN_IN_ATTEMPT_LIMIT,
|
|
1368
|
+
windowSeconds: SIGN_IN_WINDOW_SECONDS,
|
|
1369
|
+
keyParts: [email],
|
|
1370
|
+
});
|
|
1371
|
+
if (!attemptAllowed) {
|
|
1372
|
+
var nowSec = Math.floor(Date.now() / 1000);
|
|
1373
|
+
return { allowed: false, retryAfter: Math.max(1, SIGN_IN_WINDOW_SECONDS - (nowSec % SIGN_IN_WINDOW_SECONDS)) };
|
|
1374
|
+
}
|
|
1375
|
+
return { allowed: true, retryAfter: 0 };
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
async function countSignInFailure(req, email) {
|
|
1379
|
+
try {
|
|
1380
|
+
var security = require('../security-runtime');
|
|
1381
|
+
var visitor = signInVisitorRequest(req);
|
|
1382
|
+
await security.rateLimitRequest(visitor, null, {
|
|
1383
|
+
scope: SIGN_IN_FAILURE_SCOPE,
|
|
1384
|
+
limit: SIGN_IN_FAILURE_LIMIT,
|
|
1385
|
+
windowSeconds: SIGN_IN_WINDOW_SECONDS,
|
|
1386
|
+
keyParts: [signInEmailKey(email)],
|
|
1387
|
+
});
|
|
1388
|
+
await security.rateLimitRequest(visitor, null, {
|
|
1389
|
+
scope: SIGN_IN_VISITOR_FAILURE_SCOPE,
|
|
1390
|
+
limit: SIGN_IN_VISITOR_FAILURE_LIMIT,
|
|
1391
|
+
windowSeconds: SIGN_IN_WINDOW_SECONDS,
|
|
1392
|
+
});
|
|
1393
|
+
} catch (err) {
|
|
1394
|
+
console.error('[auth] Could not count a failed sign-in:', err && err.message ? err.message : err);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// The shared session-stamp check (utils/auth/session-revocation.js).
|
|
1399
|
+
function sessionRevocationModule() {
|
|
1400
|
+
try {
|
|
1401
|
+
var revocation = require('./${SESSION_REVOCATION_FILE_NAME}');
|
|
1402
|
+
return revocation && typeof revocation.stampVerdict === 'function' ? revocation : null;
|
|
1403
|
+
} catch (e) {
|
|
1404
|
+
return null;
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// A signed-in user's session carries the stamp of the account it was issued for.
|
|
1409
|
+
function withSessionStamp(safeUser, row) {
|
|
1410
|
+
var revocation = sessionRevocationModule();
|
|
1411
|
+
var stamp = revocation ? revocation.credentialStamp(row) : null;
|
|
1412
|
+
if (safeUser && stamp) {
|
|
1413
|
+
safeUser[revocation.SESSION_STAMP_KEY] = stamp;
|
|
1414
|
+
}
|
|
1415
|
+
if (safeUser) {
|
|
1416
|
+
safeUser[TOKEN_PASSWORD_FINGERPRINT] = passwordFingerprint(row ? row.password : null);
|
|
1417
|
+
}
|
|
1418
|
+
return safeUser;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// 'revoked' when the stored account no longer matches the token's stamp;
|
|
1422
|
+
// otherwise the token takes the account's current stamp.
|
|
1423
|
+
function sessionStampCheck(token, row) {
|
|
1424
|
+
var revocation = sessionRevocationModule();
|
|
1425
|
+
if (!revocation) {
|
|
1426
|
+
return 'unchecked';
|
|
1427
|
+
}
|
|
1428
|
+
var verdict = revocation.stampVerdict(token, row || null);
|
|
1429
|
+
if (verdict !== 'revoked' && row) {
|
|
1430
|
+
var stamp = revocation.credentialStamp(row);
|
|
1431
|
+
if (stamp) {
|
|
1432
|
+
token[revocation.SESSION_STAMP_KEY] = stamp;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
return verdict;
|
|
1436
|
+
}`;
|
|
1437
|
+
/**
|
|
1438
|
+
* The user stores whose own account ids can never equal an OAuth provider's id:
|
|
1439
|
+
* MongoDB (`_id`, a 24-hex ObjectId), Airtable (`rec…` record ids) and
|
|
1440
|
+
* Firestore (20-character auto ids). Read from `auth.dataSourceType`, the same
|
|
1441
|
+
* value that picks the auth driver.
|
|
1442
|
+
*/
|
|
1443
|
+
const STORES_WITH_DISJOINT_IDS = new Set(['mongodb', 'airtable', 'firestore']);
|
|
1444
|
+
/**
|
|
1445
|
+
* Whether an OAuth session's id must be namespaced (`oauth:<provider>:<id>`)
|
|
1446
|
+
* because the provider's raw account id could equal another account's id:
|
|
1447
|
+
* - the project has more than one OAuth provider: two providers' id spaces
|
|
1448
|
+
* can collide, whatever the store;
|
|
1449
|
+
* - otherwise a store whose ids are its own (STORES_WITH_DISJOINT_IDS) keeps
|
|
1450
|
+
* the raw id;
|
|
1451
|
+
* - any other store (SQL or not) is decided by the declared `users` id type:
|
|
1452
|
+
* a UUID keeps the raw id; any other type (a provider's integer id can equal
|
|
1453
|
+
* a local integer id on every owner rule), or none declared, namespaces.
|
|
1454
|
+
* The editor declares the type the database reported for its `users` table
|
|
1455
|
+
* (teleport-gui `liveUsersIdType`), and no type when none was recorded.
|
|
1456
|
+
* Where the raw id stays it never equals a local id, and every record a
|
|
1457
|
+
* published site wrote under it (entitlements, cart, favourites, chats,
|
|
1458
|
+
* downloads) stays the visitor's. Wherever a collision cannot be ruled out the
|
|
1459
|
+
* id is namespaced: a collision hands one person another's records, which no
|
|
1460
|
+
* later fix undoes; lost continuity leaves the records in place under the old
|
|
1461
|
+
* id.
|
|
1462
|
+
*/
|
|
1463
|
+
export const oauthIdsNeedNamespace = (auth) => {
|
|
1464
|
+
if ((auth.providers || []).length > 1) {
|
|
1465
|
+
return true;
|
|
1466
|
+
}
|
|
1467
|
+
if (auth.dataSourceType && STORES_WITH_DISJOINT_IDS.has(auth.dataSourceType)) {
|
|
1468
|
+
return false;
|
|
1469
|
+
}
|
|
1470
|
+
const idColumn = ((auth.tables && auth.tables.users) || []).find((column) => !!column && String(column.name || '').toLowerCase() === 'id');
|
|
1471
|
+
return !(idColumn && /^\s*uuid\b/i.test(String(idColumn.type || '')));
|
|
1472
|
+
};
|
|
1473
|
+
/**
|
|
1474
|
+
* auth-options helpers for a project with password sign-in AND an OAuth
|
|
1475
|
+
* provider: whether the provider vouches for the email an OAuth sign-in
|
|
1476
|
+
* presents. Only then may that sign-in become the local account with the same
|
|
1477
|
+
* email (its id and role); otherwise it is a session of its own.
|
|
1478
|
+
*/
|
|
1479
|
+
const generateOAuthEmailVerificationHelpers = (auth) => {
|
|
1480
|
+
const tenants = microsoftTenantEnvKeys(auth);
|
|
1481
|
+
const tenantEntries = Object.keys(tenants)
|
|
1482
|
+
.map((id) => `${JSON.stringify(id)}: process.env.${tenants[id]}`)
|
|
1483
|
+
.join(', ');
|
|
1484
|
+
return `// Providers that only ever hand out an email its owner proved. Any other
|
|
1485
|
+
// provider must say so in the profile (email_verified); an explicit "not
|
|
1486
|
+
// verified" outweighs the list.
|
|
1487
|
+
var VERIFIED_EMAIL_PROVIDERS = { google: true, apple: true };
|
|
1488
|
+
var OAUTH_UNVERIFIED = '__oauthUnverified';
|
|
1489
|
+
// A provider's raw account id could equal another account's id here (see
|
|
1490
|
+
// oauthIdsNeedNamespace in the code generator): sessions without a local
|
|
1491
|
+
// account take the provider's id as "oauth:<provider>:<id>".
|
|
1492
|
+
var OAUTH_IDS_NAMESPACED = ${JSON.stringify(oauthIdsNeedNamespace(auth))};
|
|
1493
|
+
|
|
1494
|
+
// Microsoft (Azure AD / Entra ID) tokens carry no email_verified, and on the
|
|
1495
|
+
// multi-tenant endpoint the email claim is whatever the signing-in tenant's
|
|
1496
|
+
// administrator set (the "nOAuth" class). An email counts only when the token
|
|
1497
|
+
// says its domain is verified (xms_edov), or when the provider is configured
|
|
1498
|
+
// for one specific tenant (an id or a domain, never common / organizations /
|
|
1499
|
+
// consumers): the site owner's own directory.
|
|
1500
|
+
var MICROSOFT_PROVIDERS = { 'azure-ad': true, 'microsoft-entra-id': true };
|
|
1501
|
+
var MICROSOFT_TENANTS = { ${tenantEntries} };
|
|
1502
|
+
var MICROSOFT_TENANT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
1503
|
+
|
|
1504
|
+
// The one tenant a Microsoft provider is configured for, or '' (multi-tenant).
|
|
1505
|
+
function microsoftTenantOf(value) {
|
|
1506
|
+
var raw = String(value == null ? '' : value).trim().toLowerCase();
|
|
1507
|
+
if (!raw || raw.indexOf('teleporthq.secrets.') === 0) {
|
|
1508
|
+
return '';
|
|
1509
|
+
}
|
|
1510
|
+
var fromIssuer = /^https:\\/\\/login\\.microsoftonline\\.com\\/([^\\/?#]+)/.exec(raw);
|
|
1511
|
+
var tenant = fromIssuer ? fromIssuer[1] : raw;
|
|
1512
|
+
if (tenant === 'common' || tenant === 'organizations' || tenant === 'consumers') {
|
|
1513
|
+
return '';
|
|
1514
|
+
}
|
|
1515
|
+
return MICROSOFT_TENANT_ID.test(tenant) || /^[a-z0-9-]+(\\.[a-z0-9-]+)+$/.test(tenant) ? tenant : '';
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
function microsoftEmailIsVerified(provider, claims) {
|
|
1519
|
+
if (claims.xms_edov === true || claims.xms_edov === 'true' || claims.xms_edov === 1) {
|
|
1520
|
+
return true;
|
|
1521
|
+
}
|
|
1522
|
+
var tenant = microsoftTenantOf(MICROSOFT_TENANTS[provider]);
|
|
1523
|
+
if (!tenant) {
|
|
1524
|
+
return false;
|
|
1525
|
+
}
|
|
1526
|
+
// A tenant given by id must be the one the token was issued in.
|
|
1527
|
+
if (MICROSOFT_TENANT_ID.test(tenant) && claims.tid != null && String(claims.tid).toLowerCase() !== tenant) {
|
|
1528
|
+
return false;
|
|
1529
|
+
}
|
|
1530
|
+
return true;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// GitHub's profile email is not always a verified one: ask GitHub (the
|
|
1534
|
+
// provider requests the user:email scope) whether this address is.
|
|
1535
|
+
async function githubEmailIsVerified(account, email) {
|
|
1536
|
+
if (!account || !account.access_token || !email) {
|
|
1537
|
+
return false;
|
|
1538
|
+
}
|
|
1539
|
+
try {
|
|
1540
|
+
var res = await fetch('https://api.github.com/user/emails', {
|
|
1541
|
+
headers: {
|
|
1542
|
+
Authorization: 'Bearer ' + account.access_token,
|
|
1543
|
+
Accept: 'application/vnd.github+json',
|
|
1544
|
+
'User-Agent': 'teleporthq-auth',
|
|
1545
|
+
},
|
|
1546
|
+
});
|
|
1547
|
+
if (!res.ok) {
|
|
1548
|
+
return false;
|
|
1549
|
+
}
|
|
1550
|
+
var list = await res.json();
|
|
1551
|
+
var wanted = String(email).toLowerCase();
|
|
1552
|
+
return Array.isArray(list) && list.some(function (entry) {
|
|
1553
|
+
return !!entry && entry.verified === true && String(entry.email || '').toLowerCase() === wanted;
|
|
1554
|
+
});
|
|
1555
|
+
} catch (e) {
|
|
1556
|
+
return false;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
async function oauthEmailIsVerified(account, profile, token) {
|
|
1561
|
+
var claims = profile || {};
|
|
1562
|
+
if (claims.email_verified === false || claims.email_verified === 'false') {
|
|
1563
|
+
return false;
|
|
1564
|
+
}
|
|
1565
|
+
if (claims.email_verified === true || claims.email_verified === 'true') {
|
|
1566
|
+
return true;
|
|
1567
|
+
}
|
|
1568
|
+
var provider = String((account && account.provider) || '');
|
|
1569
|
+
if (provider === 'github') {
|
|
1570
|
+
return githubEmailIsVerified(account, token && token.email);
|
|
1571
|
+
}
|
|
1572
|
+
if (MICROSOFT_PROVIDERS[provider] === true) {
|
|
1573
|
+
return microsoftEmailIsVerified(provider, claims);
|
|
1574
|
+
}
|
|
1575
|
+
return VERIFIED_EMAIL_PROVIDERS[provider] === true;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// The provider's own account id as "oauth:<provider>:<id>": a namespace no
|
|
1579
|
+
// local account id can share (a provider's integer ids would otherwise match
|
|
1580
|
+
// local integer ids on owner rules). Where no id can collide, the raw id stays.
|
|
1581
|
+
function namespaceOAuthId(token, account) {
|
|
1582
|
+
if (!OAUTH_IDS_NAMESPACED) {
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
var accountId = String((account && account.providerAccountId) || token.sub || token.id || '');
|
|
1586
|
+
var id = 'oauth:' + String((account && account.provider) || 'provider') + ':' + accountId;
|
|
1587
|
+
token.id = id;
|
|
1588
|
+
token.sub = id;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// Not vouched for: the visitor keeps a session under an id no local account can
|
|
1592
|
+
// have (namespaced where one could), with no email to be matched by and no role.
|
|
1593
|
+
function asDistinctOAuthIdentity(token, account) {
|
|
1594
|
+
namespaceOAuthId(token, account);
|
|
1595
|
+
delete token.email;
|
|
1596
|
+
delete token.role;
|
|
1597
|
+
delete token.roleName;
|
|
1598
|
+
delete token.roles;
|
|
1599
|
+
token[OAUTH_UNVERIFIED] = true;
|
|
1600
|
+
}`;
|
|
1601
|
+
};
|
|
814
1602
|
/**
|
|
815
1603
|
* Why a sign-in failed, in words, plus the one-line codes `authorize` throws.
|
|
816
1604
|
*
|
|
@@ -831,6 +1619,7 @@ const AUTH_ERROR_MESSAGES = {
|
|
|
831
1619
|
InvalidCredentials: 'That email and password do not match an account.',
|
|
832
1620
|
ServiceUnavailable:
|
|
833
1621
|
'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.',
|
|
1622
|
+
TooManyAttempts: 'Too many sign-in attempts. Please wait a few minutes and try again.',
|
|
834
1623
|
|
|
835
1624
|
// NextAuth's own.
|
|
836
1625
|
CredentialsSignin: 'That email and password do not match an account.',
|
|
@@ -928,36 +1717,155 @@ module.exports = {
|
|
|
928
1717
|
};
|
|
929
1718
|
`;
|
|
930
1719
|
};
|
|
1720
|
+
/** The password a site accepts when one is set (sign-up, reset, a change). */
|
|
1721
|
+
export const MIN_PASSWORD_LENGTH = 8;
|
|
1722
|
+
export const MAX_PASSWORD_BYTES = 128;
|
|
931
1723
|
export const generateHashPasswordFile = () => {
|
|
932
1724
|
return `const bcrypt = require('bcryptjs');
|
|
933
1725
|
const crypto = require('crypto');
|
|
934
1726
|
|
|
935
|
-
const PEPPER = process.env.AUTH_PEPPER || '';
|
|
936
1727
|
const COST_FACTOR = 12;
|
|
1728
|
+
const MIN_PASSWORD_LENGTH = ${MIN_PASSWORD_LENGTH};
|
|
1729
|
+
const MAX_PASSWORD_BYTES = ${MAX_PASSWORD_BYTES};
|
|
1730
|
+
|
|
1731
|
+
// Two peppers. AUTH_PEPPER_SECRET is the project's own random secret, which the
|
|
1732
|
+
// deploy step provisions; every new hash uses it. AUTH_PEPPER (the project id,
|
|
1733
|
+
// no secret) is the one every hash was made with before it existed: such a hash
|
|
1734
|
+
// still verifies, and the sign-in replaces it. Without the secret, hashing and
|
|
1735
|
+
// verifying stay on the old pepper — never on none.
|
|
1736
|
+
function pepperSecret() {
|
|
1737
|
+
var value = process.env.AUTH_PEPPER_SECRET;
|
|
1738
|
+
return typeof value === 'string' && value && value.indexOf('teleporthq.secrets.') !== 0 ? value : '';
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
function legacyPepper() {
|
|
1742
|
+
return process.env.AUTH_PEPPER || '';
|
|
1743
|
+
}
|
|
937
1744
|
|
|
938
|
-
|
|
1745
|
+
// SHA-256 first, so bcrypt (which reads at most 72 bytes) sees all of it.
|
|
1746
|
+
function prepare(password, pepper) {
|
|
939
1747
|
const str = String(password);
|
|
940
1748
|
if (str.length > 1000) {
|
|
941
1749
|
throw new Error('Password exceeds maximum length');
|
|
942
1750
|
}
|
|
943
|
-
|
|
944
|
-
return sha;
|
|
1751
|
+
return crypto.createHash('sha256').update(str + pepper).digest('base64');
|
|
945
1752
|
}
|
|
946
1753
|
|
|
947
1754
|
function hashPassword(password) {
|
|
948
|
-
return bcrypt.hashSync(prepare(password), COST_FACTOR);
|
|
1755
|
+
return bcrypt.hashSync(prepare(password, pepperSecret() || legacyPepper()), COST_FACTOR);
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// A hash no password matches, made once per process with the project pepper
|
|
1759
|
+
// at the same cost. An address without an account, or a row without a
|
|
1760
|
+
// password (a subscriber, a guest), is checked against it, so every failed
|
|
1761
|
+
// sign-in does the bcrypt work a wrong password does: the time never tells
|
|
1762
|
+
// them apart.
|
|
1763
|
+
var __standInHash = null;
|
|
1764
|
+
function standInHash() {
|
|
1765
|
+
if (!__standInHash) {
|
|
1766
|
+
__standInHash = bcrypt.hashSync(prepare(crypto.randomBytes(32).toString('hex'), pepperSecret() || legacyPepper()), COST_FACTOR);
|
|
1767
|
+
}
|
|
1768
|
+
return __standInHash;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// { ok, needsRehash }: ok when the password matches; needsRehash when it
|
|
1772
|
+
// matched through the old pepper while the project has its own.
|
|
1773
|
+
function checkPassword(password, hash) {
|
|
1774
|
+
var known = typeof hash === 'string' && hash !== '';
|
|
1775
|
+
var against = known ? hash : standInHash();
|
|
1776
|
+
var secret = pepperSecret();
|
|
1777
|
+
if (secret && bcrypt.compareSync(prepare(password, secret), against) && known) {
|
|
1778
|
+
return { ok: true, needsRehash: false };
|
|
1779
|
+
}
|
|
1780
|
+
// Run on every miss, whatever the hash was.
|
|
1781
|
+
if (bcrypt.compareSync(prepare(password, legacyPepper()), against) && known) {
|
|
1782
|
+
return { ok: true, needsRehash: !!secret };
|
|
1783
|
+
}
|
|
1784
|
+
return { ok: false, needsRehash: false };
|
|
949
1785
|
}
|
|
950
1786
|
|
|
951
1787
|
function verifyPassword(password, hash) {
|
|
952
|
-
return
|
|
1788
|
+
return checkPassword(password, hash).ok;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
// Why a password may not be set, or '' when it may.
|
|
1792
|
+
function passwordPolicyError(password) {
|
|
1793
|
+
if (typeof password !== 'string' || password.length < MIN_PASSWORD_LENGTH) {
|
|
1794
|
+
return 'Password must be at least ' + MIN_PASSWORD_LENGTH + ' characters';
|
|
1795
|
+
}
|
|
1796
|
+
if (Buffer.byteLength(password, 'utf8') > MAX_PASSWORD_BYTES) {
|
|
1797
|
+
return 'Password is too long (at most ' + MAX_PASSWORD_BYTES + ' bytes)';
|
|
1798
|
+
}
|
|
1799
|
+
return '';
|
|
953
1800
|
}
|
|
954
1801
|
|
|
955
1802
|
module.exports = hashPassword;
|
|
956
1803
|
module.exports.hashPassword = hashPassword;
|
|
957
1804
|
module.exports.verifyPassword = verifyPassword;
|
|
1805
|
+
module.exports.checkPassword = checkPassword;
|
|
1806
|
+
module.exports.passwordPolicyError = passwordPolicyError;
|
|
958
1807
|
`;
|
|
959
1808
|
};
|
|
960
|
-
|
|
1809
|
+
// Answered to a credentials sign-in the throttle refuses, in the shape
|
|
1810
|
+
// next-auth/react's signIn() reads (it parses \`url\` for the error code, which
|
|
1811
|
+
// describeAuthError turns into copy).
|
|
1812
|
+
const SIGN_IN_THROTTLE_CODE = `var __security = require('${securityRuntimeSpecifier('../../..')}');
|
|
1813
|
+
|
|
1814
|
+
function isCredentialsSignIn(req) {
|
|
1815
|
+
var parts = req && req.query ? req.query.nextauth : null;
|
|
1816
|
+
return (
|
|
1817
|
+
req && req.method === 'POST' && Array.isArray(parts) && parts[0] === 'callback' && parts[1] === 'credentials'
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
function refuseSignIn(res, status, code, message, retryAfter) {
|
|
1822
|
+
var origin = process.env.NEXTAUTH_URL || 'http://localhost:3000';
|
|
1823
|
+
var url;
|
|
1824
|
+
try {
|
|
1825
|
+
url = new URL('/api/auth/error?error=' + code, origin).toString();
|
|
1826
|
+
} catch (e) {
|
|
1827
|
+
url = 'http://localhost:3000/api/auth/error?error=' + code;
|
|
1828
|
+
}
|
|
1829
|
+
if (retryAfter) {
|
|
1830
|
+
res.setHeader('Retry-After', String(retryAfter));
|
|
1831
|
+
}
|
|
1832
|
+
res.status(status).json({ url: url, error: message });
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// Too many failed (or attempted) sign-ins for this email from this visitor:
|
|
1836
|
+
// refused before the password is even looked at.
|
|
1837
|
+
async function throttleCredentialsSignIn(req, res) {
|
|
1838
|
+
if (req && req.headers) {
|
|
1839
|
+
delete req.headers[authOptions.SIGN_IN_VISITOR_HEADER];
|
|
1840
|
+
}
|
|
1841
|
+
if (!isCredentialsSignIn(req)) {
|
|
1842
|
+
return false;
|
|
1843
|
+
}
|
|
1844
|
+
req.headers[authOptions.SIGN_IN_VISITOR_HEADER] = __security.clientIp(req);
|
|
1845
|
+
var verdict;
|
|
1846
|
+
try {
|
|
1847
|
+
verdict = await authOptions.checkSignInAllowed(req);
|
|
1848
|
+
} catch (err) {
|
|
1849
|
+
console.error('[auth] The sign-in throttle failed:', err && err.message ? err.message : err);
|
|
1850
|
+
refuseSignIn(res, 503, 'ServiceUnavailable', 'Sign-in is unavailable right now. Please try again in a moment.', 0);
|
|
1851
|
+
return true;
|
|
1852
|
+
}
|
|
1853
|
+
if (verdict && verdict.allowed) {
|
|
1854
|
+
return false;
|
|
1855
|
+
}
|
|
1856
|
+
refuseSignIn(
|
|
1857
|
+
res,
|
|
1858
|
+
429,
|
|
1859
|
+
'TooManyAttempts',
|
|
1860
|
+
'Too many sign-in attempts. Try again later.',
|
|
1861
|
+
verdict && verdict.retryAfter ? verdict.retryAfter : ${SIGN_IN_WINDOW_SECONDS}
|
|
1862
|
+
);
|
|
1863
|
+
return true;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
`;
|
|
1867
|
+
export const generateNextAuthRouteFile = (options = {}) => {
|
|
1868
|
+
const throttled = options.passwordAuthEnabled === true;
|
|
961
1869
|
return `let NextAuth = require('next-auth');
|
|
962
1870
|
if (NextAuth.default) NextAuth = NextAuth.default;
|
|
963
1871
|
const authOptions = require('../../../utils/auth/auth-options');
|
|
@@ -1052,45 +1960,287 @@ function resolveRequestOrigin(req) {
|
|
|
1052
1960
|
return proto + '://' + host;
|
|
1053
1961
|
}
|
|
1054
1962
|
|
|
1055
|
-
module.exports = function nextAuthRoute(req, res) {
|
|
1963
|
+
${throttled ? SIGN_IN_THROTTLE_CODE : ''}module.exports = ${throttled ? 'async ' : ''}function nextAuthRoute(req, res) {
|
|
1056
1964
|
try {
|
|
1057
1965
|
process.env.NEXTAUTH_URL = resolveRequestOrigin(req);
|
|
1058
1966
|
} catch (e) {
|
|
1059
1967
|
/* fall back to whatever NEXTAUTH_URL holds */
|
|
1060
1968
|
}
|
|
1061
|
-
|
|
1969
|
+
${throttled
|
|
1970
|
+
? ` if (await throttleCredentialsSignIn(req, res)) {
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
`
|
|
1974
|
+
: ''} return authHandler(req, res);
|
|
1975
|
+
};
|
|
1976
|
+
`;
|
|
1062
1977
|
};
|
|
1978
|
+
/**
|
|
1979
|
+
* `recordAccountEvent(type, user, source, req)`, for the account routes of a
|
|
1980
|
+
* project with automations: records `user-signed-up` / `user-deleted` on the
|
|
1981
|
+
* in-app event bus, which writes the `teleport_events` row and wakes the
|
|
1982
|
+
* automations off the request path, on the app's own origin as `req` reached
|
|
1983
|
+
* it. Only the profile fields go into the event, never a credential. It never
|
|
1984
|
+
* throws, so the event cannot fail the request it records. `relativePrefix`
|
|
1985
|
+
* leads from the route to the project root.
|
|
1986
|
+
*/
|
|
1987
|
+
export const generateAccountEventRecorder = (options) => `
|
|
1988
|
+
// The project has automations: the account event wakes them. Best effort,
|
|
1989
|
+
// like the emails: recording it can never fail the request.
|
|
1990
|
+
function accountEventTime(value) {
|
|
1991
|
+
var date = value ? new Date(value) : new Date();
|
|
1992
|
+
return isNaN(date.getTime()) ? new Date().toISOString() : date.toISOString();
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
async function recordAccountEvent(type, user, source, req) {
|
|
1996
|
+
try {
|
|
1997
|
+
var userId = user ? user.id || user._id : null;
|
|
1998
|
+
if (!userId) { return; }
|
|
1999
|
+
var id = String(userId);
|
|
2000
|
+
var email = user.email ? String(user.email) : '';
|
|
2001
|
+
var bus = require('${options.relativePrefix}/${AUTOMATIONS_PATH.join('/')}/event-bus');
|
|
2002
|
+
await bus.emitBusinessEvent({
|
|
2003
|
+
type: type,
|
|
2004
|
+
entityType: 'user',
|
|
2005
|
+
entityId: id,
|
|
2006
|
+
payload: {
|
|
2007
|
+
user: {
|
|
2008
|
+
id: id,
|
|
2009
|
+
email: email || null,
|
|
2010
|
+
name: user.name || null,
|
|
2011
|
+
role: user.role || null,
|
|
2012
|
+
source: source || user.source || null,
|
|
2013
|
+
createdAt: accountEventTime(user.created_at || user.createdAt),
|
|
2014
|
+
},
|
|
2015
|
+
},
|
|
2016
|
+
actor: email,
|
|
2017
|
+
actorType: 'customer',
|
|
2018
|
+
dedupeKey: type + ':' + id,
|
|
2019
|
+
}, req);
|
|
2020
|
+
} catch (eventErr) {
|
|
2021
|
+
var reason = eventErr && eventErr.message ? eventErr.message : eventErr;
|
|
2022
|
+
console.error('${options.logTag} could not record the ' + type + ' event:', reason);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
1063
2025
|
`;
|
|
2026
|
+
/**
|
|
2027
|
+
* A user column that grants something — a role, an admin or staff flag, a
|
|
2028
|
+
* verified email, credit, a plan or an entitlement — in any spelling
|
|
2029
|
+
* (`is_admin`, `isAdmin`, `Email-Verified`). Only the server sets these; a
|
|
2030
|
+
* sign-up never takes them from the visitor. The one shared rule
|
|
2031
|
+
* (`AccountFields.isPrivilegedAccountField`).
|
|
2032
|
+
*/
|
|
2033
|
+
export const isPrivilegedUserField = (key) => AccountFields.isPrivilegedAccountField(key);
|
|
2034
|
+
/** Config keys of an `account-signup` node that are not user fields. */
|
|
2035
|
+
const SIGNUP_NODE_NON_FIELD_KEYS = new Set([
|
|
2036
|
+
'email',
|
|
2037
|
+
'password',
|
|
2038
|
+
'name',
|
|
2039
|
+
'image',
|
|
2040
|
+
'role',
|
|
2041
|
+
...WELCOME_EMAIL_CONFIG_KEYS,
|
|
2042
|
+
]);
|
|
2043
|
+
/**
|
|
2044
|
+
* The fields a sign-up may write besides email, password and name: the avatar
|
|
2045
|
+
* (`image`) and each declared custom account property that a sign-up form of
|
|
2046
|
+
* the project collects (a key of an `account-signup` node), never a
|
|
2047
|
+
* privileged one.
|
|
2048
|
+
*/
|
|
2049
|
+
export const resolveSignupFields = (auth, signupNodeConfigs) => {
|
|
2050
|
+
const declared = new Set((auth.customUserProperties || []).map((prop) => prop.key));
|
|
2051
|
+
const collected = new Set();
|
|
2052
|
+
for (const config of signupNodeConfigs) {
|
|
2053
|
+
for (const key of Object.keys(config || {})) {
|
|
2054
|
+
if (!SIGNUP_NODE_NON_FIELD_KEYS.has(key) &&
|
|
2055
|
+
declared.has(key) &&
|
|
2056
|
+
!isPrivilegedUserField(key)) {
|
|
2057
|
+
collected.add(key);
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
return ['image', ...Array.from(collected).sort()];
|
|
1064
2062
|
};
|
|
1065
|
-
|
|
2063
|
+
/**
|
|
2064
|
+
* After a new account with a ticked consent box: the newsletter helper adds it
|
|
2065
|
+
* (double opt-in, the `newsletter-subscribed` event, the newsletter's welcome
|
|
2066
|
+
* unless the account's own went out). Best effort, like the welcome email.
|
|
2067
|
+
*/
|
|
2068
|
+
const SIGNUP_NEWSLETTER_CODE = ` // The ticked box also subscribes the person to the newsletter.
|
|
2069
|
+
if (userData.marketing_consent) {
|
|
2070
|
+
try {
|
|
2071
|
+
const __newsletter = require('../../../utils/newsletter/newsletter');
|
|
2072
|
+
await __newsletter.withNewsletterDb(function (db) {
|
|
2073
|
+
return __newsletter.subscribeNewsletterPerson({ db: db, email: email, signUpPoint: 'signup', req: req });
|
|
2074
|
+
});
|
|
2075
|
+
} catch (newsletterErr) {
|
|
2076
|
+
console.error('[account-signup] newsletter sign-up failed:', newsletterErr && newsletterErr.message ? newsletterErr.message : newsletterErr);
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
`;
|
|
2080
|
+
/**
|
|
2081
|
+
* The answer to a sign-up for an address the site already knows (an account,
|
|
2082
|
+
* or a row without a password) takes at least this long, as the
|
|
2083
|
+
* forgot-password answer does: the two are answered alike, and the time must
|
|
2084
|
+
* not tell them apart either.
|
|
2085
|
+
*/
|
|
2086
|
+
export const KNOWN_ADDRESS_MIN_RESPONSE_MS = 1200;
|
|
2087
|
+
/**
|
|
2088
|
+
* The sign-up route's emails to an address the site already knows, sent
|
|
2089
|
+
* through `sender` (the sign-up node's, else the forgot-password node's):
|
|
2090
|
+
* `sendAccountNotice(emailType, msg, logged)`, which sends and records the
|
|
2091
|
+
* email under its own purpose (with what the log may keep), and
|
|
2092
|
+
* `sendSetPasswordEmail(user, siteName, req)`, the link for an address without
|
|
2093
|
+
* a password, built on the forgot-password token flow (a CSPRNG token whose
|
|
2094
|
+
* SHA-256 alone is stored against the account, the same lifetime, origin rule
|
|
2095
|
+
* and reset page).
|
|
2096
|
+
*/
|
|
2097
|
+
const generateKnownAddressEmailCode = (options) => {
|
|
2098
|
+
const { sender } = options;
|
|
2099
|
+
const senderConstants = `
|
|
2100
|
+
// Who sends the emails to an address the site already knows: the sign-up
|
|
2101
|
+
// node's email settings, else the forgot-password node's.
|
|
2102
|
+
const NOTICE_EMAIL_PROVIDER = ${JSON.stringify(sender.provider)};
|
|
2103
|
+
const NOTICE_EMAIL_FROM = ${JSON.stringify(sender.from)};
|
|
2104
|
+
const NOTICE_EMAIL_SECRET_ENV_NAME = ${JSON.stringify(sender.secretEnv)};`;
|
|
2105
|
+
if (!sender.provider) {
|
|
2106
|
+
return `${senderConstants}
|
|
2107
|
+
|
|
2108
|
+
// No email provider: nothing is sent to an address the site already knows.
|
|
2109
|
+
async function sendAccountNotice() {}
|
|
2110
|
+
|
|
2111
|
+
// A sign-up for an address the site knows without a password mails its owner
|
|
2112
|
+
// a link to set one; this project has no email provider, so none is sent.
|
|
2113
|
+
async function sendSetPasswordEmail() {
|
|
2114
|
+
console.warn('[account-signup] set-password link not sent: no email provider is configured');
|
|
2115
|
+
}`;
|
|
2116
|
+
}
|
|
2117
|
+
const ttlMinutes = Math.round(PASSWORD_RESET_TOKEN_TTL_SECONDS / 60);
|
|
2118
|
+
// Where the link carries the token, as the forgot-password link does.
|
|
2119
|
+
const tokenLinkPrefix = options.tokenInFragment ? '#token=' : '?token=';
|
|
2120
|
+
const noticeSend = generateProviderSendFunction(sender.provider).replace('async function __sendProviderEmail(', 'async function __sendNoticeEmail(');
|
|
2121
|
+
return `${senderConstants}
|
|
2122
|
+
|
|
2123
|
+
${noticeSend}
|
|
2124
|
+
|
|
2125
|
+
var __noticeEmailLog = require('../../../utils/email/sent-email-log');
|
|
2126
|
+
|
|
2127
|
+
// Sends one of these emails and records it under its own purpose, with what
|
|
2128
|
+
// the log may keep of it (\`logged\`: a link's token hidden).
|
|
2129
|
+
async function sendAccountNotice(emailType, msg, logged) {
|
|
2130
|
+
var failure = null;
|
|
2131
|
+
try {
|
|
2132
|
+
await __sendNoticeEmail(msg);
|
|
2133
|
+
} catch (err) {
|
|
2134
|
+
failure = err;
|
|
2135
|
+
}
|
|
2136
|
+
__noticeEmailLog.recordSentEmail({
|
|
2137
|
+
emailType: emailType,
|
|
2138
|
+
audience: 'customer',
|
|
2139
|
+
to: msg.to,
|
|
2140
|
+
from: msg.from,
|
|
2141
|
+
subject: logged.subject,
|
|
2142
|
+
html: logged.html,
|
|
2143
|
+
payload: logged.payload,
|
|
2144
|
+
provider: NOTICE_EMAIL_PROVIDER,
|
|
2145
|
+
status: failure ? 'failed' : 'sent',
|
|
2146
|
+
error: failure ? (failure.message || String(failure)) : null,
|
|
2147
|
+
source: 'signup',
|
|
2148
|
+
sourceRef: 'api/auth/signup',
|
|
2149
|
+
userId: msg.userId,
|
|
2150
|
+
});
|
|
2151
|
+
if (failure) { throw failure; }
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
// A sign-up for an address the site knows without a password (a newsletter
|
|
2155
|
+
// subscriber, a checkout or booking guest, a contact the owner added) never
|
|
2156
|
+
// takes that row: whoever typed the address may not own it. Its owner is mailed
|
|
2157
|
+
// a link to set a password instead, a password-reset link minted and stored as
|
|
2158
|
+
// the forgot-password route mints it, at most ${EXISTING_ACCOUNT_EMAIL_LIMIT} an hour per address
|
|
2159
|
+
// (counted with the "already have an account" email).
|
|
2160
|
+
var SET_PASSWORD_SUBJECT = 'Set your password to finish creating your account';
|
|
2161
|
+
var SET_PASSWORD_BODY_HTML =
|
|
2162
|
+
'<p>Someone, hopefully you, tried to create an account{{siteNameSuffix}} with this email address.</p>' +
|
|
2163
|
+
'<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>' +
|
|
2164
|
+
'<p><a href="{{resetUrl}}">Set your password</a></p>' +
|
|
2165
|
+
'<p>The link works for ${ttlMinutes} minutes. If this was not you, you can ignore this email: nothing has changed.</p>';
|
|
2166
|
+
var SET_PASSWORD_PAGE_PATH = ${JSON.stringify(options.pagePath)};
|
|
2167
|
+
var SET_PASSWORD_TOKEN_LINK_PREFIX = ${JSON.stringify(tokenLinkPrefix)};
|
|
2168
|
+
var SET_PASSWORD_TOKEN_TTL_SECONDS = ${PASSWORD_RESET_TOKEN_TTL_SECONDS};
|
|
2169
|
+
|
|
2170
|
+
${TRUSTED_ORIGIN_CODE}
|
|
2171
|
+
|
|
2172
|
+
// The token, wherever it appears, for what the sent-email log keeps: whoever
|
|
2173
|
+
// can read the log must not be able to set the password with it.
|
|
2174
|
+
function hideToken(text, token) {
|
|
2175
|
+
return String(text == null ? '' : text).split(token).join('[hidden]');
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
async function sendSetPasswordEmail(user, siteName, req) {
|
|
2179
|
+
var toEmail = String(user.email || '');
|
|
2180
|
+
var userId = user.id != null ? user.id : user._id;
|
|
2181
|
+
var apiKey = NOTICE_EMAIL_SECRET_ENV_NAME ? process.env[NOTICE_EMAIL_SECRET_ENV_NAME] : '';
|
|
2182
|
+
if (apiKey && String(apiKey).indexOf('teleporthq.secrets.') === 0) { apiKey = ''; }
|
|
2183
|
+
var from = NOTICE_EMAIL_FROM || process.env.EMAIL_FROM || '';
|
|
2184
|
+
if (!apiKey || !from) { console.warn('[account-signup] set-password link not sent: the email credential or sender is not set'); return; }
|
|
2185
|
+
if (!toEmail || userId == null) { return; }
|
|
2186
|
+
var allowed = await __security.rateLimit({
|
|
2187
|
+
key: 'auth-signup-existing:' + toEmail.trim().toLowerCase(),
|
|
2188
|
+
limit: ${EXISTING_ACCOUNT_EMAIL_LIMIT},
|
|
2189
|
+
windowSeconds: 3600,
|
|
2190
|
+
});
|
|
2191
|
+
if (!allowed.allowed) { return; }
|
|
2192
|
+
var crypto = require('crypto');
|
|
2193
|
+
var token = crypto.randomBytes(32).toString('hex');
|
|
2194
|
+
await authUtils.storePasswordReset(userId, crypto.createHash('sha256').update(token).digest('hex'), SET_PASSWORD_TOKEN_TTL_SECONDS);
|
|
2195
|
+
var resetUrl = trustedOrigin(req) + SET_PASSWORD_PAGE_PATH + SET_PASSWORD_TOKEN_LINK_PREFIX + token;
|
|
2196
|
+
var html = fillTemplate(SET_PASSWORD_BODY_HTML, { siteNameSuffix: siteName ? ' on ' + siteName : '', resetUrl: resetUrl });
|
|
2197
|
+
await sendAccountNotice(
|
|
2198
|
+
${JSON.stringify(SET_PASSWORD_EMAIL)},
|
|
2199
|
+
{ from: from, to: toEmail, subject: SET_PASSWORD_SUBJECT, html: html, apiKey: apiKey, userId: userId },
|
|
2200
|
+
{
|
|
2201
|
+
subject: SET_PASSWORD_SUBJECT,
|
|
2202
|
+
html: hideToken(html, token),
|
|
2203
|
+
payload: { resetUrl: hideToken(resetUrl, token), recipientEmail: toEmail },
|
|
2204
|
+
}
|
|
2205
|
+
);
|
|
2206
|
+
}`;
|
|
2207
|
+
};
|
|
2208
|
+
export const generateSignupRouteFile = (auth, welcome = {}, events = {}, signup = {}) => {
|
|
1066
2209
|
const needsDb = !!auth.dataSourceType;
|
|
2210
|
+
const recordsSignup = needsDb && events.withAutomations === true;
|
|
1067
2211
|
const welcomeProvider = welcome.emailProvider && SUPPORTED_EMAIL_PROVIDERS.has(welcome.emailProvider)
|
|
1068
2212
|
? welcome.emailProvider
|
|
1069
2213
|
: null;
|
|
1070
|
-
//
|
|
1071
|
-
//
|
|
1072
|
-
//
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
//
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
2214
|
+
// An ALLOW-list: the only body keys copied onto the new user row. Whatever
|
|
2215
|
+
// else the body carries (a role, `is_admin`, `email_verified`, credits, a
|
|
2216
|
+
// plan, a welcome-email config key) never reaches createUser. The columns
|
|
2217
|
+
// the route sets itself (source, consent) are never copied from it either.
|
|
2218
|
+
const signupFields = (signup.fields || ['image']).filter((field) => field && !isPrivilegedUserField(field) && SIGNUP_OWN_COLUMNS.indexOf(field) === -1);
|
|
2219
|
+
const setPasswordPagePath = resolveResetPagePath(signup.resetPagePath);
|
|
2220
|
+
// The emails to an address the site already knows go through the sign-up
|
|
2221
|
+
// node's sender, else the forgot-password node's.
|
|
2222
|
+
const fallbackSender = signup.noticeEmail || {};
|
|
2223
|
+
const noticeSender = welcomeProvider
|
|
2224
|
+
? {
|
|
2225
|
+
provider: welcomeProvider,
|
|
2226
|
+
from: welcome.fromEmail || '',
|
|
2227
|
+
secretEnv: welcome.emailSecretEnvName || '',
|
|
1084
2228
|
}
|
|
1085
|
-
|
|
2229
|
+
: {
|
|
2230
|
+
provider: fallbackSender.emailProvider &&
|
|
2231
|
+
SUPPORTED_EMAIL_PROVIDERS.has(fallbackSender.emailProvider)
|
|
2232
|
+
? fallbackSender.emailProvider
|
|
2233
|
+
: null,
|
|
2234
|
+
from: fallbackSender.fromEmail || '',
|
|
2235
|
+
secretEnv: fallbackSender.emailSecretEnvName || '',
|
|
2236
|
+
};
|
|
1086
2237
|
let dbImport = '';
|
|
1087
2238
|
let createUserCall = '';
|
|
1088
2239
|
let welcomeHelpers = '';
|
|
1089
2240
|
if (needsDb) {
|
|
1090
2241
|
dbImport = `const authUtils = require('../../../utils/auth/auth-options');
|
|
1091
2242
|
const createUser = authUtils.createUser;
|
|
1092
|
-
const
|
|
1093
|
-
const sanitizeUser = authUtils.sanitizeUser;`;
|
|
2243
|
+
const findSignupUserByEmail = authUtils.findSignupUserByEmail;`;
|
|
1094
2244
|
// Baked welcome-email helpers + config (no-op send when no provider set).
|
|
1095
2245
|
welcomeHelpers = `
|
|
1096
2246
|
const WELCOME_EMAIL_PROVIDER = ${JSON.stringify(welcomeProvider)};
|
|
@@ -1129,18 +2279,90 @@ async function sendWelcomeEmail(toEmail, tokenValues, userId, locale) {
|
|
|
1129
2279
|
var from = WELCOME_EMAIL_FROM || process.env.EMAIL_FROM || '';
|
|
1130
2280
|
if (!from) { console.warn('[account-signup] welcome email skipped: sender not configured'); return; }
|
|
1131
2281
|
var copy = resolveWelcomeEmailCopy(locale);
|
|
1132
|
-
var subject =
|
|
2282
|
+
var subject = fillSubjectTemplate(copy.subject, tokenValues);
|
|
1133
2283
|
var html = fillTemplate(copy.body, tokenValues);
|
|
1134
2284
|
await __sendProviderEmail({ from: from, fromName: WELCOME_EMAIL_FROM_NAME, to: toEmail, subject: subject, html: html, apiKey: apiKey, tokenValues: tokenValues, userId: userId });
|
|
1135
2285
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
2286
|
+
|
|
2287
|
+
// A sign-up for an address that already has an account gets the same status,
|
|
2288
|
+
// body and headers as a new one; its time differs from a new address's, which
|
|
2289
|
+
// the account a new sign-up makes reveals anyway. The address's owner is told
|
|
2290
|
+
// by email instead (only the mailbox owner reads it), at most
|
|
2291
|
+
// ${EXISTING_ACCOUNT_EMAIL_LIMIT} times an hour per address, through the known-address sender.
|
|
2292
|
+
var EXISTING_ACCOUNT_SUBJECT = 'You already have an account';
|
|
2293
|
+
var EXISTING_ACCOUNT_BODY_HTML =
|
|
2294
|
+
'<p>Someone, hopefully you, tried to create an account{{siteNameSuffix}} with this email address.</p>' +
|
|
2295
|
+
'<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>' +
|
|
2296
|
+
'<p>If this was not you, you can ignore this email: nothing has changed.</p>';
|
|
2297
|
+
|
|
2298
|
+
async function sendExistingAccountEmail(toEmail, siteName, userId) {
|
|
2299
|
+
if (!NOTICE_EMAIL_PROVIDER || !toEmail) { return; }
|
|
2300
|
+
var apiKey = NOTICE_EMAIL_SECRET_ENV_NAME ? process.env[NOTICE_EMAIL_SECRET_ENV_NAME] : '';
|
|
2301
|
+
if (apiKey && String(apiKey).indexOf('teleporthq.secrets.') === 0) { apiKey = ''; }
|
|
2302
|
+
var from = NOTICE_EMAIL_FROM || process.env.EMAIL_FROM || '';
|
|
2303
|
+
if (!apiKey || !from) { return; }
|
|
2304
|
+
var allowed = await __security.rateLimit({
|
|
2305
|
+
key: 'auth-signup-existing:' + String(toEmail).trim().toLowerCase(),
|
|
2306
|
+
limit: ${EXISTING_ACCOUNT_EMAIL_LIMIT},
|
|
2307
|
+
windowSeconds: 3600,
|
|
2308
|
+
});
|
|
2309
|
+
if (!allowed.allowed) { return; }
|
|
2310
|
+
var tokenValues = { siteNameSuffix: siteName ? ' on ' + siteName : '' };
|
|
2311
|
+
var html = fillTemplate(EXISTING_ACCOUNT_BODY_HTML, tokenValues);
|
|
2312
|
+
await sendAccountNotice(
|
|
2313
|
+
${JSON.stringify(EXISTING_ACCOUNT_EMAIL)},
|
|
2314
|
+
{ from: from, to: toEmail, subject: EXISTING_ACCOUNT_SUBJECT, html: html, apiKey: apiKey, userId: userId },
|
|
2315
|
+
{ subject: EXISTING_ACCOUNT_SUBJECT, html: html, payload: tokenValues }
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
${generateKnownAddressEmailCode({
|
|
2319
|
+
sender: noticeSender,
|
|
2320
|
+
pagePath: setPasswordPagePath,
|
|
2321
|
+
tokenInFragment: signup.tokenInFragment === true,
|
|
2322
|
+
})}
|
|
2323
|
+
|
|
2324
|
+
function waitUntilElapsed(startedAt, ms) {
|
|
2325
|
+
var left = ms - (Date.now() - startedAt);
|
|
2326
|
+
return left > 0 ? new Promise(function (resolve) { setTimeout(resolve, left); }) : Promise.resolve();
|
|
2327
|
+
}
|
|
2328
|
+
${recordsSignup
|
|
2329
|
+
? generateAccountEventRecorder({ relativePrefix: '../../..', logTag: '[account-signup]' })
|
|
2330
|
+
: ''}`;
|
|
2331
|
+
createUserCall = ` // The answer is the same for a new account and for an address that already
|
|
2332
|
+
// has one (see sendExistingAccountEmail): built from what the visitor sent.
|
|
2333
|
+
const answer = { user: { email: email, name: name || null } };
|
|
2334
|
+
const __siteName = WELCOME_EMAIL_SITE_NAME ||
|
|
2335
|
+
(req.headers && req.headers.host ? String(req.headers.host).replace(/^www\\./, '').split(':')[0] : '');
|
|
2336
|
+
// In any letter case: \`Ava@site.test\` is the \`ava@site.test\` row.
|
|
2337
|
+
const existing = await findSignupUserByEmail(email);
|
|
2338
|
+
if (existing) {
|
|
2339
|
+
// A newsletter subscriber, a checkout or booking guest or a contact the
|
|
2340
|
+
// owner added has a row but no password (and no OAuth sign-in). Signing
|
|
2341
|
+
// up must not hand that row — and what the site knows about the person —
|
|
2342
|
+
// to whoever types the address first: its owner is mailed a link to set
|
|
2343
|
+
// a password, which proves the mailbox is theirs. An account's owner is
|
|
2344
|
+
// told it already has one. Either way the page is answered alike, so it
|
|
2345
|
+
// never tells a subscriber or a guest from an account.
|
|
2346
|
+
const knownEmail = String(existing.email || email);
|
|
2347
|
+
const existingId = existing.id != null ? existing.id : existing._id;
|
|
2348
|
+
try {
|
|
2349
|
+
if (!existing.password && !existing.provider && authUtils.PASSWORD_RESET_SUPPORTED) {
|
|
2350
|
+
await sendSetPasswordEmail(existing, __siteName, req);
|
|
2351
|
+
} else {
|
|
2352
|
+
// An account; or a row without a password where the store cannot
|
|
2353
|
+
// keep reset links (not Postgres): told it is already known here.
|
|
2354
|
+
await sendExistingAccountEmail(knownEmail, __siteName, existingId);
|
|
2355
|
+
}
|
|
2356
|
+
} catch (existingErr) {
|
|
2357
|
+
console.error('[account-signup] existing-account email failed:', existingErr && existingErr.message ? existingErr.message : existingErr);
|
|
2358
|
+
}
|
|
2359
|
+
${noticeSender.provider
|
|
2360
|
+
? ` await __noticeEmailLog.settleSentEmailLog();
|
|
2361
|
+
`
|
|
2362
|
+
: ''} // At least ${KNOWN_ADDRESS_MIN_RESPONSE_MS} ms, like the forgot-password answer: the time tells
|
|
2363
|
+
// neither which email went out nor whether one did.
|
|
2364
|
+
await waitUntilElapsed(startedAt, ${KNOWN_ADDRESS_MIN_RESPONSE_MS});
|
|
2365
|
+
res.status(201).json(answer);
|
|
1144
2366
|
return;
|
|
1145
2367
|
}
|
|
1146
2368
|
|
|
@@ -1149,12 +2371,16 @@ async function sendWelcomeEmail(toEmail, tokenValues, userId, locale) {
|
|
|
1149
2371
|
email: email,
|
|
1150
2372
|
password: hashedPassword,
|
|
1151
2373
|
role: 'user',
|
|
2374
|
+
// Where the person came from, and their consent as they gave it: the
|
|
2375
|
+
// "Email me news and offers" box ticked ('true') or not. Never assumed.
|
|
2376
|
+
source: 'signup',
|
|
2377
|
+
marketing_consent: body.marketing_consent === 'true',
|
|
1152
2378
|
};
|
|
1153
|
-
|
|
1154
|
-
const
|
|
1155
|
-
for (let i = 0; i <
|
|
1156
|
-
if (
|
|
1157
|
-
userData[
|
|
2379
|
+
// Only the fields this project's sign-up forms collect.
|
|
2380
|
+
const signupFields = ${JSON.stringify(signupFields)};
|
|
2381
|
+
for (let i = 0; i < signupFields.length; i++) {
|
|
2382
|
+
if (Object.prototype.hasOwnProperty.call(body, signupFields[i])) {
|
|
2383
|
+
userData[signupFields[i]] = body[signupFields[i]];
|
|
1158
2384
|
}
|
|
1159
2385
|
}
|
|
1160
2386
|
|
|
@@ -1167,12 +2393,10 @@ async function sendWelcomeEmail(toEmail, tokenValues, userId, locale) {
|
|
|
1167
2393
|
|
|
1168
2394
|
// Best-effort welcome email — a failed send must never fail the signup.
|
|
1169
2395
|
try {
|
|
1170
|
-
const __welcomeSiteName = WELCOME_EMAIL_SITE_NAME ||
|
|
1171
|
-
(req.headers && req.headers.host ? String(req.headers.host).replace(/^www\\./, '').split(':')[0] : '');
|
|
1172
2396
|
await sendWelcomeEmail(email, {
|
|
1173
2397
|
userName: name || (newUser && newUser.name) || 'there',
|
|
1174
2398
|
userEmail: email,
|
|
1175
|
-
siteName:
|
|
2399
|
+
siteName: __siteName,
|
|
1176
2400
|
}, newUser && newUser.id, __emailLocale.resolveRequestLocale(req));
|
|
1177
2401
|
} catch (welcomeErr) {
|
|
1178
2402
|
console.error('[account-signup] welcome email failed:', welcomeErr && welcomeErr.message ? welcomeErr.message : welcomeErr);
|
|
@@ -1181,13 +2405,18 @@ ${welcomeProvider
|
|
|
1181
2405
|
? ` // Land the welcome email's ledger row before replying.
|
|
1182
2406
|
if (typeof __sentEmailLog !== 'undefined') { await __sentEmailLog.settleSentEmailLog(); }
|
|
1183
2407
|
`
|
|
1184
|
-
: ''}
|
|
1185
|
-
|
|
2408
|
+
: ''}${recordsSignup
|
|
2409
|
+
? ` // The account exists: the last thing the signup does is record it.
|
|
2410
|
+
await recordAccountEvent('user-signed-up', newUser, 'signup', req);
|
|
2411
|
+
`
|
|
2412
|
+
: ''}${signup.newsletter === true ? SIGNUP_NEWSLETTER_CODE : ''}
|
|
2413
|
+
res.status(201).json(answer);`;
|
|
1186
2414
|
}
|
|
1187
2415
|
else {
|
|
1188
2416
|
createUserCall = ` res.status(501).json({ error: 'No data source configured for user storage' });`;
|
|
1189
2417
|
}
|
|
1190
2418
|
return `const hashPassword = require('../../../utils/auth/hash-password');
|
|
2419
|
+
const __security = require('${securityRuntimeSpecifier('../../..')}');
|
|
1191
2420
|
${dbImport}
|
|
1192
2421
|
${welcomeHelpers}
|
|
1193
2422
|
|
|
@@ -1197,11 +2426,16 @@ module.exports = async function handler(req, res) {
|
|
|
1197
2426
|
return;
|
|
1198
2427
|
}
|
|
1199
2428
|
|
|
2429
|
+
// ${SIGN_UP_LIMIT} sign-ups per visitor per hour.
|
|
2430
|
+
if (!(await __security.rateLimitRequest(req, res, { scope: 'auth-signup', limit: ${SIGN_UP_LIMIT}, windowSeconds: ${SIGN_UP_WINDOW_SECONDS} }))) {
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
${needsDb ? ' const startedAt = Date.now();\n' : ''}
|
|
1200
2434
|
try {
|
|
1201
|
-
const body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
|
|
1202
|
-
const email = body.email;
|
|
1203
|
-
const password = body.password;
|
|
1204
|
-
const name = body.name;
|
|
2435
|
+
const body = (typeof req.body === 'string' ? JSON.parse(req.body) : req.body) || {};
|
|
2436
|
+
const email = typeof body.email === 'string' ? body.email : '';
|
|
2437
|
+
const password = typeof body.password === 'string' ? body.password : '';
|
|
2438
|
+
const name = typeof body.name === 'string' ? body.name : '';
|
|
1205
2439
|
|
|
1206
2440
|
if (!email || !password) {
|
|
1207
2441
|
res.status(400).json({ error: 'Email and password are required' });
|
|
@@ -1214,13 +2448,9 @@ module.exports = async function handler(req, res) {
|
|
|
1214
2448
|
return;
|
|
1215
2449
|
}
|
|
1216
2450
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
if (password.length > 128) {
|
|
1223
|
-
res.status(400).json({ error: 'Password must not exceed 128 characters' });
|
|
2451
|
+
const policyError = hashPassword.passwordPolicyError(password);
|
|
2452
|
+
if (policyError) {
|
|
2453
|
+
res.status(400).json({ error: policyError });
|
|
1224
2454
|
return;
|
|
1225
2455
|
}
|
|
1226
2456
|
|
|
@@ -1228,8 +2458,7 @@ module.exports = async function handler(req, res) {
|
|
|
1228
2458
|
|
|
1229
2459
|
${createUserCall}
|
|
1230
2460
|
} catch (error) {
|
|
1231
|
-
|
|
1232
|
-
res.status(500).json({ error: 'Internal server error' });
|
|
2461
|
+
__security.sendClientError(res, 500, 'Sign-up failed. Please try again.', error);
|
|
1233
2462
|
}
|
|
1234
2463
|
};
|
|
1235
2464
|
`;
|
|
@@ -1480,40 +2709,19 @@ function getUserRoleFromToken(token) {
|
|
|
1480
2709
|
return null;
|
|
1481
2710
|
}
|
|
1482
2711
|
|
|
1483
|
-
//
|
|
1484
|
-
//
|
|
1485
|
-
function readCookie(request, name) {
|
|
1486
|
-
try {
|
|
1487
|
-
var c = request.cookies.get(name);
|
|
1488
|
-
if (!c) return null;
|
|
1489
|
-
return typeof c === 'string' ? c : (c.value || null);
|
|
1490
|
-
} catch (e) {
|
|
1491
|
-
return null;
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
// next-auth stores the session token under a secure-prefixed cookie on https
|
|
1496
|
-
// and a plain one on http, and splits large JWTs into ".0"/".1" chunks. The
|
|
2712
|
+
// The session token lives in the "__Host-" cookie on https and the plain one on
|
|
2713
|
+
// http (see the auth options), split into ".0"/".1" chunks when large. The
|
|
1497
2714
|
// Edge runtime's getToken() can return null for a session the Node runtime
|
|
1498
2715
|
// considers valid (e.g. NEXTAUTH_SECRET not identical in the Edge runtime, or
|
|
1499
2716
|
// the JWE simply failing to decode at the edge), which previously redirected
|
|
1500
2717
|
// logged-in users to sign-in. So cookie PRESENCE is the source of truth for
|
|
1501
2718
|
// "is there a session"; getToken is only trusted for the optional role check.
|
|
1502
|
-
//
|
|
1503
|
-
//
|
|
2719
|
+
// Only the cookie the request's protocol allows counts — the same one
|
|
2720
|
+
// __tqSessionTokenCandidates names: a cookie a sibling site planted under
|
|
2721
|
+
// another name is never a session. Server-side (getServerSideProps / API
|
|
2722
|
+
// routes) remain the authoritative validators of the session and role.
|
|
1504
2723
|
function hasSessionCookie(request) {
|
|
1505
|
-
|
|
1506
|
-
'__Secure-next-auth.session-token',
|
|
1507
|
-
'next-auth.session-token',
|
|
1508
|
-
'__Secure-authjs.session-token',
|
|
1509
|
-
'authjs.session-token'
|
|
1510
|
-
];
|
|
1511
|
-
for (var i = 0; i < names.length; i++) {
|
|
1512
|
-
if (readCookie(request, names[i]) || readCookie(request, names[i] + '.0')) {
|
|
1513
|
-
return true;
|
|
1514
|
-
}
|
|
1515
|
-
}
|
|
1516
|
-
return false;
|
|
2724
|
+
return __tqSessionTokenCandidates(request).length > 0;
|
|
1517
2725
|
}
|
|
1518
2726
|
|
|
1519
2727
|
// Turns a Next.js route pattern into an anchored matcher: "[param]" matches a
|
|
@@ -1776,8 +2984,10 @@ export const config = {
|
|
|
1776
2984
|
// Next.js does NOT run the second (negative-lookahead) matcher for the root
|
|
1777
2985
|
// path, which would leave a page published at "/" (e.g. a protected dashboard)
|
|
1778
2986
|
// publicly reachable. '/' compiles to ^/$ and covers exactly the home route;
|
|
1779
|
-
// the second entry covers every deeper path.
|
|
1780
|
-
|
|
2987
|
+
// the second entry covers every deeper path. Only the API routes are left
|
|
2988
|
+
// out — "/api" and "/api/..." — never a page whose path merely starts with
|
|
2989
|
+
// "api" (a protected "/api-keys" page).
|
|
2990
|
+
matcher: ['/', '/((?!api(?:/|$)|_next/static|_next/image|favicon\\\\.ico).*)'],
|
|
1781
2991
|
};
|
|
1782
2992
|
`;
|
|
1783
2993
|
};
|