@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,66 @@
|
|
|
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
|
+
* Bulk uid → person resolution for staff surfaces (AGL-938).
|
|
19
|
+
*
|
|
20
|
+
* A staff page must never show a raw uid where a name or email will do
|
|
21
|
+
* (AGL-810), but "resolve the uid" is a three-store question:
|
|
22
|
+
*
|
|
23
|
+
* 1. **Firebase Auth pools** — the project pool plus every GCIP tenant, via
|
|
24
|
+
* `findUserByUidAcrossPools` (AGL-1122). A project-level lookup alone
|
|
25
|
+
* would silently miss every SSO account.
|
|
26
|
+
* 2. **The org member roster**, `orgs/{orgId}/members/{uid}` — the identity
|
|
27
|
+
* source for members whose auth record cannot be reached (a deleted
|
|
28
|
+
* account whose membership lingers, or a tenant-listing outage while the
|
|
29
|
+
* pools fail soft). The roster carries `email`/`displayName` precisely so
|
|
30
|
+
* other people stay nameable without an auth read (AGL-1127).
|
|
31
|
+
* 3. **Nobody** — `system:cron` actors, erased accounts. The uid comes back
|
|
32
|
+
* marked unresolved so the caller can render it as itself, with a hint,
|
|
33
|
+
* rather than crash or spin.
|
|
34
|
+
*
|
|
35
|
+
* Fail-soft by contract: an outage in any store degrades a name back to a
|
|
36
|
+
* uid. It must never take down the page that asked.
|
|
37
|
+
*/
|
|
38
|
+
import type { PooledUserRecord } from './auth-pools';
|
|
39
|
+
/** Where a uid resolved: an auth pool, the org roster, or nowhere. */
|
|
40
|
+
export type PersonSource = 'auth' | 'roster';
|
|
41
|
+
export interface ResolvedPerson {
|
|
42
|
+
uid: string;
|
|
43
|
+
email: string | null;
|
|
44
|
+
displayName: string | null;
|
|
45
|
+
/** Null when no store answered — render the uid itself, flagged unknown. */
|
|
46
|
+
source: PersonSource | null;
|
|
47
|
+
}
|
|
48
|
+
export interface ResolveUidsOptions {
|
|
49
|
+
/**
|
|
50
|
+
* Org whose member roster backs up the auth pools. Without it, uids that
|
|
51
|
+
* miss every pool simply come back unresolved.
|
|
52
|
+
*/
|
|
53
|
+
orgId?: string;
|
|
54
|
+
/** Injectable for tests; defaults to the admin app's Firestore. */
|
|
55
|
+
firestore?: any;
|
|
56
|
+
/** Injectable for tests; defaults to `findUserByUidAcrossPools`. */
|
|
57
|
+
findUser?: (uid: string) => Promise<PooledUserRecord | null>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resolve a batch of uids to people, deduplicated, keyed by uid. Every
|
|
61
|
+
* requested uid is present in the result — unresolvable ones as
|
|
62
|
+
* `{ email: null, displayName: null, source: null }` — so callers can index
|
|
63
|
+
* without existence checks.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveUidsToPeople(uids: ReadonlyArray<string | null | undefined>, options?: ResolveUidsOptions): Promise<Record<string, ResolvedPerson>>;
|
|
66
|
+
export default resolveUidsToPeople;
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
* Bulk uid → person resolution for staff surfaces (AGL-938).
|
|
18
|
+
*
|
|
19
|
+
* A staff page must never show a raw uid where a name or email will do
|
|
20
|
+
* (AGL-810), but "resolve the uid" is a three-store question:
|
|
21
|
+
*
|
|
22
|
+
* 1. **Firebase Auth pools** — the project pool plus every GCIP tenant, via
|
|
23
|
+
* `findUserByUidAcrossPools` (AGL-1122). A project-level lookup alone
|
|
24
|
+
* would silently miss every SSO account.
|
|
25
|
+
* 2. **The org member roster**, `orgs/{orgId}/members/{uid}` — the identity
|
|
26
|
+
* source for members whose auth record cannot be reached (a deleted
|
|
27
|
+
* account whose membership lingers, or a tenant-listing outage while the
|
|
28
|
+
* pools fail soft). The roster carries `email`/`displayName` precisely so
|
|
29
|
+
* other people stay nameable without an auth read (AGL-1127).
|
|
30
|
+
* 3. **Nobody** — `system:cron` actors, erased accounts. The uid comes back
|
|
31
|
+
* marked unresolved so the caller can render it as itself, with a hint,
|
|
32
|
+
* rather than crash or spin.
|
|
33
|
+
*
|
|
34
|
+
* Fail-soft by contract: an outage in any store degrades a name back to a
|
|
35
|
+
* uid. It must never take down the page that asked.
|
|
36
|
+
*/ import { findUserByUidAcrossPools } from "./auth-pools.js";
|
|
37
|
+
import firebaseAdmin from "./firebase-admin.js";
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a batch of uids to people, deduplicated, keyed by uid. Every
|
|
40
|
+
* requested uid is present in the result — unresolvable ones as
|
|
41
|
+
* `{ email: null, displayName: null, source: null }` — so callers can index
|
|
42
|
+
* without existence checks.
|
|
43
|
+
*/ export async function resolveUidsToPeople(uids, options = {}) {
|
|
44
|
+
var _options_findUser;
|
|
45
|
+
const find = (_options_findUser = options.findUser) != null ? _options_findUser : findUserByUidAcrossPools;
|
|
46
|
+
const unique = [
|
|
47
|
+
...new Set(uids.filter((uid)=>typeof uid === 'string' && !!uid))
|
|
48
|
+
];
|
|
49
|
+
const people = {};
|
|
50
|
+
const missed = [];
|
|
51
|
+
await Promise.all(unique.map(async (uid)=>{
|
|
52
|
+
try {
|
|
53
|
+
const found = await find(uid);
|
|
54
|
+
if (found) {
|
|
55
|
+
var _found_record_email, _found_record_displayName;
|
|
56
|
+
people[uid] = {
|
|
57
|
+
uid,
|
|
58
|
+
email: (_found_record_email = found.record.email) != null ? _found_record_email : null,
|
|
59
|
+
displayName: (_found_record_displayName = found.record.displayName) != null ? _found_record_displayName : null,
|
|
60
|
+
source: 'auth'
|
|
61
|
+
};
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
} catch (unused) {
|
|
65
|
+
// An auth outage is a miss, not a failure — the roster below may
|
|
66
|
+
// still name this person.
|
|
67
|
+
}
|
|
68
|
+
missed.push(uid);
|
|
69
|
+
}));
|
|
70
|
+
if (missed.length && options.orgId) {
|
|
71
|
+
try {
|
|
72
|
+
var _options_firestore;
|
|
73
|
+
const db = (_options_firestore = options.firestore) != null ? _options_firestore : firebaseAdmin.app().firestore();
|
|
74
|
+
const members = db.collection('orgs').doc(options.orgId).collection('members');
|
|
75
|
+
const snapshots = await db.getAll(...missed.map((uid)=>members.doc(uid)));
|
|
76
|
+
snapshots.forEach((snapshot, index)=>{
|
|
77
|
+
if (!snapshot.exists) return;
|
|
78
|
+
const email = snapshot.get('email');
|
|
79
|
+
const displayName = snapshot.get('displayName');
|
|
80
|
+
// A roster doc with neither field names nobody — that uid stays
|
|
81
|
+
// unresolved rather than resolving to an empty person.
|
|
82
|
+
if (typeof email !== 'string' && typeof displayName !== 'string') {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
people[missed[index]] = {
|
|
86
|
+
uid: missed[index],
|
|
87
|
+
email: typeof email === 'string' && email ? email : null,
|
|
88
|
+
displayName: typeof displayName === 'string' && displayName ? displayName : null,
|
|
89
|
+
source: 'roster'
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
// Roster unreachable: those uids render as themselves. Logged, not
|
|
94
|
+
// thrown — a labelling nicety must not 500 the page that asked.
|
|
95
|
+
console.error('roster fallback failed while resolving uids', error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const uid of unique){
|
|
99
|
+
if (!people[uid]) {
|
|
100
|
+
people[uid] = {
|
|
101
|
+
uid,
|
|
102
|
+
email: null,
|
|
103
|
+
displayName: null,
|
|
104
|
+
source: null
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return people;
|
|
109
|
+
}
|
|
110
|
+
export default resolveUidsToPeople;
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=resolve-people.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/resolve-people.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Bulk uid → person resolution for staff surfaces (AGL-938).\n *\n * A staff page must never show a raw uid where a name or email will do\n * (AGL-810), but \"resolve the uid\" is a three-store question:\n *\n * 1. **Firebase Auth pools** — the project pool plus every GCIP tenant, via\n * `findUserByUidAcrossPools` (AGL-1122). A project-level lookup alone\n * would silently miss every SSO account.\n * 2. **The org member roster**, `orgs/{orgId}/members/{uid}` — the identity\n * source for members whose auth record cannot be reached (a deleted\n * account whose membership lingers, or a tenant-listing outage while the\n * pools fail soft). The roster carries `email`/`displayName` precisely so\n * other people stay nameable without an auth read (AGL-1127).\n * 3. **Nobody** — `system:cron` actors, erased accounts. The uid comes back\n * marked unresolved so the caller can render it as itself, with a hint,\n * rather than crash or spin.\n *\n * Fail-soft by contract: an outage in any store degrades a name back to a\n * uid. It must never take down the page that asked.\n */\n\nimport type { PooledUserRecord } from './auth-pools'\nimport { findUserByUidAcrossPools } from './auth-pools'\nimport firebaseAdmin from './firebase-admin'\n\n/** Where a uid resolved: an auth pool, the org roster, or nowhere. */\nexport type PersonSource = 'auth' | 'roster'\n\nexport interface ResolvedPerson {\n uid: string\n email: string | null\n displayName: string | null\n /** Null when no store answered — render the uid itself, flagged unknown. */\n source: PersonSource | null\n}\n\nexport interface ResolveUidsOptions {\n /**\n * Org whose member roster backs up the auth pools. Without it, uids that\n * miss every pool simply come back unresolved.\n */\n orgId?: string\n /** Injectable for tests; defaults to the admin app's Firestore. */\n firestore?: any\n /** Injectable for tests; defaults to `findUserByUidAcrossPools`. */\n findUser?: (uid: string) => Promise<PooledUserRecord | null>\n}\n\n/**\n * Resolve a batch of uids to people, deduplicated, keyed by uid. Every\n * requested uid is present in the result — unresolvable ones as\n * `{ email: null, displayName: null, source: null }` — so callers can index\n * without existence checks.\n */\nexport async function resolveUidsToPeople(\n uids: ReadonlyArray<string | null | undefined>,\n options: ResolveUidsOptions = {},\n): Promise<Record<string, ResolvedPerson>> {\n const find = options.findUser ?? findUserByUidAcrossPools\n const unique = [\n ...new Set(\n uids.filter((uid): uid is string => typeof uid === 'string' && !!uid),\n ),\n ]\n const people: Record<string, ResolvedPerson> = {}\n const missed: string[] = []\n\n await Promise.all(\n unique.map(async (uid) => {\n try {\n const found = await find(uid)\n if (found) {\n people[uid] = {\n uid,\n email: found.record.email ?? null,\n displayName: found.record.displayName ?? null,\n source: 'auth',\n }\n return\n }\n } catch {\n // An auth outage is a miss, not a failure — the roster below may\n // still name this person.\n }\n missed.push(uid)\n }),\n )\n\n if (missed.length && options.orgId) {\n try {\n const db = options.firestore ?? firebaseAdmin.app().firestore()\n const members = db\n .collection('orgs')\n .doc(options.orgId)\n .collection('members')\n const snapshots = await db.getAll(\n ...missed.map((uid: string) => members.doc(uid)),\n )\n snapshots.forEach((snapshot: any, index: number) => {\n if (!snapshot.exists) return\n const email = snapshot.get('email')\n const displayName = snapshot.get('displayName')\n // A roster doc with neither field names nobody — that uid stays\n // unresolved rather than resolving to an empty person.\n if (typeof email !== 'string' && typeof displayName !== 'string') {\n return\n }\n people[missed[index]] = {\n uid: missed[index],\n email: typeof email === 'string' && email ? email : null,\n displayName:\n typeof displayName === 'string' && displayName\n ? displayName\n : null,\n source: 'roster',\n }\n })\n } catch (error) {\n // Roster unreachable: those uids render as themselves. Logged, not\n // thrown — a labelling nicety must not 500 the page that asked.\n console.error('roster fallback failed while resolving uids', error)\n }\n }\n\n for (const uid of unique) {\n if (!people[uid]) {\n people[uid] = { uid, email: null, displayName: null, source: null }\n }\n }\n return people\n}\n\nexport default resolveUidsToPeople\n"],"names":["findUserByUidAcrossPools","firebaseAdmin","resolveUidsToPeople","uids","options","find","findUser","unique","Set","filter","uid","people","missed","Promise","all","map","found","email","record","displayName","source","push","length","orgId","db","firestore","app","members","collection","doc","snapshots","getAll","forEach","snapshot","index","exists","get","error","console"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;CAoBC,GAGD,SAASA,wBAAwB,QAAQ,kBAAc;AACvD,OAAOC,mBAAmB,sBAAkB;AAyB5C;;;;;CAKC,GACD,OAAO,eAAeC,oBACpBC,IAA8C,EAC9CC,UAA8B,CAAC,CAAC;QAEnBA;IAAb,MAAMC,QAAOD,oBAAAA,QAAQE,QAAQ,YAAhBF,oBAAoBJ;IACjC,MAAMO,SAAS;WACV,IAAIC,IACLL,KAAKM,MAAM,CAAC,CAACC,MAAuB,OAAOA,QAAQ,YAAY,CAAC,CAACA;KAEpE;IACD,MAAMC,SAAyC,CAAC;IAChD,MAAMC,SAAmB,EAAE;IAE3B,MAAMC,QAAQC,GAAG,CACfP,OAAOQ,GAAG,CAAC,OAAOL;QAChB,IAAI;YACF,MAAMM,QAAQ,MAAMX,KAAKK;YACzB,IAAIM,OAAO;oBAGAA,qBACMA;gBAHfL,MAAM,CAACD,IAAI,GAAG;oBACZA;oBACAO,KAAK,GAAED,sBAAAA,MAAME,MAAM,CAACD,KAAK,YAAlBD,sBAAsB;oBAC7BG,WAAW,GAAEH,4BAAAA,MAAME,MAAM,CAACC,WAAW,YAAxBH,4BAA4B;oBACzCI,QAAQ;gBACV;gBACA;YACF;QACF,EAAE,eAAM;QACN,iEAAiE;QACjE,0BAA0B;QAC5B;QACAR,OAAOS,IAAI,CAACX;IACd;IAGF,IAAIE,OAAOU,MAAM,IAAIlB,QAAQmB,KAAK,EAAE;QAClC,IAAI;gBACSnB;YAAX,MAAMoB,MAAKpB,qBAAAA,QAAQqB,SAAS,YAAjBrB,qBAAqBH,cAAcyB,GAAG,GAAGD,SAAS;YAC7D,MAAME,UAAUH,GACbI,UAAU,CAAC,QACXC,GAAG,CAACzB,QAAQmB,KAAK,EACjBK,UAAU,CAAC;YACd,MAAME,YAAY,MAAMN,GAAGO,MAAM,IAC5BnB,OAAOG,GAAG,CAAC,CAACL,MAAgBiB,QAAQE,GAAG,CAACnB;YAE7CoB,UAAUE,OAAO,CAAC,CAACC,UAAeC;gBAChC,IAAI,CAACD,SAASE,MAAM,EAAE;gBACtB,MAAMlB,QAAQgB,SAASG,GAAG,CAAC;gBAC3B,MAAMjB,cAAcc,SAASG,GAAG,CAAC;gBACjC,gEAAgE;gBAChE,uDAAuD;gBACvD,IAAI,OAAOnB,UAAU,YAAY,OAAOE,gBAAgB,UAAU;oBAChE;gBACF;gBACAR,MAAM,CAACC,MAAM,CAACsB,MAAM,CAAC,GAAG;oBACtBxB,KAAKE,MAAM,CAACsB,MAAM;oBAClBjB,OAAO,OAAOA,UAAU,YAAYA,QAAQA,QAAQ;oBACpDE,aACE,OAAOA,gBAAgB,YAAYA,cAC/BA,cACA;oBACNC,QAAQ;gBACV;YACF;QACF,EAAE,OAAOiB,OAAO;YACd,mEAAmE;YACnE,gEAAgE;YAChEC,QAAQD,KAAK,CAAC,+CAA+CA;QAC/D;IACF;IAEA,KAAK,MAAM3B,OAAOH,OAAQ;QACxB,IAAI,CAACI,MAAM,CAACD,IAAI,EAAE;YAChBC,MAAM,CAACD,IAAI,GAAG;gBAAEA;gBAAKO,OAAO;gBAAME,aAAa;gBAAMC,QAAQ;YAAK;QACpE;IACF;IACA,OAAOT;AACT;AAEA,eAAeT,oBAAmB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
* Constant-time string comparison that tolerates a length mismatch
|
|
19
|
+
* (AGL-1353 D8, extraction 2).
|
|
20
|
+
*
|
|
21
|
+
* The length-check-then-`timingSafeEqual` dance was hand-written in six files:
|
|
22
|
+
* `media-signing.ts`, `edit-access-token.ts`, commerce `download.ts`,
|
|
23
|
+
* `membership.ts`, `workflows/server.ts` and `apps/console/utils/cron-auth.ts`.
|
|
24
|
+
* Every one of them carries its own note explaining the same hazard, which is
|
|
25
|
+
* the shape of a helper that has not been written yet.
|
|
26
|
+
*
|
|
27
|
+
* **The length check is the load-bearing half.** `timingSafeEqual` THROWS on
|
|
28
|
+
* buffers of different lengths, and in every one of those call sites the
|
|
29
|
+
* length is attacker-controlled — a presented signature, a token segment, a
|
|
30
|
+
* header. Forgetting it turns a `401`/`403` into an unhandled `500`, which is
|
|
31
|
+
* both an outage and an oracle: a thrown request and a refused one are
|
|
32
|
+
* distinguishable from outside.
|
|
33
|
+
*
|
|
34
|
+
* **What this does and does not hide.** The comparison itself is constant-time
|
|
35
|
+
* for equal-length inputs. The length is compared first and in variable time,
|
|
36
|
+
* so this leaks whether the candidate was the right LENGTH and nothing else —
|
|
37
|
+
* the same property every hand-written copy had, and an acceptable one when
|
|
38
|
+
* the expected length is a public constant (a hex digest, a base64url secret)
|
|
39
|
+
* rather than a secret.
|
|
40
|
+
*
|
|
41
|
+
* Byte length, not code points: both sides are encoded as UTF-8 before
|
|
42
|
+
* comparison, so a multi-byte candidate cannot slip past the length check and
|
|
43
|
+
* reach the throwing call.
|
|
44
|
+
*/
|
|
45
|
+
export declare function safeEqual(presented: string | null | undefined, expected: string | null | undefined): boolean;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { timingSafeEqual } from "crypto";
|
|
17
|
+
/**
|
|
18
|
+
* Constant-time string comparison that tolerates a length mismatch
|
|
19
|
+
* (AGL-1353 D8, extraction 2).
|
|
20
|
+
*
|
|
21
|
+
* The length-check-then-`timingSafeEqual` dance was hand-written in six files:
|
|
22
|
+
* `media-signing.ts`, `edit-access-token.ts`, commerce `download.ts`,
|
|
23
|
+
* `membership.ts`, `workflows/server.ts` and `apps/console/utils/cron-auth.ts`.
|
|
24
|
+
* Every one of them carries its own note explaining the same hazard, which is
|
|
25
|
+
* the shape of a helper that has not been written yet.
|
|
26
|
+
*
|
|
27
|
+
* **The length check is the load-bearing half.** `timingSafeEqual` THROWS on
|
|
28
|
+
* buffers of different lengths, and in every one of those call sites the
|
|
29
|
+
* length is attacker-controlled — a presented signature, a token segment, a
|
|
30
|
+
* header. Forgetting it turns a `401`/`403` into an unhandled `500`, which is
|
|
31
|
+
* both an outage and an oracle: a thrown request and a refused one are
|
|
32
|
+
* distinguishable from outside.
|
|
33
|
+
*
|
|
34
|
+
* **What this does and does not hide.** The comparison itself is constant-time
|
|
35
|
+
* for equal-length inputs. The length is compared first and in variable time,
|
|
36
|
+
* so this leaks whether the candidate was the right LENGTH and nothing else —
|
|
37
|
+
* the same property every hand-written copy had, and an acceptable one when
|
|
38
|
+
* the expected length is a public constant (a hex digest, a base64url secret)
|
|
39
|
+
* rather than a secret.
|
|
40
|
+
*
|
|
41
|
+
* Byte length, not code points: both sides are encoded as UTF-8 before
|
|
42
|
+
* comparison, so a multi-byte candidate cannot slip past the length check and
|
|
43
|
+
* reach the throwing call.
|
|
44
|
+
*/ export function safeEqual(presented, expected) {
|
|
45
|
+
if (typeof presented !== 'string' || typeof expected !== 'string') {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const a = Buffer.from(presented, 'utf8');
|
|
49
|
+
const b = Buffer.from(expected, 'utf8');
|
|
50
|
+
// Both empty is a match only in the vacuous sense, and every caller here is
|
|
51
|
+
// comparing against a value that must exist. An empty expectation means a
|
|
52
|
+
// secret was not configured, so refusing is the fail-closed answer.
|
|
53
|
+
if (a.length === 0 || b.length === 0) return false;
|
|
54
|
+
if (a.length !== b.length) return false;
|
|
55
|
+
return timingSafeEqual(new Uint8Array(a), new Uint8Array(b));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=safe-equal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/safe-equal.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { timingSafeEqual } from 'crypto'\n\n/**\n * Constant-time string comparison that tolerates a length mismatch\n * (AGL-1353 D8, extraction 2).\n *\n * The length-check-then-`timingSafeEqual` dance was hand-written in six files:\n * `media-signing.ts`, `edit-access-token.ts`, commerce `download.ts`,\n * `membership.ts`, `workflows/server.ts` and `apps/console/utils/cron-auth.ts`.\n * Every one of them carries its own note explaining the same hazard, which is\n * the shape of a helper that has not been written yet.\n *\n * **The length check is the load-bearing half.** `timingSafeEqual` THROWS on\n * buffers of different lengths, and in every one of those call sites the\n * length is attacker-controlled — a presented signature, a token segment, a\n * header. Forgetting it turns a `401`/`403` into an unhandled `500`, which is\n * both an outage and an oracle: a thrown request and a refused one are\n * distinguishable from outside.\n *\n * **What this does and does not hide.** The comparison itself is constant-time\n * for equal-length inputs. The length is compared first and in variable time,\n * so this leaks whether the candidate was the right LENGTH and nothing else —\n * the same property every hand-written copy had, and an acceptable one when\n * the expected length is a public constant (a hex digest, a base64url secret)\n * rather than a secret.\n *\n * Byte length, not code points: both sides are encoded as UTF-8 before\n * comparison, so a multi-byte candidate cannot slip past the length check and\n * reach the throwing call.\n */\nexport function safeEqual(\n presented: string | null | undefined,\n expected: string | null | undefined,\n): boolean {\n if (typeof presented !== 'string' || typeof expected !== 'string') {\n return false\n }\n const a = Buffer.from(presented, 'utf8')\n const b = Buffer.from(expected, 'utf8')\n // Both empty is a match only in the vacuous sense, and every caller here is\n // comparing against a value that must exist. An empty expectation means a\n // secret was not configured, so refusing is the fail-closed answer.\n if (a.length === 0 || b.length === 0) return false\n if (a.length !== b.length) return false\n return timingSafeEqual(new Uint8Array(a), new Uint8Array(b))\n}\n"],"names":["timingSafeEqual","safeEqual","presented","expected","a","Buffer","from","b","length","Uint8Array"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,eAAe,QAAQ,SAAQ;AAExC;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2BC,GACD,OAAO,SAASC,UACdC,SAAoC,EACpCC,QAAmC;IAEnC,IAAI,OAAOD,cAAc,YAAY,OAAOC,aAAa,UAAU;QACjE,OAAO;IACT;IACA,MAAMC,IAAIC,OAAOC,IAAI,CAACJ,WAAW;IACjC,MAAMK,IAAIF,OAAOC,IAAI,CAACH,UAAU;IAChC,4EAA4E;IAC5E,0EAA0E;IAC1E,oEAAoE;IACpE,IAAIC,EAAEI,MAAM,KAAK,KAAKD,EAAEC,MAAM,KAAK,GAAG,OAAO;IAC7C,IAAIJ,EAAEI,MAAM,KAAKD,EAAEC,MAAM,EAAE,OAAO;IAClC,OAAOR,gBAAgB,IAAIS,WAAWL,IAAI,IAAIK,WAAWF;AAC3D"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type HostSendingDomainTeardown } from './host-sending-domain';
|
|
18
|
+
/**
|
|
19
|
+
* The console-side teardown, injected rather than imported.
|
|
20
|
+
*
|
|
21
|
+
* Releasing the provider's domain object needs a full-access mail credential
|
|
22
|
+
* and removing the zone records needs a DNS token, and neither may be
|
|
23
|
+
* reachable from the tenant runtime — which is what makes them live in
|
|
24
|
+
* `apps/console` and makes this library structurally unable to import them
|
|
25
|
+
* (`sending-domain-credential-isolation.spec.ts` sweeps the tree for it).
|
|
26
|
+
*
|
|
27
|
+
* A caller that HAS those credentials passes the driver in and the domain is
|
|
28
|
+
* released inline. A caller that does not — the tenant runtime, an operator
|
|
29
|
+
* script, a self-host with no provider configured — records the debt and the
|
|
30
|
+
* console's reaper settles it. Both are correct; only the latency differs.
|
|
31
|
+
*/
|
|
32
|
+
export type TeardownSendingDomainDriver = (teardown: HostSendingDomainTeardown) => Promise<{
|
|
33
|
+
outcome: 'removed' | 'skipped' | 'failed';
|
|
34
|
+
detail: string | null;
|
|
35
|
+
}>;
|
|
36
|
+
/** What became of one site's dedicated sending domain. */
|
|
37
|
+
export type SendingDomainDisposition =
|
|
38
|
+
/** The site had no dedicated domain of its own. Nothing to release. */
|
|
39
|
+
'none'
|
|
40
|
+
/** Provider slot freed, zone records removed, our records dropped. */
|
|
41
|
+
| 'released'
|
|
42
|
+
/** The debt is recorded on the label claim; the reaper owes it a visit. */
|
|
43
|
+
| 'deferred'
|
|
44
|
+
/** A shared pool member. Never torn down, by anything, ever. */
|
|
45
|
+
| 'protected';
|
|
46
|
+
export interface SendingDomainDebtRecord {
|
|
47
|
+
/** The claimed label, and the id of the claim document. */
|
|
48
|
+
label: string;
|
|
49
|
+
hostId: string | null;
|
|
50
|
+
orgId: string | null;
|
|
51
|
+
domain: string | null;
|
|
52
|
+
claimedAtMs: number | null;
|
|
53
|
+
/** Set when a teardown could not be completed. Null on a healthy claim. */
|
|
54
|
+
orphanedAtMs: number | null;
|
|
55
|
+
/** A short code naming what refused, safe to store, log and show. */
|
|
56
|
+
teardownDetail: string | null;
|
|
57
|
+
/** How many passes have tried and failed to settle this. */
|
|
58
|
+
teardownAttempts: number;
|
|
59
|
+
providerDomainId: string | null;
|
|
60
|
+
dkimSelector: string | null;
|
|
61
|
+
}
|
|
62
|
+
/** One claim document as a debt record. */
|
|
63
|
+
export declare function readSendingDomainDebt(snapshot: FirebaseFirestore.DocumentSnapshot): SendingDomainDebtRecord | null;
|
|
64
|
+
/**
|
|
65
|
+
* Everything needed to release one PINNED label's domain, read by label.
|
|
66
|
+
*
|
|
67
|
+
* Keyed on the label and never on `hosts/{hostId}.sendingDomain`, and the
|
|
68
|
+
* difference is not cosmetic. That field is the site's CURRENT SELECTION and a
|
|
69
|
+
* site may point it at the org's own verified domain through the sending
|
|
70
|
+
* identity surface — a name this deployment did not provision, holds no
|
|
71
|
+
* provider slot for, and whose record belongs to the ORG and is shared with
|
|
72
|
+
* every other site of theirs. A teardown that read the selection would release
|
|
73
|
+
* an agency's verified domain because one of its sites was deleted.
|
|
74
|
+
*
|
|
75
|
+
* The label is the thing this deployment actually created, it is written once
|
|
76
|
+
* and never rewritten, and {@link platformSendingDomainFor} re-derives the
|
|
77
|
+
* domain from it under the current apex and the current reserved set.
|
|
78
|
+
*
|
|
79
|
+
* Works for a host that no longer exists: the claim carries the org, and the
|
|
80
|
+
* provider id and selector are copied onto the claim before an erasure
|
|
81
|
+
* destroys the record they came from.
|
|
82
|
+
*/
|
|
83
|
+
export declare function readSendingDomainTeardownByLabel(label: string | null | undefined): Promise<HostSendingDomainTeardown | null>;
|
|
84
|
+
/**
|
|
85
|
+
* Copy the vendor handles onto the claim and mark the owner gone.
|
|
86
|
+
*
|
|
87
|
+
* Written with `merge` onto a document that already exists, so it adds the
|
|
88
|
+
* debt to the claim rather than replacing it — the `hostId` the claim carries
|
|
89
|
+
* is what `releaseHostSendingDomain` matches on before deleting it, and losing
|
|
90
|
+
* that would strand the claim permanently.
|
|
91
|
+
*
|
|
92
|
+
* `teardownAttempts` increments so a debt that cannot be settled — a provider
|
|
93
|
+
* that has forgotten the domain, a token that was never restored — is visible
|
|
94
|
+
* as a number climbing rather than as a row that looks the same every day.
|
|
95
|
+
*/
|
|
96
|
+
export declare function recordSendingDomainDebt(teardown: HostSendingDomainTeardown, detail: string | null): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Release one site's dedicated sending domain, or record what is still owed.
|
|
99
|
+
*
|
|
100
|
+
* The single entry point for every path that destroys a site. It never throws
|
|
101
|
+
* and it never blocks its caller on a vendor: the worst outcome is a debt on
|
|
102
|
+
* the label claim and a loud line naming the domain.
|
|
103
|
+
*
|
|
104
|
+
* Ordered provider → zone → our records by the driver it calls, for the reason
|
|
105
|
+
* stated there: the provider slot is the scarce resource, and our own record
|
|
106
|
+
* is what keeps a half-finished teardown findable.
|
|
107
|
+
*/
|
|
108
|
+
export declare function disposeHostSendingDomain(options: {
|
|
109
|
+
/** The site being destroyed. Its `sendingLabel` is read before it goes. */
|
|
110
|
+
hostId?: string | null;
|
|
111
|
+
/** Pre-read teardown, for a caller that has already lost the host document. */
|
|
112
|
+
teardown?: HostSendingDomainTeardown | null;
|
|
113
|
+
tearDown?: TeardownSendingDomainDriver | null;
|
|
114
|
+
}): Promise<SendingDomainDisposition>;
|
|
115
|
+
/**
|
|
116
|
+
* Every label claim, oldest first, for the orphan sweep.
|
|
117
|
+
*
|
|
118
|
+
* Ordered by document id rather than by a field, and paged with a cursor. A
|
|
119
|
+
* `limit()` with no ordering returns an arbitrary but STABLE sample — the same
|
|
120
|
+
* handful every run — and ordering on `claimedAtMs` would silently DROP any
|
|
121
|
+
* claim written before that field existed, which for a reaper means an orphan
|
|
122
|
+
* it can never see. `__name__` is present on every document by definition.
|
|
123
|
+
*/
|
|
124
|
+
export declare function listSendingLabelClaims(options: {
|
|
125
|
+
limit: number;
|
|
126
|
+
after?: string | null;
|
|
127
|
+
}): Promise<SendingDomainDebtRecord[]>;
|