@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,837 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* Server-side organization operations (AGL-233/234). Everything here is
|
|
19
|
+
* Admin-SDK-only by design: org creation, slug reservation, membership
|
|
20
|
+
* and the projections the security rules authorize against are never
|
|
21
|
+
* client-writable (docs/MULTI_TENANT_FIRESTORE.md §8).
|
|
22
|
+
*/
|
|
23
|
+
import { type ConsentGroup, type AglynOrganization, type AglynOrgBilling, type AglynOrgMember, type HostAccessRole, type OrgPermission, type OrgRole } from '@aglyn/aglyn/server';
|
|
24
|
+
import type { PluginActivityTargetType } from '@aglyn/aglyn/plugin-manager/plugin-activity-actions';
|
|
25
|
+
import type { HostActivityActor } from '@aglyn/aglyn/app-utils/activity-presenter';
|
|
26
|
+
export declare class OrgSlugTakenError extends Error {
|
|
27
|
+
constructor(slug: string);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* ⛔ NOTHING WRITES A NEW `reservedUntil` ANY MORE — AND THE RULES THAT READ
|
|
31
|
+
* ONE STAY (AGL-2590).
|
|
32
|
+
*
|
|
33
|
+
* AGL-2585 gave a workspace created by an UNVERIFIED owner a held address
|
|
34
|
+
* rather than a granted one, because a signup could create a workspace before
|
|
35
|
+
* anything proved the email belonged to the person typing it. That is no
|
|
36
|
+
* longer possible: `/api/orgs/create` refuses an unverified caller outright,
|
|
37
|
+
* the sign-up form holds its typed name against the account instead, and the
|
|
38
|
+
* workspace is created on the first verified session. There is no path left
|
|
39
|
+
* that can produce an unproven address, so `createOrganization` writes the
|
|
40
|
+
* plain grant this collection has always held and the twenty-one-day
|
|
41
|
+
* reservation window is gone with the code that set it.
|
|
42
|
+
*
|
|
43
|
+
* The READ side below is deliberately kept, and it is not dead weight:
|
|
44
|
+
* production holds `orgSlugs` documents that WERE written with an expiry,
|
|
45
|
+
* before this. Deleting the lapse rules would silently promote every one of
|
|
46
|
+
* those squats to a permanent grant — the exact outcome AGL-2585 existed to
|
|
47
|
+
* end. They stay until `reap-unverified-orgs` has erased or promoted the last
|
|
48
|
+
* of them.
|
|
49
|
+
*
|
|
50
|
+
* Has a PENDING address reservation run out?
|
|
51
|
+
*
|
|
52
|
+
* A reservation with no `reservedUntil` is a GRANT and never lapses, which is
|
|
53
|
+
* what keeps every workspace made by a verified owner — and every one that
|
|
54
|
+
* predates the field — untouchable by this rule.
|
|
55
|
+
*
|
|
56
|
+
* A `reservedUntil` that is not a finite number never lapses either: a
|
|
57
|
+
* corrupt or half-written expiry is a reason to leave an address alone, not a
|
|
58
|
+
* reason to hand it to the next caller.
|
|
59
|
+
*/
|
|
60
|
+
export declare function isSlugReservationLapsed(reservation: {
|
|
61
|
+
orgId?: unknown;
|
|
62
|
+
movedTo?: unknown;
|
|
63
|
+
reservedUntil?: unknown;
|
|
64
|
+
} | undefined, now?: number): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Whether an `orgSlugs/{slug}` reservation may be (re)claimed (AGL-585):
|
|
67
|
+
* free when the doc is missing, when the claimant already owns it, or when
|
|
68
|
+
* it is a tombstone (`movedTo` set) — a renamed-away slug keeps redirecting
|
|
69
|
+
* old URLs only until someone wants it, it is never reserved forever.
|
|
70
|
+
* Claiming writes a full-replace `{ orgId }`, which ends the redirect —
|
|
71
|
+
* links to a reclaimed slug resolve to the new owner from then on.
|
|
72
|
+
*
|
|
73
|
+
* A LAPSED PENDING RESERVATION is claimable too (AGL-2585). The reservation
|
|
74
|
+
* an unverified signup takes is a hold, not a grant, and a hold that never
|
|
75
|
+
* expires is the squat this rule exists to end.
|
|
76
|
+
*
|
|
77
|
+
* ⚠️ Claimable here is NOT the whole answer for a lapsed reservation — see
|
|
78
|
+
* {@link lapsedReservationIsStillHeld}, which both call sites consult before
|
|
79
|
+
* they act on a `true` that came from the lapse branch. This function is pure
|
|
80
|
+
* and cannot ask whether the owner has verified since; treating its answer as
|
|
81
|
+
* final would let a customer who verified on day one lose their address on
|
|
82
|
+
* day twenty-one because a sweep was down.
|
|
83
|
+
*/
|
|
84
|
+
export declare function isSlugReservationClaimable(reservation: {
|
|
85
|
+
orgId?: unknown;
|
|
86
|
+
movedTo?: unknown;
|
|
87
|
+
reservedUntil?: unknown;
|
|
88
|
+
} | undefined, claimingOrgId: string | null, now?: number): boolean;
|
|
89
|
+
export interface CreateOrganizationOptions {
|
|
90
|
+
name: string;
|
|
91
|
+
slug: string;
|
|
92
|
+
ownerUid: string;
|
|
93
|
+
ownerEmail?: string | null;
|
|
94
|
+
ownerDisplayName?: string | null;
|
|
95
|
+
/**
|
|
96
|
+
* Skip the AGL-2265 free-workspace ceiling.
|
|
97
|
+
*
|
|
98
|
+
* For staff provisioning on a customer's behalf and for the migration and
|
|
99
|
+
* backfill scripts — a ceiling that stops support from fixing a workspace
|
|
100
|
+
* is a ceiling that produces the ticket it was meant to prevent. Never set
|
|
101
|
+
* from a self-serve path; `/api/orgs/create` passes the staff claim and
|
|
102
|
+
* nothing else.
|
|
103
|
+
*/
|
|
104
|
+
bypassFreeWorkspaceCap?: boolean;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Creates an org in one transaction: slug reservation (uniqueness), org
|
|
108
|
+
* doc, owner membership, and the owner's reverse-index entry. Throws
|
|
109
|
+
* `OrgSlugTakenError` when the slug is reserved; slug validity is the
|
|
110
|
+
* caller's job (API routes return 400 with policy copy).
|
|
111
|
+
*/
|
|
112
|
+
export declare function createOrganization(options: CreateOrganizationOptions): Promise<string>;
|
|
113
|
+
export interface OrgMembershipResolution {
|
|
114
|
+
orgId: string;
|
|
115
|
+
member: AglynOrgMember;
|
|
116
|
+
/**
|
|
117
|
+
* True only when THIS call provisioned the org, so a caller can report the
|
|
118
|
+
* activation (AGL-2587). `ensureOrgForUser` is the third org-creation door
|
|
119
|
+
* and the only server-side one, and it looked identical from outside to a
|
|
120
|
+
* resolution of an org that already existed — which is why `org_created`
|
|
121
|
+
* counted none of the workspaces it makes. Absent on `resolveOrgMembership`,
|
|
122
|
+
* which never creates anything.
|
|
123
|
+
*/
|
|
124
|
+
created?: boolean;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The signed-in user's membership in one org, or null. When `orgId` is
|
|
128
|
+
* omitted, resolves the user's first org from the reverse index (the
|
|
129
|
+
* single-org case every pre-org account lands in after backfill).
|
|
130
|
+
*/
|
|
131
|
+
export declare function resolveOrgMembership(uid: string, orgId?: string | null): Promise<OrgMembershipResolution | null>;
|
|
132
|
+
/**
|
|
133
|
+
* The user's org, creating a personal one on first need (signup flows and
|
|
134
|
+
* pre-backfill accounts): name from the display name or email local part,
|
|
135
|
+
* slug generated with numeric-suffix retries on collision.
|
|
136
|
+
*/
|
|
137
|
+
export declare function ensureOrgForUser(uid: string, profile?: {
|
|
138
|
+
email?: string | null;
|
|
139
|
+
displayName?: string | null;
|
|
140
|
+
}): Promise<OrgMembershipResolution>;
|
|
141
|
+
/**
|
|
142
|
+
* Changes an org's workspace slug (AGL-236): reserves the new slug and
|
|
143
|
+
* updates the org doc in one transaction, leaving the old reservation as
|
|
144
|
+
* a tombstone (`movedTo`) so existing workspace URLs keep resolving —
|
|
145
|
+
* the middleware redirects them. Reverse-index slugs fan out after.
|
|
146
|
+
* Throws `OrgSlugTakenError` only when another org ACTIVELY holds the new
|
|
147
|
+
* slug — tombstones are claimable (AGL-585). Slug validity/authorization
|
|
148
|
+
* are the API route's job.
|
|
149
|
+
*/
|
|
150
|
+
export declare function changeOrgSlug(orgId: string, newSlug: string): Promise<{
|
|
151
|
+
previousSlug: string | null;
|
|
152
|
+
}>;
|
|
153
|
+
/**
|
|
154
|
+
* Host → org resolution via the server-written `hostIndex` mirror.
|
|
155
|
+
*
|
|
156
|
+
* `React.cache`-deduped PER REQUEST (AGL-1302): one tenant render resolved
|
|
157
|
+
* this hop up to five times — org billing, datasets, plugin installs, realm
|
|
158
|
+
* installs and the publish-schedule executor each re-read the same
|
|
159
|
+
* `hostIndex/{hostId}` doc. Per-request memoization is zero-staleness by
|
|
160
|
+
* construction; outside a React render (route handlers, jest) `cache` is a
|
|
161
|
+
* pass-through, so nothing changes for the console's authz paths.
|
|
162
|
+
*/
|
|
163
|
+
export declare const resolveOrgIdForHost: (hostId: string) => Promise<string | null>;
|
|
164
|
+
/**
|
|
165
|
+
* The org doc itself — billing, plan, entitlements and suspension (the
|
|
166
|
+
* shape the legacy tenants/{uid} doc carried; orgs are the only billing
|
|
167
|
+
* source since AGL-238). Null when the doc is missing.
|
|
168
|
+
*/
|
|
169
|
+
/**
|
|
170
|
+
* `React.cache`-deduped per request like {@link resolveOrgIdForHost}
|
|
171
|
+
* (AGL-1302). NOTE: within one render every caller receives the SAME object
|
|
172
|
+
* — treat it as read-only, as every current caller already does.
|
|
173
|
+
*/
|
|
174
|
+
export declare const getOrgDoc: (orgId: string) => Promise<Partial<AglynOrganization> | null>;
|
|
175
|
+
/**
|
|
176
|
+
* Billing/entitlement source for a host (AGL-238): the owning org's doc
|
|
177
|
+
* via the hostIndex mirror. Null for unindexed hosts — callers treat that
|
|
178
|
+
* as the pre-billing fail-open (every feature on), the same contract the
|
|
179
|
+
* legacy tenants/{uid} read had.
|
|
180
|
+
*/
|
|
181
|
+
export declare function getOrgForHost(hostId: string): Promise<{
|
|
182
|
+
orgId: string;
|
|
183
|
+
org: Partial<AglynOrganization>;
|
|
184
|
+
} | null>;
|
|
185
|
+
/**
|
|
186
|
+
* The raw host doc, `React.cache`-deduped per request like
|
|
187
|
+
* {@link resolveOrgIdForHost}. Null when missing. Added for AGL-1506 so a
|
|
188
|
+
* dispatcher that already pays this read for the plugin deny-list can also
|
|
189
|
+
* feed the host's `suspendedAt` family to the lockdown verdict without a
|
|
190
|
+
* second get. Same read-only contract as {@link getOrgDoc}.
|
|
191
|
+
*/
|
|
192
|
+
export declare const getHostDocAdmin: (hostId: string) => Promise<Record<string, unknown> | null>;
|
|
193
|
+
/**
|
|
194
|
+
* The host's per-site plugin deny-list (AGL-1014), for API dispatch and any
|
|
195
|
+
* other server consumer of `resolveHostEnabledPlugins`. Rides
|
|
196
|
+
* {@link getHostDocAdmin}'s request-cached read. Fail-open to [] — an absent
|
|
197
|
+
* host doc or field means "nothing disabled here", never a lockout.
|
|
198
|
+
*/
|
|
199
|
+
export declare const getHostDisabledPlugins: (hostId: string) => Promise<string[]>;
|
|
200
|
+
/**
|
|
201
|
+
* Billing/entitlement source for a user without host context (account-
|
|
202
|
+
* level APIs): the explicit workspace org when given, else the first org
|
|
203
|
+
* from the reverse index. Null for accounts with no org yet.
|
|
204
|
+
*/
|
|
205
|
+
export declare function getOrgForUser(uid: string, orgId?: string | null): Promise<{
|
|
206
|
+
orgId: string;
|
|
207
|
+
org: Partial<AglynOrganization>;
|
|
208
|
+
member: AglynOrgMember;
|
|
209
|
+
} | null>;
|
|
210
|
+
/**
|
|
211
|
+
* Org-scoped data collection for a host (AGL-237): datasets, contacts and
|
|
212
|
+
* contactSegments live on the org so every host shares them.
|
|
213
|
+
*
|
|
214
|
+
* The pre-migration fallback to `hosts/{hostId}/{name}` is GONE (AGL-1050).
|
|
215
|
+
* The AGL-1040 backfill counted the docs still on it in production and
|
|
216
|
+
* found zero, so it was dead code rather than a migration — and a second
|
|
217
|
+
* storage path that can still be WRITTEN is a second boundary to enforce
|
|
218
|
+
* forever, which undoes the premise of scoped sharing: one home per
|
|
219
|
+
* resource plus an explicit scope.
|
|
220
|
+
*
|
|
221
|
+
* A host with no org is now an error rather than a silent write into a
|
|
222
|
+
* collection nothing reads. Every host has an org; `hostIndex` is written
|
|
223
|
+
* by `registerOrgHost` at creation.
|
|
224
|
+
*/
|
|
225
|
+
/**
|
|
226
|
+
* The org-owned collections a host reads in its own context. Every one of
|
|
227
|
+
* these carries `visibleTo` (AGL-1037) and so must go through
|
|
228
|
+
* `scopedToHost` on any Admin-SDK path — `media` and `mediaFolders` were
|
|
229
|
+
* added for the export route (AGL-1046), which had been reading the
|
|
230
|
+
* legacy host path and exporting nothing at all.
|
|
231
|
+
*/
|
|
232
|
+
export type OrgDataCollection = 'datasets' | 'contacts' | 'contactSegments' | 'crmViews' | 'lists' | 'media' | 'mediaFolders';
|
|
233
|
+
export declare function orgDataCollectionForHost(hostId: string, name: OrgDataCollection): Promise<FirebaseFirestore.CollectionReference>;
|
|
234
|
+
/**
|
|
235
|
+
* Narrows an org-scoped collection to what ONE host may see (AGL-1039).
|
|
236
|
+
*
|
|
237
|
+
* The Admin SDK does not evaluate Firestore rules, so AGL-1041's
|
|
238
|
+
* `visibleTo.hasAny(...)` protects the console and nothing else — every
|
|
239
|
+
* server read has to filter for itself or a client site can render another
|
|
240
|
+
* client's data. Use this instead of the bare collection ref anywhere a
|
|
241
|
+
* request is being served in the context of a single host.
|
|
242
|
+
*
|
|
243
|
+
* Only the ORG path is filtered — but no longer because of the legacy
|
|
244
|
+
* `hosts/{hostId}/…` fallback, which AGL-1050 removed on both the server
|
|
245
|
+
* (above) and the client. What survives it is the reason stated at the
|
|
246
|
+
* check itself: callers may hand this helper a ref they built themselves,
|
|
247
|
+
* and a host-library ref must never be filtered, since its docs carry no
|
|
248
|
+
* `visibleTo` and the filter would match nothing and blank the site.
|
|
249
|
+
*/
|
|
250
|
+
export declare function scopedToHost(ref: FirebaseFirestore.CollectionReference, hostId: string): FirebaseFirestore.Query;
|
|
251
|
+
/**
|
|
252
|
+
* `orgDataCollectionForHost` + `scopedToHost` in one call — the form every
|
|
253
|
+
* host-context read should use. Returns the collection ref too, for the
|
|
254
|
+
* writes and `doc()` lookups a Query cannot express.
|
|
255
|
+
*/
|
|
256
|
+
export declare function orgDataQueryForHost(hostId: string, name: OrgDataCollection): Promise<{
|
|
257
|
+
ref: FirebaseFirestore.CollectionReference;
|
|
258
|
+
query: FirebaseFirestore.Query;
|
|
259
|
+
}>;
|
|
260
|
+
/**
|
|
261
|
+
* Server-side permission check (AGL-243): the member's org-role defaults
|
|
262
|
+
* refined by their custom role doc (one read, only when assigned). API
|
|
263
|
+
* routes call this before privileged mutations.
|
|
264
|
+
*/
|
|
265
|
+
/**
|
|
266
|
+
* The member's FULL granular permission set, custom role and per-member
|
|
267
|
+
* overrides applied (AGL-2350).
|
|
268
|
+
*
|
|
269
|
+
* `memberHasOrgPermission` below is the single-permission form and now
|
|
270
|
+
* delegates here, so the two cannot answer differently. Split out because
|
|
271
|
+
* `resolveOrgPermissions` in `libs/tenant/runtime` needs the whole set to
|
|
272
|
+
* project onto the legacy flag map that the marketplace install and publish
|
|
273
|
+
* gates read — it previously derived those flags from the built-in role tier
|
|
274
|
+
* alone, which silently ignored both refinements.
|
|
275
|
+
*
|
|
276
|
+
* One conditional read, only when a custom role is actually assigned. A
|
|
277
|
+
* dangling `roleId` resolves to `null` and falls back to the role defaults
|
|
278
|
+
* rather than denying, matching what the console hook does with the same
|
|
279
|
+
* dangling id — a deleted role must not lock a member out of surfaces their
|
|
280
|
+
* base role allows.
|
|
281
|
+
*/
|
|
282
|
+
export declare function resolveMemberOrgPermissions(orgId: string, member: Partial<AglynOrgMember> | null | undefined): Promise<Record<OrgPermission, boolean>>;
|
|
283
|
+
export declare function memberHasOrgPermission(orgId: string, member: Partial<AglynOrgMember> | null | undefined, permission: OrgPermission): Promise<boolean>;
|
|
284
|
+
/**
|
|
285
|
+
* May this member hold a catalog permission on this site (AGL-2927,
|
|
286
|
+
* AGL-2984)?
|
|
287
|
+
*
|
|
288
|
+
* The one resolver a door calls for a key a plugin declared with host-role
|
|
289
|
+
* defaults, so the two membership axes cannot be answered differently by two
|
|
290
|
+
* routes. An org-wide member is decided by the org catalog through
|
|
291
|
+
* `resolveMemberOrgPermissions` — custom role and overrides applied, one
|
|
292
|
+
* conditional read. A site collaborator is decided by the host role they hold
|
|
293
|
+
* on the site the request NAMES, refined by the per-site toggle on their
|
|
294
|
+
* member document; a collaborator whose request names no site is refused,
|
|
295
|
+
* because there is no host role to read a default from and omitting the site
|
|
296
|
+
* must not be a way around the toggle.
|
|
297
|
+
*
|
|
298
|
+
* `hostId` is whatever the body carried, trimmed by the caller or not — an
|
|
299
|
+
* empty string and `undefined` both mean "no site named".
|
|
300
|
+
*/
|
|
301
|
+
export declare function memberHasPermissionOnHost(orgId: string, hostId: string | null | undefined, member: Partial<AglynOrgMember> | null | undefined, permission: OrgPermission): Promise<boolean>;
|
|
302
|
+
/**
|
|
303
|
+
* The 403 a door sends when `memberHasPermissionOnHost` says no: the same
|
|
304
|
+
* customer-safe shape the doors' other refusals use — one sentence, a
|
|
305
|
+
* `reason` a client can branch on — naming the permission by its catalog
|
|
306
|
+
* label so the reader can find it on the Team page, and who to ask.
|
|
307
|
+
*/
|
|
308
|
+
export declare function permissionRefusal(permission: OrgPermission): Response;
|
|
309
|
+
/**
|
|
310
|
+
* An org-wide member's verdict for every key PLUGINS declared into the
|
|
311
|
+
* catalog, read fresh (AGL-2929, AGL-2984): what `memberHasPermissionOnHost`
|
|
312
|
+
* answers for them with no site named, as a whole map. `null` for a uid with
|
|
313
|
+
* no member document and for a site collaborator, whose per-site keys are
|
|
314
|
+
* decided per site by `setHostPermissions` and have no org-level verdict to
|
|
315
|
+
* compare. Read on either side of a membership write, it is how the members
|
|
316
|
+
* route tells which declared keys the write moved.
|
|
317
|
+
*/
|
|
318
|
+
export declare function resolveMemberPluginPermissionsOnOrg(orgId: string, uid: string): Promise<Record<string, boolean> | null>;
|
|
319
|
+
/** A collaborator's per-site verdict on either side of a toggle write. */
|
|
320
|
+
export interface HostPermissionsWrite {
|
|
321
|
+
/** The verdict before the write; `null` when the member had no access to the site. */
|
|
322
|
+
before: Record<string, boolean> | null;
|
|
323
|
+
after: Record<string, boolean>;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Set a collaborator's per-site toggles (AGL-2927, AGL-2984) and re-project.
|
|
327
|
+
*
|
|
328
|
+
* A merge on the nested map, so the member's other sites and every other
|
|
329
|
+
* field stay untouched; keys no plugin declared as per-site are dropped
|
|
330
|
+
* rather than stored, and a non-boolean is ignored rather than coerced.
|
|
331
|
+
* Re-projection is scoped to the one host whose `memberPermissions` changed.
|
|
332
|
+
*
|
|
333
|
+
* The verdict before the write comes back beside the one after it, because
|
|
334
|
+
* the caller records one activity row per key that moved (AGL-2929) and a
|
|
335
|
+
* toggle set to the value it already had is not a change.
|
|
336
|
+
*/
|
|
337
|
+
export declare function setHostPermissions(options: {
|
|
338
|
+
orgId: string;
|
|
339
|
+
uid: string;
|
|
340
|
+
hostId: string;
|
|
341
|
+
permissions: Partial<Record<string, unknown>>;
|
|
342
|
+
}): Promise<HostPermissionsWrite>;
|
|
343
|
+
export declare function listOrgMembers(orgId: string): Promise<AglynOrgMember[]>;
|
|
344
|
+
/**
|
|
345
|
+
* Recomputes the denormalized authorization projections after a membership
|
|
346
|
+
* change: `memberRoles` and `memberPermissions` on every host the org owns
|
|
347
|
+
* (or one host when given), and `scopeTokens` + `resolvedPermissions` on
|
|
348
|
+
* every member doc.
|
|
349
|
+
*
|
|
350
|
+
* The rules resolve a request from these reads — the host doc for host
|
|
351
|
+
* content (docs/MULTI_TENANT_FIRESTORE.md §5), the member doc for scoped
|
|
352
|
+
* org resources (AGL-1038) — so this is what makes a membership effective.
|
|
353
|
+
* They live here, in one writer called by every mutation below, because a
|
|
354
|
+
* grant path that updates one projection and forgets another silently over-
|
|
355
|
+
* or under-grants.
|
|
356
|
+
*
|
|
357
|
+
* Everything is recomputed for the whole roster rather than the changed
|
|
358
|
+
* member: the roster is already loaded for `memberRoles`, and a full pass
|
|
359
|
+
* self-heals rows that an earlier partial failure left stale.
|
|
360
|
+
*
|
|
361
|
+
* ## `resolvedPermissions`, and why the rules need it denormalized
|
|
362
|
+
*
|
|
363
|
+
* Security rules cannot resolve a custom role. `member.roleId` points at
|
|
364
|
+
* `orgs/{orgId}/roles/{roleId}`, and reproducing the three-layer precedence
|
|
365
|
+
* (per-member beats custom role beats role default) in CEL takes a second
|
|
366
|
+
* cross-document get() plus a correct handling of a dangling id — where a
|
|
367
|
+
* naive version over-denies and locks out paying customers. So the rules read
|
|
368
|
+
* the ANSWER instead of the inputs, which is the same trade `scopeTokens`
|
|
369
|
+
* already makes for a reason the rules language shares: it has no `.map()`
|
|
370
|
+
* either.
|
|
371
|
+
*
|
|
372
|
+
* The map is `projectMemberResolvedPermissions`: `resolveOrgPermissions`'
|
|
373
|
+
* own catalog verdict, plus the plugin-declared keys a custom role or an
|
|
374
|
+
* override set explicitly (AGL-2974), so the rules and every
|
|
375
|
+
* server route are reading one resolver's verdict rather than two
|
|
376
|
+
* implementations of it.
|
|
377
|
+
*
|
|
378
|
+
* ONE READ PER DISTINCT ROLE, not per member: an org assigns a handful of
|
|
379
|
+
* custom roles across a roster that can run to hundreds, and resolving each
|
|
380
|
+
* member independently would re-read the same few documents once each.
|
|
381
|
+
*/
|
|
382
|
+
export declare function syncOrgAuthProjections(orgId: string, hostId?: string): Promise<void>;
|
|
383
|
+
/**
|
|
384
|
+
* @deprecated Renamed to `syncOrgAuthProjections` (AGL-1038) now that it
|
|
385
|
+
* also writes member `scopeTokens`. Kept as an alias for out-of-tree
|
|
386
|
+
* callers; delete once none remain.
|
|
387
|
+
*/
|
|
388
|
+
export declare const syncHostMemberRoles: typeof syncOrgAuthProjections;
|
|
389
|
+
/** What an org activity entry points at; `id` lets detail views filter. */
|
|
390
|
+
export interface OrgActivityTarget {
|
|
391
|
+
/**
|
|
392
|
+
* `host` and `subscription` are the two facts about a workspace that no
|
|
393
|
+
* host feed can hold (AGL-118).
|
|
394
|
+
*
|
|
395
|
+
* A site's own log lives at `hosts/{hostId}/activity` and is destroyed with
|
|
396
|
+
* the site — `eraseHost` recursive-deletes the whole tree — so "this site
|
|
397
|
+
* was deleted" written there is an entry with no reader by construction.
|
|
398
|
+
* A subscription belongs to no single site at all. Both are org-level
|
|
399
|
+
* events, and this is the only feed that outlives them.
|
|
400
|
+
*/
|
|
401
|
+
type: 'org' | 'member' | 'invite' | 'host' | 'subscription' | 'contact' | 'company' | 'deal' | 'lead' | 'task' | 'aiJob' | 'role' | 'screen' | 'layout' | 'component' | 'template' | 'workflow' | 'content' | 'theme' | PluginActivityTargetType;
|
|
402
|
+
id?: string;
|
|
403
|
+
name?: string;
|
|
404
|
+
/** Present on a generated screen output so the deep link can hit the exact version. */
|
|
405
|
+
versionId?: string;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Org-level counterpart to the host activity log (AGL-118): fire-and-
|
|
409
|
+
* forget append to `orgs/{orgId}/activity` from the org API routes. Never
|
|
410
|
+
* throws — an audit miss must not break the mutation that triggered it.
|
|
411
|
+
* Admin-SDK-only, like the rest of this file; the rules deny client writes.
|
|
412
|
+
*/
|
|
413
|
+
export declare function logOrgActivity(orgId: string,
|
|
414
|
+
/**
|
|
415
|
+
* `uid` is nullable because some org events HAVE no actor (AGL-118). Stripe
|
|
416
|
+
* cancels a subscription after a month of failed retries with nobody
|
|
417
|
+
* present, and the honest record of that says so. Naming the last person
|
|
418
|
+
* who touched billing instead would put a real name on an act nobody
|
|
419
|
+
* performed — and `actorId` is a filterable field, so the invented
|
|
420
|
+
* attribution would then show up under that person when somebody asks what
|
|
421
|
+
* they have done.
|
|
422
|
+
*/
|
|
423
|
+
actor: {
|
|
424
|
+
uid: string | null;
|
|
425
|
+
email?: string | null;
|
|
426
|
+
}, action: string, target: OrgActivityTarget): Promise<void>;
|
|
427
|
+
/** What a host activity entry points at. Mirrors `HostActivityTarget`. */
|
|
428
|
+
export interface HostActivityTarget {
|
|
429
|
+
type: 'host' | 'screen' | 'layout' | 'theme' | 'media' | 'content' | 'variable' | 'function' | 'workflow' | 'member' | 'component' | 'template' | 'contact' | 'company' | 'deal' | 'lead';
|
|
430
|
+
id?: string;
|
|
431
|
+
name?: string;
|
|
432
|
+
versionId?: string;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Append to `hosts/{hostId}/activity` with the ADMIN SDK (AGL-118).
|
|
436
|
+
*
|
|
437
|
+
* The host log's twin of {@link logOrgActivity}, and the beginning of the
|
|
438
|
+
* migration off the browser. Every entry in this collection has been written
|
|
439
|
+
* by the client since the log existed, which makes it an audit trail its
|
|
440
|
+
* subject can decline to write: three template surfaces created screens,
|
|
441
|
+
* layouts and components while calling no logger at all, and nothing noticed
|
|
442
|
+
* for months because a log that is missing an entry looks exactly like a
|
|
443
|
+
* person who did nothing.
|
|
444
|
+
*
|
|
445
|
+
* A route that already authenticated the caller has the two things the client
|
|
446
|
+
* cannot be trusted for — a VERIFIED uid, and the certainty that the write it
|
|
447
|
+
* is recording actually happened, because it performed it. So an entry from
|
|
448
|
+
* here is worth more than the one it replaces, not merely more reliable.
|
|
449
|
+
*
|
|
450
|
+
* Never throws, for the reason the client logger never throws: an audit miss
|
|
451
|
+
* must not turn a successful create into a failed request. It is `await`ed
|
|
452
|
+
* rather than floated because a serverless response ending cancels in-flight
|
|
453
|
+
* work, which would make the drop the common case rather than the rare one.
|
|
454
|
+
*/
|
|
455
|
+
export declare function logHostActivity(hostId: string, actor: HostActivityActor, action: string, target: HostActivityTarget): Promise<void>;
|
|
456
|
+
/**
|
|
457
|
+
* A collaborator seat refusal, raised from INSIDE the grant transaction
|
|
458
|
+
* (AGL-2068).
|
|
459
|
+
*
|
|
460
|
+
* An exception rather than a return value because it has to travel out of
|
|
461
|
+
* `upsertOrgMember` / `grantHostAccess`, whose contract is "make it so" and
|
|
462
|
+
* which four routes already call as a bare `await`. Returning a verdict would
|
|
463
|
+
* have let every existing call site ignore it silently, which is the shape of
|
|
464
|
+
* the bug being fixed.
|
|
465
|
+
*/
|
|
466
|
+
export declare class CollaboratorSeatLimitError extends Error {
|
|
467
|
+
readonly hostId: string;
|
|
468
|
+
readonly limit: number;
|
|
469
|
+
readonly upgradeRequired: boolean;
|
|
470
|
+
readonly addonPriceUsd: number | null;
|
|
471
|
+
/**
|
|
472
|
+
* Seats this site holds ABOVE `limit` (AGL-2439). Non-zero means the site
|
|
473
|
+
* is GRANDFATHERED: those collaborators keep their access, and the refusal
|
|
474
|
+
* is only of the NEXT one. Carried on the error so the refusal copy can say
|
|
475
|
+
* that rather than letting the admin read a 403 as "somebody was removed".
|
|
476
|
+
*/
|
|
477
|
+
readonly retainedOverCap: number;
|
|
478
|
+
constructor(hostId: string, quota: {
|
|
479
|
+
limit: number;
|
|
480
|
+
upgradeRequired: boolean;
|
|
481
|
+
addonPriceUsd: number | null;
|
|
482
|
+
retainedOverCap?: number;
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Turn a seat refusal into the 403 the four admitting routes return, or null
|
|
487
|
+
* when the error is something else and must keep propagating to the 500.
|
|
488
|
+
*
|
|
489
|
+
* Lives here beside `emailUnverifiedResponse` and `lockdownRefusal` so a
|
|
490
|
+
* route's catch block is one line and cannot accidentally mask a real fault.
|
|
491
|
+
*/
|
|
492
|
+
export declare function collaboratorSeatRefusalResponse(error: unknown): Response | null;
|
|
493
|
+
/**
|
|
494
|
+
* The same cap, asked BEFORE anything is written (AGL-2068).
|
|
495
|
+
*
|
|
496
|
+
* Not the enforcement — the transaction inside the grant is. This exists so
|
|
497
|
+
* the two doors that only ever create an INVITE (`/api/hosts/members` for an
|
|
498
|
+
* address with no account yet, and `/api/orgs/invites` create) refuse at the
|
|
499
|
+
* point the admin is looking at, rather than mailing someone a link that will
|
|
500
|
+
* be refused when they click it. A race here over-reserves invites; it cannot
|
|
501
|
+
* over-grant access, because access is only ever granted through the
|
|
502
|
+
* transactional path.
|
|
503
|
+
*/
|
|
504
|
+
export declare function collaboratorSeatRefusal(options: {
|
|
505
|
+
orgId: string;
|
|
506
|
+
org: Partial<AglynOrgBilling>;
|
|
507
|
+
hostIds: string[];
|
|
508
|
+
self?: {
|
|
509
|
+
uid?: string | null;
|
|
510
|
+
email?: string | null;
|
|
511
|
+
};
|
|
512
|
+
}): Promise<Response | null>;
|
|
513
|
+
/**
|
|
514
|
+
* A manager seat refused, thrown rather than returned, for the reason
|
|
515
|
+
* {@link CollaboratorSeatLimitError} is thrown: it has to travel out of
|
|
516
|
+
* `upsertOrgMember`, whose contract is "make it so" and which three routes
|
|
517
|
+
* already call as a bare `await`. A verdict would be silently discarded by
|
|
518
|
+
* every one of them, which is the shape of the bug being fixed.
|
|
519
|
+
*/
|
|
520
|
+
export declare class ManagerSeatLimitError extends Error {
|
|
521
|
+
readonly limit: number;
|
|
522
|
+
readonly upgradeRequired: boolean;
|
|
523
|
+
readonly addonPriceUsd: number | null;
|
|
524
|
+
/**
|
|
525
|
+
* Seats the org holds ABOVE `limit`. Non-zero means it is GRANDFATHERED:
|
|
526
|
+
* those managers keep their access and only the NEXT one is refused, so the
|
|
527
|
+
* console can say that instead of letting an admin read a 403 as "somebody
|
|
528
|
+
* was removed".
|
|
529
|
+
*/
|
|
530
|
+
readonly retainedOverCap: number;
|
|
531
|
+
constructor(quota: {
|
|
532
|
+
limit: number;
|
|
533
|
+
upgradeRequired: boolean;
|
|
534
|
+
addonPriceUsd: number | null;
|
|
535
|
+
retainedOverCap?: number;
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Turn a manager-seat refusal into the 403 the admitting routes return, or
|
|
540
|
+
* null when the error is something else and must keep propagating to the 500.
|
|
541
|
+
*
|
|
542
|
+
* Sits beside `collaboratorSeatRefusalResponse` and stacks with it in a
|
|
543
|
+
* route's catch block, each returning null for a non-match.
|
|
544
|
+
*/
|
|
545
|
+
export declare function managerSeatRefusalResponse(error: unknown): Response | null;
|
|
546
|
+
/**
|
|
547
|
+
* The same cap, asked BEFORE anything is written.
|
|
548
|
+
*
|
|
549
|
+
* Not the enforcement — the transaction inside `upsertOrgMember` is. This
|
|
550
|
+
* exists for the one door that never calls it: `/api/orgs/invites` create
|
|
551
|
+
* writes an invite document directly, so it refuses at the point the admin is
|
|
552
|
+
* looking at rather than mailing someone a link that will be refused when
|
|
553
|
+
* they click it. A race here over-reserves invites; it cannot over-grant
|
|
554
|
+
* access, because access is only ever granted through the transactional path.
|
|
555
|
+
*/
|
|
556
|
+
export declare function managerSeatRefusal(options: {
|
|
557
|
+
orgId: string;
|
|
558
|
+
org: Partial<AglynOrgBilling>;
|
|
559
|
+
becomesManager: boolean;
|
|
560
|
+
self?: {
|
|
561
|
+
uid?: string | null;
|
|
562
|
+
email?: string | null;
|
|
563
|
+
};
|
|
564
|
+
}): Promise<Response | null>;
|
|
565
|
+
export interface UpsertOrgMemberOptions {
|
|
566
|
+
orgId: string;
|
|
567
|
+
uid: string;
|
|
568
|
+
role: OrgRole;
|
|
569
|
+
allHosts?: boolean;
|
|
570
|
+
/** Per-site grants. `author` (AGL-2334) rides the shared union. */
|
|
571
|
+
hostAccess?: Record<string, HostAccessRole>;
|
|
572
|
+
/**
|
|
573
|
+
* Further CONFIRMED addresses on the joining account (AGL-2486), so a
|
|
574
|
+
* pending invite addressed to a secondary is recognised as this same
|
|
575
|
+
* person and does not bill them a second collaborator seat. Must contain
|
|
576
|
+
* only addresses proven to belong to `uid`.
|
|
577
|
+
*/
|
|
578
|
+
seatAliasEmails?: readonly (string | null | undefined)[] | null;
|
|
579
|
+
/** Custom role reference (AGL-243); null clears it. */
|
|
580
|
+
roleId?: string | null;
|
|
581
|
+
email?: string | null;
|
|
582
|
+
displayName?: string | null;
|
|
583
|
+
/**
|
|
584
|
+
* The member's provider photo, mirrored onto the roster (AGL-1126).
|
|
585
|
+
*
|
|
586
|
+
* Every member surface reads the roster; none of them can read Firebase
|
|
587
|
+
* Auth for an SSO member, whose record lives in a per-org tenant pool
|
|
588
|
+
* (AGL-1122). Without this the console falls back to drawn initials for
|
|
589
|
+
* everyone — fine, but it means a member who HAS a picture still never
|
|
590
|
+
* shows it. This is the ONLY source of a real face now that the Gravatar
|
|
591
|
+
* fallback is gone (AGL-1683), so keeping it populated matters more than
|
|
592
|
+
* it did. Display data only: never an identity or authorization source.
|
|
593
|
+
*/
|
|
594
|
+
photoURL?: string | null;
|
|
595
|
+
/** Job title shown on the roster/member page (AGL-364). */
|
|
596
|
+
title?: string | null;
|
|
597
|
+
invitedBy?: string | null;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* The owner seat is not writable through the membership door (AGL-1888).
|
|
601
|
+
*
|
|
602
|
+
* An exception, and modelled on {@link CollaboratorSeatLimitError}, for the
|
|
603
|
+
* same reason: it has to travel out of a function whose contract is "make it
|
|
604
|
+
* so" and which three routes call as a bare `await`. A returned verdict would
|
|
605
|
+
* be ignorable at every one of them, which is the shape of the bug.
|
|
606
|
+
*/
|
|
607
|
+
export declare class OrgOwnerSeatError extends Error {
|
|
608
|
+
/** Which invariant refused, for the log and the tests. */
|
|
609
|
+
readonly reason: 'grant' | 'demote';
|
|
610
|
+
constructor(reason: 'grant' | 'demote');
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Turn an owner-seat refusal into a 409, or null when the error is something
|
|
614
|
+
* else and must keep propagating to the 500.
|
|
615
|
+
*
|
|
616
|
+
* Beside {@link collaboratorSeatRefusalResponse} so a route's catch block
|
|
617
|
+
* stays one line and cannot accidentally mask a real fault.
|
|
618
|
+
*/
|
|
619
|
+
export declare function orgOwnerSeatRefusalResponse(error: unknown): Response | null;
|
|
620
|
+
/**
|
|
621
|
+
* Creates or updates a member transactionally with its reverse-index
|
|
622
|
+
* entry, then re-syncs host projections.
|
|
623
|
+
*
|
|
624
|
+
* ## The owner seat is refused here, not only in the routes (AGL-1888)
|
|
625
|
+
*
|
|
626
|
+
* It used to say "owner-role guards live in the API routes — this is the
|
|
627
|
+
* mechanism", and that was the defect. Both halves of the org-owner invariant
|
|
628
|
+
* were enforced only at the doors an admin clicks, and invite ACCEPTANCE is a
|
|
629
|
+
* door that re-validates neither:
|
|
630
|
+
*
|
|
631
|
+
* - **Granting.** `/api/orgs/members` and `/api/orgs/invites` create both
|
|
632
|
+
* refuse `role === 'owner'` outright, but acceptance passes the invite
|
|
633
|
+
* doc's STORED role straight through (`/api/orgs/invites` accept, and
|
|
634
|
+
* `/api/auth/sso-jit`). That is safe today only because every writer of an
|
|
635
|
+
* invite doc refuses `owner` and the collection is `allow write: if false`
|
|
636
|
+
* — a latent escalation the moment a fourth invite-writer forgets, and the
|
|
637
|
+
* invariant that an org has exactly ONE owner is what the whole SSO
|
|
638
|
+
* break-glass guarantee rests on ({@link transferOrgOwnership} MOVES the
|
|
639
|
+
* seat; nothing else may create one).
|
|
640
|
+
* - **Demoting**, which was reachable, self-serve, and irreversible. Invite
|
|
641
|
+
* creation never checked that the address is already a member, and
|
|
642
|
+
* acceptance accommodates an existing member re-accepting. So any admin
|
|
643
|
+
* could invite the OWNER'S own verified address as `viewer`; the owner
|
|
644
|
+
* clicks a normal-looking invitation to their own organization; this
|
|
645
|
+
* function merge-writes `role: 'viewer'`, `allHosts: false` onto the owner's
|
|
646
|
+
* member doc. `orgs/{orgId}.ownerUid` still names them, but every
|
|
647
|
+
* authorization read goes through the member doc — so `canManageOrg` is
|
|
648
|
+
* now false, `transfer-ownership` checks `membership.member.role ===
|
|
649
|
+
* 'owner'` and refuses them, `/api/orgs/members` refuses to edit the owner's
|
|
650
|
+
* membership at all, and `findBreakGlassOrgOwners` (`where role == owner`)
|
|
651
|
+
* finds nobody. The org loses its owner permanently, recoverable only by
|
|
652
|
+
* staff. It is the AGL-1375 one-way door rebuilt out of the invite path,
|
|
653
|
+
* and it needs no SSO to reach.
|
|
654
|
+
*
|
|
655
|
+
* Both checks live HERE because this is the single transaction every door
|
|
656
|
+
* funnels through, and the org doc and the existing member doc are already in
|
|
657
|
+
* its read set — so it costs nothing and cannot be forgotten by a fifth
|
|
658
|
+
* caller. The route-level refusals stay: they are better error messages at
|
|
659
|
+
* the point the admin is looking, not the control.
|
|
660
|
+
*
|
|
661
|
+
* The demotion guard asks BOTH `org.ownerUid` and the stored role, rather
|
|
662
|
+
* than trusting either to stand for the other. They are supposed to agree;
|
|
663
|
+
* an org where they have already diverged is exactly the one that most needs
|
|
664
|
+
* the write refused.
|
|
665
|
+
*
|
|
666
|
+
* {@link createOrganization} and {@link transferOrgOwnership} are unaffected —
|
|
667
|
+
* both write `role: 'owner'` with their own `tx.set`, and remain the only two
|
|
668
|
+
* producers of an owner in the product.
|
|
669
|
+
*/
|
|
670
|
+
export declare function upsertOrgMember(options: UpsertOrgMemberOptions): Promise<void>;
|
|
671
|
+
/**
|
|
672
|
+
* Fill in a roster row's display identity from an identity provider, writing
|
|
673
|
+
* ONLY the fields that are currently blank (AGL-1131).
|
|
674
|
+
*
|
|
675
|
+
* Separate from `upsertOrgMember` because the caller is the SSO sign-in path
|
|
676
|
+
* on its already-a-member branch, where the member's role, host access and
|
|
677
|
+
* invite state are settled and must not be touched. `upsertOrgMember`
|
|
678
|
+
* requires a `role` and re-asserts it, so reusing it here would let an SSO
|
|
679
|
+
* sign-in quietly reset an admin to the org's `sso.defaultRole`.
|
|
680
|
+
*
|
|
681
|
+
* Absent-only, so it is safe on every sign-in: it backfills the rows that
|
|
682
|
+
* predate the IdP mapping and then never writes again, and it can never
|
|
683
|
+
* overwrite a name or photo a person chose.
|
|
684
|
+
*
|
|
685
|
+
* @returns the field names it wrote, for logging and tests.
|
|
686
|
+
*/
|
|
687
|
+
export declare function backfillMemberIdentity(orgId: string, uid: string, identity: {
|
|
688
|
+
displayName?: string | null;
|
|
689
|
+
photoURL?: string | null;
|
|
690
|
+
}, db?: FirebaseFirestore.Firestore): Promise<string[]>;
|
|
691
|
+
/**
|
|
692
|
+
* The same absent-only backfill, across every roster row that names `uid`.
|
|
693
|
+
*
|
|
694
|
+
* ## The hole this closes
|
|
695
|
+
*
|
|
696
|
+
* `orgs/{orgId}/members/{uid}.photoURL` is the ONLY avatar a member surface
|
|
697
|
+
* can read — a colleague's auth record is unreadable from another member's
|
|
698
|
+
* session, and an SSO member's lives in a pool the project cannot see at all
|
|
699
|
+
* (AGL-1122). Three writers filled it, and between them they missed the
|
|
700
|
+
* commonest account there is:
|
|
701
|
+
*
|
|
702
|
+
* - `upsertOrgMember` — someone ADDED you, so the adder's lookup had a record
|
|
703
|
+
* to copy from.
|
|
704
|
+
* - `backfillMemberIdentity` via the SSO sign-in (AGL-1131) — enterprise only.
|
|
705
|
+
* - `propagateMemberPhoto` via Manage Account → Profile image (AGL-1976) — a
|
|
706
|
+
* photo the person typed or browsed to.
|
|
707
|
+
*
|
|
708
|
+
* Nobody adds the person who CREATES a workspace, `createOrganization` writes
|
|
709
|
+
* their row with a name and an email and no photo, and a Google sign-in never
|
|
710
|
+
* visits the other two. So the owner of a workspace saw their own face in the
|
|
711
|
+
* app bar, which reads the live auth record, and a grey initial in their own
|
|
712
|
+
* Team list — measured on both rows of `test-org`, each with `photoURL` absent
|
|
713
|
+
* while the auth record and `users/{uid}.photoUrl` carried the picture.
|
|
714
|
+
*
|
|
715
|
+
* ## Absent-only, like the function it fans out
|
|
716
|
+
*
|
|
717
|
+
* It runs on EVERY sign-in, so the reasoning in `backfillMemberIdentity`
|
|
718
|
+
* applies unchanged and is the reason this is a fan-out of that function
|
|
719
|
+
* rather than a second writer: an overwriting version would replace a photo
|
|
720
|
+
* the person chose in Manage Account with their provider thumbnail on their
|
|
721
|
+
* next sign-in, silently, forever. `propagateMemberPhoto` is the overwriting
|
|
722
|
+
* direction and stays the only one, because its input is a choice the person
|
|
723
|
+
* made rather than an assertion a directory made about them.
|
|
724
|
+
*
|
|
725
|
+
* Memberships come from `users/{uid}/orgs`, the reverse index — never a
|
|
726
|
+
* collection-group query over `members`, which would read every workspace's
|
|
727
|
+
* roster in the estate to find one person's rows.
|
|
728
|
+
*
|
|
729
|
+
* @returns the org ids whose row was written, for logging and tests.
|
|
730
|
+
*/
|
|
731
|
+
export declare function backfillMemberIdentityEverywhere(uid: string, identity: {
|
|
732
|
+
displayName?: string | null;
|
|
733
|
+
photoURL?: string | null;
|
|
734
|
+
}, db?: FirebaseFirestore.Firestore): Promise<string[]>;
|
|
735
|
+
/**
|
|
736
|
+
* Transfers org ownership (AGL-232): the target must already be on the
|
|
737
|
+
* roster; the previous owner steps down to admin. One transaction across
|
|
738
|
+
* the org doc, both member docs and both reverse-index entries, then the
|
|
739
|
+
* host projections re-sync.
|
|
740
|
+
*
|
|
741
|
+
* **It moves `ownerUid` and must never touch `createdByUid`** (AGL-2265).
|
|
742
|
+
* That field is the creator attribution the free-workspace ceiling counts
|
|
743
|
+
* against, and it is what stops a transfer from being a way to launder the
|
|
744
|
+
* count: hand a workspace to an alt account, create a fourth, take it back.
|
|
745
|
+
* Nothing here writes it, and `free-workspace-cap.spec.ts` runs exactly that
|
|
746
|
+
* sequence to keep it that way.
|
|
747
|
+
*/
|
|
748
|
+
export declare function transferOrgOwnership(orgId: string, fromUid: string, toUid: string): Promise<void>;
|
|
749
|
+
/**
|
|
750
|
+
* Grants (or updates) per-host access for a uid without disturbing an
|
|
751
|
+
* existing membership's org role or allHosts flag (AGL-238: the host user
|
|
752
|
+
* manager rides org membership). Creates a viewer membership scoped to
|
|
753
|
+
* just this host when the uid is not on the roster yet.
|
|
754
|
+
*/
|
|
755
|
+
export declare function grantHostAccess(options: {
|
|
756
|
+
orgId: string;
|
|
757
|
+
uid: string;
|
|
758
|
+
hostId: string;
|
|
759
|
+
/** `author` (AGL-2334) edits content and cannot publish. */
|
|
760
|
+
role: HostAccessRole;
|
|
761
|
+
email?: string | null;
|
|
762
|
+
displayName?: string | null;
|
|
763
|
+
invitedBy?: string;
|
|
764
|
+
}): Promise<void>;
|
|
765
|
+
/**
|
|
766
|
+
* Drops one host from a member's hostAccess map, then re-projects.
|
|
767
|
+
*
|
|
768
|
+
* `updateExisting`, not a merge-set (AGL-1766). A merge-set whose entire
|
|
769
|
+
* payload is a delete sentinel still CREATES the document when it is absent,
|
|
770
|
+
* and the row it minted here is not merely untidy — it is a MEMBERSHIP, and
|
|
771
|
+
* one that reads as org-wide. `isOrgWideMember` treats "no `role`, no
|
|
772
|
+
* `allHosts`, empty `hostAccess`" as the pre-`allHosts` LEGACY shape and
|
|
773
|
+
* answers true (deliberately: reading it as "scoped, with access to nothing"
|
|
774
|
+
* would lock real members out). A genuine site collaborator never looks like
|
|
775
|
+
* that — `grantHostAccess` always writes `allHosts: false` — but a document
|
|
776
|
+
* conjured from this patch alone does, exactly.
|
|
777
|
+
*
|
|
778
|
+
* So the consequences land away from here, which is what made it hard to see:
|
|
779
|
+
* `resolveOrgMembership` finds the doc and returns a membership for someone
|
|
780
|
+
* who was removed from the org; `syncOrgAuthProjections` on the next line
|
|
781
|
+
* stamps it `scopeTokens: ['org']`, the read set the rules and every
|
|
782
|
+
* Admin-SDK `memberCanSee` resolve from; and `countManagerSeats` bills it as
|
|
783
|
+
* a manager seat. (It does NOT reach `hosts/*.memberRoles`, as AGL-1763
|
|
784
|
+
* supposed — `hostRoleFor` requires an `isOrgRole(role)` and the phantom has
|
|
785
|
+
* none.)
|
|
786
|
+
*
|
|
787
|
+
* Reachable without any race: `removeOrgMember` deletes the org member doc
|
|
788
|
+
* but leaves the `hosts/{hostId}/members` roster row, which is what this is
|
|
789
|
+
* called from. Deleting that leftover row re-created the membership it was
|
|
790
|
+
* meant to finish removing. (AGL-1766's "stale double-submit" is NOT a route:
|
|
791
|
+
* the caller 404s on the missing roster row before reaching here.)
|
|
792
|
+
*
|
|
793
|
+
* DOTTED FIELD PATH, not the nested map: `update()` accepts a delete sentinel
|
|
794
|
+
* only at the top level of its patch (`@google-cloud/firestore` serializer,
|
|
795
|
+
* `allowDeletes: 'root'`), so the nested form would throw INVALID_ARGUMENT.
|
|
796
|
+
* The dotted path is top-level and clears the one key while leaving the rest
|
|
797
|
+
* of `hostAccess` alone — the same field-by-field semantics the merge had.
|
|
798
|
+
* Safe as a string path because host ids are `createResourceUid()` nanoids
|
|
799
|
+
* (`A-Za-z0-9_-`), so none can contain the `.` the SDK splits on.
|
|
800
|
+
*
|
|
801
|
+
* REFUSE, and ignore the answer: revoking a grant that is not there is a
|
|
802
|
+
* no-op and discards nothing (AGL-1760). The projections still run — they are
|
|
803
|
+
* recomputed from the roster, so a pass that finds no member doc is exactly
|
|
804
|
+
* the self-heal a stale row needs.
|
|
805
|
+
*/
|
|
806
|
+
export declare function revokeHostAccess(orgId: string, uid: string, hostId: string): Promise<void>;
|
|
807
|
+
/**
|
|
808
|
+
* Removes a member + reverse index entry, then re-syncs projections.
|
|
809
|
+
*
|
|
810
|
+
* The addresses the member added in this workspace (AGL-2975) go in the
|
|
811
|
+
* same batch. They sit beside the roster row rather than under it, so no
|
|
812
|
+
* delete of the row reaches them, and an erasure of the person runs through
|
|
813
|
+
* here once per workspace.
|
|
814
|
+
*/
|
|
815
|
+
export declare function removeOrgMember(orgId: string, uid: string): Promise<void>;
|
|
816
|
+
/**
|
|
817
|
+
* Registers a host under its org: org directory entry, hostIndex mirror,
|
|
818
|
+
* and the initial memberRoles projection on the host doc.
|
|
819
|
+
*/
|
|
820
|
+
export declare function registerOrgHost(orgId: string, hostId: string, subdomain?: string): Promise<void>;
|
|
821
|
+
/**
|
|
822
|
+
* The consent group a site belongs to, read off its owning org.
|
|
823
|
+
*
|
|
824
|
+
* The ONE server-side door to pooling. Every capture surface and every send
|
|
825
|
+
* path resolves a group through this rather than reading
|
|
826
|
+
* `CONSENT_GROUPS_FIELD` itself, so there is one place that decides what a
|
|
827
|
+
* site's consent covers and one place a mistake could live.
|
|
828
|
+
*
|
|
829
|
+
* FAILS TO THE GROUP OF ONE. An org that cannot be resolved, or a read that
|
|
830
|
+
* throws, answers "this site alone" — which withholds mail from an org that
|
|
831
|
+
* had legitimately pooled and never sends mail on a pooling nobody could
|
|
832
|
+
* confirm. That is the only direction a failure here may fall.
|
|
833
|
+
*
|
|
834
|
+
* The org read is `React.cache`-deduped per request by {@link getOrgForHost},
|
|
835
|
+
* so a send that already resolved the org for its policy pays nothing extra.
|
|
836
|
+
*/
|
|
837
|
+
export declare function consentGroupForSite(hostId: string, org?: Record<string, unknown> | null): Promise<ConsentGroup>;
|