@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,81 @@
|
|
|
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
|
+
export interface ConsoleApiRateLimitOptions {
|
|
18
|
+
/** Dispatcher path, e.g. `email/list-members-add`. Decides machine vs not. */
|
|
19
|
+
path: string;
|
|
20
|
+
/**
|
|
21
|
+
* The verified Firebase uid, or null when the request carried no bearer
|
|
22
|
+
* token or one that did not decode. The console dispatcher has already
|
|
23
|
+
* decoded it for the lockdown gate, so this costs no second verification.
|
|
24
|
+
*/
|
|
25
|
+
uid: string | null;
|
|
26
|
+
/** Read for its method and its client-address headers. */
|
|
27
|
+
request: {
|
|
28
|
+
method?: string;
|
|
29
|
+
headers?: {
|
|
30
|
+
get?: (name: string) => unknown;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
limit?: number;
|
|
34
|
+
windowMs?: number;
|
|
35
|
+
nowMs?: number;
|
|
36
|
+
/** Injectable for tests; defaults to the Admin SDK's Firestore. */
|
|
37
|
+
firestore?: unknown;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Counts one console write and returns a 429 once the window is spent.
|
|
41
|
+
*
|
|
42
|
+
* ## Reads are not limited
|
|
43
|
+
*
|
|
44
|
+
* The standing rule in `RATE_LIMITING.md` is "`checkRateLimit` for volume,
|
|
45
|
+
* `consumeRateLimit` for consequence": a durable call costs one Firestore read
|
|
46
|
+
* plus one write, which is the right price beside a handler that installs a
|
|
47
|
+
* bundle or calls a paid model, and the wrong price in front of a listing GET.
|
|
48
|
+
* Every route on this surface that spends something spends it on a POST — the
|
|
49
|
+
* two email list previews, the member add, the suppression writes, `ai/assist`,
|
|
50
|
+
* every marketplace install and publish — so writes-only covers the exposure
|
|
51
|
+
* rather than merely most of it.
|
|
52
|
+
*
|
|
53
|
+
* ## Fail SOFT, inherited from `consumeRateLimit`
|
|
54
|
+
*
|
|
55
|
+
* A Firestore outage lets traffic through, flagged `degraded`, rather than
|
|
56
|
+
* refusing it; a contended key is refused. That classification is the store's
|
|
57
|
+
* and is not re-decided here. Failing closed would take the console's write
|
|
58
|
+
* surface down during an unrelated blip — the operator's own recovery tools
|
|
59
|
+
* among them — to bound spend that the outage has already stopped, since the
|
|
60
|
+
* counter and the documents these handlers write are one Firestore. CSRF fails
|
|
61
|
+
* closed because forging a request is the threat; here the threat is volume,
|
|
62
|
+
* and volume during an outage is not the incident.
|
|
63
|
+
*
|
|
64
|
+
* ## No staff bypass
|
|
65
|
+
*
|
|
66
|
+
* The lockdown gates above this one grant staff a bypass, because their
|
|
67
|
+
* question is "should anyone be doing this right now" and staff are how an
|
|
68
|
+
* incident gets diagnosed. This one's question is "how much is this costing",
|
|
69
|
+
* and a staff session spends exactly what a customer session spends. A bypass
|
|
70
|
+
* would also make the surface unlimited for the accounts most likely to be
|
|
71
|
+
* driving it from a script.
|
|
72
|
+
*
|
|
73
|
+
* ## What the caller is told
|
|
74
|
+
*
|
|
75
|
+
* A bare 429 with `Retry-After`, following `visitorWriteRateLimitRefusal` and
|
|
76
|
+
* `forms/submit`. The fallback bucket below is reached by callers that proved
|
|
77
|
+
* nothing, so the response says nothing a caller could spend the rest of the
|
|
78
|
+
* window on.
|
|
79
|
+
*/
|
|
80
|
+
export declare function consoleApiRateLimitRefusal(options: ConsoleApiRateLimitOptions): Promise<Response | null>;
|
|
81
|
+
export default consoleApiRateLimitRefusal;
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
*/ import { CONSOLE_API_RATE_LIMIT, CONSOLE_API_RATE_WINDOW_MS, consoleApiRateLimitKey, isMachinePluginApiPath, lockdownIntentForMethod } from "@aglyn/aglyn/server";
|
|
17
|
+
import { consumeRateLimit } from "./rate-limit-store.js";
|
|
18
|
+
import { NO_CLIENT_ADDRESS_BUCKET, readClientIp } from "@aglyn/aglyn/app-utils/request-ip";
|
|
19
|
+
/**
|
|
20
|
+
* The rate-limit refusal a console plugin-API write returns, or null to
|
|
21
|
+
* proceed:
|
|
22
|
+
*
|
|
23
|
+
* const limited = await consoleApiRateLimitRefusal({ path, uid, request })
|
|
24
|
+
* if (limited) return limited
|
|
25
|
+
*
|
|
26
|
+
* The same split as `visitorWriteRateLimitRefusal` beside it — the policy
|
|
27
|
+
* (ceiling, window, key, which paths are exempt) is pure and lives in
|
|
28
|
+
* `@aglyn/aglyn/server`; this is only the durable half, which needs Firestore.
|
|
29
|
+
* The counter is `consumeRateLimit`, unchanged and unwrapped, so the console
|
|
30
|
+
* inherits its postures rather than acquiring a second set.
|
|
31
|
+
*/ /**
|
|
32
|
+
* The caller's address, through the shared reader, or the no-address bucket.
|
|
33
|
+
*
|
|
34
|
+
* `readClientIp` counts from the RIGHT of the forwarding chain and is the only
|
|
35
|
+
* reader allowed to answer this question, so a caller cannot choose its own
|
|
36
|
+
* bucket by prepending an address.
|
|
37
|
+
*/ function clientIp(request) {
|
|
38
|
+
var _readClientIp;
|
|
39
|
+
const headers = request == null ? void 0 : request.headers;
|
|
40
|
+
if (typeof (headers == null ? void 0 : headers.get) !== 'function') return NO_CLIENT_ADDRESS_BUCKET;
|
|
41
|
+
return (_readClientIp = readClientIp(headers)) != null ? _readClientIp : NO_CLIENT_ADDRESS_BUCKET;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Counts one console write and returns a 429 once the window is spent.
|
|
45
|
+
*
|
|
46
|
+
* ## Reads are not limited
|
|
47
|
+
*
|
|
48
|
+
* The standing rule in `RATE_LIMITING.md` is "`checkRateLimit` for volume,
|
|
49
|
+
* `consumeRateLimit` for consequence": a durable call costs one Firestore read
|
|
50
|
+
* plus one write, which is the right price beside a handler that installs a
|
|
51
|
+
* bundle or calls a paid model, and the wrong price in front of a listing GET.
|
|
52
|
+
* Every route on this surface that spends something spends it on a POST — the
|
|
53
|
+
* two email list previews, the member add, the suppression writes, `ai/assist`,
|
|
54
|
+
* every marketplace install and publish — so writes-only covers the exposure
|
|
55
|
+
* rather than merely most of it.
|
|
56
|
+
*
|
|
57
|
+
* ## Fail SOFT, inherited from `consumeRateLimit`
|
|
58
|
+
*
|
|
59
|
+
* A Firestore outage lets traffic through, flagged `degraded`, rather than
|
|
60
|
+
* refusing it; a contended key is refused. That classification is the store's
|
|
61
|
+
* and is not re-decided here. Failing closed would take the console's write
|
|
62
|
+
* surface down during an unrelated blip — the operator's own recovery tools
|
|
63
|
+
* among them — to bound spend that the outage has already stopped, since the
|
|
64
|
+
* counter and the documents these handlers write are one Firestore. CSRF fails
|
|
65
|
+
* closed because forging a request is the threat; here the threat is volume,
|
|
66
|
+
* and volume during an outage is not the incident.
|
|
67
|
+
*
|
|
68
|
+
* ## No staff bypass
|
|
69
|
+
*
|
|
70
|
+
* The lockdown gates above this one grant staff a bypass, because their
|
|
71
|
+
* question is "should anyone be doing this right now" and staff are how an
|
|
72
|
+
* incident gets diagnosed. This one's question is "how much is this costing",
|
|
73
|
+
* and a staff session spends exactly what a customer session spends. A bypass
|
|
74
|
+
* would also make the surface unlimited for the accounts most likely to be
|
|
75
|
+
* driving it from a script.
|
|
76
|
+
*
|
|
77
|
+
* ## What the caller is told
|
|
78
|
+
*
|
|
79
|
+
* A bare 429 with `Retry-After`, following `visitorWriteRateLimitRefusal` and
|
|
80
|
+
* `forms/submit`. The fallback bucket below is reached by callers that proved
|
|
81
|
+
* nothing, so the response says nothing a caller could spend the rest of the
|
|
82
|
+
* window on.
|
|
83
|
+
*/ export async function consoleApiRateLimitRefusal(options) {
|
|
84
|
+
var _options_nowMs, _options_limit, _options_windowMs;
|
|
85
|
+
var _options_request;
|
|
86
|
+
if (lockdownIntentForMethod((_options_request = options.request) == null ? void 0 : _options_request.method) === 'read') return null;
|
|
87
|
+
if (isMachinePluginApiPath(options.path)) return null;
|
|
88
|
+
const nowMs = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.now();
|
|
89
|
+
// Prefixed, so an address can never collide with a uid: Firebase uids are
|
|
90
|
+
// opaque and a bucket shared between an identified operator and everyone
|
|
91
|
+
// arriving from one address would refuse the wrong one of them.
|
|
92
|
+
const subject = options.uid ? `uid:${options.uid}` : `ip:${clientIp(options.request)}`;
|
|
93
|
+
const rate = await consumeRateLimit(consoleApiRateLimitKey(subject), {
|
|
94
|
+
limit: (_options_limit = options.limit) != null ? _options_limit : CONSOLE_API_RATE_LIMIT,
|
|
95
|
+
windowMs: (_options_windowMs = options.windowMs) != null ? _options_windowMs : CONSOLE_API_RATE_WINDOW_MS,
|
|
96
|
+
now: nowMs,
|
|
97
|
+
firestore: options.firestore
|
|
98
|
+
});
|
|
99
|
+
if (rate.allowed) return null;
|
|
100
|
+
return Response.json({
|
|
101
|
+
error: 'Too many requests'
|
|
102
|
+
}, {
|
|
103
|
+
status: 429,
|
|
104
|
+
headers: {
|
|
105
|
+
'Retry-After': String(Math.max(1, Math.ceil((rate.resetMs - nowMs) / 1000)))
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
export default consoleApiRateLimitRefusal;
|
|
110
|
+
|
|
111
|
+
//# sourceMappingURL=console-api-rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/console-api-rate-limit.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CONSOLE_API_RATE_LIMIT,\n CONSOLE_API_RATE_WINDOW_MS,\n consoleApiRateLimitKey,\n isMachinePluginApiPath,\n lockdownIntentForMethod,\n} from '@aglyn/aglyn/server'\nimport { consumeRateLimit } from './rate-limit-store'\nimport {\n NO_CLIENT_ADDRESS_BUCKET,\n readClientIp,\n} from '@aglyn/aglyn/app-utils/request-ip'\n\n/**\n * The rate-limit refusal a console plugin-API write returns, or null to\n * proceed:\n *\n * const limited = await consoleApiRateLimitRefusal({ path, uid, request })\n * if (limited) return limited\n *\n * The same split as `visitorWriteRateLimitRefusal` beside it — the policy\n * (ceiling, window, key, which paths are exempt) is pure and lives in\n * `@aglyn/aglyn/server`; this is only the durable half, which needs Firestore.\n * The counter is `consumeRateLimit`, unchanged and unwrapped, so the console\n * inherits its postures rather than acquiring a second set.\n */\n\n/**\n * The caller's address, through the shared reader, or the no-address bucket.\n *\n * `readClientIp` counts from the RIGHT of the forwarding chain and is the only\n * reader allowed to answer this question, so a caller cannot choose its own\n * bucket by prepending an address.\n */\nfunction clientIp(request: {\n headers?: { get?: (name: string) => unknown }\n}): string {\n const headers = request?.headers\n if (typeof headers?.get !== 'function') return NO_CLIENT_ADDRESS_BUCKET\n return (\n readClientIp(headers as { get(name: string): string | null }) ??\n NO_CLIENT_ADDRESS_BUCKET\n )\n}\n\nexport interface ConsoleApiRateLimitOptions {\n /** Dispatcher path, e.g. `email/list-members-add`. Decides machine vs not. */\n path: string\n /**\n * The verified Firebase uid, or null when the request carried no bearer\n * token or one that did not decode. The console dispatcher has already\n * decoded it for the lockdown gate, so this costs no second verification.\n */\n uid: string | null\n /** Read for its method and its client-address headers. */\n request: { method?: string; headers?: { get?: (name: string) => unknown } }\n limit?: number\n windowMs?: number\n nowMs?: number\n /** Injectable for tests; defaults to the Admin SDK's Firestore. */\n firestore?: unknown\n}\n\n/**\n * Counts one console write and returns a 429 once the window is spent.\n *\n * ## Reads are not limited\n *\n * The standing rule in `RATE_LIMITING.md` is \"`checkRateLimit` for volume,\n * `consumeRateLimit` for consequence\": a durable call costs one Firestore read\n * plus one write, which is the right price beside a handler that installs a\n * bundle or calls a paid model, and the wrong price in front of a listing GET.\n * Every route on this surface that spends something spends it on a POST — the\n * two email list previews, the member add, the suppression writes, `ai/assist`,\n * every marketplace install and publish — so writes-only covers the exposure\n * rather than merely most of it.\n *\n * ## Fail SOFT, inherited from `consumeRateLimit`\n *\n * A Firestore outage lets traffic through, flagged `degraded`, rather than\n * refusing it; a contended key is refused. That classification is the store's\n * and is not re-decided here. Failing closed would take the console's write\n * surface down during an unrelated blip — the operator's own recovery tools\n * among them — to bound spend that the outage has already stopped, since the\n * counter and the documents these handlers write are one Firestore. CSRF fails\n * closed because forging a request is the threat; here the threat is volume,\n * and volume during an outage is not the incident.\n *\n * ## No staff bypass\n *\n * The lockdown gates above this one grant staff a bypass, because their\n * question is \"should anyone be doing this right now\" and staff are how an\n * incident gets diagnosed. This one's question is \"how much is this costing\",\n * and a staff session spends exactly what a customer session spends. A bypass\n * would also make the surface unlimited for the accounts most likely to be\n * driving it from a script.\n *\n * ## What the caller is told\n *\n * A bare 429 with `Retry-After`, following `visitorWriteRateLimitRefusal` and\n * `forms/submit`. The fallback bucket below is reached by callers that proved\n * nothing, so the response says nothing a caller could spend the rest of the\n * window on.\n */\nexport async function consoleApiRateLimitRefusal(\n options: ConsoleApiRateLimitOptions,\n): Promise<Response | null> {\n if (lockdownIntentForMethod(options.request?.method) === 'read') return null\n if (isMachinePluginApiPath(options.path)) return null\n\n const nowMs = options.nowMs ?? Date.now()\n // Prefixed, so an address can never collide with a uid: Firebase uids are\n // opaque and a bucket shared between an identified operator and everyone\n // arriving from one address would refuse the wrong one of them.\n const subject = options.uid\n ? `uid:${options.uid}`\n : `ip:${clientIp(options.request)}`\n const rate = await consumeRateLimit(consoleApiRateLimitKey(subject), {\n limit: options.limit ?? CONSOLE_API_RATE_LIMIT,\n windowMs: options.windowMs ?? CONSOLE_API_RATE_WINDOW_MS,\n now: nowMs,\n firestore: options.firestore,\n })\n if (rate.allowed) return null\n\n return Response.json(\n { error: 'Too many requests' },\n {\n status: 429,\n headers: {\n 'Retry-After': String(\n Math.max(1, Math.ceil((rate.resetMs - nowMs) / 1000)),\n ),\n },\n },\n )\n}\n\nexport default consoleApiRateLimitRefusal\n"],"names":["CONSOLE_API_RATE_LIMIT","CONSOLE_API_RATE_WINDOW_MS","consoleApiRateLimitKey","isMachinePluginApiPath","lockdownIntentForMethod","consumeRateLimit","NO_CLIENT_ADDRESS_BUCKET","readClientIp","clientIp","request","headers","get","consoleApiRateLimitRefusal","options","method","path","nowMs","Date","now","subject","uid","rate","limit","windowMs","firestore","allowed","Response","json","error","status","String","Math","max","ceil","resetMs"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,sBAAsB,EACtBC,0BAA0B,EAC1BC,sBAAsB,EACtBC,sBAAsB,EACtBC,uBAAuB,QAClB,sBAAqB;AAC5B,SAASC,gBAAgB,QAAQ,wBAAoB;AACrD,SACEC,wBAAwB,EACxBC,YAAY,QACP,oCAAmC;AAE1C;;;;;;;;;;;;CAYC,GAED;;;;;;CAMC,GACD,SAASC,SAASC,OAEjB;QAIGF;IAHF,MAAMG,UAAUD,2BAAAA,QAASC,OAAO;IAChC,IAAI,QAAOA,2BAAAA,QAASC,GAAG,MAAK,YAAY,OAAOL;IAC/C,QACEC,gBAAAA,aAAaG,oBAAbH,gBACAD;AAEJ;AAoBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCC,GACD,OAAO,eAAeM,2BACpBC,OAAmC;QAKrBA,gBAQLA,gBACGA;QAZgBA;IAA5B,IAAIT,yBAAwBS,mBAAAA,QAAQJ,OAAO,qBAAfI,iBAAiBC,MAAM,MAAM,QAAQ,OAAO;IACxE,IAAIX,uBAAuBU,QAAQE,IAAI,GAAG,OAAO;IAEjD,MAAMC,SAAQH,iBAAAA,QAAQG,KAAK,YAAbH,iBAAiBI,KAAKC,GAAG;IACvC,0EAA0E;IAC1E,yEAAyE;IACzE,gEAAgE;IAChE,MAAMC,UAAUN,QAAQO,GAAG,GACvB,CAAC,IAAI,EAAEP,QAAQO,GAAG,EAAE,GACpB,CAAC,GAAG,EAAEZ,SAASK,QAAQJ,OAAO,GAAG;IACrC,MAAMY,OAAO,MAAMhB,iBAAiBH,uBAAuBiB,UAAU;QACnEG,KAAK,GAAET,iBAAAA,QAAQS,KAAK,YAAbT,iBAAiBb;QACxBuB,QAAQ,GAAEV,oBAAAA,QAAQU,QAAQ,YAAhBV,oBAAoBZ;QAC9BiB,KAAKF;QACLQ,WAAWX,QAAQW,SAAS;IAC9B;IACA,IAAIH,KAAKI,OAAO,EAAE,OAAO;IAEzB,OAAOC,SAASC,IAAI,CAClB;QAAEC,OAAO;IAAoB,GAC7B;QACEC,QAAQ;QACRnB,SAAS;YACP,eAAeoB,OACbC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,IAAI,CAAC,AAACZ,CAAAA,KAAKa,OAAO,GAAGlB,KAAI,IAAK;QAEnD;IACF;AAEJ;AAEA,eAAeJ,2BAA0B"}
|
|
@@ -0,0 +1,326 @@
|
|
|
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 DomainCheck } from './sso-provisioning';
|
|
18
|
+
import { type RecaptchaAllowlistResult } from './recaptcha-allowlist';
|
|
19
|
+
import { type WorkspaceDomainResult } from './workspace-domains';
|
|
20
|
+
import { normalizePlatformDomain } from './platform-domain-names';
|
|
21
|
+
/** Collection name, kept in one place so the rules file and this agree. */
|
|
22
|
+
export declare const CONSOLE_DOMAINS_COLLECTION = "consoleDomains";
|
|
23
|
+
/**
|
|
24
|
+
* Trim, lowercase, drop a trailing root dot, and tolerate a pasted URL.
|
|
25
|
+
*
|
|
26
|
+
* The console-flavoured name for the shared normaliser, kept because callers
|
|
27
|
+
* and the docs use it.
|
|
28
|
+
*/
|
|
29
|
+
export declare const normalizeConsoleDomain: typeof normalizePlatformDomain;
|
|
30
|
+
/**
|
|
31
|
+
* Shape, length and reserved-name check.
|
|
32
|
+
*
|
|
33
|
+
* The RULES moved to `platform-domain-names.ts` (AGL-1430) so the site
|
|
34
|
+
* custom-domain path could stop being the only surface without them — it was
|
|
35
|
+
* the one with the live hole. Only the wording stays here: these two strings
|
|
36
|
+
* are what a customer reads in the console-domain wizard.
|
|
37
|
+
*
|
|
38
|
+
* `{ domain, error }` with both keys always present rather than a discriminated
|
|
39
|
+
* union, matching `validateSsoDomain` — `strictNullChecks` is off repo-wide and
|
|
40
|
+
* an `{ ok: true } | { ok: false }` union does not narrow reliably once it
|
|
41
|
+
* crosses a library boundary.
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateConsoleDomain(input: string): DomainCheck;
|
|
44
|
+
/**
|
|
45
|
+
* Every name this claim covers: the primary, then its twins.
|
|
46
|
+
*
|
|
47
|
+
* **A twin is a name we attach to Vercel that is not the one the customer
|
|
48
|
+
* asked for**, and it is the sharp edge of the whole file. Attaching
|
|
49
|
+
* `acme.com` without also attaching `www.acme.com` leaves half the traffic on
|
|
50
|
+
* the floor; attaching `www.acme.com` without *claiming* it leaves a name in
|
|
51
|
+
* our Vercel project that no Firestore document accounts for — and then a
|
|
52
|
+
* second org claims it, `attachProjectDomain` answers `already-exists`, and
|
|
53
|
+
* that org's visitors are redirected to the first org's console. The name is
|
|
54
|
+
* therefore derived here, claimed here, and attached from the same list.
|
|
55
|
+
*
|
|
56
|
+
* The apex test is deliberately narrow: exactly two labels. `acme.co.uk` is
|
|
57
|
+
* also an apex and gets no twin, which costs a customer a `www` redirect they
|
|
58
|
+
* can ask for by hand. Guessing wrong in the other direction — treating
|
|
59
|
+
* `console.acme.com` as an apex and quietly reserving `www.console.acme.com` —
|
|
60
|
+
* would reserve names on an org's behalf that it never asked for, and a
|
|
61
|
+
* reservation is the thing that locks another org out.
|
|
62
|
+
*/
|
|
63
|
+
export declare function consoleDomainNames(domain: string): string[];
|
|
64
|
+
export type ConsoleDomainStatus = 'pending' | 'verified' | 'active' | 'suspended';
|
|
65
|
+
export interface ConsoleDomainClaimView {
|
|
66
|
+
domain: string;
|
|
67
|
+
orgId: string;
|
|
68
|
+
status: ConsoleDomainStatus;
|
|
69
|
+
token: string;
|
|
70
|
+
/** Every name the claim reserves — primary first. */
|
|
71
|
+
names: string[];
|
|
72
|
+
/** Fully-qualified host the TXT record goes on. */
|
|
73
|
+
recordHost: string;
|
|
74
|
+
/** Exact value the TXT record must carry. */
|
|
75
|
+
recordValue: string;
|
|
76
|
+
verified: boolean;
|
|
77
|
+
lastRecords?: string[];
|
|
78
|
+
}
|
|
79
|
+
/** Both keys always present, one always null — see `validateConsoleDomain`. */
|
|
80
|
+
export interface ConsoleDomainResult {
|
|
81
|
+
claim: ConsoleDomainClaimView | null;
|
|
82
|
+
error: string | null;
|
|
83
|
+
/** HTTP status a route should return. 200 whenever `claim` is set. */
|
|
84
|
+
status: number;
|
|
85
|
+
}
|
|
86
|
+
export declare class ConsoleDomainTakenError extends Error {
|
|
87
|
+
readonly domain: string;
|
|
88
|
+
constructor(domain: string);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Claim the primary and every twin in ONE transaction, or none of them.
|
|
92
|
+
*
|
|
93
|
+
* Modelled on the `orgSlugs` reservation in `organizations.ts`: read inside the
|
|
94
|
+
* transaction, refuse if someone else holds it, write in the same transaction.
|
|
95
|
+
* Every name is read with a single `getAll` so the whole set is covered by one
|
|
96
|
+
* transaction's read-set — claiming a twin in a follow-up write is precisely
|
|
97
|
+
* the AGL-743 hole this exists to close.
|
|
98
|
+
*
|
|
99
|
+
* Idempotent for the owning org, and it **keeps the existing token**: reissuing
|
|
100
|
+
* would invalidate a TXT record the customer may already have published and
|
|
101
|
+
* turn a working setup into a mysterious failure (the same reasoning as
|
|
102
|
+
* `issueDomainClaim`).
|
|
103
|
+
*
|
|
104
|
+
* Throws `ConsoleDomainTakenError` when any name in the set belongs to another
|
|
105
|
+
* org. Exported for the integration test, which needs to drive the transaction
|
|
106
|
+
* under real contention.
|
|
107
|
+
*/
|
|
108
|
+
export declare function claimConsoleDomain(orgId: string, domain: string): Promise<ConsoleDomainClaimView>;
|
|
109
|
+
/**
|
|
110
|
+
* Start (or re-read) a registration: entitlement, validation, then the claim.
|
|
111
|
+
*
|
|
112
|
+
* The order matters. An org without `whiteLabel` must not be able to learn
|
|
113
|
+
* whether a domain is free, and must not leave a `pending` claim behind that
|
|
114
|
+
* blocks the org that *is* entitled to it.
|
|
115
|
+
*/
|
|
116
|
+
export declare function registerConsoleDomain(options: {
|
|
117
|
+
orgId: string;
|
|
118
|
+
domain: string;
|
|
119
|
+
}): Promise<ConsoleDomainResult>;
|
|
120
|
+
/**
|
|
121
|
+
* Re-read DNS and record the verdict.
|
|
122
|
+
*
|
|
123
|
+
* Re-reads the claim first and refuses if the caller does not own it — the
|
|
124
|
+
* `publishSsoDomains` discipline, where nothing reaches the collection sign-in
|
|
125
|
+
* consults until a fresh lookup has seen the record. The comparison itself is
|
|
126
|
+
* `recordsProveOwnership`, an exact match on a whole TXT record: a substring
|
|
127
|
+
* test would accept `aglyn-domain-verification=<token>-and-more`, and a prefix
|
|
128
|
+
* test would accept anything that merely starts the same way.
|
|
129
|
+
*/
|
|
130
|
+
export declare function verifyConsoleDomain(options: {
|
|
131
|
+
orgId: string;
|
|
132
|
+
domain: string;
|
|
133
|
+
}): Promise<ConsoleDomainResult>;
|
|
134
|
+
export interface ConsoleDomainAttachment {
|
|
135
|
+
domain: string;
|
|
136
|
+
results: WorkspaceDomainResult[];
|
|
137
|
+
/** Vercel's verdict alone: every claimed name is on the console project. */
|
|
138
|
+
attached: boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Attached **and** able to attest — the only thing a caller may treat as
|
|
141
|
+
* "the customer can use this domain". `attached && !ready` is a real and
|
|
142
|
+
* reachable state: Vercel serves the name, App Check refuses it.
|
|
143
|
+
*/
|
|
144
|
+
ready: boolean;
|
|
145
|
+
/**
|
|
146
|
+
* Non-null when a name this claim SERVES on cannot complete a large upload
|
|
147
|
+
* (AGL-1452), carrying the exact ordered command that fixes it.
|
|
148
|
+
*
|
|
149
|
+
* The attach itself still succeeds — the console does serve on the name, and
|
|
150
|
+
* refusing the domain over the upload path would be the worse outcome. But
|
|
151
|
+
* the customer's video, PDF and ZIP uploads will fail behind a generic "try
|
|
152
|
+
* again" snackbar until this is cleared, and the whole point of surfacing it
|
|
153
|
+
* here is that it stops being something a person has to remember: GCS
|
|
154
|
+
* matches CORS origins EXACTLY, so a new console origin needs its own entry
|
|
155
|
+
* and nothing about the eventual symptom points at bucket configuration.
|
|
156
|
+
*
|
|
157
|
+
* `attachProjectDomain` tries to add the entry itself; this is what is left
|
|
158
|
+
* when it could not — in practice, a runtime service account without
|
|
159
|
+
* `storage.buckets.update`.
|
|
160
|
+
*/
|
|
161
|
+
uploadCorsRemedy: string | null;
|
|
162
|
+
/**
|
|
163
|
+
* The App Check reCAPTCHA allowlist write for the SERVING name (AGL-1378).
|
|
164
|
+
*
|
|
165
|
+
* Unlike `uploadCorsRemedy` this is **not** advisory. A console origin the
|
|
166
|
+
* key has never heard of cannot solve reCAPTCHA, so `getToken` fails with
|
|
167
|
+
* `appCheck/recaptcha-error` and Identity Platform refuses the first
|
|
168
|
+
* `signInWithCustomToken` with `401 UNAUTHENTICATED` — before it looks at
|
|
169
|
+
* the token. The domain would serve a console that renders and can never
|
|
170
|
+
* sign anyone in, and the customer-visible symptom is "Missing or
|
|
171
|
+
* insufficient permissions", indistinguishable from a rules verdict.
|
|
172
|
+
*
|
|
173
|
+
* So this participates in `attached`: a claim does not reach `active` unless
|
|
174
|
+
* `allowlistSatisfied()` says the origin can attest.
|
|
175
|
+
*/
|
|
176
|
+
allowlist: RecaptchaAllowlistResult;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Attach every claimed name to the console Vercel project.
|
|
180
|
+
*
|
|
181
|
+
* ⚠️ Not called from anywhere. It exists so the mechanism is complete and
|
|
182
|
+
* tested; wiring it to a route is 1099c's job and must not happen before 1099d
|
|
183
|
+
* (see the header).
|
|
184
|
+
*
|
|
185
|
+
* Refuses unless the claim is this org's **and** currently verified, and unless
|
|
186
|
+
* the org still passes `whiteLabel`. The names come from the stored claim, not
|
|
187
|
+
* from `consoleDomainNames`, so a change to the twin rule can never attach a
|
|
188
|
+
* name the transaction did not reserve.
|
|
189
|
+
*
|
|
190
|
+
* Twins are registered as per-domain **redirects to the primary**, and the
|
|
191
|
+
* redirect value is a **bare hostname** — `attachProjectDomain` enforces that,
|
|
192
|
+
* because `https://${target}` is rejected by Vercel with a message that blames
|
|
193
|
+
* the target for being absent rather than the format (AGL-1365).
|
|
194
|
+
*/
|
|
195
|
+
export declare function activateConsoleDomain(options: {
|
|
196
|
+
orgId: string;
|
|
197
|
+
domain: string;
|
|
198
|
+
}): Promise<ConsoleDomainResult & {
|
|
199
|
+
attachment: ConsoleDomainAttachment | null;
|
|
200
|
+
}>;
|
|
201
|
+
/**
|
|
202
|
+
* Kill the sessions, detach every name, then drop the claim — in that order.
|
|
203
|
+
*
|
|
204
|
+
* **The epoch bump comes first, and that is D7's own ordering**: *"`sessionEpoch
|
|
205
|
+
* = now` first, then Vercel `DELETE`, then delete the doc. Order matters: kill
|
|
206
|
+
* the sessions while we still control the host."* It was the one step of that
|
|
207
|
+
* sentence this function never performed, while
|
|
208
|
+
* {@link consoleSessionEpochRefuses} documented it as something release
|
|
209
|
+
* already did.
|
|
210
|
+
*
|
|
211
|
+
* Two things went wrong without it, and neither is covered by the Vercel
|
|
212
|
+
* detach:
|
|
213
|
+
*
|
|
214
|
+
* - **The in-flight window.** Vercel's `DELETE` and the allowlist reclaim are
|
|
215
|
+
* network round trips. Across all of them the claim still reads `active`, so
|
|
216
|
+
* `resolveConsoleDomain` says `servable` and `redeemConsoleHandoff` — which
|
|
217
|
+
* sits on `/api/*`, outside the middleware matcher — happily cashes an
|
|
218
|
+
* authorized handoff into a brand-new session on a domain we are in the
|
|
219
|
+
* middle of giving up.
|
|
220
|
+
* - **And that session then outlives the release entirely**, because the
|
|
221
|
+
* document carrying the epoch is deleted a moment later and
|
|
222
|
+
* `readConsoleSessionEpoch` reads a missing document as `0`, which every
|
|
223
|
+
* caller treats as "this control has nothing to say" and FAILS OPEN. The
|
|
224
|
+
* effect was that `suspendOnDowngrade` — the *lesser* action — revoked
|
|
225
|
+
* sessions properly while an explicit release did not revoke them at all.
|
|
226
|
+
*
|
|
227
|
+
* Bumping first inverts that: an authorization that predates the bump is
|
|
228
|
+
* refused `revoked` at redemption, so the window closes rather than merely
|
|
229
|
+
* being short. What survives the release is still only Vercel's allowlist,
|
|
230
|
+
* which is the boundary; this is the defence in depth behind it that D7 asked
|
|
231
|
+
* for and the code had stopped providing.
|
|
232
|
+
*
|
|
233
|
+
* Vercel before the documents, though, for the reason the header gives: a claim
|
|
234
|
+
* released while the name is still on the project is the unindexed-name hole,
|
|
235
|
+
* opened deliberately. If a detach fails the claim is kept and the caller is
|
|
236
|
+
* told, so the name stays reserved to the org that still holds it in Vercel —
|
|
237
|
+
* and the epoch bump is exactly the right residue on that path too, since
|
|
238
|
+
* sessions on a domain we just tried to release should not survive the attempt.
|
|
239
|
+
*/
|
|
240
|
+
export declare function releaseConsoleDomain(options: {
|
|
241
|
+
orgId: string;
|
|
242
|
+
domain: string;
|
|
243
|
+
}): Promise<{
|
|
244
|
+
released: boolean;
|
|
245
|
+
results: WorkspaceDomainResult[];
|
|
246
|
+
/** Present once the reclaim was attempted — absent when Vercel refused first. */
|
|
247
|
+
allowlist?: RecaptchaAllowlistResult;
|
|
248
|
+
error: string | null;
|
|
249
|
+
}>;
|
|
250
|
+
/**
|
|
251
|
+
* Drop a claim that was never attached, without touching Vercel.
|
|
252
|
+
*
|
|
253
|
+
* The counterpart to claiming from the branding form: an org that clears or
|
|
254
|
+
* changes `customConsoleDomain` must not leave its old reservation standing,
|
|
255
|
+
* or it locks another org out of a name nobody is using. Deliberately refuses
|
|
256
|
+
* once the claim has proved ownership or reached Vercel — releasing *that*
|
|
257
|
+
* needs `releaseConsoleDomain`, because a claim dropped while the name is
|
|
258
|
+
* still on the project is the unindexed-name hole in the header.
|
|
259
|
+
*/
|
|
260
|
+
export declare function releasePendingConsoleDomain(options: {
|
|
261
|
+
orgId: string;
|
|
262
|
+
domain: string;
|
|
263
|
+
}): Promise<boolean>;
|
|
264
|
+
/**
|
|
265
|
+
* Why a host may — or may not — be served the console (AGL-1099c).
|
|
266
|
+
*
|
|
267
|
+
* `reason` is not decoration. `unknown` and `degraded` both mean "serve it,
|
|
268
|
+
* this gate has no opinion", and they mean it for opposite causes; every other
|
|
269
|
+
* value means "refuse". Collapsing them into a boolean is how a lookup outage
|
|
270
|
+
* would come to read the same as a revoked entitlement.
|
|
271
|
+
*/
|
|
272
|
+
export type ConsoleDomainReason = 'active' | 'unknown' | 'no-org' | 'not-entitled' | 'not-active' | 'degraded';
|
|
273
|
+
export interface ConsoleDomainVerdict {
|
|
274
|
+
/** A claim exists for this exact host. */
|
|
275
|
+
known: boolean;
|
|
276
|
+
/** The console may be served here, pinned to `orgSlug`. */
|
|
277
|
+
servable: boolean;
|
|
278
|
+
/**
|
|
279
|
+
* Workspace slug of the owning org — where a refused visitor is sent, and
|
|
280
|
+
* the org the path is rewritten under when servable. Never the `orgId`: a
|
|
281
|
+
* verdict is read by an unauthenticated route.
|
|
282
|
+
*/
|
|
283
|
+
orgSlug: string | null;
|
|
284
|
+
reason: ConsoleDomainReason;
|
|
285
|
+
/** The lookup could not be completed. Honour it, never cache it. */
|
|
286
|
+
degraded: boolean;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Host → org, for the middleware gate and the session-route boundary.
|
|
290
|
+
*
|
|
291
|
+
* **One host pins exactly one org.** That is enforced by the document being
|
|
292
|
+
* keyed on the host and claimed transactionally (see the header), not by
|
|
293
|
+
* anything here — this function only ever reads the pin, and there is no input
|
|
294
|
+
* by which a caller can ask for a different org.
|
|
295
|
+
*
|
|
296
|
+
* The three "serve it anyway" answers, and why each is right:
|
|
297
|
+
*
|
|
298
|
+
* - **`unknown`** — no claim on this host. localhost, preview deployments and
|
|
299
|
+
* self-hosted installs all land here, and they must keep working. It is safe
|
|
300
|
+
* because of the detach ordering `releaseConsoleDomain` enforces: Vercel
|
|
301
|
+
* first, documents second, and the claim is kept if the detach fails. So a
|
|
302
|
+
* name the console project still answers for always has a document, and a
|
|
303
|
+
* name with no document cannot reach this deployment through the console
|
|
304
|
+
* project at all (AGL-1430's correspondence — do not break it).
|
|
305
|
+
* - **`degraded`** — Firestore could not be reached. Fails OPEN, matching the
|
|
306
|
+
* workspace gate and for the reason AGL-1135 recorded: the Vercel domain
|
|
307
|
+
* allowlist is the boundary, and a customer's console going dark because a
|
|
308
|
+
* lookup timed out is worse than the residual exposure. Never cached.
|
|
309
|
+
* - a claim that is `active` and entitled.
|
|
310
|
+
*
|
|
311
|
+
* Everything else refuses. In particular a `verified` claim is **not**
|
|
312
|
+
* servable: until AGL-1378 clears, activation includes a manual App Check
|
|
313
|
+
* allowlist entry, so "ownership proved" must never imply "serve it".
|
|
314
|
+
*/
|
|
315
|
+
export declare function resolveConsoleDomain(host: string): Promise<ConsoleDomainVerdict>;
|
|
316
|
+
/**
|
|
317
|
+
* The org a console domain belongs to, or null.
|
|
318
|
+
*
|
|
319
|
+
* Read-only, and used today only to refuse a `customConsoleDomain` that another
|
|
320
|
+
* org already holds. Routing reads `resolveConsoleDomain` instead, which asks
|
|
321
|
+
* the entitlement question this one does not.
|
|
322
|
+
*/
|
|
323
|
+
export declare function getConsoleDomainClaim(domain: string): Promise<{
|
|
324
|
+
orgId: string;
|
|
325
|
+
status: ConsoleDomainStatus;
|
|
326
|
+
} | null>;
|