@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,311 @@
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
+ * EVERY ADDRESS AN ACCOUNT HOLDS — the one answer, for every consumer.
18
+ *
19
+ * ## Why this exists
20
+ *
21
+ * Two stores in this product are keyed by ADDRESS rather than by uid:
22
+ * `emailDeliveries/{sha256(address)}` (the per-recipient delivery log) and
23
+ * `emailSuppressions/{sha256(address)}`. Everything that reached them passed
24
+ * ONE address — the Firebase Auth record's current `email` — and an account
25
+ * is not one address:
26
+ *
27
+ * - A changed primary leaves the old address holding the mail. The new hash
28
+ * names an empty document, so the staff card renders a blank table for a
29
+ * person we demonstrably emailed. That card's own copy warns that reading
30
+ * a blank table as "we never emailed them" is how staff mislead a
31
+ * customer; before this module, an email change made the card do exactly
32
+ * that.
33
+ * - A federated provider carries its own address. `providerData[].email` can
34
+ * differ from the primary and is a real mailbox that has received real
35
+ * mail.
36
+ * - `users/{uid}/emails/{address}` (AGL-2486) holds confirmed aliases and
37
+ * every address this account has been moved OFF of.
38
+ *
39
+ * Erasure inherited the same single-address assumption, which is the sharp
40
+ * version of the bug: a prior or secondary address kept its full delivery
41
+ * history — recipient address, subjects, open and click times — after an
42
+ * erasure request had been honoured and reported complete.
43
+ *
44
+ * ## One resolver, not four call sites
45
+ *
46
+ * The delivery-history card, erasure, the audit subject resolution and the
47
+ * suppression lookups all need the same rule, and four copies of it would
48
+ * drift apart the way the dead-status set did. So the rule lives here and
49
+ * every consumer calls in. A caller that wants "the addresses" must not
50
+ * re-derive them from a record it happens to be holding.
51
+ *
52
+ * ## Why `verifiedAccountEmails` is not this function
53
+ *
54
+ * `account-emails.ts` already answers a question that reads like this one,
55
+ * and reusing it here would be a correctness bug in both directions. It is
56
+ * built for matching an INVITATION, and its three defining choices are all
57
+ * right for that and wrong for this:
58
+ *
59
+ * - **It returns VERIFIED addresses only.** Correct for an invitation: an
60
+ * unverified address must not match one, or adding an address becomes a
61
+ * way into somebody else's organization. Wrong here, because unverified
62
+ * addresses are precisely where the mail is — a verification message is by
63
+ * definition sent to an address that is not yet verified, and it is the
64
+ * single most common row in the delivery log. Filtering it out would make
65
+ * the card miss the mail a staffer is most often asked about, and would
66
+ * make erasure leave it behind.
67
+ * - **It reads the subcollection alone.** The primary lives in the Firebase
68
+ * Auth record and is mirrored into `users/{uid}/emails` only LAZILY, on
69
+ * the account's first read of its own settings. An account that has never
70
+ * opened that page has an empty subcollection and a real primary, so this
71
+ * would return nothing at all for it.
72
+ * - **It `slice`s to `MAX_ACCOUNT_EMAILS`.** A silent truncation is
73
+ * tolerable when the cost is an invitation not matching. It is the worst
74
+ * available outcome for erasure, which would report success over addresses
75
+ * it never swept.
76
+ *
77
+ * So this module composes over that one's STORAGE — same subcollection, same
78
+ * index, and the reverse lookup is delegated to `findAccountByVerifiedAlias`
79
+ * rather than re-read — and adds the two sources it does not have.
80
+ *
81
+ * ⛔ Do not "simplify" this into `verifiedAccountEmails`. The three
82
+ * differences above are each a defect if collapsed.
83
+ *
84
+ * ## THE UNIQUENESS INDEX ONCE HAD A HOLE, AND IT STILL HAS A TAIL
85
+ *
86
+ * `emailIdentityIndex` is what stops two accounts holding one address, and it
87
+ * was claimed in exactly two places: the primary backfill in
88
+ * `listAccountEmails`, and `confirmAccountEmail`. Neither read `providerData`
89
+ * — so an address supplied by a federated provider never reached the index,
90
+ * and two accounts could hold one address without the guard ever seeing them.
91
+ * The live shape of it is ordinary: an account whose Google provider address
92
+ * is another account's primary.
93
+ *
94
+ * `registerProviderAddresses` now claims for provider-asserted addresses at
95
+ * the session mint, so this closes going forward and backfills each account
96
+ * on its next sign-in. Two things follow that this module must keep saying:
97
+ *
98
+ * - **The tail is still open.** An account that has not signed in since is
99
+ * still unregistered, and a collision that already exists is not repaired
100
+ * by a claim that correctly refuses. `tools/scripts/audit-provider-address-claims.mjs`
101
+ * reports both, read-only — resolving a collision means deciding about two
102
+ * real accounts, which is not a decision code makes.
103
+ * - **A refused claim leaves an address held and unindexed.** That is the
104
+ * intended outcome, not a failure, so the reverse direction below is no
105
+ * more complete than it was: it still cannot enumerate provider holders.
106
+ *
107
+ * The registration writes an account-email row and nothing else. It cannot
108
+ * reach SSO provisioning — `sso-jit` reads the address the IdP asserted at
109
+ * sign-in and never consults this store, and
110
+ * `apps/console/specs/account-emails-never-reach-sso.spec.ts` fails if that
111
+ * stops being true. That guard is what makes the write safe.
112
+ *
113
+ * ## The direction that works, and the one that cannot
114
+ *
115
+ * FORWARD (uid → addresses) is complete: all three sources are readable from
116
+ * the uid. {@link resolveAccountAddresses} is that direction.
117
+ *
118
+ * REVERSE (address → uid) is STRUCTURALLY INCOMPLETE and no amount of care
119
+ * here fixes it. Firebase Auth can look an address up as a primary
120
+ * (`getUserByEmail`) and `emailIdentityIndex` answers for confirmed aliases,
121
+ * but there is no query for "which accounts carry this as a provider
122
+ * address" — that field is only readable once you already have the record.
123
+ * So {@link findAccountsHoldingAddress} can prove an address is AMBIGUOUS and
124
+ * can never prove it is unique, and its callers are written to treat a single
125
+ * answer as "no reason to think otherwise" rather than as a fact.
126
+ *
127
+ * That distinction is the whole reason `subjectUid` is omitted rather than
128
+ * guessed: naming one customer on another customer's data access is worse
129
+ * than naming nobody.
130
+ */ import { createHash } from "crypto";
131
+ import { normalizeAccountEmail } from "@aglyn/aglyn/app-utils/account-emails";
132
+ import { ACCOUNT_EMAILS_SUBCOLLECTION, findAccountByVerifiedAlias } from "./account-emails.js";
133
+ import { findUserByEmailAcrossPools } from "./auth-pools.js";
134
+ import firebaseAdmin from "./firebase-admin.js";
135
+ const defaultFirestore = ()=>firebaseAdmin.app().firestore();
136
+ /** `sha256` of the normalized address. */ export function accountAddressKey(address) {
137
+ return createHash('sha256').update(address).digest('hex');
138
+ }
139
+ /**
140
+ * Every address `uid` holds, from all three sources.
141
+ *
142
+ * `record` is passed in rather than fetched: every caller already has the
143
+ * Auth record in hand, and re-fetching it would add a pooled lookup to a page
144
+ * that has already paid for one.
145
+ *
146
+ * Best-effort per source. A Firestore outage must not make the delivery card
147
+ * fall back to the single-address behaviour SILENTLY — it reports
148
+ * `incomplete` instead, and the callers that destroy data refuse on it.
149
+ *
150
+ * @param detectShared consult the reverse direction for each non-primary
151
+ * address. One extra lookup per address; skip it where the answer is
152
+ * not used.
153
+ */ export async function resolveAccountAddresses(options) {
154
+ var _options_firestore, _ref, _ref1, _ref2;
155
+ const { uid, record } = options;
156
+ const db = (_options_firestore = options.firestore) != null ? _options_firestore : defaultFirestore();
157
+ const found = new Map();
158
+ /** Addresses whose provider-asserted claim was refused. */ const conflicted = new Set();
159
+ let incomplete = false;
160
+ const add = (input, source)=>{
161
+ var _found_get;
162
+ const address = normalizeAccountEmail(input);
163
+ if (address === null) return;
164
+ const sources = (_found_get = found.get(address)) != null ? _found_get : new Set();
165
+ sources.add(source);
166
+ found.set(address, sources);
167
+ };
168
+ const primary = normalizeAccountEmail((_ref = record == null ? void 0 : record.email) != null ? _ref : null);
169
+ add((_ref1 = record == null ? void 0 : record.email) != null ? _ref1 : null, 'primary');
170
+ for (const provider of (_ref2 = record == null ? void 0 : record.providerData) != null ? _ref2 : []){
171
+ var _ref3;
172
+ add((_ref3 = provider == null ? void 0 : provider.email) != null ? _ref3 : null, 'provider');
173
+ }
174
+ try {
175
+ const stored = await db.collection('users').doc(uid).collection(ACCOUNT_EMAILS_SUBCOLLECTION).get();
176
+ for (const doc of stored.docs){
177
+ var _doc_get;
178
+ const address = normalizeAccountEmail((_doc_get = doc.get('address')) != null ? _doc_get : doc.id);
179
+ add(address, 'stored');
180
+ if (address !== null && doc.get('indexConflict') === true) {
181
+ conflicted.add(address);
182
+ }
183
+ }
184
+ } catch (error) {
185
+ // The subcollection is where a FORMER primary lives, so losing it is
186
+ // precisely how the orphaned-history bug comes back. Never silent.
187
+ console.error('[account-addresses] stored addresses unreadable', uid, error);
188
+ incomplete = true;
189
+ }
190
+ // Primary first: it is the address staff recognise, and a card that led
191
+ // with an alias would read as the wrong account.
192
+ const ordered = [
193
+ ...found.keys()
194
+ ].sort((a, b)=>{
195
+ if (a === primary) return -1;
196
+ if (b === primary) return 1;
197
+ return a.localeCompare(b);
198
+ });
199
+ const addresses = [];
200
+ for (const address of ordered){
201
+ var _found_get;
202
+ let shared = false;
203
+ if (options.detectShared === true) {
204
+ try {
205
+ const holders = await findAccountsHoldingAddress(address, {
206
+ firestore: db
207
+ });
208
+ shared = holders.uids.some((holder)=>holder !== uid);
209
+ } catch (unused) {
210
+ // Not `incomplete`: a failed sharing probe under-reports a note on a
211
+ // card. It does not shorten the address list, which is the thing
212
+ // erasure depends on.
213
+ shared = false;
214
+ }
215
+ }
216
+ addresses.push({
217
+ address,
218
+ sources: [
219
+ ...(_found_get = found.get(address)) != null ? _found_get : []
220
+ ],
221
+ key: accountAddressKey(address),
222
+ shared,
223
+ indexConflict: conflicted.has(address)
224
+ });
225
+ }
226
+ return {
227
+ uid,
228
+ primary,
229
+ addresses,
230
+ incomplete
231
+ };
232
+ }
233
+ /** Just the addresses, for a caller that needs nothing else. */ export function addressList(set) {
234
+ return set.addresses.map((entry)=>entry.address);
235
+ }
236
+ /** Just the hashed keys, for a query against a store keyed by them. */ export function addressKeys(set) {
237
+ return set.addresses.map((entry)=>entry.key);
238
+ }
239
+ /**
240
+ * Which accounts hold `address`, as far as can be determined.
241
+ *
242
+ * ## What it can and cannot see
243
+ *
244
+ * It consults the two sources an address can be looked up IN:
245
+ *
246
+ * - Firebase Auth, across pools — the account holding it as its PRIMARY.
247
+ * - `emailIdentityIndex/{address}` — the account holding it as a CONFIRMED
248
+ * alias.
249
+ *
250
+ * It cannot see a `providerData` holder, because Firebase Auth offers no
251
+ * lookup by provider address; that field is readable only from a record you
252
+ * already have. This is not a gap that can be closed by trying harder, and
253
+ * the live shape of it is ordinary: an account whose Google provider address
254
+ * is another account's primary.
255
+ *
256
+ * So the result is sound in ONE direction only. `ambiguous: true` is proof
257
+ * that more than one account holds the address. `ambiguous: false` is the
258
+ * absence of evidence, never evidence of absence — a caller must not treat a
259
+ * single uid as "this address belongs to this person".
260
+ */ export async function findAccountsHoldingAddress(input, options) {
261
+ var _ref;
262
+ const address = normalizeAccountEmail(input);
263
+ if (address === null) return {
264
+ address: '',
265
+ uids: [],
266
+ ambiguous: false
267
+ };
268
+ const db = (_ref = options == null ? void 0 : options.firestore) != null ? _ref : defaultFirestore();
269
+ const uids = new Set();
270
+ try {
271
+ var _pooled_record;
272
+ const pooled = await findUserByEmailAcrossPools(address);
273
+ if (pooled == null ? void 0 : (_pooled_record = pooled.record) == null ? void 0 : _pooled_record.uid) uids.add(pooled.record.uid);
274
+ } catch (unused) {
275
+ // A pool that would not answer leaves the set smaller, which under-reports
276
+ // sharing rather than inventing it.
277
+ }
278
+ try {
279
+ // `account-emails.ts`'s own reverse lookup, not a second read of its
280
+ // index. That function owns what the index means — including that an
281
+ // unverified row is absent from it by construction — and a private copy
282
+ // of the read here would be a second place to keep that in step.
283
+ const alias = await findAccountByVerifiedAlias(address);
284
+ if (alias == null ? void 0 : alias.uid) uids.add(alias.uid);
285
+ } catch (unused) {
286
+ // Same reasoning.
287
+ }
288
+ return {
289
+ address,
290
+ uids: [
291
+ ...uids
292
+ ],
293
+ ambiguous: uids.size > 1
294
+ };
295
+ }
296
+ /**
297
+ * The single account an address can be attributed to, or null.
298
+ *
299
+ * Null for BOTH "nobody" and "more than one", deliberately. The caller writes
300
+ * an audit subject with it, and a row naming the wrong customer is worse than
301
+ * a row naming none: the collection exists to answer "who at Aglyn read my
302
+ * data", and an answer that points at an innocent account is not a weaker
303
+ * answer, it is a false one.
304
+ */ export async function attributableAccountForAddress(input, options) {
305
+ var _holders_uids_;
306
+ const holders = await findAccountsHoldingAddress(input, options);
307
+ if (holders.ambiguous) return null;
308
+ return (_holders_uids_ = holders.uids[0]) != null ? _holders_uids_ : null;
309
+ }
310
+
311
+ //# sourceMappingURL=account-addresses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/account-addresses.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 * EVERY ADDRESS AN ACCOUNT HOLDS — the one answer, for every consumer.\n *\n * ## Why this exists\n *\n * Two stores in this product are keyed by ADDRESS rather than by uid:\n * `emailDeliveries/{sha256(address)}` (the per-recipient delivery log) and\n * `emailSuppressions/{sha256(address)}`. Everything that reached them passed\n * ONE address — the Firebase Auth record's current `email` — and an account\n * is not one address:\n *\n * - A changed primary leaves the old address holding the mail. The new hash\n * names an empty document, so the staff card renders a blank table for a\n * person we demonstrably emailed. That card's own copy warns that reading\n * a blank table as \"we never emailed them\" is how staff mislead a\n * customer; before this module, an email change made the card do exactly\n * that.\n * - A federated provider carries its own address. `providerData[].email` can\n * differ from the primary and is a real mailbox that has received real\n * mail.\n * - `users/{uid}/emails/{address}` (AGL-2486) holds confirmed aliases and\n * every address this account has been moved OFF of.\n *\n * Erasure inherited the same single-address assumption, which is the sharp\n * version of the bug: a prior or secondary address kept its full delivery\n * history — recipient address, subjects, open and click times — after an\n * erasure request had been honoured and reported complete.\n *\n * ## One resolver, not four call sites\n *\n * The delivery-history card, erasure, the audit subject resolution and the\n * suppression lookups all need the same rule, and four copies of it would\n * drift apart the way the dead-status set did. So the rule lives here and\n * every consumer calls in. A caller that wants \"the addresses\" must not\n * re-derive them from a record it happens to be holding.\n *\n * ## Why `verifiedAccountEmails` is not this function\n *\n * `account-emails.ts` already answers a question that reads like this one,\n * and reusing it here would be a correctness bug in both directions. It is\n * built for matching an INVITATION, and its three defining choices are all\n * right for that and wrong for this:\n *\n * - **It returns VERIFIED addresses only.** Correct for an invitation: an\n * unverified address must not match one, or adding an address becomes a\n * way into somebody else's organization. Wrong here, because unverified\n * addresses are precisely where the mail is — a verification message is by\n * definition sent to an address that is not yet verified, and it is the\n * single most common row in the delivery log. Filtering it out would make\n * the card miss the mail a staffer is most often asked about, and would\n * make erasure leave it behind.\n * - **It reads the subcollection alone.** The primary lives in the Firebase\n * Auth record and is mirrored into `users/{uid}/emails` only LAZILY, on\n * the account's first read of its own settings. An account that has never\n * opened that page has an empty subcollection and a real primary, so this\n * would return nothing at all for it.\n * - **It `slice`s to `MAX_ACCOUNT_EMAILS`.** A silent truncation is\n * tolerable when the cost is an invitation not matching. It is the worst\n * available outcome for erasure, which would report success over addresses\n * it never swept.\n *\n * So this module composes over that one's STORAGE — same subcollection, same\n * index, and the reverse lookup is delegated to `findAccountByVerifiedAlias`\n * rather than re-read — and adds the two sources it does not have.\n *\n * ⛔ Do not \"simplify\" this into `verifiedAccountEmails`. The three\n * differences above are each a defect if collapsed.\n *\n * ## THE UNIQUENESS INDEX ONCE HAD A HOLE, AND IT STILL HAS A TAIL\n *\n * `emailIdentityIndex` is what stops two accounts holding one address, and it\n * was claimed in exactly two places: the primary backfill in\n * `listAccountEmails`, and `confirmAccountEmail`. Neither read `providerData`\n * — so an address supplied by a federated provider never reached the index,\n * and two accounts could hold one address without the guard ever seeing them.\n * The live shape of it is ordinary: an account whose Google provider address\n * is another account's primary.\n *\n * `registerProviderAddresses` now claims for provider-asserted addresses at\n * the session mint, so this closes going forward and backfills each account\n * on its next sign-in. Two things follow that this module must keep saying:\n *\n * - **The tail is still open.** An account that has not signed in since is\n * still unregistered, and a collision that already exists is not repaired\n * by a claim that correctly refuses. `tools/scripts/audit-provider-address-claims.mjs`\n * reports both, read-only — resolving a collision means deciding about two\n * real accounts, which is not a decision code makes.\n * - **A refused claim leaves an address held and unindexed.** That is the\n * intended outcome, not a failure, so the reverse direction below is no\n * more complete than it was: it still cannot enumerate provider holders.\n *\n * The registration writes an account-email row and nothing else. It cannot\n * reach SSO provisioning — `sso-jit` reads the address the IdP asserted at\n * sign-in and never consults this store, and\n * `apps/console/specs/account-emails-never-reach-sso.spec.ts` fails if that\n * stops being true. That guard is what makes the write safe.\n *\n * ## The direction that works, and the one that cannot\n *\n * FORWARD (uid → addresses) is complete: all three sources are readable from\n * the uid. {@link resolveAccountAddresses} is that direction.\n *\n * REVERSE (address → uid) is STRUCTURALLY INCOMPLETE and no amount of care\n * here fixes it. Firebase Auth can look an address up as a primary\n * (`getUserByEmail`) and `emailIdentityIndex` answers for confirmed aliases,\n * but there is no query for \"which accounts carry this as a provider\n * address\" — that field is only readable once you already have the record.\n * So {@link findAccountsHoldingAddress} can prove an address is AMBIGUOUS and\n * can never prove it is unique, and its callers are written to treat a single\n * answer as \"no reason to think otherwise\" rather than as a fact.\n *\n * That distinction is the whole reason `subjectUid` is omitted rather than\n * guessed: naming one customer on another customer's data access is worse\n * than naming nobody.\n */\n\nimport { createHash } from 'crypto'\nimport { normalizeAccountEmail } from '@aglyn/aglyn/app-utils/account-emails'\nimport {\n ACCOUNT_EMAILS_SUBCOLLECTION,\n findAccountByVerifiedAlias,\n} from './account-emails'\nimport { findUserByEmailAcrossPools } from './auth-pools'\nimport firebaseAdmin from './firebase-admin'\n\nconst defaultFirestore = () => firebaseAdmin.app().firestore()\n\n/** Where an address came from. An address can have more than one. */\nexport type AccountAddressSource =\n /** The Firebase Auth record's `email` — what a token carries today. */\n | 'primary'\n /** A federated provider's own address, from `providerData[].email`. */\n | 'provider'\n /** A row in `users/{uid}/emails` — a confirmed alias, or a former primary. */\n | 'stored'\n\n/** One address, with everything a consumer needs to decide what to do. */\nexport interface AccountAddress {\n /** Lowercased and trimmed. The form both hashed stores key on. */\n address: string\n /** Every source that named it, so a card can say WHY it is listed. */\n sources: AccountAddressSource[]\n /**\n * `sha256(address)` — the SAME derivation `emailSuppressionKey` uses, so a\n * consumer never has to re-derive it and the two can never disagree about\n * which document describes which person.\n */\n key: string\n /**\n * Another account holds this address too.\n *\n * ⚠️ `false` means \"nothing found\", NOT \"nobody else holds it\" — see the\n * reverse-direction note in this module's header.\n */\n shared: boolean\n /**\n * A provider asserted this address for this account and the uniqueness\n * claim was REFUSED, because another account already held it.\n *\n * Carried onto the staff surface rather than left in a log line. The\n * refusal is the whole mechanism: silently skipping the claim is what let\n * the live collision exist unrecorded, so an account carrying a provider\n * address it does not own has to be visible on its own page.\n */\n indexConflict: boolean\n}\n\n/**\n * ## How big this can get, and why nothing here slices\n *\n * `MAX_ACCOUNT_EMAILS` is 5 and caps the SUBCOLLECTION only; the primary and\n * the provider addresses are additional, so the set can exceed 5. It stays\n * far under Firestore's 30-element `in` limit in every realistic shape, but\n * \"far under\" is not a guarantee and a truncated erasure is the worst outcome\n * available in this whole area — it reports success over addresses it never\n * swept.\n *\n * So the rule is: consumers ITERATE this list, they do not slice it, and any\n * consumer feeding it to an `in` query CHUNKS. No function in this module\n * truncates its result.\n */\nexport interface AccountAddressSet {\n uid: string\n /** The Auth record's current address, or null for an addressless account. */\n primary: string | null\n /** Primary first, then the rest in a stable order. */\n addresses: AccountAddress[]\n /**\n * A source threw and the set may be SHORT.\n *\n * Load-bearing for erasure: erasing \"every address\" from an incomplete set\n * is not erasing every address, and the caller has to be able to say so\n * rather than report a complete erasure it did not perform.\n */\n incomplete: boolean\n}\n\n/** `sha256` of the normalized address. */\nexport function accountAddressKey(address: string): string {\n return createHash('sha256').update(address).digest('hex')\n}\n\n/**\n * Every address `uid` holds, from all three sources.\n *\n * `record` is passed in rather than fetched: every caller already has the\n * Auth record in hand, and re-fetching it would add a pooled lookup to a page\n * that has already paid for one.\n *\n * Best-effort per source. A Firestore outage must not make the delivery card\n * fall back to the single-address behaviour SILENTLY — it reports\n * `incomplete` instead, and the callers that destroy data refuse on it.\n *\n * @param detectShared consult the reverse direction for each non-primary\n * address. One extra lookup per address; skip it where the answer is\n * not used.\n */\nexport async function resolveAccountAddresses(options: {\n uid: string\n /** The Auth record — `email` and `providerData` are read from it. */\n record?: {\n email?: string | null\n providerData?: readonly { email?: string | null }[]\n } | null\n detectShared?: boolean\n firestore?: any\n}): Promise<AccountAddressSet> {\n const { uid, record } = options\n const db = options.firestore ?? defaultFirestore()\n const found = new Map<string, Set<AccountAddressSource>>()\n /** Addresses whose provider-asserted claim was refused. */\n const conflicted = new Set<string>()\n let incomplete = false\n\n const add = (input: unknown, source: AccountAddressSource) => {\n const address = normalizeAccountEmail(input)\n if (address === null) return\n const sources = found.get(address) ?? new Set<AccountAddressSource>()\n sources.add(source)\n found.set(address, sources)\n }\n\n const primary = normalizeAccountEmail(record?.email ?? null)\n add(record?.email ?? null, 'primary')\n for (const provider of record?.providerData ?? []) {\n add(provider?.email ?? null, 'provider')\n }\n\n try {\n const stored = await db\n .collection('users')\n .doc(uid)\n .collection(ACCOUNT_EMAILS_SUBCOLLECTION)\n .get()\n for (const doc of stored.docs) {\n const address = normalizeAccountEmail(doc.get('address') ?? doc.id)\n add(address, 'stored')\n if (address !== null && doc.get('indexConflict') === true) {\n conflicted.add(address)\n }\n }\n } catch (error) {\n // The subcollection is where a FORMER primary lives, so losing it is\n // precisely how the orphaned-history bug comes back. Never silent.\n console.error('[account-addresses] stored addresses unreadable', uid, error)\n incomplete = true\n }\n\n // Primary first: it is the address staff recognise, and a card that led\n // with an alias would read as the wrong account.\n const ordered = [...found.keys()].sort((a, b) => {\n if (a === primary) return -1\n if (b === primary) return 1\n return a.localeCompare(b)\n })\n\n const addresses: AccountAddress[] = []\n for (const address of ordered) {\n let shared = false\n if (options.detectShared === true) {\n try {\n const holders = await findAccountsHoldingAddress(address, {\n firestore: db,\n })\n shared = holders.uids.some((holder) => holder !== uid)\n } catch {\n // Not `incomplete`: a failed sharing probe under-reports a note on a\n // card. It does not shorten the address list, which is the thing\n // erasure depends on.\n shared = false\n }\n }\n addresses.push({\n address,\n sources: [...(found.get(address) ?? [])],\n key: accountAddressKey(address),\n shared,\n indexConflict: conflicted.has(address),\n })\n }\n\n return { uid, primary, addresses, incomplete }\n}\n\n/** Just the addresses, for a caller that needs nothing else. */\nexport function addressList(set: AccountAddressSet): string[] {\n return set.addresses.map((entry) => entry.address)\n}\n\n/** Just the hashed keys, for a query against a store keyed by them. */\nexport function addressKeys(set: AccountAddressSet): string[] {\n return set.addresses.map((entry) => entry.key)\n}\n\nexport interface AddressHolders {\n address: string\n /** Every uid found to hold it. May be SHORT — see below. */\n uids: string[]\n /** More than one distinct account was found. */\n ambiguous: boolean\n}\n\n/**\n * Which accounts hold `address`, as far as can be determined.\n *\n * ## What it can and cannot see\n *\n * It consults the two sources an address can be looked up IN:\n *\n * - Firebase Auth, across pools — the account holding it as its PRIMARY.\n * - `emailIdentityIndex/{address}` — the account holding it as a CONFIRMED\n * alias.\n *\n * It cannot see a `providerData` holder, because Firebase Auth offers no\n * lookup by provider address; that field is readable only from a record you\n * already have. This is not a gap that can be closed by trying harder, and\n * the live shape of it is ordinary: an account whose Google provider address\n * is another account's primary.\n *\n * So the result is sound in ONE direction only. `ambiguous: true` is proof\n * that more than one account holds the address. `ambiguous: false` is the\n * absence of evidence, never evidence of absence — a caller must not treat a\n * single uid as \"this address belongs to this person\".\n */\nexport async function findAccountsHoldingAddress(\n input: unknown,\n options?: { firestore?: any },\n): Promise<AddressHolders> {\n const address = normalizeAccountEmail(input)\n if (address === null) return { address: '', uids: [], ambiguous: false }\n const db = options?.firestore ?? defaultFirestore()\n const uids = new Set<string>()\n\n try {\n const pooled = await findUserByEmailAcrossPools(address)\n if (pooled?.record?.uid) uids.add(pooled.record.uid)\n } catch {\n // A pool that would not answer leaves the set smaller, which under-reports\n // sharing rather than inventing it.\n }\n\n try {\n // `account-emails.ts`'s own reverse lookup, not a second read of its\n // index. That function owns what the index means — including that an\n // unverified row is absent from it by construction — and a private copy\n // of the read here would be a second place to keep that in step.\n const alias = await findAccountByVerifiedAlias(address)\n if (alias?.uid) uids.add(alias.uid)\n } catch {\n // Same reasoning.\n }\n\n return { address, uids: [...uids], ambiguous: uids.size > 1 }\n}\n\n/**\n * The single account an address can be attributed to, or null.\n *\n * Null for BOTH \"nobody\" and \"more than one\", deliberately. The caller writes\n * an audit subject with it, and a row naming the wrong customer is worse than\n * a row naming none: the collection exists to answer \"who at Aglyn read my\n * data\", and an answer that points at an innocent account is not a weaker\n * answer, it is a false one.\n */\nexport async function attributableAccountForAddress(\n input: unknown,\n options?: { firestore?: any },\n): Promise<string | null> {\n const holders = await findAccountsHoldingAddress(input, options)\n if (holders.ambiguous) return null\n return holders.uids[0] ?? null\n}\n"],"names":["createHash","normalizeAccountEmail","ACCOUNT_EMAILS_SUBCOLLECTION","findAccountByVerifiedAlias","findUserByEmailAcrossPools","firebaseAdmin","defaultFirestore","app","firestore","accountAddressKey","address","update","digest","resolveAccountAddresses","options","uid","record","db","found","Map","conflicted","Set","incomplete","add","input","source","sources","get","set","primary","email","provider","providerData","stored","collection","doc","docs","id","error","console","ordered","keys","sort","a","b","localeCompare","addresses","shared","detectShared","holders","findAccountsHoldingAddress","uids","some","holder","push","key","indexConflict","has","addressList","map","entry","addressKeys","ambiguous","pooled","alias","size","attributableAccountForAddress"],"mappings":"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"}
@@ -0,0 +1,219 @@
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 AccountEmail, type PrimaryChangeVerdict } from '@aglyn/aglyn/app-utils/account-emails';
18
+ export declare const ACCOUNT_EMAILS_SUBCOLLECTION = "emails";
19
+ export declare const EMAIL_IDENTITY_INDEX_COLLECTION = "emailIdentityIndex";
20
+ export declare const EMAIL_VERIFICATIONS_COLLECTION = "emailVerifications";
21
+ /**
22
+ * Long enough that a confirmation link survives a mail client's queue and a
23
+ * user who reads mail in the evening; short enough that a link sitting in a
24
+ * forwarded thread stops working. Firebase's own action links use one day.
25
+ */
26
+ export declare const EMAIL_VERIFICATION_TTL_MS: number;
27
+ export interface AccountEmailRow extends AccountEmail {
28
+ /** Null until the round-trip completes. ISO string for the API surface. */
29
+ verifiedAt: string | null;
30
+ }
31
+ /** Every refusal this module can produce, as a stable machine-readable code. */
32
+ export type AccountEmailRefusal = 'invalid-address' | 'cap-reached' | 'already-on-this-account' | 'claimed-by-another-account' | 'unknown-address' | 'cannot-remove-primary' | 'last-verified-address' | 'token-invalid' | 'token-expired' | PrimaryChangeVerdict;
33
+ export interface AccountEmailResult {
34
+ ok: boolean;
35
+ refusal: AccountEmailRefusal | null;
36
+ message: string | null;
37
+ }
38
+ /**
39
+ * Read the account's addresses, seeding the primary row from Firebase Auth on
40
+ * first read.
41
+ *
42
+ * THE BACKFILL IS NOT OPTIONAL. Every account that exists today predates this
43
+ * subcollection, so without the seed an established user opens the card and
44
+ * is told they have no addresses — while `decoded.email` says otherwise — and
45
+ * then "you cannot remove your last verified address" would be enforced
46
+ * against an empty set. The Firebase Auth record is the authority for the
47
+ * primary, so the seed is a mirror rather than a new fact.
48
+ *
49
+ * `authEmailVerified` is carried through honestly: an account that has never
50
+ * confirmed its own sign-up address gets an UNVERIFIED primary row, which is
51
+ * the truth and which the card then offers to fix.
52
+ */
53
+ export declare function listAccountEmails(uid: string, authEmail: string | null | undefined, authEmailVerified: boolean): Promise<AccountEmailRow[]>;
54
+ /**
55
+ * Stage a new address and return the secret for its confirmation link.
56
+ *
57
+ * The address is stored UNVERIFIED and does nothing until the round-trip
58
+ * completes: it is not a sign-in identifier, it receives no account mail, and
59
+ * — importantly — it does NOT claim the uniqueness index. Claiming on `add`
60
+ * would make this endpoint a squatting tool: anyone could park an address
61
+ * they do not own and lock its real owner out of adding it. The index is
62
+ * claimed by {@link confirmAccountEmail}, so the first account to prove
63
+ * delivery wins, and proving delivery is the only way to win.
64
+ *
65
+ * The pre-flight `addressTakenByAnother` here is a courtesy, not the guard —
66
+ * it turns the common case into an immediate, honest error instead of a
67
+ * confirmation email that fails at the end. The real check runs again inside
68
+ * the confirmation transaction, because anything can change in between.
69
+ */
70
+ export declare function addAccountEmail(uid: string, input: unknown): Promise<AccountEmailResult & {
71
+ address: string | null;
72
+ secret: string | null;
73
+ }>;
74
+ /**
75
+ * Mint a single-use confirmation secret and store only its digest.
76
+ *
77
+ * The returned string is `{tokenId}.{secret}`; the caller puts it in the
78
+ * emailed link and never persists it. A reader of the database therefore
79
+ * cannot confirm anybody's address — they hold a SHA-256 of the half that
80
+ * matters.
81
+ */
82
+ export declare function issueVerificationToken(uid: string, address: string): Promise<string>;
83
+ /**
84
+ * Complete the round-trip: the bearer of this link can receive mail at the
85
+ * address, so the address becomes verified and the account claims it.
86
+ *
87
+ * Deliberately NOT authenticated against the current session. The person
88
+ * clicking the link is whoever opened the mailbox, and requiring them to be
89
+ * signed in as the right account first is how confirmation links strand
90
+ * people who read mail on a different device. The token itself carries the
91
+ * uid, and it can do nothing except verify the one address it was minted for.
92
+ *
93
+ * The uniqueness claim happens HERE and inside the same logical step as the
94
+ * flag flip, so two accounts racing on one address cannot both end up
95
+ * verified.
96
+ */
97
+ export declare function confirmAccountEmail(token: unknown): Promise<AccountEmailResult & {
98
+ address: string | null;
99
+ uid: string | null;
100
+ }>;
101
+ /**
102
+ * Remove an address, freeing its index entry.
103
+ *
104
+ * The index entry is released so the address can be used again — on this
105
+ * account or another. Leaving it behind would make removal a way to burn an
106
+ * address permanently, including somebody else's if they ever wanted it.
107
+ */
108
+ export declare function removeAccountEmail(uid: string, input: unknown): Promise<AccountEmailResult>;
109
+ export interface SetPrimaryContext {
110
+ /** `decoded.firebase?.tenant` — the pool the caller signed in through. */
111
+ tenantId: string | null;
112
+ }
113
+ /**
114
+ * Re-designate the primary address.
115
+ *
116
+ * THE MOST SECURITY-SENSITIVE WRITE IN THIS MODULE, because the primary IS
117
+ * the Firebase Auth record's email, which is what `decoded.email` carries,
118
+ * which is what `evaluateSsoDomainPolicy` reads at the session mint and what
119
+ * the invite-accept comparison matches on. Changing it changes an
120
+ * authorization input — so it goes through `evaluatePrimaryChange` first, and
121
+ * that function refuses to move a primary off a domain the deployment
122
+ * requires SSO for. See its docstring for why that refusal ignores the
123
+ * enforcement switch.
124
+ *
125
+ * The Auth record is updated in the account's OWN pool via `authForPool`: a
126
+ * project-level `updateUser` cannot see a GCIP tenant account, so the narrow
127
+ * call would silently fail for exactly the enterprise users this policy is
128
+ * about.
129
+ *
130
+ * `emailVerified: true` is set alongside the address, and it is honest — the
131
+ * only addresses that can reach this point are ones this module verified by
132
+ * an emailed round-trip. Omitting it would flip the account to unverified and
133
+ * lock it out of every route behind `emailUnverifiedResponse()`.
134
+ */
135
+ export declare function setPrimaryAccountEmail(uid: string, input: unknown, context: SetPrimaryContext): Promise<AccountEmailResult>;
136
+ /**
137
+ * The account that holds `address` as a VERIFIED alias, or null.
138
+ *
139
+ * ## Read this before calling it
140
+ *
141
+ * This is the one lookup that maps an arbitrary address to an account, and it
142
+ * exists for exactly two callers: resolving a sign-in identifier, and
143
+ * matching an org invitation. Both are safe for the same reason — neither
144
+ * GRANTS anything on the strength of the address:
145
+ *
146
+ * - a sign-in identifier still has to be followed by the account's password;
147
+ * - an invitation is an explicit grant the ORG made, and this only decides
148
+ * which of the recipient's mailboxes it may arrive at.
149
+ *
150
+ * It must never be wired into an SSO path. `sso-jit` resolves the org from
151
+ * the GCIP tenant on a re-verified token and matches the domain the IdP
152
+ * asserted; giving it this function would let a user add an address and be
153
+ * provisioned into the matching organization, which is the escalation this
154
+ * whole feature was designed around.
155
+ *
156
+ * Returns null for an unverified row by construction: the index is only ever
157
+ * written by the confirmation path.
158
+ */
159
+ export declare function findAccountByVerifiedAlias(input: unknown): Promise<{
160
+ uid: string;
161
+ address: string;
162
+ } | null>;
163
+ /** What one {@link registerProviderAddresses} pass did. */
164
+ export interface ProviderAddressRegistration {
165
+ /** Addresses newly claimed for this account. */
166
+ claimed: string[];
167
+ /** Addresses another account already holds — recorded, never taken. */
168
+ conflicted: string[];
169
+ }
170
+ /**
171
+ * Register the addresses a federated provider asserts for this account.
172
+ *
173
+ * Called from the session mint — the one place every interactive sign-in
174
+ * passes through with a verified token — and BEST-EFFORT by contract.
175
+ *
176
+ * ## Three rules, in priority order
177
+ *
178
+ * 1. **Sign-in must not depend on this.** It is bookkeeping. A person locked
179
+ * out because an index write failed is a worse outcome than the collision
180
+ * it was preventing, so every failure here is swallowed and logged, and
181
+ * the caller runs it off the critical path.
182
+ * 2. **A claim never takes an entry another account holds.**
183
+ * {@link claimIndexEntry} already refuses; what was missing is that the
184
+ * refusal went nowhere. A conflict now lands on the row as
185
+ * `indexConflict`, which the staff account page reads — silently skipping
186
+ * is exactly what produced the live collision.
187
+ * 3. **A conflicted address is stored UNVERIFIED.** This is the conservative
188
+ * half and it matters: `verifiedAccountEmails` feeds invitation matching,
189
+ * so marking a contested address verified on both accounts would make one
190
+ * invitation match two people. Unverified, the row is a record that the
191
+ * address exists on this account and grants nothing.
192
+ *
193
+ * ⛔ It does not merge, reassign or disable anything. Two real accounts
194
+ * sharing an address is a human decision — the row makes it visible and stops
195
+ * there.
196
+ *
197
+ * ⚠️ Registering here CANNOT reach SSO provisioning. `sso-jit` reads the
198
+ * address the IdP asserted at sign-in and never consults this store;
199
+ * `apps/console/specs/account-emails-never-reach-sso.spec.ts` fails if that
200
+ * stops being true. That guard is what makes this safe to write at all.
201
+ */
202
+ export declare function registerProviderAddresses(uid: string, record: {
203
+ email?: string | null;
204
+ providerData?: readonly {
205
+ providerId?: string | null;
206
+ email?: string | null;
207
+ }[];
208
+ } | null): Promise<ProviderAddressRegistration>;
209
+ /**
210
+ * Every VERIFIED address on an account, for matching an invitation.
211
+ *
212
+ * Bounded by {@link MAX_ACCOUNT_EMAILS}, which is what makes it safe to feed
213
+ * straight into a Firestore `in` query (limit 30).
214
+ *
215
+ * ⛔ NOT the answer to "every address this account holds" — it is verified-only,
216
+ * subcollection-only and silently truncating, all correct here and all wrong
217
+ * there. `account-addresses.ts` is that resolver and explains why at length.
218
+ */
219
+ export declare function verifiedAccountEmails(uid: string): Promise<string[]>;