@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,779 @@
|
|
|
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
|
+
*/ /**
|
|
18
|
+
* Registration, DNS ownership proof and Vercel attachment for a custom
|
|
19
|
+
* **console** domain — the "verify + attach" half of AGL-1099 (AGL-1373).
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ **THIS LANDS DARK, AND MUST STAY DARK.** AGL-1099 says it outright:
|
|
22
|
+
* *"Do NOT ship 1099b alone: attaching a domain that then cannot authenticate
|
|
23
|
+
* is worse than not attaching it, because it looks finished."* Nothing here is
|
|
24
|
+
* reachable from a user-facing path, nothing calls `activateConsoleDomain`, and
|
|
25
|
+
* no domain has been attached. One thing must still land first:
|
|
26
|
+
*
|
|
27
|
+
* - **1099e — the handoff itself.** A console domain shares no parent domain
|
|
28
|
+
* with `aglyn.com`, so the `.aglyn.com` session cookie cannot reach it.
|
|
29
|
+
*
|
|
30
|
+
* ## 1099d is no longer one of them (AGL-1378, closed 2026-08-23)
|
|
31
|
+
*
|
|
32
|
+
* The 1099a proof-of-concept (`docs/design/agl-1099a-poc-findings.md`)
|
|
33
|
+
* measured that App Check, *not* Firebase's authorized-domain list, is the
|
|
34
|
+
* gate: an unattested `signInWithCustomToken` 401s with "Firebase App Check
|
|
35
|
+
* token is invalid" **before** token validation. Same page, same app id, only
|
|
36
|
+
* the origin differing — an allowlisted origin minted a token and reached
|
|
37
|
+
* credential validation; a non-allowlisted one failed at
|
|
38
|
+
* `appCheck/recaptcha-error`. That finding stands, and it is why the allowlist
|
|
39
|
+
* entry is a hard **functional** prerequisite rather than the commercial
|
|
40
|
+
* ceiling the design called it.
|
|
41
|
+
*
|
|
42
|
+
* What was wrong was the conclusion that the entry could only be added by
|
|
43
|
+
* hand. That rested on probing `aglyn-main`, which is not the project the key
|
|
44
|
+
* lives in; the key was auto-migrated into `recaptcha-migrated-…`, where the
|
|
45
|
+
* Enterprise API is enabled, we hold owner, and `projects.keys.patch` on
|
|
46
|
+
* `webSettings.allowedDomains` answers 200. `activateConsoleDomain` now writes
|
|
47
|
+
* the serving name through `recaptcha-allowlist.ts` and refuses to mark the
|
|
48
|
+
* claim `active` if that write does not land.
|
|
49
|
+
*
|
|
50
|
+
* ## Why this is not `/api/domains/attach|verify|detach`
|
|
51
|
+
*
|
|
52
|
+
* Those routes are shaped around `hosts/{hostId}`, a `cname` field and the
|
|
53
|
+
* `customDomain` entitlement. A console domain is keyed on the **domain**,
|
|
54
|
+
* owned by an **org**, and gated on `whiteLabel`. Re-pointing them at the
|
|
55
|
+
* console project would mean parameterising the auth model, the entitlement,
|
|
56
|
+
* the uniqueness query and the Firestore shape — at which point it is not
|
|
57
|
+
* reuse. What *is* reused is `workspace-domains.ts`, which already talks to
|
|
58
|
+
* `VERCEL_CONSOLE_PROJECT_ID`, already carries a deadline, and already never
|
|
59
|
+
* throws (AGL-1353 D8).
|
|
60
|
+
*
|
|
61
|
+
* ## The uniqueness invariant, which is the whole security property
|
|
62
|
+
*
|
|
63
|
+
* `attachProjectDomain` tolerates Vercel's `domain_already_in_use` as success.
|
|
64
|
+
* That is only safe while the Firestore claim indexes **every** name Vercel
|
|
65
|
+
* holds for us. If a name reaches Vercel without a claim, a second org can
|
|
66
|
+
* claim it here, read `already-exists` as health, and have its visitors served
|
|
67
|
+
* — or redirected — to a stranger's console. That is AGL-743 exactly: there,
|
|
68
|
+
* `host.cname` was written client-side *before* the uniqueness check, so losing
|
|
69
|
+
* the race still left the loser holding the domain and `get-host.ts` resolved
|
|
70
|
+
* the duplicate by Firestore document order.
|
|
71
|
+
*
|
|
72
|
+
* So: **`claimConsoleDomain` claims the primary and every twin in ONE
|
|
73
|
+
* transaction, or none of them**, and `activateConsoleDomain` attaches exactly
|
|
74
|
+
* the set that transaction claimed. Neither half is optional, and a twin
|
|
75
|
+
* introduced later without going through `consoleDomainNames` reopens the hole.
|
|
76
|
+
*/ import { randomBytes } from "crypto";
|
|
77
|
+
import { checkEntitlement } from "@aglyn/aglyn/server";
|
|
78
|
+
import firebaseAdmin from "./firebase-admin.js";
|
|
79
|
+
import { challengeValue, recordsProveOwnership, resolveChallengeTxt, SSO_CHALLENGE_PREFIX } from "./sso-provisioning.js";
|
|
80
|
+
import { allowConsoleOrigin, allowlistSatisfied, reclaimConsoleOrigin } from "./recaptcha-allowlist.js";
|
|
81
|
+
import { attachProjectDomain, detachProjectDomain, pendingUploadCorsRemedy } from "./workspace-domains.js";
|
|
82
|
+
// The blocklist and the shape check, shared with the site custom-domain path
|
|
83
|
+
// so a name added to one is added to both (AGL-1430).
|
|
84
|
+
import { normalizePlatformDomain, validatePlatformDomain } from "./platform-domain-names.js";
|
|
85
|
+
const firestore = ()=>firebaseAdmin.app().firestore();
|
|
86
|
+
/** Collection name, kept in one place so the rules file and this agree. */ export const CONSOLE_DOMAINS_COLLECTION = 'consoleDomains';
|
|
87
|
+
/**
|
|
88
|
+
* Trim, lowercase, drop a trailing root dot, and tolerate a pasted URL.
|
|
89
|
+
*
|
|
90
|
+
* The console-flavoured name for the shared normaliser, kept because callers
|
|
91
|
+
* and the docs use it.
|
|
92
|
+
*/ export const normalizeConsoleDomain = normalizePlatformDomain;
|
|
93
|
+
/**
|
|
94
|
+
* Shape, length and reserved-name check.
|
|
95
|
+
*
|
|
96
|
+
* The RULES moved to `platform-domain-names.ts` (AGL-1430) so the site
|
|
97
|
+
* custom-domain path could stop being the only surface without them — it was
|
|
98
|
+
* the one with the live hole. Only the wording stays here: these two strings
|
|
99
|
+
* are what a customer reads in the console-domain wizard.
|
|
100
|
+
*
|
|
101
|
+
* `{ domain, error }` with both keys always present rather than a discriminated
|
|
102
|
+
* union, matching `validateSsoDomain` — `strictNullChecks` is off repo-wide and
|
|
103
|
+
* an `{ ok: true } | { ok: false }` union does not narrow reliably once it
|
|
104
|
+
* crosses a library boundary.
|
|
105
|
+
*/ export function validateConsoleDomain(input) {
|
|
106
|
+
return validatePlatformDomain(input, {
|
|
107
|
+
invalid: 'Enter a valid domain, for example console.acme.com',
|
|
108
|
+
reserved: 'That domain is reserved and cannot be used as a console domain'
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Every name this claim covers: the primary, then its twins.
|
|
113
|
+
*
|
|
114
|
+
* **A twin is a name we attach to Vercel that is not the one the customer
|
|
115
|
+
* asked for**, and it is the sharp edge of the whole file. Attaching
|
|
116
|
+
* `acme.com` without also attaching `www.acme.com` leaves half the traffic on
|
|
117
|
+
* the floor; attaching `www.acme.com` without *claiming* it leaves a name in
|
|
118
|
+
* our Vercel project that no Firestore document accounts for — and then a
|
|
119
|
+
* second org claims it, `attachProjectDomain` answers `already-exists`, and
|
|
120
|
+
* that org's visitors are redirected to the first org's console. The name is
|
|
121
|
+
* therefore derived here, claimed here, and attached from the same list.
|
|
122
|
+
*
|
|
123
|
+
* The apex test is deliberately narrow: exactly two labels. `acme.co.uk` is
|
|
124
|
+
* also an apex and gets no twin, which costs a customer a `www` redirect they
|
|
125
|
+
* can ask for by hand. Guessing wrong in the other direction — treating
|
|
126
|
+
* `console.acme.com` as an apex and quietly reserving `www.console.acme.com` —
|
|
127
|
+
* would reserve names on an org's behalf that it never asked for, and a
|
|
128
|
+
* reservation is the thing that locks another org out.
|
|
129
|
+
*/ export function consoleDomainNames(domain) {
|
|
130
|
+
const primary = normalizeConsoleDomain(domain);
|
|
131
|
+
if (!primary) return [];
|
|
132
|
+
const labels = primary.split('.');
|
|
133
|
+
if (labels.length !== 2) return [
|
|
134
|
+
primary
|
|
135
|
+
];
|
|
136
|
+
return [
|
|
137
|
+
primary,
|
|
138
|
+
`www.${primary}`
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
export class ConsoleDomainTakenError extends Error {
|
|
142
|
+
constructor(domain){
|
|
143
|
+
super(`Console domain already claimed: ${domain}`);
|
|
144
|
+
this.name = 'ConsoleDomainTakenError';
|
|
145
|
+
this.domain = domain;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const claimRef = (domain)=>firestore().collection(CONSOLE_DOMAINS_COLLECTION).doc(domain);
|
|
149
|
+
function claimView(domain, data) {
|
|
150
|
+
var _ref, _ref1, _ref2, _ref3;
|
|
151
|
+
const token = String((_ref = data == null ? void 0 : data.txtToken) != null ? _ref : '');
|
|
152
|
+
const names = Array.isArray(data == null ? void 0 : data.names) ? data.names : consoleDomainNames(domain);
|
|
153
|
+
return {
|
|
154
|
+
domain,
|
|
155
|
+
orgId: String((_ref1 = data == null ? void 0 : data.orgId) != null ? _ref1 : ''),
|
|
156
|
+
status: (_ref2 = data == null ? void 0 : data.status) != null ? _ref2 : 'pending',
|
|
157
|
+
token,
|
|
158
|
+
names,
|
|
159
|
+
recordHost: `${SSO_CHALLENGE_PREFIX}.${domain}`,
|
|
160
|
+
recordValue: challengeValue(token),
|
|
161
|
+
verified: (data == null ? void 0 : data.status) === 'verified' || (data == null ? void 0 : data.status) === 'active',
|
|
162
|
+
lastRecords: (_ref3 = data == null ? void 0 : data.lastRecords) != null ? _ref3 : undefined
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The org's `whiteLabel` entitlement, read server-side.
|
|
167
|
+
*
|
|
168
|
+
* AGL-1354 removed `brandingProfile` from what a client may write, so a server
|
|
169
|
+
* route is now the only writer of `customConsoleDomain` — which makes this the
|
|
170
|
+
* only place the gate can live. A gate that is bypassable is not a gate, and
|
|
171
|
+
* this one was, on any plan, straight from the client SDK, until that landed.
|
|
172
|
+
*/ async function orgIsEntitled(orgId) {
|
|
173
|
+
const snapshot = await firestore().collection('orgs').doc(orgId).get();
|
|
174
|
+
if (!snapshot.exists) return false;
|
|
175
|
+
return checkEntitlement(snapshot.data(), 'whiteLabel');
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Claim the primary and every twin in ONE transaction, or none of them.
|
|
179
|
+
*
|
|
180
|
+
* Modelled on the `orgSlugs` reservation in `organizations.ts`: read inside the
|
|
181
|
+
* transaction, refuse if someone else holds it, write in the same transaction.
|
|
182
|
+
* Every name is read with a single `getAll` so the whole set is covered by one
|
|
183
|
+
* transaction's read-set — claiming a twin in a follow-up write is precisely
|
|
184
|
+
* the AGL-743 hole this exists to close.
|
|
185
|
+
*
|
|
186
|
+
* Idempotent for the owning org, and it **keeps the existing token**: reissuing
|
|
187
|
+
* would invalidate a TXT record the customer may already have published and
|
|
188
|
+
* turn a working setup into a mysterious failure (the same reasoning as
|
|
189
|
+
* `issueDomainClaim`).
|
|
190
|
+
*
|
|
191
|
+
* Throws `ConsoleDomainTakenError` when any name in the set belongs to another
|
|
192
|
+
* org. Exported for the integration test, which needs to drive the transaction
|
|
193
|
+
* under real contention.
|
|
194
|
+
*/ export async function claimConsoleDomain(orgId, domain) {
|
|
195
|
+
const names = consoleDomainNames(domain);
|
|
196
|
+
const primary = names[0];
|
|
197
|
+
const db = firestore();
|
|
198
|
+
const now = firebaseAdmin.firestore.FieldValue.serverTimestamp();
|
|
199
|
+
return db.runTransaction(async (tx)=>{
|
|
200
|
+
var _existing_get, _existing_get1;
|
|
201
|
+
const refs = names.map((name)=>db.collection(CONSOLE_DOMAINS_COLLECTION).doc(name));
|
|
202
|
+
// ONE read covering the whole set. Firestore requires every read before
|
|
203
|
+
// every write in a transaction, so this also fixes the ordering.
|
|
204
|
+
const snapshots = await tx.getAll(...refs);
|
|
205
|
+
for (const snapshot of snapshots){
|
|
206
|
+
if (snapshot.exists && snapshot.get('orgId') !== orgId) {
|
|
207
|
+
throw new ConsoleDomainTakenError(snapshot.id);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const existing = snapshots[0];
|
|
211
|
+
const token = existing.exists ? String((_existing_get = existing.get('txtToken')) != null ? _existing_get : '') : randomBytes(24).toString('hex');
|
|
212
|
+
const status = existing.exists ? (_existing_get1 = existing.get('status')) != null ? _existing_get1 : 'pending' : 'pending';
|
|
213
|
+
snapshots.forEach((snapshot, index)=>{
|
|
214
|
+
var _snapshot_get;
|
|
215
|
+
tx.set(refs[index], {
|
|
216
|
+
orgId,
|
|
217
|
+
primaryHost: primary,
|
|
218
|
+
role: index === 0 ? 'primary' : 'redirect',
|
|
219
|
+
names,
|
|
220
|
+
txtToken: token,
|
|
221
|
+
status,
|
|
222
|
+
vercelState: (_snapshot_get = snapshot.get('vercelState')) != null ? _snapshot_get : 'pending',
|
|
223
|
+
createdAt: snapshot.exists ? snapshot.get('createdAt') : now,
|
|
224
|
+
updatedAt: now
|
|
225
|
+
}, {
|
|
226
|
+
merge: true
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
return claimView(primary, {
|
|
230
|
+
orgId,
|
|
231
|
+
status,
|
|
232
|
+
txtToken: token,
|
|
233
|
+
names
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Start (or re-read) a registration: entitlement, validation, then the claim.
|
|
239
|
+
*
|
|
240
|
+
* The order matters. An org without `whiteLabel` must not be able to learn
|
|
241
|
+
* whether a domain is free, and must not leave a `pending` claim behind that
|
|
242
|
+
* blocks the org that *is* entitled to it.
|
|
243
|
+
*/ export async function registerConsoleDomain(options) {
|
|
244
|
+
var _ref;
|
|
245
|
+
const orgId = String((_ref = options == null ? void 0 : options.orgId) != null ? _ref : '');
|
|
246
|
+
if (!orgId) return {
|
|
247
|
+
claim: null,
|
|
248
|
+
error: 'Unknown organization',
|
|
249
|
+
status: 400
|
|
250
|
+
};
|
|
251
|
+
if (!await orgIsEntitled(orgId)) {
|
|
252
|
+
return {
|
|
253
|
+
claim: null,
|
|
254
|
+
error: 'A custom console domain requires the Agency plan',
|
|
255
|
+
status: 403
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const { domain, error } = validateConsoleDomain(options == null ? void 0 : options.domain);
|
|
259
|
+
if (!domain) return {
|
|
260
|
+
claim: null,
|
|
261
|
+
error: error != null ? error : 'Invalid domain',
|
|
262
|
+
status: 400
|
|
263
|
+
};
|
|
264
|
+
try {
|
|
265
|
+
return {
|
|
266
|
+
claim: await claimConsoleDomain(orgId, domain),
|
|
267
|
+
error: null,
|
|
268
|
+
status: 200
|
|
269
|
+
};
|
|
270
|
+
} catch (thrown) {
|
|
271
|
+
if (thrown instanceof ConsoleDomainTakenError) {
|
|
272
|
+
return {
|
|
273
|
+
claim: null,
|
|
274
|
+
error: 'That domain is already connected to another organization',
|
|
275
|
+
status: 409
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
throw thrown;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Re-read DNS and record the verdict.
|
|
283
|
+
*
|
|
284
|
+
* Re-reads the claim first and refuses if the caller does not own it — the
|
|
285
|
+
* `publishSsoDomains` discipline, where nothing reaches the collection sign-in
|
|
286
|
+
* consults until a fresh lookup has seen the record. The comparison itself is
|
|
287
|
+
* `recordsProveOwnership`, an exact match on a whole TXT record: a substring
|
|
288
|
+
* test would accept `aglyn-domain-verification=<token>-and-more`, and a prefix
|
|
289
|
+
* test would accept anything that merely starts the same way.
|
|
290
|
+
*/ export async function verifyConsoleDomain(options) {
|
|
291
|
+
var _ref, _snapshot_get, _snapshot_get1;
|
|
292
|
+
const orgId = String((_ref = options == null ? void 0 : options.orgId) != null ? _ref : '');
|
|
293
|
+
const { domain, error } = validateConsoleDomain(options == null ? void 0 : options.domain);
|
|
294
|
+
if (!domain) return {
|
|
295
|
+
claim: null,
|
|
296
|
+
error: error != null ? error : 'Invalid domain',
|
|
297
|
+
status: 400
|
|
298
|
+
};
|
|
299
|
+
const snapshot = await claimRef(domain).get();
|
|
300
|
+
if (!snapshot.exists || snapshot.get('orgId') !== orgId) {
|
|
301
|
+
return {
|
|
302
|
+
claim: null,
|
|
303
|
+
error: 'No claim on that domain',
|
|
304
|
+
status: 404
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (!await orgIsEntitled(orgId)) {
|
|
308
|
+
return {
|
|
309
|
+
claim: null,
|
|
310
|
+
error: 'A custom console domain requires the Agency plan',
|
|
311
|
+
status: 403
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const token = String((_snapshot_get = snapshot.get('txtToken')) != null ? _snapshot_get : '');
|
|
315
|
+
const records = await resolveChallengeTxt(domain);
|
|
316
|
+
const proved = recordsProveOwnership(records, token);
|
|
317
|
+
const status = proved ? snapshot.get('status') === 'active' ? 'active' : 'verified' : 'pending';
|
|
318
|
+
await claimRef(domain).set({
|
|
319
|
+
status,
|
|
320
|
+
lastRecords: records.slice(0, 10),
|
|
321
|
+
verifiedAt: proved ? (_snapshot_get1 = snapshot.get('verifiedAt')) != null ? _snapshot_get1 : firebaseAdmin.firestore.FieldValue.serverTimestamp() : null,
|
|
322
|
+
updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
|
|
323
|
+
}, {
|
|
324
|
+
merge: true
|
|
325
|
+
});
|
|
326
|
+
const view = claimView(domain, _extends({}, snapshot.data(), {
|
|
327
|
+
status,
|
|
328
|
+
txtToken: token,
|
|
329
|
+
lastRecords: records
|
|
330
|
+
}));
|
|
331
|
+
return proved ? {
|
|
332
|
+
claim: view,
|
|
333
|
+
error: null,
|
|
334
|
+
status: 200
|
|
335
|
+
} : {
|
|
336
|
+
claim: view,
|
|
337
|
+
error: `No matching TXT record at ${view.recordHost}`,
|
|
338
|
+
status: 409
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Attach every claimed name to the console Vercel project.
|
|
343
|
+
*
|
|
344
|
+
* ⚠️ Not called from anywhere. It exists so the mechanism is complete and
|
|
345
|
+
* tested; wiring it to a route is 1099c's job and must not happen before 1099d
|
|
346
|
+
* (see the header).
|
|
347
|
+
*
|
|
348
|
+
* Refuses unless the claim is this org's **and** currently verified, and unless
|
|
349
|
+
* the org still passes `whiteLabel`. The names come from the stored claim, not
|
|
350
|
+
* from `consoleDomainNames`, so a change to the twin rule can never attach a
|
|
351
|
+
* name the transaction did not reserve.
|
|
352
|
+
*
|
|
353
|
+
* Twins are registered as per-domain **redirects to the primary**, and the
|
|
354
|
+
* redirect value is a **bare hostname** — `attachProjectDomain` enforces that,
|
|
355
|
+
* because `https://${target}` is rejected by Vercel with a message that blames
|
|
356
|
+
* the target for being absent rather than the format (AGL-1365).
|
|
357
|
+
*/ export async function activateConsoleDomain(options) {
|
|
358
|
+
var _ref, _snapshot_get, _allowlist_detail;
|
|
359
|
+
const orgId = String((_ref = options == null ? void 0 : options.orgId) != null ? _ref : '');
|
|
360
|
+
const { domain, error } = validateConsoleDomain(options == null ? void 0 : options.domain);
|
|
361
|
+
if (!domain) {
|
|
362
|
+
return {
|
|
363
|
+
claim: null,
|
|
364
|
+
error: error != null ? error : 'Invalid domain',
|
|
365
|
+
status: 400,
|
|
366
|
+
attachment: null
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
const snapshot = await claimRef(domain).get();
|
|
370
|
+
if (!snapshot.exists || snapshot.get('orgId') !== orgId) {
|
|
371
|
+
return {
|
|
372
|
+
claim: null,
|
|
373
|
+
error: 'No claim on that domain',
|
|
374
|
+
status: 404,
|
|
375
|
+
attachment: null
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (!await orgIsEntitled(orgId)) {
|
|
379
|
+
return {
|
|
380
|
+
claim: null,
|
|
381
|
+
error: 'A custom console domain requires the Agency plan',
|
|
382
|
+
status: 403,
|
|
383
|
+
attachment: null
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
const status = snapshot.get('status');
|
|
387
|
+
if (status !== 'verified' && status !== 'active') {
|
|
388
|
+
return {
|
|
389
|
+
claim: null,
|
|
390
|
+
error: 'Prove ownership of the domain before attaching it',
|
|
391
|
+
status: 409,
|
|
392
|
+
attachment: null
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
const names = Array.isArray(snapshot.get('names')) ? snapshot.get('names') : [
|
|
396
|
+
domain
|
|
397
|
+
];
|
|
398
|
+
const primary = names[0];
|
|
399
|
+
const results = [];
|
|
400
|
+
for (const [index, name] of names.entries()){
|
|
401
|
+
results.push(await attachProjectDomain(name, index === 0 ? {} : {
|
|
402
|
+
redirectTo: primary
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
const attached = results.every((result)=>result.outcome === 'attached' || result.outcome === 'already-exists');
|
|
406
|
+
const uploadCorsRemedy = pendingUploadCorsRemedy(results);
|
|
407
|
+
// AGL-1378. Vercel first, deliberately: allowlisting a name we do not
|
|
408
|
+
// actually serve would hand that origin the right to use our App Check site
|
|
409
|
+
// key for nothing in return. Only the primary — a twin is a 308 and never
|
|
410
|
+
// executes the console.
|
|
411
|
+
//
|
|
412
|
+
// If this loses, the claim stays `verified`. A domain reported ready while
|
|
413
|
+
// unlisted is the failure that costs a customer a day of "permission
|
|
414
|
+
// denied" tickets against rules that are perfectly correct.
|
|
415
|
+
const allowlist = attached ? await allowConsoleOrigin(primary) : {
|
|
416
|
+
outcome: 'failed',
|
|
417
|
+
domain: primary,
|
|
418
|
+
detail: 'Vercel did not accept every name'
|
|
419
|
+
};
|
|
420
|
+
const ready = attached && allowlistSatisfied(allowlist.outcome);
|
|
421
|
+
await claimRef(domain).set({
|
|
422
|
+
status: ready ? 'active' : 'verified',
|
|
423
|
+
vercelState: attached ? 'attached' : 'pending',
|
|
424
|
+
appCheckState: allowlist.outcome,
|
|
425
|
+
activatedAt: ready ? (_snapshot_get = snapshot.get('activatedAt')) != null ? _snapshot_get : firebaseAdmin.firestore.FieldValue.serverTimestamp() : null,
|
|
426
|
+
// Bumped on every activation so a session minted against a previous
|
|
427
|
+
// attachment of the same name is not honoured (AGL-1353 D6).
|
|
428
|
+
sessionEpoch: Date.now(),
|
|
429
|
+
updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
|
|
430
|
+
}, {
|
|
431
|
+
merge: true
|
|
432
|
+
});
|
|
433
|
+
const view = claimView(domain, _extends({}, snapshot.data(), {
|
|
434
|
+
status: ready ? 'active' : 'verified'
|
|
435
|
+
}));
|
|
436
|
+
return {
|
|
437
|
+
claim: view,
|
|
438
|
+
error: ready ? null : attached ? `The domain is attached but cannot pass App Check: ${(_allowlist_detail = allowlist.detail) != null ? _allowlist_detail : allowlist.outcome}` : 'Vercel did not accept every name',
|
|
439
|
+
status: ready ? 200 : 502,
|
|
440
|
+
attachment: {
|
|
441
|
+
domain: primary,
|
|
442
|
+
results,
|
|
443
|
+
attached,
|
|
444
|
+
ready,
|
|
445
|
+
uploadCorsRemedy,
|
|
446
|
+
allowlist
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* `sessionEpoch = now` across every name a claim covers.
|
|
452
|
+
*
|
|
453
|
+
* The revocation lever of AGL-1353 D6, factored out because D7 pulls it twice:
|
|
454
|
+
* `suspendOnDowngrade` writes it alongside the suspension, and a release writes
|
|
455
|
+
* it alone, before anything else happens. Epoch-ms via `Date.now()`, never
|
|
456
|
+
* `serverTimestamp()` — the value is compared against a cookie's `iat` and an
|
|
457
|
+
* `authorizedAt`, both plain numbers, and a sentinel that resolves later would
|
|
458
|
+
* read as `NaN` at exactly the moment it is supposed to refuse.
|
|
459
|
+
*
|
|
460
|
+
* Never throws, for `suspendOnDowngrade`'s reason: this is defence in depth
|
|
461
|
+
* behind the Vercel domain allowlist, and a caller that cannot persist the bump
|
|
462
|
+
* still has work to do that is more important than the bump.
|
|
463
|
+
*/ async function bumpConsoleSessionEpoch(names) {
|
|
464
|
+
if (!Array.isArray(names) || names.length === 0) return;
|
|
465
|
+
try {
|
|
466
|
+
const batch = firestore().batch();
|
|
467
|
+
for (const name of names){
|
|
468
|
+
batch.set(firestore().collection(CONSOLE_DOMAINS_COLLECTION).doc(name), {
|
|
469
|
+
sessionEpoch: Date.now(),
|
|
470
|
+
updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
|
|
471
|
+
}, {
|
|
472
|
+
merge: true
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
await batch.commit();
|
|
476
|
+
} catch (error) {
|
|
477
|
+
console.error('[console-domains] session epoch bump failed', error);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Kill the sessions, detach every name, then drop the claim — in that order.
|
|
482
|
+
*
|
|
483
|
+
* **The epoch bump comes first, and that is D7's own ordering**: *"`sessionEpoch
|
|
484
|
+
* = now` first, then Vercel `DELETE`, then delete the doc. Order matters: kill
|
|
485
|
+
* the sessions while we still control the host."* It was the one step of that
|
|
486
|
+
* sentence this function never performed, while
|
|
487
|
+
* {@link consoleSessionEpochRefuses} documented it as something release
|
|
488
|
+
* already did.
|
|
489
|
+
*
|
|
490
|
+
* Two things went wrong without it, and neither is covered by the Vercel
|
|
491
|
+
* detach:
|
|
492
|
+
*
|
|
493
|
+
* - **The in-flight window.** Vercel's `DELETE` and the allowlist reclaim are
|
|
494
|
+
* network round trips. Across all of them the claim still reads `active`, so
|
|
495
|
+
* `resolveConsoleDomain` says `servable` and `redeemConsoleHandoff` — which
|
|
496
|
+
* sits on `/api/*`, outside the middleware matcher — happily cashes an
|
|
497
|
+
* authorized handoff into a brand-new session on a domain we are in the
|
|
498
|
+
* middle of giving up.
|
|
499
|
+
* - **And that session then outlives the release entirely**, because the
|
|
500
|
+
* document carrying the epoch is deleted a moment later and
|
|
501
|
+
* `readConsoleSessionEpoch` reads a missing document as `0`, which every
|
|
502
|
+
* caller treats as "this control has nothing to say" and FAILS OPEN. The
|
|
503
|
+
* effect was that `suspendOnDowngrade` — the *lesser* action — revoked
|
|
504
|
+
* sessions properly while an explicit release did not revoke them at all.
|
|
505
|
+
*
|
|
506
|
+
* Bumping first inverts that: an authorization that predates the bump is
|
|
507
|
+
* refused `revoked` at redemption, so the window closes rather than merely
|
|
508
|
+
* being short. What survives the release is still only Vercel's allowlist,
|
|
509
|
+
* which is the boundary; this is the defence in depth behind it that D7 asked
|
|
510
|
+
* for and the code had stopped providing.
|
|
511
|
+
*
|
|
512
|
+
* Vercel before the documents, though, for the reason the header gives: a claim
|
|
513
|
+
* released while the name is still on the project is the unindexed-name hole,
|
|
514
|
+
* opened deliberately. If a detach fails the claim is kept and the caller is
|
|
515
|
+
* told, so the name stays reserved to the org that still holds it in Vercel —
|
|
516
|
+
* and the epoch bump is exactly the right residue on that path too, since
|
|
517
|
+
* sessions on a domain we just tried to release should not survive the attempt.
|
|
518
|
+
*/ export async function releaseConsoleDomain(options) {
|
|
519
|
+
var _ref;
|
|
520
|
+
const orgId = String((_ref = options == null ? void 0 : options.orgId) != null ? _ref : '');
|
|
521
|
+
const { domain } = validateConsoleDomain(options == null ? void 0 : options.domain);
|
|
522
|
+
if (!domain) return {
|
|
523
|
+
released: false,
|
|
524
|
+
results: [],
|
|
525
|
+
error: 'Invalid domain'
|
|
526
|
+
};
|
|
527
|
+
const snapshot = await claimRef(domain).get();
|
|
528
|
+
if (!snapshot.exists || snapshot.get('orgId') !== orgId) {
|
|
529
|
+
return {
|
|
530
|
+
released: false,
|
|
531
|
+
results: [],
|
|
532
|
+
error: 'No claim on that domain'
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
const names = Array.isArray(snapshot.get('names')) ? snapshot.get('names') : [
|
|
536
|
+
domain
|
|
537
|
+
];
|
|
538
|
+
// D7, step one. Across EVERY name, so a twin cannot outlive its primary —
|
|
539
|
+
// the same reasoning `suspendOnDowngrade` uses, and the same failure posture:
|
|
540
|
+
// never throw. A bump that does not persist still leaves the detach below to
|
|
541
|
+
// run, and refusing to release over a transient Firestore error would leave
|
|
542
|
+
// the domain both attached AND serving, which is the worse of the two.
|
|
543
|
+
await bumpConsoleSessionEpoch(names);
|
|
544
|
+
const results = [];
|
|
545
|
+
for (const name of names)results.push(await detachProjectDomain(name));
|
|
546
|
+
const clear = results.every((result)=>result.outcome === 'detached' || result.outcome === 'not-found' || result.outcome === 'skipped');
|
|
547
|
+
if (!clear) {
|
|
548
|
+
return {
|
|
549
|
+
released: false,
|
|
550
|
+
results,
|
|
551
|
+
error: 'The domain is still attached to the project'
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
// AGL-1378, the reclaim half. Same ordering rule as Vercel: give the
|
|
555
|
+
// capability back before dropping the record of it. A claim deleted while
|
|
556
|
+
// the name is still on the reCAPTCHA key leaves an origin we no longer
|
|
557
|
+
// serve holding a permanent right to mint App Check tokens against our site
|
|
558
|
+
// key, with nothing left in Firestore that says why it is there — and it
|
|
559
|
+
// silently spends one of the 250 slots forever.
|
|
560
|
+
const allowlist = await reclaimConsoleOrigin(names[0]);
|
|
561
|
+
if (!allowlistSatisfied(allowlist.outcome)) {
|
|
562
|
+
var _allowlist_detail;
|
|
563
|
+
return {
|
|
564
|
+
released: false,
|
|
565
|
+
results,
|
|
566
|
+
allowlist,
|
|
567
|
+
error: `The domain is off Vercel but still on the App Check allowlist: ${(_allowlist_detail = allowlist.detail) != null ? _allowlist_detail : allowlist.outcome}`
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
const batch = firestore().batch();
|
|
571
|
+
for (const name of names){
|
|
572
|
+
batch.delete(firestore().collection(CONSOLE_DOMAINS_COLLECTION).doc(name));
|
|
573
|
+
}
|
|
574
|
+
await batch.commit();
|
|
575
|
+
return {
|
|
576
|
+
released: true,
|
|
577
|
+
results,
|
|
578
|
+
allowlist,
|
|
579
|
+
error: null
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Drop a claim that was never attached, without touching Vercel.
|
|
584
|
+
*
|
|
585
|
+
* The counterpart to claiming from the branding form: an org that clears or
|
|
586
|
+
* changes `customConsoleDomain` must not leave its old reservation standing,
|
|
587
|
+
* or it locks another org out of a name nobody is using. Deliberately refuses
|
|
588
|
+
* once the claim has proved ownership or reached Vercel — releasing *that*
|
|
589
|
+
* needs `releaseConsoleDomain`, because a claim dropped while the name is
|
|
590
|
+
* still on the project is the unindexed-name hole in the header.
|
|
591
|
+
*/ export async function releasePendingConsoleDomain(options) {
|
|
592
|
+
var _ref;
|
|
593
|
+
const orgId = String((_ref = options == null ? void 0 : options.orgId) != null ? _ref : '');
|
|
594
|
+
const domain = normalizeConsoleDomain(options == null ? void 0 : options.domain);
|
|
595
|
+
if (!orgId || !domain) return false;
|
|
596
|
+
const snapshot = await claimRef(domain).get();
|
|
597
|
+
if (!snapshot.exists || snapshot.get('orgId') !== orgId) return false;
|
|
598
|
+
if (snapshot.get('status') !== 'pending') return false;
|
|
599
|
+
if (snapshot.get('vercelState') === 'attached') return false;
|
|
600
|
+
const names = Array.isArray(snapshot.get('names')) ? snapshot.get('names') : [
|
|
601
|
+
domain
|
|
602
|
+
];
|
|
603
|
+
const batch = firestore().batch();
|
|
604
|
+
for (const name of names){
|
|
605
|
+
batch.delete(firestore().collection(CONSOLE_DOMAINS_COLLECTION).doc(name));
|
|
606
|
+
}
|
|
607
|
+
await batch.commit();
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
const UNKNOWN_VERDICT = {
|
|
611
|
+
known: false,
|
|
612
|
+
servable: false,
|
|
613
|
+
orgSlug: null,
|
|
614
|
+
reason: 'unknown',
|
|
615
|
+
degraded: false
|
|
616
|
+
};
|
|
617
|
+
const DEGRADED_VERDICT = {
|
|
618
|
+
known: false,
|
|
619
|
+
servable: false,
|
|
620
|
+
orgSlug: null,
|
|
621
|
+
reason: 'degraded',
|
|
622
|
+
degraded: true
|
|
623
|
+
};
|
|
624
|
+
/**
|
|
625
|
+
* Stop serving a domain whose org no longer holds `whiteLabel`, and make it
|
|
626
|
+
* stick.
|
|
627
|
+
*
|
|
628
|
+
* **This is the fail-closed half of the entitlement, and it is a write on a
|
|
629
|
+
* read path on purpose.** Nothing in the codebase reacts to a plan change —
|
|
630
|
+
* the Stripe webhook writes `plan` and says outright that entitlements resolve
|
|
631
|
+
* at read time, which is sufficient for rendering and insufficient for a
|
|
632
|
+
* hostname (AGL-1353 D7). Rather than add a fan-out to the webhook or a cron
|
|
633
|
+
* that has to be believed, the first request to arrive after a downgrade
|
|
634
|
+
* converts the read-time verdict into stored state: `status: 'suspended'` plus
|
|
635
|
+
* a `sessionEpoch` bump, across **every** name the claim covers, so the twin
|
|
636
|
+
* cannot outlive its primary.
|
|
637
|
+
*
|
|
638
|
+
* The epoch is what makes this more than cosmetic. A refusal that only lives
|
|
639
|
+
* in a verdict leaves already-minted cookies valid; bumping the epoch is the
|
|
640
|
+
* lever that invalidates them at our boundary (AGL-1353 D6).
|
|
641
|
+
*
|
|
642
|
+
* Deliberately NOT the inverse: re-upgrading does not re-activate. Activation
|
|
643
|
+
* attaches names to Vercel and — until AGL-1378 resolves — depends on a manual
|
|
644
|
+
* App Check allowlist entry, so it stays an explicit act through
|
|
645
|
+
* `activateConsoleDomain`, never a side effect of a plan read.
|
|
646
|
+
*
|
|
647
|
+
* Never throws: a suspension that fails to persist still refuses this request,
|
|
648
|
+
* and the next one tries again.
|
|
649
|
+
*/ async function suspendOnDowngrade(domain, snapshot) {
|
|
650
|
+
const status = snapshot.get('status');
|
|
651
|
+
if (status === 'suspended') return;
|
|
652
|
+
const names = Array.isArray(snapshot.get('names')) ? snapshot.get('names') : [
|
|
653
|
+
domain
|
|
654
|
+
];
|
|
655
|
+
try {
|
|
656
|
+
const batch = firestore().batch();
|
|
657
|
+
for (const name of names){
|
|
658
|
+
batch.set(firestore().collection(CONSOLE_DOMAINS_COLLECTION).doc(name), {
|
|
659
|
+
status: 'suspended',
|
|
660
|
+
suspendedReason: 'entitlement',
|
|
661
|
+
sessionEpoch: Date.now(),
|
|
662
|
+
suspendedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),
|
|
663
|
+
updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
|
|
664
|
+
}, {
|
|
665
|
+
merge: true
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
await batch.commit();
|
|
669
|
+
} catch (error) {
|
|
670
|
+
console.error('[console-domains] suspend on downgrade failed', error);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Host → org, for the middleware gate and the session-route boundary.
|
|
675
|
+
*
|
|
676
|
+
* **One host pins exactly one org.** That is enforced by the document being
|
|
677
|
+
* keyed on the host and claimed transactionally (see the header), not by
|
|
678
|
+
* anything here — this function only ever reads the pin, and there is no input
|
|
679
|
+
* by which a caller can ask for a different org.
|
|
680
|
+
*
|
|
681
|
+
* The three "serve it anyway" answers, and why each is right:
|
|
682
|
+
*
|
|
683
|
+
* - **`unknown`** — no claim on this host. localhost, preview deployments and
|
|
684
|
+
* self-hosted installs all land here, and they must keep working. It is safe
|
|
685
|
+
* because of the detach ordering `releaseConsoleDomain` enforces: Vercel
|
|
686
|
+
* first, documents second, and the claim is kept if the detach fails. So a
|
|
687
|
+
* name the console project still answers for always has a document, and a
|
|
688
|
+
* name with no document cannot reach this deployment through the console
|
|
689
|
+
* project at all (AGL-1430's correspondence — do not break it).
|
|
690
|
+
* - **`degraded`** — Firestore could not be reached. Fails OPEN, matching the
|
|
691
|
+
* workspace gate and for the reason AGL-1135 recorded: the Vercel domain
|
|
692
|
+
* allowlist is the boundary, and a customer's console going dark because a
|
|
693
|
+
* lookup timed out is worse than the residual exposure. Never cached.
|
|
694
|
+
* - a claim that is `active` and entitled.
|
|
695
|
+
*
|
|
696
|
+
* Everything else refuses. In particular a `verified` claim is **not**
|
|
697
|
+
* servable: until AGL-1378 clears, activation includes a manual App Check
|
|
698
|
+
* allowlist entry, so "ownership proved" must never imply "serve it".
|
|
699
|
+
*/ export async function resolveConsoleDomain(host) {
|
|
700
|
+
var _snapshot_get, _ref;
|
|
701
|
+
const domain = normalizeConsoleDomain(host);
|
|
702
|
+
if (!domain) return UNKNOWN_VERDICT;
|
|
703
|
+
let snapshot;
|
|
704
|
+
try {
|
|
705
|
+
snapshot = await claimRef(domain).get();
|
|
706
|
+
} catch (unused) {
|
|
707
|
+
return DEGRADED_VERDICT;
|
|
708
|
+
}
|
|
709
|
+
if (!snapshot.exists) return UNKNOWN_VERDICT;
|
|
710
|
+
const orgId = String((_snapshot_get = snapshot.get('orgId')) != null ? _snapshot_get : '');
|
|
711
|
+
if (!orgId) return _extends({}, UNKNOWN_VERDICT, {
|
|
712
|
+
known: true,
|
|
713
|
+
reason: 'no-org'
|
|
714
|
+
});
|
|
715
|
+
let orgSnapshot;
|
|
716
|
+
try {
|
|
717
|
+
orgSnapshot = await firestore().collection('orgs').doc(orgId).get();
|
|
718
|
+
} catch (unused) {
|
|
719
|
+
return DEGRADED_VERDICT;
|
|
720
|
+
}
|
|
721
|
+
// A claim pointing at an org that no longer exists refuses, but is NOT
|
|
722
|
+
// suspended: `checkEntitlement(undefined)` resolves to the free plan, so
|
|
723
|
+
// writing a suspension off a missing document would be recording an
|
|
724
|
+
// entitlement verdict that was never actually read (the loading-default
|
|
725
|
+
// class this repo keeps hitting). Refuse now, decide when there is data.
|
|
726
|
+
if (!orgSnapshot.exists) {
|
|
727
|
+
return _extends({}, UNKNOWN_VERDICT, {
|
|
728
|
+
known: true,
|
|
729
|
+
reason: 'no-org'
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
const org = orgSnapshot.data();
|
|
733
|
+
const orgSlug = String((_ref = org == null ? void 0 : org.slug) != null ? _ref : '') || null;
|
|
734
|
+
if (!checkEntitlement(org, 'whiteLabel')) {
|
|
735
|
+
await suspendOnDowngrade(domain, snapshot);
|
|
736
|
+
return {
|
|
737
|
+
known: true,
|
|
738
|
+
servable: false,
|
|
739
|
+
orgSlug,
|
|
740
|
+
reason: 'not-entitled',
|
|
741
|
+
degraded: false
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
if (snapshot.get('status') !== 'active') {
|
|
745
|
+
return {
|
|
746
|
+
known: true,
|
|
747
|
+
servable: false,
|
|
748
|
+
orgSlug,
|
|
749
|
+
reason: 'not-active',
|
|
750
|
+
degraded: false
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
known: true,
|
|
755
|
+
servable: true,
|
|
756
|
+
orgSlug,
|
|
757
|
+
reason: 'active',
|
|
758
|
+
degraded: false
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* The org a console domain belongs to, or null.
|
|
763
|
+
*
|
|
764
|
+
* Read-only, and used today only to refuse a `customConsoleDomain` that another
|
|
765
|
+
* org already holds. Routing reads `resolveConsoleDomain` instead, which asks
|
|
766
|
+
* the entitlement question this one does not.
|
|
767
|
+
*/ export async function getConsoleDomainClaim(domain) {
|
|
768
|
+
var _snapshot_get, _snapshot_get1;
|
|
769
|
+
const normalized = normalizeConsoleDomain(domain);
|
|
770
|
+
if (!normalized) return null;
|
|
771
|
+
const snapshot = await claimRef(normalized).get();
|
|
772
|
+
if (!snapshot.exists) return null;
|
|
773
|
+
return {
|
|
774
|
+
orgId: String((_snapshot_get = snapshot.get('orgId')) != null ? _snapshot_get : ''),
|
|
775
|
+
status: (_snapshot_get1 = snapshot.get('status')) != null ? _snapshot_get1 : 'pending'
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
//# sourceMappingURL=console-domains.js.map
|