@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.
Files changed (374) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/jest.integration.config.d.ts +39 -0
  4. package/jest.integration.setup.d.ts +17 -0
  5. package/package.json +51 -0
  6. package/src/index.d.ts +17 -0
  7. package/src/index.js +18 -0
  8. package/src/index.js.map +1 -0
  9. package/src/lib/render-cache.d.ts +184 -0
  10. package/src/lib/render-cache.js +198 -0
  11. package/src/lib/render-cache.js.map +1 -0
  12. package/src/lib/server/account-addresses.d.ts +160 -0
  13. package/src/lib/server/account-addresses.js +311 -0
  14. package/src/lib/server/account-addresses.js.map +1 -0
  15. package/src/lib/server/account-emails.d.ts +219 -0
  16. package/src/lib/server/account-emails.js +640 -0
  17. package/src/lib/server/account-emails.js.map +1 -0
  18. package/src/lib/server/admin-audit.d.ts +180 -0
  19. package/src/lib/server/admin-audit.js +233 -0
  20. package/src/lib/server/admin-audit.js.map +1 -0
  21. package/src/lib/server/analytics-retention.d.ts +63 -0
  22. package/src/lib/server/analytics-retention.js +66 -0
  23. package/src/lib/server/analytics-retention.js.map +1 -0
  24. package/src/lib/server/api-http.d.ts +159 -0
  25. package/src/lib/server/api-http.js +230 -0
  26. package/src/lib/server/api-http.js.map +1 -0
  27. package/src/lib/server/api-keys.d.ts +117 -0
  28. package/src/lib/server/api-keys.js +298 -0
  29. package/src/lib/server/api-keys.js.map +1 -0
  30. package/src/lib/server/auth-handoff.d.ts +165 -0
  31. package/src/lib/server/auth-handoff.js +482 -0
  32. package/src/lib/server/auth-handoff.js.map +1 -0
  33. package/src/lib/server/auth-pools.d.ts +246 -0
  34. package/src/lib/server/auth-pools.js +551 -0
  35. package/src/lib/server/auth-pools.js.map +1 -0
  36. package/src/lib/server/billing-addons.d.ts +171 -0
  37. package/src/lib/server/billing-addons.js +277 -0
  38. package/src/lib/server/billing-addons.js.map +1 -0
  39. package/src/lib/server/campaign-attribution-store.d.ts +55 -0
  40. package/src/lib/server/campaign-attribution-store.js +95 -0
  41. package/src/lib/server/campaign-attribution-store.js.map +1 -0
  42. package/src/lib/server/campaign-conversion-attribution.d.ts +205 -0
  43. package/src/lib/server/campaign-conversion-attribution.js +279 -0
  44. package/src/lib/server/campaign-conversion-attribution.js.map +1 -0
  45. package/src/lib/server/client-error-report.d.ts +301 -0
  46. package/src/lib/server/client-error-report.js +755 -0
  47. package/src/lib/server/client-error-report.js.map +1 -0
  48. package/src/lib/server/connect-account-status.d.ts +74 -0
  49. package/src/lib/server/connect-account-status.js +94 -0
  50. package/src/lib/server/connect-account-status.js.map +1 -0
  51. package/src/lib/server/connect-payout-failure.d.ts +103 -0
  52. package/src/lib/server/connect-payout-failure.js +127 -0
  53. package/src/lib/server/connect-payout-failure.js.map +1 -0
  54. package/src/lib/server/console-api-rate-limit.d.ts +81 -0
  55. package/src/lib/server/console-api-rate-limit.js +111 -0
  56. package/src/lib/server/console-api-rate-limit.js.map +1 -0
  57. package/src/lib/server/console-domains.d.ts +326 -0
  58. package/src/lib/server/console-domains.js +779 -0
  59. package/src/lib/server/console-domains.js.map +1 -0
  60. package/src/lib/server/consume-once.d.ts +91 -0
  61. package/src/lib/server/consume-once.js +96 -0
  62. package/src/lib/server/consume-once.js.map +1 -0
  63. package/src/lib/server/contact-company-link.d.ts +85 -0
  64. package/src/lib/server/contact-company-link.js +105 -0
  65. package/src/lib/server/contact-company-link.js.map +1 -0
  66. package/src/lib/server/contact-email-engagement.d.ts +32 -0
  67. package/src/lib/server/contact-email-engagement.js +145 -0
  68. package/src/lib/server/contact-email-engagement.js.map +1 -0
  69. package/src/lib/server/contact-email-index.d.ts +56 -0
  70. package/src/lib/server/contact-email-index.js +158 -0
  71. package/src/lib/server/contact-email-index.js.map +1 -0
  72. package/src/lib/server/contact-lifecycle-floor.d.ts +89 -0
  73. package/src/lib/server/contact-lifecycle-floor.js +88 -0
  74. package/src/lib/server/contact-lifecycle-floor.js.map +1 -0
  75. package/src/lib/server/contact-merge.d.ts +55 -0
  76. package/src/lib/server/contact-merge.js +279 -0
  77. package/src/lib/server/contact-merge.js.map +1 -0
  78. package/src/lib/server/contact-suppression.d.ts +153 -0
  79. package/src/lib/server/contact-suppression.js +259 -0
  80. package/src/lib/server/contact-suppression.js.map +1 -0
  81. package/src/lib/server/crm-booking-activity.d.ts +86 -0
  82. package/src/lib/server/crm-booking-activity.js +251 -0
  83. package/src/lib/server/crm-booking-activity.js.map +1 -0
  84. package/src/lib/server/crm-email-activity.d.ts +112 -0
  85. package/src/lib/server/crm-email-activity.js +143 -0
  86. package/src/lib/server/crm-email-activity.js.map +1 -0
  87. package/src/lib/server/crm-inbound-email.d.ts +145 -0
  88. package/src/lib/server/crm-inbound-email.js +287 -0
  89. package/src/lib/server/crm-inbound-email.js.map +1 -0
  90. package/src/lib/server/crm-next-activity.d.ts +74 -0
  91. package/src/lib/server/crm-next-activity.js +194 -0
  92. package/src/lib/server/crm-next-activity.js.map +1 -0
  93. package/src/lib/server/crm-records.d.ts +144 -0
  94. package/src/lib/server/crm-records.js +218 -0
  95. package/src/lib/server/crm-records.js.map +1 -0
  96. package/src/lib/server/csp-aggregate.d.ts +62 -0
  97. package/src/lib/server/csp-aggregate.js +276 -0
  98. package/src/lib/server/csp-aggregate.js.map +1 -0
  99. package/src/lib/server/data-storage-gate.d.ts +66 -0
  100. package/src/lib/server/data-storage-gate.js +48 -0
  101. package/src/lib/server/data-storage-gate.js.map +1 -0
  102. package/src/lib/server/dataset-live-pages.d.ts +179 -0
  103. package/src/lib/server/dataset-live-pages.js +421 -0
  104. package/src/lib/server/dataset-live-pages.js.map +1 -0
  105. package/src/lib/server/dns-probe.d.ts +53 -0
  106. package/src/lib/server/dns-probe.js +184 -0
  107. package/src/lib/server/dns-probe.js.map +1 -0
  108. package/src/lib/server/document-id.d.ts +29 -0
  109. package/src/lib/server/document-id.js +100 -0
  110. package/src/lib/server/document-id.js.map +1 -0
  111. package/src/lib/server/domain-provider-vercel.d.ts +33 -0
  112. package/src/lib/server/domain-provider-vercel.js +298 -0
  113. package/src/lib/server/domain-provider-vercel.js.map +1 -0
  114. package/src/lib/server/domain-provider-webhook.d.ts +73 -0
  115. package/src/lib/server/domain-provider-webhook.js +223 -0
  116. package/src/lib/server/domain-provider-webhook.js.map +1 -0
  117. package/src/lib/server/domain-provider-wildcard.d.ts +54 -0
  118. package/src/lib/server/domain-provider-wildcard.js +163 -0
  119. package/src/lib/server/domain-provider-wildcard.js.map +1 -0
  120. package/src/lib/server/domain-provider.d.ts +197 -0
  121. package/src/lib/server/domain-provider.js +221 -0
  122. package/src/lib/server/domain-provider.js.map +1 -0
  123. package/src/lib/server/duplicate-activity.d.ts +54 -0
  124. package/src/lib/server/duplicate-activity.js +67 -0
  125. package/src/lib/server/duplicate-activity.js.map +1 -0
  126. package/src/lib/server/duplicate-resource.d.ts +55 -0
  127. package/src/lib/server/duplicate-resource.js +474 -0
  128. package/src/lib/server/duplicate-resource.js.map +1 -0
  129. package/src/lib/server/dynamic-list-materialize.d.ts +153 -0
  130. package/src/lib/server/dynamic-list-materialize.js +581 -0
  131. package/src/lib/server/dynamic-list-materialize.js.map +1 -0
  132. package/src/lib/server/edit-access-authz.d.ts +56 -0
  133. package/src/lib/server/edit-access-authz.js +107 -0
  134. package/src/lib/server/edit-access-authz.js.map +1 -0
  135. package/src/lib/server/edit-access-token.d.ts +72 -0
  136. package/src/lib/server/edit-access-token.js +116 -0
  137. package/src/lib/server/edit-access-token.js.map +1 -0
  138. package/src/lib/server/edit-hint-token.d.ts +83 -0
  139. package/src/lib/server/edit-hint-token.js +119 -0
  140. package/src/lib/server/edit-hint-token.js.map +1 -0
  141. package/src/lib/server/email-delivery-log.d.ts +509 -0
  142. package/src/lib/server/email-delivery-log.js +1043 -0
  143. package/src/lib/server/email-delivery-log.js.map +1 -0
  144. package/src/lib/server/email-flow-gate.d.ts +80 -0
  145. package/src/lib/server/email-flow-gate.js +153 -0
  146. package/src/lib/server/email-flow-gate.js.map +1 -0
  147. package/src/lib/server/email-marketing-gate.d.ts +333 -0
  148. package/src/lib/server/email-marketing-gate.js +639 -0
  149. package/src/lib/server/email-marketing-gate.js.map +1 -0
  150. package/src/lib/server/email-metering.d.ts +293 -0
  151. package/src/lib/server/email-metering.js +359 -0
  152. package/src/lib/server/email-metering.js.map +1 -0
  153. package/src/lib/server/email-revenue-attribution.d.ts +185 -0
  154. package/src/lib/server/email-revenue-attribution.js +300 -0
  155. package/src/lib/server/email-revenue-attribution.js.map +1 -0
  156. package/src/lib/server/email-send-rate.d.ts +232 -0
  157. package/src/lib/server/email-send-rate.js +417 -0
  158. package/src/lib/server/email-send-rate.js.map +1 -0
  159. package/src/lib/server/email-sender-reputation.d.ts +317 -0
  160. package/src/lib/server/email-sender-reputation.js +464 -0
  161. package/src/lib/server/email-sender-reputation.js.map +1 -0
  162. package/src/lib/server/email-suppression.d.ts +490 -0
  163. package/src/lib/server/email-suppression.js +648 -0
  164. package/src/lib/server/email-suppression.js.map +1 -0
  165. package/src/lib/server/email-topic-confirmation.d.ts +150 -0
  166. package/src/lib/server/email-topic-confirmation.js +272 -0
  167. package/src/lib/server/email-topic-confirmation.js.map +1 -0
  168. package/src/lib/server/email-unsubscribe-link.d.ts +174 -0
  169. package/src/lib/server/email-unsubscribe-link.js +236 -0
  170. package/src/lib/server/email-unsubscribe-link.js.map +1 -0
  171. package/src/lib/server/erase-person.d.ts +116 -0
  172. package/src/lib/server/erase-person.js +272 -0
  173. package/src/lib/server/erase-person.js.map +1 -0
  174. package/src/lib/server/erase.d.ts +458 -0
  175. package/src/lib/server/erase.js +1506 -0
  176. package/src/lib/server/erase.js.map +1 -0
  177. package/src/lib/server/firebase-admin.d.ts +71 -0
  178. package/src/lib/server/firebase-admin.js +343 -0
  179. package/src/lib/server/firebase-admin.js.map +1 -0
  180. package/src/lib/server/form-dataset-binding-token.d.ts +35 -0
  181. package/src/lib/server/form-dataset-binding-token.js +107 -0
  182. package/src/lib/server/form-dataset-binding-token.js.map +1 -0
  183. package/src/lib/server/free-workspace-cap.d.ts +201 -0
  184. package/src/lib/server/free-workspace-cap.js +404 -0
  185. package/src/lib/server/free-workspace-cap.js.map +1 -0
  186. package/src/lib/server/ga4-measurement-protocol.d.ts +256 -0
  187. package/src/lib/server/ga4-measurement-protocol.js +558 -0
  188. package/src/lib/server/ga4-measurement-protocol.js.map +1 -0
  189. package/src/lib/server/host-email-tokens.d.ts +47 -0
  190. package/src/lib/server/host-email-tokens.js +72 -0
  191. package/src/lib/server/host-email-tokens.js.map +1 -0
  192. package/src/lib/server/host-memberships.d.ts +54 -0
  193. package/src/lib/server/host-memberships.js +194 -0
  194. package/src/lib/server/host-memberships.js.map +1 -0
  195. package/src/lib/server/host-sending-domain.d.ts +292 -0
  196. package/src/lib/server/host-sending-domain.js +451 -0
  197. package/src/lib/server/host-sending-domain.js.map +1 -0
  198. package/src/lib/server/host-visitor-records.d.ts +150 -0
  199. package/src/lib/server/host-visitor-records.js +262 -0
  200. package/src/lib/server/host-visitor-records.js.map +1 -0
  201. package/src/lib/server/id-token-refusal.d.ts +28 -0
  202. package/src/lib/server/id-token-refusal.js +111 -0
  203. package/src/lib/server/id-token-refusal.js.map +1 -0
  204. package/src/lib/server/legal-acceptance.d.ts +214 -0
  205. package/src/lib/server/legal-acceptance.js +305 -0
  206. package/src/lib/server/legal-acceptance.js.map +1 -0
  207. package/src/lib/server/list-members.d.ts +211 -0
  208. package/src/lib/server/list-members.js +176 -0
  209. package/src/lib/server/list-members.js.map +1 -0
  210. package/src/lib/server/live-page-usage.d.ts +202 -0
  211. package/src/lib/server/live-page-usage.js +300 -0
  212. package/src/lib/server/live-page-usage.js.map +1 -0
  213. package/src/lib/server/lockdown.d.ts +313 -0
  214. package/src/lib/server/lockdown.js +643 -0
  215. package/src/lib/server/lockdown.js.map +1 -0
  216. package/src/lib/server/media-cdn-rate-limit.d.ts +188 -0
  217. package/src/lib/server/media-cdn-rate-limit.js +204 -0
  218. package/src/lib/server/media-cdn-rate-limit.js.map +1 -0
  219. package/src/lib/server/media-delivery.d.ts +267 -0
  220. package/src/lib/server/media-delivery.js +485 -0
  221. package/src/lib/server/media-delivery.js.map +1 -0
  222. package/src/lib/server/media-download-tokens.d.ts +161 -0
  223. package/src/lib/server/media-download-tokens.js +386 -0
  224. package/src/lib/server/media-download-tokens.js.map +1 -0
  225. package/src/lib/server/media-quarantine.d.ts +127 -0
  226. package/src/lib/server/media-quarantine.js +170 -0
  227. package/src/lib/server/media-quarantine.js.map +1 -0
  228. package/src/lib/server/media-signing.d.ts +126 -0
  229. package/src/lib/server/media-signing.js +159 -0
  230. package/src/lib/server/media-signing.js.map +1 -0
  231. package/src/lib/server/media-storage-path.d.ts +96 -0
  232. package/src/lib/server/media-storage-path.js +111 -0
  233. package/src/lib/server/media-storage-path.js.map +1 -0
  234. package/src/lib/server/media-strong-digest.d.ts +85 -0
  235. package/src/lib/server/media-strong-digest.js +190 -0
  236. package/src/lib/server/media-strong-digest.js.map +1 -0
  237. package/src/lib/server/media-tombstone.d.ts +276 -0
  238. package/src/lib/server/media-tombstone.js +421 -0
  239. package/src/lib/server/media-tombstone.js.map +1 -0
  240. package/src/lib/server/media-variants.d.ts +309 -0
  241. package/src/lib/server/media-variants.js +428 -0
  242. package/src/lib/server/media-variants.js.map +1 -0
  243. package/src/lib/server/member-email-aliases.d.ts +180 -0
  244. package/src/lib/server/member-email-aliases.js +420 -0
  245. package/src/lib/server/member-email-aliases.js.map +1 -0
  246. package/src/lib/server/member-photo.d.ts +88 -0
  247. package/src/lib/server/member-photo.js +185 -0
  248. package/src/lib/server/member-photo.js.map +1 -0
  249. package/src/lib/server/membership-recover-throttle.d.ts +211 -0
  250. package/src/lib/server/membership-recover-throttle.js +231 -0
  251. package/src/lib/server/membership-recover-throttle.js.map +1 -0
  252. package/src/lib/server/notifications.d.ts +55 -0
  253. package/src/lib/server/notifications.js +134 -0
  254. package/src/lib/server/notifications.js.map +1 -0
  255. package/src/lib/server/org-billing.d.ts +97 -0
  256. package/src/lib/server/org-billing.js +236 -0
  257. package/src/lib/server/org-billing.js.map +1 -0
  258. package/src/lib/server/organizations.d.ts +837 -0
  259. package/src/lib/server/organizations.js +1814 -0
  260. package/src/lib/server/organizations.js.map +1 -0
  261. package/src/lib/server/password-reset-throttle.d.ts +84 -0
  262. package/src/lib/server/password-reset-throttle.js +102 -0
  263. package/src/lib/server/password-reset-throttle.js.map +1 -0
  264. package/src/lib/server/personal-data-export.d.ts +241 -0
  265. package/src/lib/server/personal-data-export.js +858 -0
  266. package/src/lib/server/personal-data-export.js.map +1 -0
  267. package/src/lib/server/platform-domain-names.d.ts +62 -0
  268. package/src/lib/server/platform-domain-names.js +197 -0
  269. package/src/lib/server/platform-domain-names.js.map +1 -0
  270. package/src/lib/server/platform-marketing-consent.d.ts +133 -0
  271. package/src/lib/server/platform-marketing-consent.js +209 -0
  272. package/src/lib/server/platform-marketing-consent.js.map +1 -0
  273. package/src/lib/server/rate-limit-store.d.ts +702 -0
  274. package/src/lib/server/rate-limit-store.js +1220 -0
  275. package/src/lib/server/rate-limit-store.js.map +1 -0
  276. package/src/lib/server/realm-plugins.d.ts +67 -0
  277. package/src/lib/server/realm-plugins.js +250 -0
  278. package/src/lib/server/realm-plugins.js.map +1 -0
  279. package/src/lib/server/recaptcha-allowlist.d.ts +90 -0
  280. package/src/lib/server/recaptcha-allowlist.js +409 -0
  281. package/src/lib/server/recaptcha-allowlist.js.map +1 -0
  282. package/src/lib/server/release-flags.d.ts +105 -0
  283. package/src/lib/server/release-flags.js +183 -0
  284. package/src/lib/server/release-flags.js.map +1 -0
  285. package/src/lib/server/replay-erasures.d.ts +126 -0
  286. package/src/lib/server/replay-erasures.js +217 -0
  287. package/src/lib/server/replay-erasures.js.map +1 -0
  288. package/src/lib/server/resolve-people.d.ts +66 -0
  289. package/src/lib/server/resolve-people.js +112 -0
  290. package/src/lib/server/resolve-people.js.map +1 -0
  291. package/src/lib/server/safe-equal.d.ts +45 -0
  292. package/src/lib/server/safe-equal.js +58 -0
  293. package/src/lib/server/safe-equal.js.map +1 -0
  294. package/src/lib/server/sending-domain-debt.d.ts +127 -0
  295. package/src/lib/server/sending-domain-debt.js +268 -0
  296. package/src/lib/server/sending-domain-debt.js.map +1 -0
  297. package/src/lib/server/sending-domain-recheck.d.ts +151 -0
  298. package/src/lib/server/sending-domain-recheck.js +253 -0
  299. package/src/lib/server/sending-domain-recheck.js.map +1 -0
  300. package/src/lib/server/sending-domains.d.ts +364 -0
  301. package/src/lib/server/sending-domains.js +683 -0
  302. package/src/lib/server/sending-domains.js.map +1 -0
  303. package/src/lib/server/serve-media-cdn.d.ts +433 -0
  304. package/src/lib/server/serve-media-cdn.js +1370 -0
  305. package/src/lib/server/serve-media-cdn.js.map +1 -0
  306. package/src/lib/server/serve-plugin-fetch.d.ts +61 -0
  307. package/src/lib/server/serve-plugin-fetch.js +294 -0
  308. package/src/lib/server/serve-plugin-fetch.js.map +1 -0
  309. package/src/lib/server/sms-keywords.d.ts +63 -0
  310. package/src/lib/server/sms-keywords.js +146 -0
  311. package/src/lib/server/sms-keywords.js.map +1 -0
  312. package/src/lib/server/sso-break-glass-owners.d.ts +91 -0
  313. package/src/lib/server/sso-break-glass-owners.js +227 -0
  314. package/src/lib/server/sso-break-glass-owners.js.map +1 -0
  315. package/src/lib/server/sso-domain-policy.d.ts +140 -0
  316. package/src/lib/server/sso-domain-policy.js +239 -0
  317. package/src/lib/server/sso-domain-policy.js.map +1 -0
  318. package/src/lib/server/sso-drift-logic.d.ts +106 -0
  319. package/src/lib/server/sso-drift-logic.js +95 -0
  320. package/src/lib/server/sso-drift-logic.js.map +1 -0
  321. package/src/lib/server/sso-enforcement.d.ts +204 -0
  322. package/src/lib/server/sso-enforcement.js +309 -0
  323. package/src/lib/server/sso-enforcement.js.map +1 -0
  324. package/src/lib/server/sso-provisioning.d.ts +320 -0
  325. package/src/lib/server/sso-provisioning.js +715 -0
  326. package/src/lib/server/sso-provisioning.js.map +1 -0
  327. package/src/lib/server/staff-alert-email.d.ts +41 -0
  328. package/src/lib/server/staff-alert-email.js +63 -0
  329. package/src/lib/server/staff-alert-email.js.map +1 -0
  330. package/src/lib/server/stripe-account-mode.d.ts +156 -0
  331. package/src/lib/server/stripe-account-mode.js +162 -0
  332. package/src/lib/server/stripe-account-mode.js.map +1 -0
  333. package/src/lib/server/tenant-write-lockdown.d.ts +131 -0
  334. package/src/lib/server/tenant-write-lockdown.js +176 -0
  335. package/src/lib/server/tenant-write-lockdown.js.map +1 -0
  336. package/src/lib/server/test-firestore.d.ts +45 -0
  337. package/src/lib/server/test-firestore.js +124 -0
  338. package/src/lib/server/test-firestore.js.map +1 -0
  339. package/src/lib/server/token-revocation.d.ts +140 -0
  340. package/src/lib/server/token-revocation.js +255 -0
  341. package/src/lib/server/token-revocation.js.map +1 -0
  342. package/src/lib/server/update-existing.d.ts +32 -0
  343. package/src/lib/server/update-existing.js +73 -0
  344. package/src/lib/server/update-existing.js.map +1 -0
  345. package/src/lib/server/upload-cors-reconcile.d.ts +118 -0
  346. package/src/lib/server/upload-cors-reconcile.js +234 -0
  347. package/src/lib/server/upload-cors-reconcile.js.map +1 -0
  348. package/src/lib/server/upsert-contact.d.ts +262 -0
  349. package/src/lib/server/upsert-contact.js +546 -0
  350. package/src/lib/server/upsert-contact.js.map +1 -0
  351. package/src/lib/server/usage-invoice.d.ts +203 -0
  352. package/src/lib/server/usage-invoice.js +397 -0
  353. package/src/lib/server/usage-invoice.js.map +1 -0
  354. package/src/lib/server/user-profiles.d.ts +151 -0
  355. package/src/lib/server/user-profiles.js +364 -0
  356. package/src/lib/server/user-profiles.js.map +1 -0
  357. package/src/lib/server/vercel-drain-signature.d.ts +30 -0
  358. package/src/lib/server/vercel-drain-signature.js +61 -0
  359. package/src/lib/server/vercel-drain-signature.js.map +1 -0
  360. package/src/lib/server/vercel-log-drain.d.ts +259 -0
  361. package/src/lib/server/vercel-log-drain.js +479 -0
  362. package/src/lib/server/vercel-log-drain.js.map +1 -0
  363. package/src/lib/server/verify-email-cooldown.d.ts +102 -0
  364. package/src/lib/server/verify-email-cooldown.js +114 -0
  365. package/src/lib/server/verify-email-cooldown.js.map +1 -0
  366. package/src/lib/server/visitor-write-rate-limit.d.ts +87 -0
  367. package/src/lib/server/visitor-write-rate-limit.js +117 -0
  368. package/src/lib/server/visitor-write-rate-limit.js.map +1 -0
  369. package/src/lib/server/workspace-domains.d.ts +156 -0
  370. package/src/lib/server/workspace-domains.js +271 -0
  371. package/src/lib/server/workspace-domains.js.map +1 -0
  372. package/src/tenancy.d.ts +115 -0
  373. package/src/tenancy.js +142 -0
  374. package/src/tenancy.js.map +1 -0
