@aglyn/tenant-data-admin 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +7 -0
- package/jest.integration.config.d.ts +39 -0
- package/jest.integration.setup.d.ts +17 -0
- package/package.json +51 -0
- package/src/index.d.ts +17 -0
- package/src/index.js +18 -0
- package/src/index.js.map +1 -0
- package/src/lib/render-cache.d.ts +184 -0
- package/src/lib/render-cache.js +198 -0
- package/src/lib/render-cache.js.map +1 -0
- package/src/lib/server/account-addresses.d.ts +160 -0
- package/src/lib/server/account-addresses.js +311 -0
- package/src/lib/server/account-addresses.js.map +1 -0
- package/src/lib/server/account-emails.d.ts +219 -0
- package/src/lib/server/account-emails.js +640 -0
- package/src/lib/server/account-emails.js.map +1 -0
- package/src/lib/server/admin-audit.d.ts +180 -0
- package/src/lib/server/admin-audit.js +233 -0
- package/src/lib/server/admin-audit.js.map +1 -0
- package/src/lib/server/analytics-retention.d.ts +63 -0
- package/src/lib/server/analytics-retention.js +66 -0
- package/src/lib/server/analytics-retention.js.map +1 -0
- package/src/lib/server/api-http.d.ts +159 -0
- package/src/lib/server/api-http.js +230 -0
- package/src/lib/server/api-http.js.map +1 -0
- package/src/lib/server/api-keys.d.ts +117 -0
- package/src/lib/server/api-keys.js +298 -0
- package/src/lib/server/api-keys.js.map +1 -0
- package/src/lib/server/auth-handoff.d.ts +165 -0
- package/src/lib/server/auth-handoff.js +482 -0
- package/src/lib/server/auth-handoff.js.map +1 -0
- package/src/lib/server/auth-pools.d.ts +246 -0
- package/src/lib/server/auth-pools.js +551 -0
- package/src/lib/server/auth-pools.js.map +1 -0
- package/src/lib/server/billing-addons.d.ts +171 -0
- package/src/lib/server/billing-addons.js +277 -0
- package/src/lib/server/billing-addons.js.map +1 -0
- package/src/lib/server/campaign-attribution-store.d.ts +55 -0
- package/src/lib/server/campaign-attribution-store.js +95 -0
- package/src/lib/server/campaign-attribution-store.js.map +1 -0
- package/src/lib/server/campaign-conversion-attribution.d.ts +205 -0
- package/src/lib/server/campaign-conversion-attribution.js +279 -0
- package/src/lib/server/campaign-conversion-attribution.js.map +1 -0
- package/src/lib/server/client-error-report.d.ts +301 -0
- package/src/lib/server/client-error-report.js +755 -0
- package/src/lib/server/client-error-report.js.map +1 -0
- package/src/lib/server/connect-account-status.d.ts +74 -0
- package/src/lib/server/connect-account-status.js +94 -0
- package/src/lib/server/connect-account-status.js.map +1 -0
- package/src/lib/server/connect-payout-failure.d.ts +103 -0
- package/src/lib/server/connect-payout-failure.js +127 -0
- package/src/lib/server/connect-payout-failure.js.map +1 -0
- package/src/lib/server/console-api-rate-limit.d.ts +81 -0
- package/src/lib/server/console-api-rate-limit.js +111 -0
- package/src/lib/server/console-api-rate-limit.js.map +1 -0
- package/src/lib/server/console-domains.d.ts +326 -0
- package/src/lib/server/console-domains.js +779 -0
- package/src/lib/server/console-domains.js.map +1 -0
- package/src/lib/server/consume-once.d.ts +91 -0
- package/src/lib/server/consume-once.js +96 -0
- package/src/lib/server/consume-once.js.map +1 -0
- package/src/lib/server/contact-company-link.d.ts +85 -0
- package/src/lib/server/contact-company-link.js +105 -0
- package/src/lib/server/contact-company-link.js.map +1 -0
- package/src/lib/server/contact-email-engagement.d.ts +32 -0
- package/src/lib/server/contact-email-engagement.js +145 -0
- package/src/lib/server/contact-email-engagement.js.map +1 -0
- package/src/lib/server/contact-email-index.d.ts +56 -0
- package/src/lib/server/contact-email-index.js +158 -0
- package/src/lib/server/contact-email-index.js.map +1 -0
- package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
- package/src/lib/server/contact-lifecycle-floor.js +88 -0
- package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
- package/src/lib/server/contact-merge.d.ts +55 -0
- package/src/lib/server/contact-merge.js +279 -0
- package/src/lib/server/contact-merge.js.map +1 -0
- package/src/lib/server/contact-suppression.d.ts +153 -0
- package/src/lib/server/contact-suppression.js +259 -0
- package/src/lib/server/contact-suppression.js.map +1 -0
- package/src/lib/server/crm-booking-activity.d.ts +86 -0
- package/src/lib/server/crm-booking-activity.js +251 -0
- package/src/lib/server/crm-booking-activity.js.map +1 -0
- package/src/lib/server/crm-email-activity.d.ts +112 -0
- package/src/lib/server/crm-email-activity.js +143 -0
- package/src/lib/server/crm-email-activity.js.map +1 -0
- package/src/lib/server/crm-inbound-email.d.ts +145 -0
- package/src/lib/server/crm-inbound-email.js +287 -0
- package/src/lib/server/crm-inbound-email.js.map +1 -0
- package/src/lib/server/crm-next-activity.d.ts +74 -0
- package/src/lib/server/crm-next-activity.js +194 -0
- package/src/lib/server/crm-next-activity.js.map +1 -0
- package/src/lib/server/crm-records.d.ts +144 -0
- package/src/lib/server/crm-records.js +218 -0
- package/src/lib/server/crm-records.js.map +1 -0
- package/src/lib/server/csp-aggregate.d.ts +62 -0
- package/src/lib/server/csp-aggregate.js +276 -0
- package/src/lib/server/csp-aggregate.js.map +1 -0
- package/src/lib/server/data-storage-gate.d.ts +66 -0
- package/src/lib/server/data-storage-gate.js +48 -0
- package/src/lib/server/data-storage-gate.js.map +1 -0
- package/src/lib/server/dataset-live-pages.d.ts +179 -0
- package/src/lib/server/dataset-live-pages.js +421 -0
- package/src/lib/server/dataset-live-pages.js.map +1 -0
- package/src/lib/server/dns-probe.d.ts +53 -0
- package/src/lib/server/dns-probe.js +184 -0
- package/src/lib/server/dns-probe.js.map +1 -0
- package/src/lib/server/document-id.d.ts +29 -0
- package/src/lib/server/document-id.js +100 -0
- package/src/lib/server/document-id.js.map +1 -0
- package/src/lib/server/domain-provider-vercel.d.ts +33 -0
- package/src/lib/server/domain-provider-vercel.js +298 -0
- package/src/lib/server/domain-provider-vercel.js.map +1 -0
- package/src/lib/server/domain-provider-webhook.d.ts +73 -0
- package/src/lib/server/domain-provider-webhook.js +223 -0
- package/src/lib/server/domain-provider-webhook.js.map +1 -0
- package/src/lib/server/domain-provider-wildcard.d.ts +54 -0
- package/src/lib/server/domain-provider-wildcard.js +163 -0
- package/src/lib/server/domain-provider-wildcard.js.map +1 -0
- package/src/lib/server/domain-provider.d.ts +197 -0
- package/src/lib/server/domain-provider.js +221 -0
- package/src/lib/server/domain-provider.js.map +1 -0
- package/src/lib/server/duplicate-activity.d.ts +54 -0
- package/src/lib/server/duplicate-activity.js +67 -0
- package/src/lib/server/duplicate-activity.js.map +1 -0
- package/src/lib/server/duplicate-resource.d.ts +55 -0
- package/src/lib/server/duplicate-resource.js +474 -0
- package/src/lib/server/duplicate-resource.js.map +1 -0
- package/src/lib/server/dynamic-list-materialize.d.ts +153 -0
- package/src/lib/server/dynamic-list-materialize.js +581 -0
- package/src/lib/server/dynamic-list-materialize.js.map +1 -0
- package/src/lib/server/edit-access-authz.d.ts +56 -0
- package/src/lib/server/edit-access-authz.js +107 -0
- package/src/lib/server/edit-access-authz.js.map +1 -0
- package/src/lib/server/edit-access-token.d.ts +72 -0
- package/src/lib/server/edit-access-token.js +116 -0
- package/src/lib/server/edit-access-token.js.map +1 -0
- package/src/lib/server/edit-hint-token.d.ts +83 -0
- package/src/lib/server/edit-hint-token.js +119 -0
- package/src/lib/server/edit-hint-token.js.map +1 -0
- package/src/lib/server/email-delivery-log.d.ts +509 -0
- package/src/lib/server/email-delivery-log.js +1043 -0
- package/src/lib/server/email-delivery-log.js.map +1 -0
- package/src/lib/server/email-flow-gate.d.ts +80 -0
- package/src/lib/server/email-flow-gate.js +153 -0
- package/src/lib/server/email-flow-gate.js.map +1 -0
- package/src/lib/server/email-marketing-gate.d.ts +333 -0
- package/src/lib/server/email-marketing-gate.js +639 -0
- package/src/lib/server/email-marketing-gate.js.map +1 -0
- package/src/lib/server/email-metering.d.ts +293 -0
- package/src/lib/server/email-metering.js +359 -0
- package/src/lib/server/email-metering.js.map +1 -0
- package/src/lib/server/email-revenue-attribution.d.ts +185 -0
- package/src/lib/server/email-revenue-attribution.js +300 -0
- package/src/lib/server/email-revenue-attribution.js.map +1 -0
- package/src/lib/server/email-send-rate.d.ts +232 -0
- package/src/lib/server/email-send-rate.js +417 -0
- package/src/lib/server/email-send-rate.js.map +1 -0
- package/src/lib/server/email-sender-reputation.d.ts +317 -0
- package/src/lib/server/email-sender-reputation.js +464 -0
- package/src/lib/server/email-sender-reputation.js.map +1 -0
- package/src/lib/server/email-suppression.d.ts +490 -0
- package/src/lib/server/email-suppression.js +648 -0
- package/src/lib/server/email-suppression.js.map +1 -0
- package/src/lib/server/email-topic-confirmation.d.ts +150 -0
- package/src/lib/server/email-topic-confirmation.js +272 -0
- package/src/lib/server/email-topic-confirmation.js.map +1 -0
- package/src/lib/server/email-unsubscribe-link.d.ts +174 -0
- package/src/lib/server/email-unsubscribe-link.js +236 -0
- package/src/lib/server/email-unsubscribe-link.js.map +1 -0
- package/src/lib/server/erase-person.d.ts +116 -0
- package/src/lib/server/erase-person.js +272 -0
- package/src/lib/server/erase-person.js.map +1 -0
- package/src/lib/server/erase.d.ts +458 -0
- package/src/lib/server/erase.js +1506 -0
- package/src/lib/server/erase.js.map +1 -0
- package/src/lib/server/firebase-admin.d.ts +71 -0
- package/src/lib/server/firebase-admin.js +343 -0
- package/src/lib/server/firebase-admin.js.map +1 -0
- package/src/lib/server/form-dataset-binding-token.d.ts +35 -0
- package/src/lib/server/form-dataset-binding-token.js +107 -0
- package/src/lib/server/form-dataset-binding-token.js.map +1 -0
- package/src/lib/server/free-workspace-cap.d.ts +201 -0
- package/src/lib/server/free-workspace-cap.js +404 -0
- package/src/lib/server/free-workspace-cap.js.map +1 -0
- package/src/lib/server/ga4-measurement-protocol.d.ts +256 -0
- package/src/lib/server/ga4-measurement-protocol.js +558 -0
- package/src/lib/server/ga4-measurement-protocol.js.map +1 -0
- package/src/lib/server/host-email-tokens.d.ts +47 -0
- package/src/lib/server/host-email-tokens.js +72 -0
- package/src/lib/server/host-email-tokens.js.map +1 -0
- package/src/lib/server/host-memberships.d.ts +54 -0
- package/src/lib/server/host-memberships.js +194 -0
- package/src/lib/server/host-memberships.js.map +1 -0
- package/src/lib/server/host-sending-domain.d.ts +292 -0
- package/src/lib/server/host-sending-domain.js +451 -0
- package/src/lib/server/host-sending-domain.js.map +1 -0
- package/src/lib/server/host-visitor-records.d.ts +150 -0
- package/src/lib/server/host-visitor-records.js +262 -0
- package/src/lib/server/host-visitor-records.js.map +1 -0
- package/src/lib/server/id-token-refusal.d.ts +28 -0
- package/src/lib/server/id-token-refusal.js +111 -0
- package/src/lib/server/id-token-refusal.js.map +1 -0
- package/src/lib/server/legal-acceptance.d.ts +214 -0
- package/src/lib/server/legal-acceptance.js +305 -0
- package/src/lib/server/legal-acceptance.js.map +1 -0
- package/src/lib/server/list-members.d.ts +211 -0
- package/src/lib/server/list-members.js +176 -0
- package/src/lib/server/list-members.js.map +1 -0
- package/src/lib/server/live-page-usage.d.ts +202 -0
- package/src/lib/server/live-page-usage.js +300 -0
- package/src/lib/server/live-page-usage.js.map +1 -0
- package/src/lib/server/lockdown.d.ts +313 -0
- package/src/lib/server/lockdown.js +643 -0
- package/src/lib/server/lockdown.js.map +1 -0
- package/src/lib/server/media-cdn-rate-limit.d.ts +188 -0
- package/src/lib/server/media-cdn-rate-limit.js +204 -0
- package/src/lib/server/media-cdn-rate-limit.js.map +1 -0
- package/src/lib/server/media-delivery.d.ts +267 -0
- package/src/lib/server/media-delivery.js +485 -0
- package/src/lib/server/media-delivery.js.map +1 -0
- package/src/lib/server/media-download-tokens.d.ts +161 -0
- package/src/lib/server/media-download-tokens.js +386 -0
- package/src/lib/server/media-download-tokens.js.map +1 -0
- package/src/lib/server/media-quarantine.d.ts +127 -0
- package/src/lib/server/media-quarantine.js +170 -0
- package/src/lib/server/media-quarantine.js.map +1 -0
- package/src/lib/server/media-signing.d.ts +126 -0
- package/src/lib/server/media-signing.js +159 -0
- package/src/lib/server/media-signing.js.map +1 -0
- package/src/lib/server/media-storage-path.d.ts +96 -0
- package/src/lib/server/media-storage-path.js +111 -0
- package/src/lib/server/media-storage-path.js.map +1 -0
- package/src/lib/server/media-strong-digest.d.ts +85 -0
- package/src/lib/server/media-strong-digest.js +190 -0
- package/src/lib/server/media-strong-digest.js.map +1 -0
- package/src/lib/server/media-tombstone.d.ts +276 -0
- package/src/lib/server/media-tombstone.js +421 -0
- package/src/lib/server/media-tombstone.js.map +1 -0
- package/src/lib/server/media-variants.d.ts +309 -0
- package/src/lib/server/media-variants.js +428 -0
- package/src/lib/server/media-variants.js.map +1 -0
- package/src/lib/server/member-email-aliases.d.ts +180 -0
- package/src/lib/server/member-email-aliases.js +420 -0
- package/src/lib/server/member-email-aliases.js.map +1 -0
- package/src/lib/server/member-photo.d.ts +88 -0
- package/src/lib/server/member-photo.js +185 -0
- package/src/lib/server/member-photo.js.map +1 -0
- package/src/lib/server/membership-recover-throttle.d.ts +211 -0
- package/src/lib/server/membership-recover-throttle.js +231 -0
- package/src/lib/server/membership-recover-throttle.js.map +1 -0
- package/src/lib/server/notifications.d.ts +55 -0
- package/src/lib/server/notifications.js +134 -0
- package/src/lib/server/notifications.js.map +1 -0
- package/src/lib/server/org-billing.d.ts +97 -0
- package/src/lib/server/org-billing.js +236 -0
- package/src/lib/server/org-billing.js.map +1 -0
- package/src/lib/server/organizations.d.ts +837 -0
- package/src/lib/server/organizations.js +1814 -0
- package/src/lib/server/organizations.js.map +1 -0
- package/src/lib/server/password-reset-throttle.d.ts +84 -0
- package/src/lib/server/password-reset-throttle.js +102 -0
- package/src/lib/server/password-reset-throttle.js.map +1 -0
- package/src/lib/server/personal-data-export.d.ts +241 -0
- package/src/lib/server/personal-data-export.js +858 -0
- package/src/lib/server/personal-data-export.js.map +1 -0
- package/src/lib/server/platform-domain-names.d.ts +62 -0
- package/src/lib/server/platform-domain-names.js +197 -0
- package/src/lib/server/platform-domain-names.js.map +1 -0
- package/src/lib/server/platform-marketing-consent.d.ts +133 -0
- package/src/lib/server/platform-marketing-consent.js +209 -0
- package/src/lib/server/platform-marketing-consent.js.map +1 -0
- package/src/lib/server/rate-limit-store.d.ts +702 -0
- package/src/lib/server/rate-limit-store.js +1220 -0
- package/src/lib/server/rate-limit-store.js.map +1 -0
- package/src/lib/server/realm-plugins.d.ts +67 -0
- package/src/lib/server/realm-plugins.js +250 -0
- package/src/lib/server/realm-plugins.js.map +1 -0
- package/src/lib/server/recaptcha-allowlist.d.ts +90 -0
- package/src/lib/server/recaptcha-allowlist.js +409 -0
- package/src/lib/server/recaptcha-allowlist.js.map +1 -0
- package/src/lib/server/release-flags.d.ts +105 -0
- package/src/lib/server/release-flags.js +183 -0
- package/src/lib/server/release-flags.js.map +1 -0
- package/src/lib/server/replay-erasures.d.ts +126 -0
- package/src/lib/server/replay-erasures.js +217 -0
- package/src/lib/server/replay-erasures.js.map +1 -0
- package/src/lib/server/resolve-people.d.ts +66 -0
- package/src/lib/server/resolve-people.js +112 -0
- package/src/lib/server/resolve-people.js.map +1 -0
- package/src/lib/server/safe-equal.d.ts +45 -0
- package/src/lib/server/safe-equal.js +58 -0
- package/src/lib/server/safe-equal.js.map +1 -0
- package/src/lib/server/sending-domain-debt.d.ts +127 -0
- package/src/lib/server/sending-domain-debt.js +268 -0
- package/src/lib/server/sending-domain-debt.js.map +1 -0
- package/src/lib/server/sending-domain-recheck.d.ts +151 -0
- package/src/lib/server/sending-domain-recheck.js +253 -0
- package/src/lib/server/sending-domain-recheck.js.map +1 -0
- package/src/lib/server/sending-domains.d.ts +364 -0
- package/src/lib/server/sending-domains.js +683 -0
- package/src/lib/server/sending-domains.js.map +1 -0
- package/src/lib/server/serve-media-cdn.d.ts +433 -0
- package/src/lib/server/serve-media-cdn.js +1370 -0
- package/src/lib/server/serve-media-cdn.js.map +1 -0
- package/src/lib/server/serve-plugin-fetch.d.ts +61 -0
- package/src/lib/server/serve-plugin-fetch.js +294 -0
- package/src/lib/server/serve-plugin-fetch.js.map +1 -0
- package/src/lib/server/sms-keywords.d.ts +63 -0
- package/src/lib/server/sms-keywords.js +146 -0
- package/src/lib/server/sms-keywords.js.map +1 -0
- package/src/lib/server/sso-break-glass-owners.d.ts +91 -0
- package/src/lib/server/sso-break-glass-owners.js +227 -0
- package/src/lib/server/sso-break-glass-owners.js.map +1 -0
- package/src/lib/server/sso-domain-policy.d.ts +140 -0
- package/src/lib/server/sso-domain-policy.js +239 -0
- package/src/lib/server/sso-domain-policy.js.map +1 -0
- package/src/lib/server/sso-drift-logic.d.ts +106 -0
- package/src/lib/server/sso-drift-logic.js +95 -0
- package/src/lib/server/sso-drift-logic.js.map +1 -0
- package/src/lib/server/sso-enforcement.d.ts +204 -0
- package/src/lib/server/sso-enforcement.js +309 -0
- package/src/lib/server/sso-enforcement.js.map +1 -0
- package/src/lib/server/sso-provisioning.d.ts +320 -0
- package/src/lib/server/sso-provisioning.js +715 -0
- package/src/lib/server/sso-provisioning.js.map +1 -0
- package/src/lib/server/staff-alert-email.d.ts +41 -0
- package/src/lib/server/staff-alert-email.js +63 -0
- package/src/lib/server/staff-alert-email.js.map +1 -0
- package/src/lib/server/stripe-account-mode.d.ts +156 -0
- package/src/lib/server/stripe-account-mode.js +162 -0
- package/src/lib/server/stripe-account-mode.js.map +1 -0
- package/src/lib/server/tenant-write-lockdown.d.ts +131 -0
- package/src/lib/server/tenant-write-lockdown.js +176 -0
- package/src/lib/server/tenant-write-lockdown.js.map +1 -0
- package/src/lib/server/test-firestore.d.ts +45 -0
- package/src/lib/server/test-firestore.js +124 -0
- package/src/lib/server/test-firestore.js.map +1 -0
- package/src/lib/server/token-revocation.d.ts +140 -0
- package/src/lib/server/token-revocation.js +255 -0
- package/src/lib/server/token-revocation.js.map +1 -0
- package/src/lib/server/update-existing.d.ts +32 -0
- package/src/lib/server/update-existing.js +73 -0
- package/src/lib/server/update-existing.js.map +1 -0
- package/src/lib/server/upload-cors-reconcile.d.ts +118 -0
- package/src/lib/server/upload-cors-reconcile.js +234 -0
- package/src/lib/server/upload-cors-reconcile.js.map +1 -0
- package/src/lib/server/upsert-contact.d.ts +262 -0
- package/src/lib/server/upsert-contact.js +546 -0
- package/src/lib/server/upsert-contact.js.map +1 -0
- package/src/lib/server/usage-invoice.d.ts +203 -0
- package/src/lib/server/usage-invoice.js +397 -0
- package/src/lib/server/usage-invoice.js.map +1 -0
- package/src/lib/server/user-profiles.d.ts +151 -0
- package/src/lib/server/user-profiles.js +364 -0
- package/src/lib/server/user-profiles.js.map +1 -0
- package/src/lib/server/vercel-drain-signature.d.ts +30 -0
- package/src/lib/server/vercel-drain-signature.js +61 -0
- package/src/lib/server/vercel-drain-signature.js.map +1 -0
- package/src/lib/server/vercel-log-drain.d.ts +259 -0
- package/src/lib/server/vercel-log-drain.js +479 -0
- package/src/lib/server/vercel-log-drain.js.map +1 -0
- package/src/lib/server/verify-email-cooldown.d.ts +102 -0
- package/src/lib/server/verify-email-cooldown.js +114 -0
- package/src/lib/server/verify-email-cooldown.js.map +1 -0
- package/src/lib/server/visitor-write-rate-limit.d.ts +87 -0
- package/src/lib/server/visitor-write-rate-limit.js +117 -0
- package/src/lib/server/visitor-write-rate-limit.js.map +1 -0
- package/src/lib/server/workspace-domains.d.ts +156 -0
- package/src/lib/server/workspace-domains.js +271 -0
- package/src/lib/server/workspace-domains.js.map +1 -0
- package/src/tenancy.d.ts +115 -0
- package/src/tenancy.js +142 -0
- package/src/tenancy.js.map +1 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type MemberEmailAlias, type MemberEmailAliasAddRefusal } from '@aglyn/aglyn/app-utils/member-email-aliases';
|
|
18
|
+
/** Confirmation emails one member may cause per hour, across addresses. */
|
|
19
|
+
export declare const MEMBER_EMAIL_ALIAS_SENDS_PER_MEMBER_PER_HOUR = 10;
|
|
20
|
+
/**
|
|
21
|
+
* Confirmation emails one address may receive per hour, from anybody.
|
|
22
|
+
* Per member alone would let many members aim at one mailbox; per address
|
|
23
|
+
* alone would let one member walk a list — the two limits the account
|
|
24
|
+
* address route applies, for the same mailbomb shape.
|
|
25
|
+
*/
|
|
26
|
+
export declare const MEMBER_EMAIL_ALIAS_SENDS_PER_ADDRESS_PER_HOUR = 3;
|
|
27
|
+
/** Every refusal this module answers with, as a stable code. */
|
|
28
|
+
export type MemberEmailAliasRefusal = MemberEmailAliasAddRefusal | 'not-a-member' | 'unknown-address' | 'token-invalid' | 'token-expired' | 'wrong-member' | 'link-retired';
|
|
29
|
+
export interface MemberEmailAliasRefused {
|
|
30
|
+
ok: false;
|
|
31
|
+
refusal: MemberEmailAliasRefusal;
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
34
|
+
/** The addresses one member has added in one workspace, confirmed or not. */
|
|
35
|
+
export declare function listMemberEmailAliases(firestore: FirebaseFirestore.Firestore, orgId: string, uid: string): Promise<MemberEmailAlias[]>;
|
|
36
|
+
export type MemberEmailAliasAdded = {
|
|
37
|
+
ok: true;
|
|
38
|
+
alias: MemberEmailAlias;
|
|
39
|
+
/** False when the address was already waiting for confirmation. */
|
|
40
|
+
created: boolean;
|
|
41
|
+
} | MemberEmailAliasRefused;
|
|
42
|
+
/**
|
|
43
|
+
* Adds an address, unconfirmed, for a member of the workspace — or answers
|
|
44
|
+
* the entry already waiting for it, so the caller sends another link. One
|
|
45
|
+
* transaction over the roster row and the member's document, so the
|
|
46
|
+
* ceiling is counted from what is stored at the moment of the write and an
|
|
47
|
+
* address is never added for somebody who is no longer a member.
|
|
48
|
+
*/
|
|
49
|
+
export declare function addMemberEmailAlias(firestore: FirebaseFirestore.Firestore, input: {
|
|
50
|
+
orgId: string;
|
|
51
|
+
uid: string;
|
|
52
|
+
address: unknown;
|
|
53
|
+
/** The address the member signs in with, which is theirs already. */
|
|
54
|
+
signInEmail?: string | null;
|
|
55
|
+
/** Domains the deployment receives mail at — the capture domain. */
|
|
56
|
+
reservedDomains?: readonly string[];
|
|
57
|
+
nowMs?: number;
|
|
58
|
+
}): Promise<MemberEmailAliasAdded>;
|
|
59
|
+
/**
|
|
60
|
+
* Removes one address, confirmed or not; the document goes with its last
|
|
61
|
+
* address. Removal retires every link sent for the address, because a link
|
|
62
|
+
* names the entry's `addedAtMs` and there is no longer an entry to match.
|
|
63
|
+
*/
|
|
64
|
+
export type MemberEmailAliasRemoved = {
|
|
65
|
+
ok: true;
|
|
66
|
+
removed: MemberEmailAlias;
|
|
67
|
+
} | MemberEmailAliasRefused;
|
|
68
|
+
export declare function removeMemberEmailAlias(firestore: FirebaseFirestore.Firestore, input: {
|
|
69
|
+
orgId: string;
|
|
70
|
+
uid: string;
|
|
71
|
+
address: unknown;
|
|
72
|
+
nowMs?: number;
|
|
73
|
+
}): Promise<MemberEmailAliasRemoved>;
|
|
74
|
+
/** What a confirmation link asserts. */
|
|
75
|
+
export interface MemberEmailAliasClaims {
|
|
76
|
+
orgId: string;
|
|
77
|
+
uid: string;
|
|
78
|
+
address: string;
|
|
79
|
+
/** The `addedAtMs` of the entry the link was sent for. */
|
|
80
|
+
addedAtMs: number;
|
|
81
|
+
/** Expiry, epoch ms. */
|
|
82
|
+
exp: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The token a confirmation link carries for one entry. Throws when
|
|
86
|
+
* `TOKEN_SIGNING_SECRET` is not configured, like every token that secret
|
|
87
|
+
* signs; the route answers that as a failure to send.
|
|
88
|
+
*/
|
|
89
|
+
export declare function mintMemberEmailAliasToken(claims: Omit<MemberEmailAliasClaims, 'exp'>, nowMs?: number): string;
|
|
90
|
+
/**
|
|
91
|
+
* The claims a token carries, or why it carries none: `token-expired` for a
|
|
92
|
+
* genuine link past its day, `token-invalid` for anything else — tampered,
|
|
93
|
+
* truncated, another version, or a deployment with no signing secret, whose
|
|
94
|
+
* only safe reading of a link is that it proves nothing.
|
|
95
|
+
*/
|
|
96
|
+
export declare function readMemberEmailAliasToken(token: unknown, nowMs?: number): {
|
|
97
|
+
ok: true;
|
|
98
|
+
claims: MemberEmailAliasClaims;
|
|
99
|
+
} | MemberEmailAliasRefused;
|
|
100
|
+
export type MemberEmailAliasConfirmed = {
|
|
101
|
+
ok: true;
|
|
102
|
+
orgId: string;
|
|
103
|
+
address: string;
|
|
104
|
+
/** True when the address had been confirmed before this call. */
|
|
105
|
+
alreadyConfirmed: boolean;
|
|
106
|
+
} | MemberEmailAliasRefused;
|
|
107
|
+
/**
|
|
108
|
+
* Confirms the address a link was sent for, for the member signed in — who
|
|
109
|
+
* must be the member who added it (see the module comment). A link for an
|
|
110
|
+
* entry that has since been removed or added again is refused as retired,
|
|
111
|
+
* and opening a link twice is not an error.
|
|
112
|
+
*/
|
|
113
|
+
export declare function confirmMemberEmailAlias(firestore: FirebaseFirestore.Firestore, input: {
|
|
114
|
+
token: unknown;
|
|
115
|
+
callerUid: string;
|
|
116
|
+
nowMs?: number;
|
|
117
|
+
}): Promise<MemberEmailAliasConfirmed>;
|
|
118
|
+
export type MemberEmailAliasesConfirmedByProvider = {
|
|
119
|
+
ok: true;
|
|
120
|
+
/** The addresses this call confirmed, normalized, in list order. */
|
|
121
|
+
confirmed: string[];
|
|
122
|
+
} | MemberEmailAliasRefused;
|
|
123
|
+
/**
|
|
124
|
+
* Confirms a member's PENDING addresses that their connected mail account
|
|
125
|
+
* already proves are theirs (AGL-2978).
|
|
126
|
+
*
|
|
127
|
+
* The confirmation link proves an address receives mail and asks the member
|
|
128
|
+
* to open it. A provider's verified send-as list proves more: the provider
|
|
129
|
+
* confirmed the account may send as the address, and the member proved they
|
|
130
|
+
* hold that account by connecting it under their own session. So an address
|
|
131
|
+
* the member added and has not yet confirmed counts as confirmed once it
|
|
132
|
+
* appears in that list.
|
|
133
|
+
*
|
|
134
|
+
* Only addresses the member ALREADY added are touched — this never adds one,
|
|
135
|
+
* because an address the member did not ask for is not a claim they made —
|
|
136
|
+
* and an address already confirmed keeps its original `verifiedAtMs`. The
|
|
137
|
+
* caller passes only the provider's VERIFIED addresses; which of a
|
|
138
|
+
* provider's listings count as verified is the caller's rule, not this
|
|
139
|
+
* store's. Nothing is written when nothing changes.
|
|
140
|
+
*/
|
|
141
|
+
export declare function confirmMemberEmailAliasesByProvider(firestore: FirebaseFirestore.Firestore, input: {
|
|
142
|
+
orgId: string;
|
|
143
|
+
uid: string;
|
|
144
|
+
addresses: readonly unknown[];
|
|
145
|
+
nowMs?: number;
|
|
146
|
+
}): Promise<MemberEmailAliasesConfirmedByProvider>;
|
|
147
|
+
/**
|
|
148
|
+
* The link a confirmation email carries: the console origin the caller
|
|
149
|
+
* resolved from server configuration — never from a request header — the
|
|
150
|
+
* page the member added the address on, and the token.
|
|
151
|
+
*/
|
|
152
|
+
export declare function memberEmailAliasConfirmUrl(input: {
|
|
153
|
+
origin: string;
|
|
154
|
+
returnPath?: unknown;
|
|
155
|
+
token: string;
|
|
156
|
+
}): string;
|
|
157
|
+
export type MemberEmailAliasConfirmationSent = {
|
|
158
|
+
sent: true;
|
|
159
|
+
} | {
|
|
160
|
+
sent: false;
|
|
161
|
+
status: 429 | 501 | 502;
|
|
162
|
+
error: string;
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* Emails the confirmation link to the address, in the workspace's brand.
|
|
166
|
+
*
|
|
167
|
+
* Rate-limited per member and per destination address before anything is
|
|
168
|
+
* sent (see the two ceilings above), and metered to the workspace, whose
|
|
169
|
+
* member asked for it.
|
|
170
|
+
*/
|
|
171
|
+
export declare function sendMemberEmailAliasConfirmation(input: {
|
|
172
|
+
orgId: string;
|
|
173
|
+
/** The org document, for its name and its brand. */
|
|
174
|
+
org: Record<string, unknown> | null;
|
|
175
|
+
uid: string;
|
|
176
|
+
/** The member's display name, when the roster row carries one. */
|
|
177
|
+
memberName?: string | null;
|
|
178
|
+
address: string;
|
|
179
|
+
confirmUrl: string;
|
|
180
|
+
}): Promise<MemberEmailAliasConfirmationSent>;
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { evaluateMemberEmailAliasAdd, isVerifiedMemberEmailAlias, MEMBER_EMAIL_ALIAS_CONFIRM_PARAM, MEMBER_EMAIL_ALIAS_CONFIRM_TTL_MS, MEMBER_EMAIL_ALIASES_COLLECTION, normalizeMemberEmailAlias, readMemberEmailAliases } from "@aglyn/aglyn/app-utils/member-email-aliases";
|
|
18
|
+
import { resolveBrandingProfile } from "@aglyn/aglyn/server";
|
|
19
|
+
import { isEmailConfigured, sendEmail } from "@aglyn/shared-util-email";
|
|
20
|
+
import { createHmac } from "crypto";
|
|
21
|
+
import { tokenSigningSecret } from "./media-signing.js";
|
|
22
|
+
import { safeEqual } from "./safe-equal.js";
|
|
23
|
+
/**
|
|
24
|
+
* A MEMBER'S OWN ADDRESSES (AGL-2975): the half with Firestore.
|
|
25
|
+
*
|
|
26
|
+
* The pure rules — what an address is, which are confirmed, whose an
|
|
27
|
+
* address is — are `@aglyn/aglyn`'s `member-email-aliases.ts`. This module
|
|
28
|
+
* is the store under `orgs/{orgId}/memberEmailAliases/{uid}`, the signed
|
|
29
|
+
* confirmation link, and the email that carries it.
|
|
30
|
+
*
|
|
31
|
+
* ## Every write is here
|
|
32
|
+
*
|
|
33
|
+
* The rules deny the collection to every client, reads included, so
|
|
34
|
+
* `verifiedAtMs` is only ever set here, by one of two writers, each running
|
|
35
|
+
* behind the member's own verified session:
|
|
36
|
+
* {@link confirmMemberEmailAlias}, for a member who opened the link mailed
|
|
37
|
+
* to the address, and {@link confirmMemberEmailAliasesByProvider}, for a
|
|
38
|
+
* member whose connected mail account lists the address as one its provider
|
|
39
|
+
* has already verified they may send as. A client that could write the
|
|
40
|
+
* document could mark any address it liked as its own, and the capture
|
|
41
|
+
* webhook would then file a stranger's mail as the member's.
|
|
42
|
+
*
|
|
43
|
+
* ## The confirmation link
|
|
44
|
+
*
|
|
45
|
+
* `{ org, uid, address, addedAtMs, exp }`, HMAC-signed with the shared,
|
|
46
|
+
* fail-closed `TOKEN_SIGNING_SECRET` under its own `member-email-alias:`
|
|
47
|
+
* prefix, so it can never be replayed as a media, edit-bar or form token.
|
|
48
|
+
* Nothing is stored for it: the entry's `addedAtMs` is what binds a link to
|
|
49
|
+
* one request, so removing an address and adding it again retires every
|
|
50
|
+
* link sent before, and a link for an address already confirmed confirms
|
|
51
|
+
* nothing new.
|
|
52
|
+
*
|
|
53
|
+
* Confirming takes the session of the member who added the address — not
|
|
54
|
+
* merely the link. Opening the mailbox proves the address receives mail;
|
|
55
|
+
* it does not prove the mailbox is the member's. A prospect whose address
|
|
56
|
+
* was pasted into the wrong field receives the link too, and a link that
|
|
57
|
+
* worked for whoever opened it would let that prospect turn their own
|
|
58
|
+
* address into the member's, after which every reply they sent would be
|
|
59
|
+
* filed as the member writing to nobody.
|
|
60
|
+
*
|
|
61
|
+
* Firestore is a parameter, as in `crm-inbound-email.ts`, so a spec needs a
|
|
62
|
+
* store and nothing else; the rate limiter and the email meter are loaded
|
|
63
|
+
* only when a message is actually sent.
|
|
64
|
+
*/ /** The version tag at the head of a token, so a later claim change is refused by shape. */ const TOKEN_VERSION = 'mea1';
|
|
65
|
+
/** Domain separation from every other token the shared secret signs. */ const TOKEN_CONTEXT = 'member-email-alias:v1';
|
|
66
|
+
/** Far above any real token, so an oversized value is refused unparsed. */ const TOKEN_MAX_CHARS = 2048;
|
|
67
|
+
/** Confirmation emails one member may cause per hour, across addresses. */ export const MEMBER_EMAIL_ALIAS_SENDS_PER_MEMBER_PER_HOUR = 10;
|
|
68
|
+
/**
|
|
69
|
+
* Confirmation emails one address may receive per hour, from anybody.
|
|
70
|
+
* Per member alone would let many members aim at one mailbox; per address
|
|
71
|
+
* alone would let one member walk a list — the two limits the account
|
|
72
|
+
* address route applies, for the same mailbomb shape.
|
|
73
|
+
*/ export const MEMBER_EMAIL_ALIAS_SENDS_PER_ADDRESS_PER_HOUR = 3;
|
|
74
|
+
const refused = (refusal, message)=>({
|
|
75
|
+
ok: false,
|
|
76
|
+
refusal,
|
|
77
|
+
message
|
|
78
|
+
});
|
|
79
|
+
const NOT_A_MEMBER = 'Only a member of this workspace can add addresses to it.';
|
|
80
|
+
function aliasesRef(firestore, orgId, uid) {
|
|
81
|
+
return firestore.collection('orgs').doc(orgId).collection(MEMBER_EMAIL_ALIASES_COLLECTION).doc(uid);
|
|
82
|
+
}
|
|
83
|
+
function memberRef(firestore, orgId, uid) {
|
|
84
|
+
return firestore.collection('orgs').doc(orgId).collection('members').doc(uid);
|
|
85
|
+
}
|
|
86
|
+
/** The addresses one member has added in one workspace, confirmed or not. */ export async function listMemberEmailAliases(firestore, orgId, uid) {
|
|
87
|
+
const snapshot = await aliasesRef(firestore, orgId, uid).get();
|
|
88
|
+
return readMemberEmailAliases(snapshot.exists ? snapshot.data() : null);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Adds an address, unconfirmed, for a member of the workspace — or answers
|
|
92
|
+
* the entry already waiting for it, so the caller sends another link. One
|
|
93
|
+
* transaction over the roster row and the member's document, so the
|
|
94
|
+
* ceiling is counted from what is stored at the moment of the write and an
|
|
95
|
+
* address is never added for somebody who is no longer a member.
|
|
96
|
+
*/ export async function addMemberEmailAlias(firestore, input) {
|
|
97
|
+
var _input_nowMs;
|
|
98
|
+
const nowMs = (_input_nowMs = input.nowMs) != null ? _input_nowMs : Date.now();
|
|
99
|
+
const ref = aliasesRef(firestore, input.orgId, input.uid);
|
|
100
|
+
const roster = memberRef(firestore, input.orgId, input.uid);
|
|
101
|
+
return firestore.runTransaction(async (tx)=>{
|
|
102
|
+
var _input_signInEmail, _input_reservedDomains;
|
|
103
|
+
const member = await tx.get(roster);
|
|
104
|
+
if (!member.exists) return refused('not-a-member', NOT_A_MEMBER);
|
|
105
|
+
const stored = await tx.get(ref);
|
|
106
|
+
const aliases = readMemberEmailAliases(stored.exists ? stored.data() : null);
|
|
107
|
+
const verdict = evaluateMemberEmailAliasAdd({
|
|
108
|
+
address: input.address,
|
|
109
|
+
signInEmail: (_input_signInEmail = input.signInEmail) != null ? _input_signInEmail : null,
|
|
110
|
+
aliases,
|
|
111
|
+
reservedDomains: (_input_reservedDomains = input.reservedDomains) != null ? _input_reservedDomains : []
|
|
112
|
+
});
|
|
113
|
+
if (verdict.ok === false) return refused(verdict.refusal, verdict.message);
|
|
114
|
+
if (verdict.existing) return {
|
|
115
|
+
ok: true,
|
|
116
|
+
alias: verdict.existing,
|
|
117
|
+
created: false
|
|
118
|
+
};
|
|
119
|
+
const alias = {
|
|
120
|
+
address: verdict.address,
|
|
121
|
+
addedAtMs: nowMs
|
|
122
|
+
};
|
|
123
|
+
tx.set(ref, {
|
|
124
|
+
uid: input.uid,
|
|
125
|
+
aliases: [
|
|
126
|
+
...aliases,
|
|
127
|
+
alias
|
|
128
|
+
],
|
|
129
|
+
updatedAtMs: nowMs
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
alias,
|
|
134
|
+
created: true
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
export async function removeMemberEmailAlias(firestore, input) {
|
|
139
|
+
var _input_nowMs;
|
|
140
|
+
const address = normalizeMemberEmailAlias(input.address);
|
|
141
|
+
if (!address) return refused('invalid-address', 'Enter a valid email address.');
|
|
142
|
+
const nowMs = (_input_nowMs = input.nowMs) != null ? _input_nowMs : Date.now();
|
|
143
|
+
const ref = aliasesRef(firestore, input.orgId, input.uid);
|
|
144
|
+
return firestore.runTransaction(async (tx)=>{
|
|
145
|
+
const stored = await tx.get(ref);
|
|
146
|
+
const aliases = readMemberEmailAliases(stored.exists ? stored.data() : null);
|
|
147
|
+
const removed = aliases.find((alias)=>alias.address === address);
|
|
148
|
+
if (!removed) return refused('unknown-address', 'That address is not on your list.');
|
|
149
|
+
const remaining = aliases.filter((alias)=>alias.address !== address);
|
|
150
|
+
if (remaining.length) {
|
|
151
|
+
tx.set(ref, {
|
|
152
|
+
uid: input.uid,
|
|
153
|
+
aliases: remaining,
|
|
154
|
+
updatedAtMs: nowMs
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
tx.delete(ref);
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
ok: true,
|
|
161
|
+
removed
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const signature = (payload)=>createHmac('sha256', tokenSigningSecret()).update(`${TOKEN_CONTEXT}:${payload}`).digest('base64url');
|
|
166
|
+
/**
|
|
167
|
+
* The token a confirmation link carries for one entry. Throws when
|
|
168
|
+
* `TOKEN_SIGNING_SECRET` is not configured, like every token that secret
|
|
169
|
+
* signs; the route answers that as a failure to send.
|
|
170
|
+
*/ export function mintMemberEmailAliasToken(claims, nowMs = Date.now()) {
|
|
171
|
+
const wire = {
|
|
172
|
+
o: claims.orgId,
|
|
173
|
+
u: claims.uid,
|
|
174
|
+
a: claims.address,
|
|
175
|
+
t: claims.addedAtMs,
|
|
176
|
+
e: nowMs + MEMBER_EMAIL_ALIAS_CONFIRM_TTL_MS
|
|
177
|
+
};
|
|
178
|
+
const payload = Buffer.from(JSON.stringify(wire), 'utf8').toString('base64url');
|
|
179
|
+
return `${TOKEN_VERSION}.${payload}.${signature(payload)}`;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The claims a token carries, or why it carries none: `token-expired` for a
|
|
183
|
+
* genuine link past its day, `token-invalid` for anything else — tampered,
|
|
184
|
+
* truncated, another version, or a deployment with no signing secret, whose
|
|
185
|
+
* only safe reading of a link is that it proves nothing.
|
|
186
|
+
*/ export function readMemberEmailAliasToken(token, nowMs = Date.now()) {
|
|
187
|
+
const invalid = refused('token-invalid', 'That confirmation link is not valid.');
|
|
188
|
+
if (typeof token !== 'string' || !token || token.length > TOKEN_MAX_CHARS) return invalid;
|
|
189
|
+
const parts = token.split('.');
|
|
190
|
+
if (parts.length !== 3 || parts[0] !== TOKEN_VERSION) return invalid;
|
|
191
|
+
const [, payload, signed] = parts;
|
|
192
|
+
let expected;
|
|
193
|
+
try {
|
|
194
|
+
expected = signature(payload);
|
|
195
|
+
} catch (unused) {
|
|
196
|
+
return invalid;
|
|
197
|
+
}
|
|
198
|
+
if (!safeEqual(signed, expected)) return invalid;
|
|
199
|
+
let wire;
|
|
200
|
+
try {
|
|
201
|
+
wire = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
202
|
+
} catch (unused) {
|
|
203
|
+
return invalid;
|
|
204
|
+
}
|
|
205
|
+
const address = normalizeMemberEmailAlias(wire == null ? void 0 : wire.a);
|
|
206
|
+
const orgId = typeof (wire == null ? void 0 : wire.o) === 'string' ? wire.o : '';
|
|
207
|
+
const uid = typeof (wire == null ? void 0 : wire.u) === 'string' ? wire.u : '';
|
|
208
|
+
const addedAtMs = Number(wire == null ? void 0 : wire.t);
|
|
209
|
+
const exp = Number(wire == null ? void 0 : wire.e);
|
|
210
|
+
if (!orgId || !uid || !address || !Number.isFinite(addedAtMs) || !Number.isFinite(exp)) {
|
|
211
|
+
return invalid;
|
|
212
|
+
}
|
|
213
|
+
if (exp <= nowMs) {
|
|
214
|
+
return refused('token-expired', 'That confirmation link has expired. Send a new one from the list.');
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
ok: true,
|
|
218
|
+
claims: {
|
|
219
|
+
orgId,
|
|
220
|
+
uid,
|
|
221
|
+
address,
|
|
222
|
+
addedAtMs,
|
|
223
|
+
exp
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Confirms the address a link was sent for, for the member signed in — who
|
|
229
|
+
* must be the member who added it (see the module comment). A link for an
|
|
230
|
+
* entry that has since been removed or added again is refused as retired,
|
|
231
|
+
* and opening a link twice is not an error.
|
|
232
|
+
*/ export async function confirmMemberEmailAlias(firestore, input) {
|
|
233
|
+
var _input_nowMs;
|
|
234
|
+
const nowMs = (_input_nowMs = input.nowMs) != null ? _input_nowMs : Date.now();
|
|
235
|
+
const read = readMemberEmailAliasToken(input.token, nowMs);
|
|
236
|
+
if (read.ok === false) return read;
|
|
237
|
+
const { claims } = read;
|
|
238
|
+
if (!input.callerUid || claims.uid !== input.callerUid) {
|
|
239
|
+
return refused('wrong-member', 'This link confirms an address for another member. Sign in as the member who added it, then open the link again.');
|
|
240
|
+
}
|
|
241
|
+
const ref = aliasesRef(firestore, claims.orgId, claims.uid);
|
|
242
|
+
const roster = memberRef(firestore, claims.orgId, claims.uid);
|
|
243
|
+
return firestore.runTransaction(async (tx)=>{
|
|
244
|
+
const member = await tx.get(roster);
|
|
245
|
+
if (!member.exists) return refused('not-a-member', 'You are no longer a member of that workspace.');
|
|
246
|
+
const stored = await tx.get(ref);
|
|
247
|
+
const aliases = readMemberEmailAliases(stored.exists ? stored.data() : null);
|
|
248
|
+
const entry = aliases.find((alias)=>alias.address === claims.address);
|
|
249
|
+
if (!entry || entry.addedAtMs !== claims.addedAtMs) {
|
|
250
|
+
return refused('link-retired', 'This link is for an address that has since been removed or added again. Send a new link from the list.');
|
|
251
|
+
}
|
|
252
|
+
if (isVerifiedMemberEmailAlias(entry)) {
|
|
253
|
+
return {
|
|
254
|
+
ok: true,
|
|
255
|
+
orgId: claims.orgId,
|
|
256
|
+
address: entry.address,
|
|
257
|
+
alreadyConfirmed: true
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const next = aliases.map((alias)=>alias.address === entry.address ? _extends({}, alias, {
|
|
261
|
+
verifiedAtMs: nowMs
|
|
262
|
+
}) : alias);
|
|
263
|
+
tx.set(ref, {
|
|
264
|
+
uid: claims.uid,
|
|
265
|
+
aliases: next,
|
|
266
|
+
updatedAtMs: nowMs
|
|
267
|
+
});
|
|
268
|
+
return {
|
|
269
|
+
ok: true,
|
|
270
|
+
orgId: claims.orgId,
|
|
271
|
+
address: entry.address,
|
|
272
|
+
alreadyConfirmed: false
|
|
273
|
+
};
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Confirms a member's PENDING addresses that their connected mail account
|
|
278
|
+
* already proves are theirs (AGL-2978).
|
|
279
|
+
*
|
|
280
|
+
* The confirmation link proves an address receives mail and asks the member
|
|
281
|
+
* to open it. A provider's verified send-as list proves more: the provider
|
|
282
|
+
* confirmed the account may send as the address, and the member proved they
|
|
283
|
+
* hold that account by connecting it under their own session. So an address
|
|
284
|
+
* the member added and has not yet confirmed counts as confirmed once it
|
|
285
|
+
* appears in that list.
|
|
286
|
+
*
|
|
287
|
+
* Only addresses the member ALREADY added are touched — this never adds one,
|
|
288
|
+
* because an address the member did not ask for is not a claim they made —
|
|
289
|
+
* and an address already confirmed keeps its original `verifiedAtMs`. The
|
|
290
|
+
* caller passes only the provider's VERIFIED addresses; which of a
|
|
291
|
+
* provider's listings count as verified is the caller's rule, not this
|
|
292
|
+
* store's. Nothing is written when nothing changes.
|
|
293
|
+
*/ export async function confirmMemberEmailAliasesByProvider(firestore, input) {
|
|
294
|
+
var _input_nowMs;
|
|
295
|
+
const nowMs = (_input_nowMs = input.nowMs) != null ? _input_nowMs : Date.now();
|
|
296
|
+
const proven = new Set(input.addresses.map((address)=>normalizeMemberEmailAlias(address)).filter((address)=>Boolean(address)));
|
|
297
|
+
if (!input.orgId || !input.uid) return refused('not-a-member', NOT_A_MEMBER);
|
|
298
|
+
if (!proven.size) return {
|
|
299
|
+
ok: true,
|
|
300
|
+
confirmed: []
|
|
301
|
+
};
|
|
302
|
+
const ref = aliasesRef(firestore, input.orgId, input.uid);
|
|
303
|
+
const roster = memberRef(firestore, input.orgId, input.uid);
|
|
304
|
+
return firestore.runTransaction(async (tx)=>{
|
|
305
|
+
const member = await tx.get(roster);
|
|
306
|
+
if (!member.exists) return refused('not-a-member', NOT_A_MEMBER);
|
|
307
|
+
const stored = await tx.get(ref);
|
|
308
|
+
const aliases = readMemberEmailAliases(stored.exists ? stored.data() : null);
|
|
309
|
+
const confirmed = [];
|
|
310
|
+
const next = aliases.map((alias)=>{
|
|
311
|
+
if (isVerifiedMemberEmailAlias(alias) || !proven.has(alias.address)) return alias;
|
|
312
|
+
confirmed.push(alias.address);
|
|
313
|
+
return _extends({}, alias, {
|
|
314
|
+
verifiedAtMs: nowMs
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
if (confirmed.length) {
|
|
318
|
+
tx.set(ref, {
|
|
319
|
+
uid: input.uid,
|
|
320
|
+
aliases: next,
|
|
321
|
+
updatedAtMs: nowMs
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
ok: true,
|
|
326
|
+
confirmed
|
|
327
|
+
};
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* A console path a link may land on: absolute, on the console's own
|
|
332
|
+
* origin, with no query or fragment of its own. Anything else lands on
|
|
333
|
+
* `/`, which still signs the member in.
|
|
334
|
+
*/ const RETURN_PATH = /^\/(?![/\\])[^\s?#\\]{0,511}$/;
|
|
335
|
+
/**
|
|
336
|
+
* The link a confirmation email carries: the console origin the caller
|
|
337
|
+
* resolved from server configuration — never from a request header — the
|
|
338
|
+
* page the member added the address on, and the token.
|
|
339
|
+
*/ export function memberEmailAliasConfirmUrl(input) {
|
|
340
|
+
var _input_origin;
|
|
341
|
+
const origin = String((_input_origin = input.origin) != null ? _input_origin : '').replace(/\/+$/, '');
|
|
342
|
+
const raw = typeof input.returnPath === 'string' ? input.returnPath : '';
|
|
343
|
+
const path = RETURN_PATH.test(raw) ? raw : '/';
|
|
344
|
+
return `${origin}${path}?${MEMBER_EMAIL_ALIAS_CONFIRM_PARAM}=${encodeURIComponent(input.token)}`;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Emails the confirmation link to the address, in the workspace's brand.
|
|
348
|
+
*
|
|
349
|
+
* Rate-limited per member and per destination address before anything is
|
|
350
|
+
* sent (see the two ceilings above), and metered to the workspace, whose
|
|
351
|
+
* member asked for it.
|
|
352
|
+
*/ export async function sendMemberEmailAliasConfirmation(input) {
|
|
353
|
+
var _input_org, _ref, _input_memberName;
|
|
354
|
+
var _input_org1;
|
|
355
|
+
if (!isEmailConfigured()) {
|
|
356
|
+
return {
|
|
357
|
+
sent: false,
|
|
358
|
+
status: 501,
|
|
359
|
+
error: 'Email is not configured on this deployment.'
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
const { consumeRateLimit } = await import("./rate-limit-store.js");
|
|
363
|
+
const perMember = await consumeRateLimit(`member-email-alias-confirm:${input.uid}`, {
|
|
364
|
+
limit: MEMBER_EMAIL_ALIAS_SENDS_PER_MEMBER_PER_HOUR,
|
|
365
|
+
windowMs: 60 * 60 * 1000
|
|
366
|
+
});
|
|
367
|
+
if (!perMember.allowed) {
|
|
368
|
+
return {
|
|
369
|
+
sent: false,
|
|
370
|
+
status: 429,
|
|
371
|
+
error: 'Too many confirmation emails. Try again in an hour.'
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
const perAddress = await consumeRateLimit(`member-email-alias-target:${input.address}`, {
|
|
375
|
+
limit: MEMBER_EMAIL_ALIAS_SENDS_PER_ADDRESS_PER_HOUR,
|
|
376
|
+
windowMs: 60 * 60 * 1000
|
|
377
|
+
});
|
|
378
|
+
if (!perAddress.allowed) {
|
|
379
|
+
return {
|
|
380
|
+
sent: false,
|
|
381
|
+
status: 429,
|
|
382
|
+
error: 'Too many confirmation emails for that address. Try again later.'
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
const branding = resolveBrandingProfile((_input_org = input.org) != null ? _input_org : null);
|
|
386
|
+
const workspace = String((_ref = (_input_org1 = input.org) == null ? void 0 : _input_org1['name']) != null ? _ref : '').trim() || 'your workspace';
|
|
387
|
+
const member = String((_input_memberName = input.memberName) != null ? _input_memberName : '').trim();
|
|
388
|
+
const text = [
|
|
389
|
+
`${member || 'A member'} asked to add ${input.address} as one of their own email ` + `addresses in the ${workspace} workspace on ${branding.productName}.`,
|
|
390
|
+
'',
|
|
391
|
+
`To confirm it, open this link while signed in as ${member || 'that member'}:`,
|
|
392
|
+
'',
|
|
393
|
+
input.confirmUrl,
|
|
394
|
+
'',
|
|
395
|
+
`Once it is confirmed, email sent from ${input.address} with the workspace's capture ` + 'address in BCC is filed under the person it was written to. The link works for 24 hours.',
|
|
396
|
+
'',
|
|
397
|
+
'If you did not expect this email, ignore it. Nothing changes unless the member who asked opens the link.'
|
|
398
|
+
].join('\n');
|
|
399
|
+
const result = await sendEmail({
|
|
400
|
+
to: input.address,
|
|
401
|
+
subject: `Confirm your address for ${workspace}`,
|
|
402
|
+
text,
|
|
403
|
+
fromName: branding.fromName,
|
|
404
|
+
context: 'member-email-alias-confirmation'
|
|
405
|
+
});
|
|
406
|
+
if (!result.sent) {
|
|
407
|
+
return {
|
|
408
|
+
sent: false,
|
|
409
|
+
status: 502,
|
|
410
|
+
error: 'The confirmation email could not be sent.'
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
const { meterOrgEmail } = await import("./email-metering.js");
|
|
414
|
+
await meterOrgEmail(input.orgId).catch(()=>undefined);
|
|
415
|
+
return {
|
|
416
|
+
sent: true
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
//# sourceMappingURL=member-email-aliases.js.map
|