@@ -0,0 +1,683 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ /**
18
+ * CUSTOM SENDING DOMAINS — durable half.
19
+ *
20
+ * The record, the DNS lookups and the status transitions. The decisions all
21
+ * live in `@aglyn/shared-util-email`'s `sending-domain.ts`, which is pure;
22
+ * this module is the I/O around them, and it holds no policy of its own.
23
+ *
24
+ * ## Per-org record, per-host selection
25
+ *
26
+ * The record is at `orgs/{orgId}/sendingDomains/{domain}`, alongside
27
+ * `orgs/{orgId}/ssoDomains/{domain}` and for the same reason: proving control
28
+ * of a zone is a property of the ORG that proved it, and an agency running
29
+ * four sites on `client.com` should publish the DKIM record once rather than
30
+ * four times.
31
+ *
32
+ * Which identity a given site SENDS on is a separate per-host field,
33
+ * `hosts/{hostId}.sendingDomain`. That split is what answers the two halves of
34
+ * the question at once: per-org verification keeps the DNS chore down, and a
35
+ * per-host selection is what an agency's client actually wants, because the
36
+ * `From:` their recipients see belongs to the site, not to the agency.
37
+ *
38
+ * ## Reads are cheap on purpose
39
+ *
40
+ * `resolveHostSendingIdentity` is on the path of a campaign send, so it reads
41
+ * the host document the caller already has and at most ONE org subcollection
42
+ * document. It never scans, never lists, and never touches DNS — the standing
43
+ * rule against unrequested reads on a hot path applies here more than most,
44
+ * because a campaign resolves an identity once for thousands of messages.
45
+ *
46
+ * ## Why the provider call is not in here
47
+ *
48
+ * Issuing a DKIM key means creating a domain at the mail provider, which needs
49
+ * a credential that can create things — a different one from the send-only
50
+ * `RESEND_API_KEY`, which is exactly why `email-health.ts` can use the domains
51
+ * endpoint as a read-only credential probe.
52
+ *
53
+ * That credential belongs to the console alone, so the driver that reads it
54
+ * lives in the console app — `apps/console/utils/server/` — and not here.
55
+ * **This library is imported by the tenant runtime**, which
56
+ * serves untrusted published sites; a module it can import is a module whose
57
+ * environment read is one bug away from being reachable from a site request.
58
+ * A file in `apps/console` is not importable from the tenant app at all —
59
+ * there is no path mapping to it and nx's module boundaries forbid app→app —
60
+ * so the isolation is structural rather than a convention.
61
+ *
62
+ * What stays here is the seam: {@link recordIssuedSendingDomain} takes what a
63
+ * provider returned, and {@link recordSendingDomainIssueFailure} takes what it
64
+ * refused. `requestSendingDomain` still stops at `requested`, and a domain
65
+ * stopped there refuses sends, which is the correct behavior for a domain that
66
+ * has no signing key.
67
+ */ import { assessDmarc, assessSendingRecords, isPlatformSendingDomain, isSharedSendingDomain, normalizeLocalPart, normalizeSendingDomain, resolveSendingIdentity, safeProviderDetail, SENDING_SUBDOMAIN, SENDING_TRACKING_SUBDOMAIN, sendingDnsRecords, sendingDomainRequiredRecords, sendingRecordKey, sharedTenantSendingFrom, validateSendingDomain } from "@aglyn/shared-util-email";
68
+ import firebaseAdmin from "./firebase-admin.js";
69
+ import { lookupCaa, lookupMx, lookupTxt } from "./dns-probe.js";
70
+ import { getOrgForHost } from "./organizations.js";
71
+ const firestore = ()=>firebaseAdmin.app().firestore();
72
+ /** Subcollection under the owning org. */ export const SENDING_DOMAINS_COLLECTION = 'sendingDomains';
73
+ const domainRef = (orgId, domain)=>firestore().collection('orgs').doc(orgId).collection(SENDING_DOMAINS_COLLECTION).doc(domain);
74
+ /**
75
+ * The DKIM selector issued to one org for one domain.
76
+ *
77
+ * Includes the org id so two orgs verifying the same name occupy different
78
+ * record names. Without that they would share `resend._domainkey.<domain>`,
79
+ * and whichever verified second would overwrite the first — or, worse, inherit
80
+ * the first's verification without ever publishing anything.
81
+ */ export function sendingDkimSelector(orgId) {
82
+ const safe = String(orgId != null ? orgId : '').toLowerCase().replace(/[^a-z0-9]/g, '').slice(0, 24);
83
+ return `aglyn${safe ? `-${safe}` : ''}`;
84
+ }
85
+ /**
86
+ * One stored document as a record.
87
+ *
88
+ * Exported so the re-check sweep, which reaches these documents through a
89
+ * collection-group query rather than by org and name, shapes them the same way
90
+ * every other reader here does — a second reader with its own defaults is how
91
+ * two callers come to disagree about what an absent field means.
92
+ */ export function readSendingDomainRecord(snapshot) {
93
+ var _snapshot_data, _data_domain, _data_status, _data_dkimSelector, _data_dkimPublicKey, _data_returnPathHost, _data_providerDomainId, _data_trackingTarget, _data_lastIssueError;
94
+ if (!(snapshot == null ? void 0 : snapshot.exists)) return null;
95
+ const data = (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {};
96
+ return {
97
+ domain: String((_data_domain = data.domain) != null ? _data_domain : snapshot.id),
98
+ status: (_data_status = data.status) != null ? _data_status : 'requested',
99
+ dkimSelector: String((_data_dkimSelector = data.dkimSelector) != null ? _data_dkimSelector : ''),
100
+ dkimPublicKey: (_data_dkimPublicKey = data.dkimPublicKey) != null ? _data_dkimPublicKey : null,
101
+ returnPathHost: (_data_returnPathHost = data.returnPathHost) != null ? _data_returnPathHost : null,
102
+ providerDomainId: (_data_providerDomainId = data.providerDomainId) != null ? _data_providerDomainId : null,
103
+ trackingTarget: (_data_trackingTarget = data.trackingTarget) != null ? _data_trackingTarget : null,
104
+ createdAtMs: Number(data.createdAtMs) || null,
105
+ verifiedAtMs: Number(data.verifiedAtMs) || null,
106
+ lastCheckedAtMs: Number(data.lastCheckedAtMs) || null,
107
+ lastIssueError: (_data_lastIssueError = data.lastIssueError) != null ? _data_lastIssueError : null,
108
+ lastIssueAtMs: Number(data.lastIssueAtMs) || null,
109
+ lastMissing: Array.isArray(data.lastMissing) ? data.lastMissing : null
110
+ };
111
+ }
112
+ /**
113
+ * Start (or re-read) a claim on a sending domain.
114
+ *
115
+ * Idempotent, and deliberately non-destructive on a re-request: an existing
116
+ * record keeps its selector and its key, because reissuing either would
117
+ * invalidate a record the customer may already have published and turn a
118
+ * working setup into a mysterious failure. This mirrors `issueDomainClaim`.
119
+ */ export async function requestSendingDomain(options) {
120
+ const { domain, error } = validateSendingDomain(options == null ? void 0 : options.domain);
121
+ if (!domain) return {
122
+ record: null,
123
+ error,
124
+ status: 400
125
+ };
126
+ if (!(options == null ? void 0 : options.orgId)) {
127
+ return {
128
+ record: null,
129
+ error: 'Missing organization',
130
+ status: 400
131
+ };
132
+ }
133
+ const ref = domainRef(options.orgId, domain);
134
+ const existing = await ref.get();
135
+ if (existing.exists) {
136
+ return {
137
+ record: readSendingDomainRecord(existing),
138
+ error: null,
139
+ status: 200
140
+ };
141
+ }
142
+ const record = {
143
+ domain,
144
+ status: 'requested',
145
+ dkimSelector: sendingDkimSelector(options.orgId),
146
+ dkimPublicKey: null,
147
+ returnPathHost: null,
148
+ createdAtMs: Date.now()
149
+ };
150
+ await ref.set(record, {
151
+ merge: true
152
+ });
153
+ return {
154
+ record,
155
+ error: null,
156
+ status: 201
157
+ };
158
+ }
159
+ /**
160
+ * Record the key the mail provider issued, moving the domain to
161
+ * `records-issued` so the customer has something to publish.
162
+ *
163
+ * Separated from {@link requestSendingDomain} because it needs a credential
164
+ * that may not exist. Nothing here calls the provider: the caller supplies
165
+ * what it was given, so a deployment whose key cannot create domains still has
166
+ * a working path — an operator can complete this step by hand — and this
167
+ * module never grows a dependency on a credential it cannot assume. The
168
+ * console holds the credential and the driver that uses it; this library,
169
+ * which the tenant runtime also imports, holds neither.
170
+ *
171
+ * ## `records-issued` is a promise that records exist
172
+ *
173
+ * The write is refused unless the resulting record actually yields a DKIM
174
+ * record with a value. The alternative is the failure this whole feature is
175
+ * arranged against: a status saying the customer has records to publish, next
176
+ * to a records table with an empty DKIM row, which cannot ever verify and
177
+ * reads to the customer as our bug.
178
+ *
179
+ * ## An issued key is never overwritten
180
+ *
181
+ * A second call carrying a DIFFERENT key is refused rather than applied. The
182
+ * customer may already have published the first one, and replacing it turns a
183
+ * finished setup into a domain that silently stops signing. Re-recording the
184
+ * SAME key is a no-op and succeeds, so a retried request is safe.
185
+ *
186
+ * The SELECTOR, by contrast, comes from the provider when it supplies one:
187
+ * the record a customer publishes has to be the name the provider will sign
188
+ * under, and `sendingDkimSelector` only proposes it.
189
+ */ export async function recordIssuedSendingDomain(options) {
190
+ var _ref, _ref1, _ref2, _options_trackingTarget;
191
+ const domain = normalizeSendingDomain(options == null ? void 0 : options.domain);
192
+ const key = String((_ref = options == null ? void 0 : options.dkimPublicKey) != null ? _ref : '').trim();
193
+ if (!domain) {
194
+ return {
195
+ record: null,
196
+ error: 'Missing domain',
197
+ status: 400
198
+ };
199
+ }
200
+ const ref = domainRef(options.orgId, domain);
201
+ const snapshot = await ref.get();
202
+ if (!snapshot.exists) {
203
+ return {
204
+ record: null,
205
+ error: 'No claim on that domain',
206
+ status: 404
207
+ };
208
+ }
209
+ const existing = readSendingDomainRecord(snapshot);
210
+ const held = String((_ref1 = existing == null ? void 0 : existing.dkimPublicKey) != null ? _ref1 : '').trim();
211
+ if (held && held !== key) {
212
+ return {
213
+ record: existing,
214
+ error: 'That domain already has an issued signing key. Release the domain ' + 'and add it again to start over.',
215
+ status: 409
216
+ };
217
+ }
218
+ const selector = String((_ref2 = options == null ? void 0 : options.dkimSelector) != null ? _ref2 : '').trim() || (existing == null ? void 0 : existing.dkimSelector) || '';
219
+ const returnPathHost = options.returnPathHost || (existing == null ? void 0 : existing.returnPathHost);
220
+ /*
221
+ * The only thing standing between a caller and `records-issued`, and it is
222
+ * asked of the SAME function that prints the records for the customer and
223
+ * that the verifier compares against — not a truthiness check on the key,
224
+ * which would be this write's private opinion of what "publishable" means
225
+ * and could agree with nothing else.
226
+ */ const issued = sendingDomainRequiredRecords({
227
+ domain,
228
+ dkimSelector: selector,
229
+ dkimPublicKey: key,
230
+ returnPathHost
231
+ });
232
+ if (!issued.some((entry)=>entry.purpose === 'dkim')) {
233
+ return {
234
+ record: existing,
235
+ error: 'Refusing to issue records for a domain with no publishable DKIM record',
236
+ status: 400
237
+ };
238
+ }
239
+ await ref.set(_extends({
240
+ status: 'records-issued',
241
+ dkimPublicKey: key
242
+ }, selector ? {
243
+ dkimSelector: selector
244
+ } : {}, options.returnPathHost ? {
245
+ returnPathHost: options.returnPathHost
246
+ } : {}, options.providerDomainId ? {
247
+ providerDomainId: String(options.providerDomainId)
248
+ } : {}, String((_options_trackingTarget = options.trackingTarget) != null ? _options_trackingTarget : '').trim() ? {
249
+ trackingTarget: String(options.trackingTarget).trim()
250
+ } : {}, {
251
+ // A previous failure is not part of the record once it succeeded.
252
+ lastIssueError: firebaseAdmin.firestore.FieldValue.delete()
253
+ }), {
254
+ merge: true
255
+ });
256
+ return {
257
+ record: readSendingDomainRecord(await ref.get()),
258
+ error: null,
259
+ status: 200
260
+ };
261
+ }
262
+ /**
263
+ * Record that the provider did not issue anything, WITHOUT moving the status.
264
+ *
265
+ * The point of a separate function is that there is no path from a provider
266
+ * failure to `records-issued`. A `4xx` or `5xx` means no key exists, so the
267
+ * domain stays `requested` — where it refuses sends, which is correct for a
268
+ * domain that cannot sign — and carries a reason an admin can act on instead
269
+ * of appearing to have finished.
270
+ *
271
+ * `detail` is a short code the caller built from a fixed vocabulary, never a
272
+ * provider's response body. {@link safeProviderDetail} is the second line:
273
+ * an `Authorization` header echoed back by a provider must not become a
274
+ * Firestore document.
275
+ */ export async function recordSendingDomainIssueFailure(options) {
276
+ const domain = normalizeSendingDomain(options == null ? void 0 : options.domain);
277
+ if (!(options == null ? void 0 : options.orgId) || !domain) return;
278
+ const ref = domainRef(options.orgId, domain);
279
+ if (!(await ref.get()).exists) return;
280
+ await ref.set({
281
+ lastIssueError: safeProviderDetail(options.detail) || 'unknown',
282
+ lastIssueAtMs: Date.now()
283
+ }, {
284
+ merge: true
285
+ });
286
+ }
287
+ /** One domain plus the records it needs, for a surface that shows both. */ export async function getSendingDomain(orgId, rawDomain) {
288
+ const domain = normalizeSendingDomain(rawDomain);
289
+ if (!orgId || !domain) return null;
290
+ const record = readSendingDomainRecord(await domainRef(orgId, domain).get());
291
+ if (!record) return null;
292
+ return {
293
+ record,
294
+ records: sendingDnsRecords(record),
295
+ dmarc: null
296
+ };
297
+ }
298
+ export async function listSendingDomains(orgId) {
299
+ if (!orgId) return [];
300
+ const snapshot = await firestore().collection('orgs').doc(orgId).collection(SENDING_DOMAINS_COLLECTION).get();
301
+ return snapshot.docs.map(readSendingDomainRecord).filter(Boolean);
302
+ }
303
+ /**
304
+ * Read the customer's DMARC policy.
305
+ *
306
+ * Advisory in both directions: it never blocks verification and it is never
307
+ * written. A domain under `p=reject` with our DKIM unpublished hard-fails
308
+ * every message, and a customer deserves to be told that before they wonder
309
+ * why their campaign vanished.
310
+ */ export async function readDmarcPolicy(rawDomain) {
311
+ const domain = normalizeSendingDomain(rawDomain);
312
+ if (!domain) return null;
313
+ const lookup = await lookupTxt(`_dmarc.${domain}`);
314
+ // An unreachable lookup is not "they have no policy" — saying so would tell
315
+ // a customer under p=reject that they have no protection.
316
+ if (!lookup.answered) return null;
317
+ return assessDmarc(lookup.records);
318
+ }
319
+ /**
320
+ * Start (or read) the hold that keeps a tracked domain's links alive.
321
+ *
322
+ * Stamped on FIRST sighting rather than computed from an orphan timestamp,
323
+ * because there is no reliable one: a label reassignment leaves no record of
324
+ * when it happened, and the reaper re-infers the orphan on every pass. The
325
+ * stamp is the record of when the hold began, so a domain cannot be held
326
+ * forever by a sweep that keeps starting the clock over.
327
+ *
328
+ * @returns the instant the domain may be released, or `null` when there is
329
+ * nothing to stamp it on — a domain with no org record cannot be
330
+ * held, and refusing to release it on the strength of a hold nobody
331
+ * can see would strand a provider slot permanently.
332
+ */ export async function holdTrackedSendingDomain(options) {
333
+ var _ref;
334
+ const orgId = String((_ref = options == null ? void 0 : options.orgId) != null ? _ref : '').trim();
335
+ const domain = normalizeSendingDomain(options == null ? void 0 : options.domain);
336
+ if (!orgId || !domain || options.windowMs <= 0) return null;
337
+ const ref = domainRef(orgId, domain);
338
+ const snapshot = await ref.get().catch(()=>null);
339
+ if (!(snapshot == null ? void 0 : snapshot.exists)) return null;
340
+ const held = Number(snapshot.get('trackingRetentionUntilMs')) || 0;
341
+ if (held > 0) return held;
342
+ const until = options.nowMs + options.windowMs;
343
+ await ref.set({
344
+ trackingRetentionUntilMs: until
345
+ }, {
346
+ merge: true
347
+ })// A stamp we could not write is a hold we cannot prove later, so the
348
+ // caller is told there is none rather than being asked to wait on a
349
+ // deadline that will restart on the next pass.
350
+ .catch(()=>null);
351
+ return until;
352
+ }
353
+ /**
354
+ * Whether this domain publishes a CAA policy that would REFUSE the tracking
355
+ * host's certificate.
356
+ *
357
+ * The question decides whether a customer is shown a CAA record at all, and
358
+ * asking it is the difference between guidance and a footgun. CAA restricts
359
+ * which authorities may issue, and the lookup stops at the first name in the
360
+ * tree publishing any record — so:
361
+ *
362
+ * - A domain publishing NOTHING needs nothing. Any authority may already
363
+ * issue, and handing over a CAA record to paste would be the change that
364
+ * STARTS restricting them, breaking whatever else renews on that name.
365
+ * - A domain that publishes some, none of which name our authority, has to
366
+ * add one ALONGSIDE what it has.
367
+ * - A domain that already names our authority is done.
368
+ *
369
+ * Walks up from the tracking host exactly as a certificate authority does,
370
+ * and stops where a real lookup stops: at the first name with any record.
371
+ *
372
+ * @returns `null` when DNS could not be reached, which is not "they publish
373
+ * none" — telling a customer to add a CAA on the strength of an
374
+ * unanswered lookup is how a working zone gets narrowed by mistake.
375
+ */ export async function readTrackingCaaNeed(rawDomain, authority) {
376
+ const domain = normalizeSendingDomain(rawDomain);
377
+ const ca = String(authority != null ? authority : '').trim().toLowerCase();
378
+ if (!domain || !ca) return null;
379
+ /*
380
+ * From the tracking host upward. Every label is asked because a CAA on a
381
+ * closer name overrides a broader one — a customer who already scoped a
382
+ * policy to `links.` must not be told to widen their root.
383
+ */ const labels = `${SENDING_TRACKING_SUBDOMAIN}.${domain}`.split('.');
384
+ for(let index = 0; index < labels.length - 1; index += 1){
385
+ const name = labels.slice(index).join('.');
386
+ const lookup = await lookupCaa(name);
387
+ if (!lookup.answered) return null;
388
+ if (!lookup.records.length) continue;
389
+ // The first name with any record is where issuance is decided.
390
+ const issuers = lookup.records.filter((entry)=>/ issue "/.test(entry)).map((entry)=>entry.toLowerCase());
391
+ return issuers.some((entry)=>entry.includes(`"${ca}"`)) ? 'satisfied' : 'must-add';
392
+ }
393
+ return 'not-needed';
394
+ }
395
+ /**
396
+ * Read the live DNS for one record and say what it establishes. Writes
397
+ * nothing.
398
+ *
399
+ * Split out so the two callers that need this answer ask the SAME question of
400
+ * the SAME resolvers: {@link verifySendingDomain}, which acts on it
401
+ * immediately, and the unattended re-check sweep, which counts conclusive
402
+ * failures before it acts. A sweep that assessed the records its own way would
403
+ * be a second opinion on "are these published", and the customer would meet
404
+ * whichever one happened to run last.
405
+ *
406
+ * The three lookups run together because they are independent and the caller
407
+ * is waiting on the slowest either way.
408
+ */ export async function probeSendingRecords(record) {
409
+ var _ref;
410
+ const domain = normalizeSendingDomain((_ref = record == null ? void 0 : record.domain) != null ? _ref : '');
411
+ const sendHost = `${SENDING_SUBDOMAIN}.${domain}`;
412
+ const dkimHost = `${record.dkimSelector}._domainkey.${domain}`;
413
+ const [spf, dkim, mx] = await Promise.all([
414
+ lookupTxt(sendHost),
415
+ lookupTxt(dkimHost),
416
+ lookupMx(sendHost)
417
+ ]);
418
+ return assessSendingRecords(record, {
419
+ spfTxt: spf.records,
420
+ dkimTxt: dkim.records,
421
+ mx: mx.records,
422
+ // All three, not any: a record we could not read is not a record that is
423
+ // absent, and one unreadable lookup is enough to make the whole answer
424
+ // evidence of nothing.
425
+ conclusive: spf.answered && dkim.answered && mx.answered
426
+ });
427
+ }
428
+ /**
429
+ * Check the live DNS and move the record to `verified` or `failed`.
430
+ *
431
+ * Never throws: a sweep across every org must not stop at the first bad zone.
432
+ *
433
+ * An INCONCLUSIVE result writes nothing but the check time. A resolver outage
434
+ * must not un-verify a working domain — which would silently stop that
435
+ * tenant's mail — and must not fail a customer who is midway through
436
+ * publishing either. This is the `hold` arm the SSO drift sweep uses, and the
437
+ * reason `assessSendingRecords` has three outcomes rather than two.
438
+ *
439
+ * Acts on ONE conclusive answer, and that is right for this caller: an admin
440
+ * pressed Verify and is watching the result, so the check is attended and its
441
+ * outcome is immediately visible and immediately retryable. The unattended
442
+ * sweep over already-verified domains is the caller that cannot say that, and
443
+ * `sending-domain-recheck.ts` is where the extra evidence it needs is
444
+ * gathered.
445
+ */ export async function verifySendingDomain(orgId, rawDomain) {
446
+ const domain = normalizeSendingDomain(rawDomain);
447
+ if (!orgId || !domain) {
448
+ return {
449
+ record: null,
450
+ missing: [],
451
+ inconclusive: false,
452
+ error: 'Invalid domain'
453
+ };
454
+ }
455
+ const ref = domainRef(orgId, domain);
456
+ const record = readSendingDomainRecord(await ref.get());
457
+ if (!record) {
458
+ return {
459
+ record: null,
460
+ missing: [],
461
+ inconclusive: false,
462
+ error: 'No claim on that domain — add it first.'
463
+ };
464
+ }
465
+ const verdict = await probeSendingRecords(record);
466
+ const now = Date.now();
467
+ if (verdict.status === 'inconclusive') {
468
+ await ref.set({
469
+ lastCheckedAtMs: now
470
+ }, {
471
+ merge: true
472
+ });
473
+ return {
474
+ record,
475
+ missing: [],
476
+ inconclusive: true,
477
+ error: null
478
+ };
479
+ }
480
+ const verified = verdict.status === 'verified';
481
+ await ref.set(_extends({
482
+ status: verified ? 'verified' : 'failed',
483
+ lastCheckedAtMs: now
484
+ }, verified ? {
485
+ verifiedAtMs: now,
486
+ lastMissing: firebaseAdmin.firestore.FieldValue.delete()
487
+ } : {
488
+ lastMissing: verdict.missing
489
+ }), {
490
+ merge: true
491
+ });
492
+ return {
493
+ record: readSendingDomainRecord(await ref.get()),
494
+ missing: verdict.missing,
495
+ inconclusive: false,
496
+ error: null
497
+ };
498
+ }
499
+ /**
500
+ * Drop a claim.
501
+ *
502
+ * The host selections pointing at it are NOT rewritten to the platform
503
+ * identity. A site that was sending as its own domain and now has no verified
504
+ * one must refuse, not quietly revert — silently moving a tenant's mail back
505
+ * onto the shared domain is the exact fallback this feature exists to prevent,
506
+ * and doing it during a delete would be no better than doing it during a send.
507
+ */ export async function releaseSendingDomain(orgId, rawDomain) {
508
+ const domain = normalizeSendingDomain(rawDomain);
509
+ if (!orgId || !domain) return;
510
+ await domainRef(orgId, domain).delete().catch(()=>undefined);
511
+ }
512
+ /*==========================================
513
+ The read on the send path
514
+ ==========================================*/ /**
515
+ * The identity one host sends on, ready to hand to `sendEmail`.
516
+ *
517
+ * Two document reads at most, and none at all for a host that has no
518
+ * selection. The host document is passed in rather than re-fetched because
519
+ * every caller already holds it.
520
+ *
521
+ * ## This function cannot return an `aglyn.com` address, and that is the point
522
+ *
523
+ * Every caller here is resolving mail FOR A SITE — that is what "host" in the
524
+ * name means — so every verdict it produces is tenant mail by construction.
525
+ * `audience: 'tenant'` is therefore passed unconditionally and is not a
526
+ * parameter: a caller that could choose would eventually choose wrong, and the
527
+ * wrong choice puts a merchant's list quality onto the domain the platform's
528
+ * own invoices and password resets leave on.
529
+ *
530
+ * A site that has selected NOTHING does not reach `USAGE_EMAIL_FROM` either.
531
+ * All of its mail goes on the shared pool member `hostSharedFrom` assigns it,
532
+ * marketing included. What keeps the pool usable is measurement rather than
533
+ * prohibition: a pooled campaign is graded against the stricter reputation
534
+ * thresholds, so the site earning complaints is the one that stops.
535
+ */ export async function resolveHostSendingIdentity(options) {
536
+ var _ref, _ref1, _ref2, _options_selectedLocalPart, _record_lastMissing;
537
+ const platformFrom = (_ref = (_ref1 = options == null ? void 0 : options.platformFrom) != null ? _ref1 : process.env.USAGE_EMAIL_FROM) != null ? _ref : null;
538
+ const domain = normalizeSendingDomain((_ref2 = options == null ? void 0 : options.selectedDomain) != null ? _ref2 : '');
539
+ const purpose = options == null ? void 0 : options.purpose;
540
+ const sharedFrom = hostSharedFrom(options == null ? void 0 : options.hostId, options == null ? void 0 : options.poolMember);
541
+ if (!domain || !(options == null ? void 0 : options.orgId)) {
542
+ return resolveSendingIdentity({
543
+ selection: null,
544
+ platformFrom,
545
+ sharedFrom,
546
+ purpose,
547
+ audience: 'tenant'
548
+ });
549
+ }
550
+ const record = readSendingDomainRecord(await domainRef(options.orgId, domain).get());
551
+ /*
552
+ * Whose domain this is, decided from the NAME rather than from the record.
553
+ *
554
+ * The record is what a released domain no longer has, and a site pointed at
555
+ * a subdomain of our own apex is still pointed at a subdomain of our own
556
+ * apex whether or not one exists for it. Deriving this from the name keeps
557
+ * the two possible failures apart: a missing record for OUR name is our
558
+ * provisioning that did not finish, a missing record for THEIRS is a domain
559
+ * they asked to send as and nothing has verified.
560
+ */ const platformIssued = isPlatformSendingDomain(domain);
561
+ /*
562
+ * A selection naming a CUSTOMER'S domain with no record refuses rather than
563
+ * falling back. The record can be gone because it was released, or because
564
+ * the selection was written against a different org — both mean the site is
565
+ * configured to send as a domain nothing has verified, and neither is a
566
+ * reason to send as somebody else.
567
+ *
568
+ * `platformIssued` rides on the selection either way, and the resolver reads
569
+ * it only on the unverified branch: a platform subdomain that has not
570
+ * finished provisioning drops to the pool for transactional mail instead,
571
+ * because nothing about it is a merchant instruction to contradict.
572
+ */ const selection = record ? {
573
+ domain: record.domain,
574
+ status: record.status,
575
+ localPart: normalizeLocalPart((_options_selectedLocalPart = options.selectedLocalPart) != null ? _options_selectedLocalPart : '') || 'hello',
576
+ missing: (_record_lastMissing = record.lastMissing) != null ? _record_lastMissing : [],
577
+ platformIssued
578
+ } : {
579
+ domain,
580
+ status: 'failed',
581
+ localPart: '',
582
+ missing: [],
583
+ platformIssued
584
+ };
585
+ /*
586
+ * `sharedFrom` is passed even here, where a selection exists and will decide
587
+ * the outcome. It is inert by construction — the selection branch of
588
+ * `resolveSendingIdentity` never reads it — and passing it unconditionally
589
+ * keeps the call shape identical between the two arms, so a future edit
590
+ * cannot produce a path that had a pool available and did not offer it.
591
+ *
592
+ * It must NOT become a fallback for an unverified selection, and it is the
593
+ * resolver's structure rather than this call site that guarantees that.
594
+ */ return resolveSendingIdentity({
595
+ selection,
596
+ platformFrom,
597
+ sharedFrom,
598
+ purpose,
599
+ audience: 'tenant'
600
+ });
601
+ }
602
+ /**
603
+ * The pool address for one host: the pinned member if it names a real one, the
604
+ * hash assignment otherwise.
605
+ *
606
+ * The override is validated against the LIVE pool rather than trusted, because
607
+ * a stale value is the expected case — an operator shrinks the pool, or a
608
+ * quarantine was lifted by deleting the member rather than the field — and an
609
+ * address on a domain that no longer exists is a send that fails at the
610
+ * provider instead of one that quietly goes back to normal.
611
+ */ function hostSharedFrom(hostId, poolMember) {
612
+ const id = String(hostId != null ? hostId : '').trim();
613
+ if (!id) return '';
614
+ const pinned = normalizeSendingDomain(String(poolMember != null ? poolMember : ''));
615
+ if (pinned && isSharedSendingDomain(pinned)) {
616
+ const shared = sharedTenantSendingFrom(id);
617
+ const at = shared.lastIndexOf('@');
618
+ return at > 0 ? `${shared.slice(0, at)}@${pinned}` : '';
619
+ }
620
+ return sharedTenantSendingFrom(id);
621
+ }
622
+ /**
623
+ * The identity one site sends on, from a `hostId` alone.
624
+ *
625
+ * The door every tenant sender uses. {@link resolveHostSendingIdentity} needs
626
+ * the org id and the host's two selection fields, and a survey of the tenant
627
+ * send sites found that all of them hold a `hostId` while only three hold an
628
+ * org id and half never read the host document at all. Asking nineteen call
629
+ * sites to each assemble the same three values is the shape that produces a
630
+ * twentieth which does not — and the cost of forgetting here is a message
631
+ * leaving on the platform's own domain.
632
+ *
633
+ * ## The cache is per-call-graph, not per-process
634
+ *
635
+ * Passed a `cache`, repeated resolutions for one host cost one pair of reads.
636
+ * A sweep that mails two hundred abandoned carts across a dozen sites, or a
637
+ * webhook that sends a receipt and a seller notice for the same order, holds
638
+ * one map for the run and pays for each site once.
639
+ *
640
+ * Deliberately NOT a module-level cache. Verification status is exactly the
641
+ * thing that changes underneath a long-lived process — the re-check sweep
642
+ * un-verifies a domain whose records have gone — and a stale entry would keep
643
+ * a site sending on a domain that no longer authenticates. A caller that
644
+ * declares a cache has also declared how long it may be trusted.
645
+ */ export async function hostSendingIdentity(hostId, cache) {
646
+ var _ref, _ref1, _ref2, _ref3;
647
+ const id = String(hostId != null ? hostId : '').trim();
648
+ if (!id) {
649
+ /*
650
+ * No host is not "the platform is speaking", and it is not "any pool
651
+ * member will do" either. It is a caller that does not know which site it
652
+ * is sending for, and the honest answer to that is a refusal.
653
+ *
654
+ * No `sharedFrom` is passed, which is what makes that structural rather
655
+ * than a rule: the pool is assigned FROM the host id, so a caller without
656
+ * one has nothing to be assigned.
657
+ */ return resolveSendingIdentity({
658
+ selection: null,
659
+ audience: 'tenant'
660
+ });
661
+ }
662
+ const hit = cache == null ? void 0 : cache.get(id);
663
+ if (hit) return hit;
664
+ const snapshot = await firestore().collection('hosts').doc(id).get().catch(()=>null);
665
+ const owner = await getOrgForHost(id).catch(()=>null);
666
+ const verdict = await resolveHostSendingIdentity({
667
+ orgId: (_ref = owner == null ? void 0 : owner.orgId) != null ? _ref : null,
668
+ selectedDomain: (_ref1 = snapshot == null ? void 0 : snapshot.get('sendingDomain')) != null ? _ref1 : '',
669
+ selectedLocalPart: (_ref2 = snapshot == null ? void 0 : snapshot.get('sendingLocalPart')) != null ? _ref2 : '',
670
+ hostId: id,
671
+ poolMember: (_ref3 = snapshot == null ? void 0 : snapshot.get('sendingPoolMember')) != null ? _ref3 : ''
672
+ });
673
+ cache == null ? void 0 : cache.set(id, verdict);
674
+ return verdict;
675
+ }
676
+ /** The record keys a surface highlights as outstanding. */ export function outstandingSendingRecords(record) {
677
+ var _ref;
678
+ const missing = new Set((_ref = record == null ? void 0 : record.lastMissing) != null ? _ref : []);
679
+ if (!missing.size) return [];
680
+ return sendingDnsRecords(record).filter((entry)=>missing.has(sendingRecordKey(entry)));
681
+ }
682
+
683
+ //# sourceMappingURL=sending-domains.js.map