@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,858 @@
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
+ * Personal-data export — access and portability, in a machine-readable form
19
+ * (AGL-1974).
20
+ *
21
+ * Privacy Policy §7 grants portability (GDPR Art. 20) and nothing in the
22
+ * product exported a person's or a workspace's personal data. Six surfaces
23
+ * told customers to "export first" before deleting — the org settings Delete
24
+ * tab, the Close account card, `erase-org-cli.mjs`, the staff org actions,
25
+ * live DPA §11 and Terms §13.3 — all pointing at something that did not
26
+ * exist. An access request was assembled by a staff member reading the console
27
+ * by hand.
28
+ *
29
+ * ## THE PROPERTY THIS FILE EXISTS FOR
30
+ *
31
+ * **An export whose coverage differs from erasure's is a bug in one of them.**
32
+ * Both answer the same question — *what do we hold about this subject* — and
33
+ * the honest failure modes are symmetric: an export that misses a collection
34
+ * under-discloses on an access request, and an erasure that misses the same
35
+ * one leaves data behind after we said it was gone. Historically the second
36
+ * kept happening (AGL-1444, AGL-1448, AGL-1970, AGL-1971 each found a
37
+ * collection outside the cascade), which is exactly the evidence that a list
38
+ * kept in one place and mirrored by hand in another will drift.
39
+ *
40
+ * The ideal is one enumeration both derive from. That is not available here:
41
+ * `erase.ts` is under concurrent change and its sweep list is expressed as
42
+ * code, not data. So the coverage is enumerated ONCE below, as data, and
43
+ * `personal-data-export-coverage.spec.ts` DISCOVERS every collection
44
+ * `erase.ts` touches out of its source and fails when the two disagree. A new
45
+ * sweep added to the erasure turns this file red until somebody decides
46
+ * whether the export discloses it — which is the decision that was being
47
+ * skipped.
48
+ *
49
+ * That guard is worth more than the feature.
50
+ *
51
+ * ## What must NOT come out wholesale
52
+ *
53
+ * Three categories, and each is a way an access request becomes a breach:
54
+ *
55
+ * 1. **Another person's data.** A workspace is shared. A member asking what
56
+ * we hold about THEM gets their own roster row, not the roster; their own
57
+ * support messages, not the thread's other authors. Handled by exporting
58
+ * the person and the workspace as different subjects with different
59
+ * scopes, never by filtering a single blob after the fact.
60
+ * 2. **Secrets.** An `apiKeys` row confirms that a credential exists — its
61
+ * label, scopes, creator and dates, which are all facts about the subject
62
+ * — and never re-discloses the credential. Note the document ID *is* the
63
+ * SHA-256 of the token, so the id itself is withheld too; a hash handed
64
+ * back is a verifier handed back.
65
+ * 3. **Staff-only fields.** Internal notes, override reasons and moderation
66
+ * state are about our handling, not about the person, and several name a
67
+ * staff member.
68
+ *
69
+ * ## What it is NOT
70
+ *
71
+ * Not `GET /api/hosts/export` — that is a Pro+ site-design backup whose own
72
+ * header says it never includes bookings, leads, submissions or secrets, and
73
+ * offering it as a portability answer would be misdescribing it. Not the
74
+ * `firestore-export` cron, which is an unscoped DR snapshot. Not the audit CSV.
75
+ */ import { firebaseAdmin } from "./firebase-admin.js";
76
+ import { CONSOLE_DOMAINS_COLLECTION } from "./console-domains.js";
77
+ const firestoreApp = ()=>firebaseAdmin.app().firestore();
78
+ /**
79
+ * Every collection `erase.ts` touches, and what the export does with it.
80
+ *
81
+ * Kept in the SAME order as the erasure's steps so a reader comparing the two
82
+ * files reads them in step. `personal-data-export-coverage.spec.ts` proves the
83
+ * set matches; this list carries the decision for each one.
84
+ */ export const PERSONAL_DATA_SOURCES = [
85
+ // ── The person ────────────────────────────────────────────────────────
86
+ {
87
+ collection: 'users',
88
+ keyedBy: 'doc-id',
89
+ subjects: [
90
+ 'user'
91
+ ],
92
+ exported: true,
93
+ note: 'Profile: name, phone, postal address, legalAcceptances, and every subcollection (orgs, hostMemberships, notifications, passkeys).'
94
+ },
95
+ {
96
+ collection: 'profiles',
97
+ keyedBy: 'doc-id',
98
+ subjects: [
99
+ 'user'
100
+ ],
101
+ exported: true,
102
+ note: 'The world-readable handle, display name and Stripe Connect account. Not reachable from the user tree (AGL-1970) — the same blind spot on both sides.'
103
+ },
104
+ // ── The workspace ─────────────────────────────────────────────────────
105
+ {
106
+ collection: 'orgs',
107
+ keyedBy: 'doc-id',
108
+ subjects: [
109
+ 'org'
110
+ ],
111
+ exported: true,
112
+ note: 'The org document and every subcollection: members, invites, roles, usage, apiUsage, analytics, datasets/records, contacts, lists, media, installs, activity, assistExchanges, retention.'
113
+ },
114
+ {
115
+ collection: 'hosts',
116
+ keyedBy: 'field',
117
+ subjects: [
118
+ 'org'
119
+ ],
120
+ exported: true,
121
+ note: 'Each site tree the org owns — screens, layouts, orders, form submissions. Secrets within are redacted, not omitted, so the export still says a webhook exists.'
122
+ },
123
+ {
124
+ collection: 'hostIndex',
125
+ keyedBy: 'doc-id',
126
+ subjects: [
127
+ 'org'
128
+ ],
129
+ exported: true,
130
+ note: 'Subdomain/CNAME routing for each of the org’s sites. Configuration the customer set, so it is theirs to take.'
131
+ },
132
+ {
133
+ collection: 'supplierDeliveries',
134
+ keyedBy: 'field',
135
+ subjects: [
136
+ 'org'
137
+ ],
138
+ exported: true,
139
+ note: 'The dropship outbox for each of the org’s sites, keyed by hostId (AGL-1448). Disclosed to the ORG and to no one else, and the reason is the unusual one: the personal data in the row is a SHOPPER’s — name, email and shipping address in the body we sign and POST to the supplier. That is the merchant’s own order record, which they already read in their console and for which they are the controller and we the processor, so an org access request is entitled to it. It is NOT a `user`-subject source: a console member asking what we hold about THEM is not the shopper, and returning these rows would answer their request with a third party’s address. Withholding it was not an option worth taking — `eraseHost` destroys these rows, so an export that omitted them would delete what it never disclosed.'
140
+ },
141
+ // ── Top-level, field-keyed: what a path cascade cannot see ────────────
142
+ {
143
+ collection: 'apiKeys',
144
+ keyedBy: 'field',
145
+ subjects: [
146
+ 'org'
147
+ ],
148
+ exported: true,
149
+ note: 'EXISTENCE ONLY — label, scopes, creating uid, dates. Never the credential, and never the document id, which is the SHA-256 of the token.'
150
+ },
151
+ {
152
+ collection: 'outreachMailboxCredentials',
153
+ keyedBy: 'field',
154
+ subjects: [
155
+ 'org'
156
+ ],
157
+ exported: true,
158
+ note: 'EXISTENCE ONLY — which connected mailbox a credential authorizes, its provider, granted scopes and dates (AGL-2974), and since AGL-2978 the connected account’s address, its Google account id and the member who connected it: the organization’s own record of whose mailbox it holds. Every token is redacted by name and by shape (see redactSecrets) and never disclosed; the sealed refresh token and the id of the key that sealed it both carry `token` in their names. The document id is the mailbox id, not derived from any secret, so it is disclosed.'
159
+ },
160
+ {
161
+ collection: 'ssoDomains',
162
+ keyedBy: 'field',
163
+ subjects: [
164
+ 'org'
165
+ ],
166
+ exported: true,
167
+ note: 'The public SSO routing document: domain, GCIP tenant and provider ids, IdP display name. `token` is redacted.'
168
+ },
169
+ {
170
+ collection: CONSOLE_DOMAINS_COLLECTION,
171
+ keyedBy: 'field',
172
+ subjects: [
173
+ 'org'
174
+ ],
175
+ exported: true,
176
+ note: 'Custom console domain claims the org holds.'
177
+ },
178
+ {
179
+ collection: 'apiIdempotency',
180
+ keyedBy: 'field',
181
+ subjects: [
182
+ 'org'
183
+ ],
184
+ exported: false,
185
+ note: 'Replay keys. The document id is a SHA-256 of the org id and the caller’s Idempotency-Key, and the row names a record id and nothing about a person. Disclosed as a COUNT so the export does not silently omit a collection the erasure destroys.'
186
+ },
187
+ {
188
+ collection: 'stripeCustomers',
189
+ keyedBy: 'field',
190
+ subjects: [
191
+ 'org'
192
+ ],
193
+ exported: true,
194
+ note: 'The local reverse index: which Stripe customer id belongs to this workspace. An identifier the customer already holds on their own invoices.'
195
+ },
196
+ {
197
+ collection: 'orgSlugs',
198
+ keyedBy: 'field',
199
+ subjects: [
200
+ 'org'
201
+ ],
202
+ exported: true,
203
+ note: 'Every workspace name the org has ever held, rename tombstones included — naming history is a fact about them.'
204
+ },
205
+ {
206
+ collection: 'publisherHandles',
207
+ keyedBy: 'field',
208
+ subjects: [
209
+ 'org'
210
+ ],
211
+ exported: true,
212
+ note: 'Marketplace handle reservations and rename tombstones (AGL-1970).'
213
+ },
214
+ {
215
+ collection: 'publisherProfiles',
216
+ keyedBy: 'doc-id',
217
+ subjects: [
218
+ 'org'
219
+ ],
220
+ exported: true,
221
+ note: 'The public publisher identity — handle, display name, bio, website, avatar, publisher agreement. `stripeAccountId` is the org’s own payout destination and is disclosed; nothing here is a credential.'
222
+ },
223
+ {
224
+ collection: 'marketplaceListings',
225
+ keyedBy: 'field',
226
+ subjects: [
227
+ 'org'
228
+ ],
229
+ exported: true,
230
+ note: 'Listings the org published. These deliberately OUTLIVE an erasure (AGL-1448 Tier 3) — which makes disclosing them on an access request more important, not less: they are the part that does not go away.'
231
+ },
232
+ {
233
+ collection: 'supportTickets',
234
+ keyedBy: 'field',
235
+ subjects: [
236
+ 'org',
237
+ 'user'
238
+ ],
239
+ exported: true,
240
+ note: 'For an ORG: every thread and its messages. For a PERSON: only the messages they themselves authored — a thread has other authors, and handing a member the whole conversation would disclose colleagues’ words to them.'
241
+ },
242
+ // ── Retained past erasure on statutory grounds ────────────────────────
243
+ {
244
+ collection: 'platformRevenue',
245
+ keyedBy: 'field',
246
+ subjects: [
247
+ 'org'
248
+ ],
249
+ exported: true,
250
+ note: 'Per-transaction tax filing records. GDPR Art. 17(3)(b) exempts them from ERASURE (AGL-1811); it does not exempt them from ACCESS. If a transaction of theirs is in there it is disclosable — and the export is the only place a customer can see what survived their erasure.'
251
+ },
252
+ {
253
+ collection: 'storefrontTaxCollected',
254
+ keyedBy: 'field',
255
+ subjects: [
256
+ 'org'
257
+ ],
258
+ exported: true,
259
+ note: 'Sales tax charged to shoppers on the org’s storefront (AGL-1904). Same statutory retention, same access obligation.'
260
+ },
261
+ // ── Records of our own handling ───────────────────────────────────────
262
+ {
263
+ collection: 'adminAudit',
264
+ keyedBy: 'field',
265
+ subjects: [
266
+ 'org',
267
+ 'user'
268
+ ],
269
+ exported: false,
270
+ note: 'STAFF-ONLY. Rows name the staff actor, carry override reasons and internal notes, and are the record of our handling rather than data about the subject. `/admin/audit` → Export CSV serves a request that specifically asks for them, after a staff read.'
271
+ }
272
+ ];
273
+ /** Sources this export deliberately withholds, with the reason. */ export const EXPORT_WITHHELD = PERSONAL_DATA_SOURCES.filter((s)=>!s.exported);
274
+ /** Collection names the export claims to cover, for the coverage guard. */ export const EXPORT_COVERED_COLLECTIONS = PERSONAL_DATA_SOURCES.map((source)=>source.collection);
275
+ /**
276
+ * ## WHY THIS IS SHAPE-DRIVEN AND NOT A LIST OF NAMES
277
+ *
278
+ * It used to be a list of names, anchored on separators:
279
+ *
280
+ * /(^|[._-])(secret|token|password|apikey|…)($|[._-])/i
281
+ *
282
+ * That pattern requires a `.`, `_`, `-`, or a string boundary on both sides
283
+ * of the word, so it reads `password_hash` and `webhooks.secret` and misses
284
+ * `supplierToken` and `passwordScrypt` — the exact two fields the AGL-1881
285
+ * review found leaving the platform in a customer's own export file. One is
286
+ * a live bearer credential a supplier authenticates with; the other is a
287
+ * shopper's scrypt password hash. **A denylist of names is always one naming
288
+ * convention behind, and camelCase is the proof.**
289
+ *
290
+ * So the name half is now tokenized rather than separator-anchored, and it is
291
+ * no longer the only gate. Three run, in order, and any of them redacts:
292
+ *
293
+ * * **(A) The name, tokenized.** `supplierToken` splits to
294
+ * `['supplier','token']` the same way `supplier_token` and
295
+ * `SUPPLIER-TOKEN` do, so the convention stops mattering. A name matches
296
+ * on a single secret word or on an adjacent pair (`client secret`).
297
+ * * **(B) The VALUE's shape, whatever the key is called.** This is the half
298
+ * that does not need to know the schema: a PEM block, a JWT, a vendor
299
+ * prefix (`sk_live_`, `whsec_`, `ghp_`, `AKIA`…), a `$2b$`/`$argon2` hash,
300
+ * this repo's own `salt:hash` scrypt form, or an opaque high-entropy
301
+ * string is a credential whether the field is called `supplierToken`,
302
+ * `t`, or `notes`. **Gate B is the "redact unless known safe" default**:
303
+ * a credential-shaped string is withheld unless its key names it as a
304
+ * content identifier (see {@link SAFE_VALUE_WORDS}).
305
+ * * **(C) Credentials carried inside a URL.** `?token=`, `?sig=`,
306
+ * `?key=`, and `https://user:pass@host` are stripped from the URL rather
307
+ * than the URL being blanked, because a media object's `url` field really
308
+ * does carry a live Firebase download token in a query parameter, and the
309
+ * location of the customer's own file is exactly what an access request
310
+ * asks for. Precision beats blanking here.
311
+ *
312
+ * ### Why the KEY half is still deny-based, when the value half is not
313
+ *
314
+ * A true allowlist of field names is not available on this data. The export
315
+ * walks documents whose field names our customers author — canvas nodes,
316
+ * dataset records, custom metadata, form submissions — so an allowlist of
317
+ * names would blank most of the file and turn an access request into a
318
+ * non-answer, which is the under-disclosure this feature exists to stop. The
319
+ * allowlist therefore lives where the vocabulary IS ours: value shapes. An
320
+ * unrecognized VALUE shape is passed through (it is prose, a number, a date,
321
+ * a customer's own content); an unrecognized value shape that looks like a
322
+ * credential is withheld. That is the inversion the review asked for, put
323
+ * where it can hold.
324
+ *
325
+ * ### The one deliberate carve-out
326
+ *
327
+ * `token`/`tokens` is also a metering word — `tokensUsed: 14203` on an
328
+ * assist exchange is a usage count, and blanking it would blank a real
329
+ * disclosure. A credential is a STRING; a count is a number. So a numeric or
330
+ * boolean value under a name that matched only on a metering word passes
331
+ * through. Nothing else in the vocabulary gets that treatment.
332
+ *
333
+ * A false positive costs a redacted field in an export. A false negative
334
+ * costs a credential. Everything below is tuned in that direction.
335
+ */ /**
336
+ * Split a field name into lowercase word tokens, convention-independently.
337
+ *
338
+ * `supplierToken`, `supplier_token`, `SUPPLIER-TOKEN` and `Supplier.Token`
339
+ * all become `['supplier','token']`. Acronym runs split at the last capital
340
+ * (`SSOTokenId` → `['sso','token','id']`) and digits break a word
341
+ * (`apiKey2` → `['api','key','2']`), so a numbered field cannot hide.
342
+ */ export function fieldNameTokens(key) {
343
+ return String(key != null ? key : '').replace(/[^A-Za-z0-9]+/g, ' ').replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([A-Za-z])([0-9])/g, '$1 $2').toLowerCase().split(' ').filter(Boolean);
344
+ }
345
+ /**
346
+ * Words that make a field a credential on their own.
347
+ *
348
+ * Deliberately short. Every entry here is a word that has no innocent
349
+ * meaning on a stored VALUE — `hmac`, `scrypt` and `bcrypt` name the
350
+ * algorithm that produced a verifier, `passphrase` and `credential` name the
351
+ * thing itself. `key` is NOT here and must never be: `publicKey` on a
352
+ * passkey and `sortKey` on a dataset are not secrets, which is what
353
+ * {@link SECRET_WORD_PAIRS} is for.
354
+ */ const SECRET_WORDS = new Set([
355
+ 'secret',
356
+ 'secrets',
357
+ 'password',
358
+ 'passwords',
359
+ 'passwd',
360
+ 'pwd',
361
+ 'passphrase',
362
+ 'token',
363
+ 'tokens',
364
+ 'credential',
365
+ 'credentials',
366
+ 'apikey',
367
+ 'privatekey',
368
+ 'scrypt',
369
+ 'bcrypt',
370
+ 'argon',
371
+ 'argon2',
372
+ 'pbkdf2',
373
+ 'hmac',
374
+ 'otp',
375
+ 'totp',
376
+ 'authorization'
377
+ ]);
378
+ /**
379
+ * Words that are a credential only next to another word.
380
+ *
381
+ * `key` is meaningless alone and decisive after `api`, `private`, `signing`,
382
+ * `secret`, `session` or `encryption`; `token` after `access`/`refresh` is
383
+ * already caught by {@link SECRET_WORDS} and listed here anyway so the pair
384
+ * table reads as the complete statement of the rule.
385
+ */ const SECRET_WORD_PAIRS = new Set([
386
+ 'api key',
387
+ 'private key',
388
+ 'signing key',
389
+ 'secret key',
390
+ 'session key',
391
+ 'encryption key',
392
+ 'client secret',
393
+ 'access token',
394
+ 'refresh token',
395
+ 'webhook secret',
396
+ 'shared secret',
397
+ // A Stripe payment link is a live bearer URL: whoever holds it can pay.
398
+ // Named in the AGL-1443 inventory of what the old dump actually carried.
399
+ 'payment link',
400
+ 'auth code',
401
+ 'recovery code',
402
+ 'backup code'
403
+ ]);
404
+ /**
405
+ * The metering carve-out — see the header. A name that matched ONLY on one
406
+ * of these, over a number or a boolean, is a count and is disclosed.
407
+ */ const METERING_WORDS = new Set([
408
+ 'token',
409
+ 'tokens'
410
+ ]);
411
+ /**
412
+ * Key words that mark a high-entropy value as a content identifier rather
413
+ * than a credential, defusing gate (B) only.
414
+ *
415
+ * A SHA-256 has exactly the shape of a 64-character secret and is not one.
416
+ * Gate (A) is never defused by this list: `apiKeyId` is redacted, because
417
+ * the name says credential and an id of a credential is not worth the
418
+ * argument.
419
+ */ const SAFE_VALUE_WORDS = new Set([
420
+ // A passkey's `publicKey` is public by definition and has exactly the shape
421
+ // of a secret. Safe to list because gate (A) is never defused here — a
422
+ // `publicToken` is still redacted, on the strength of `token`.
423
+ 'public',
424
+ 'id',
425
+ 'ids',
426
+ 'uid',
427
+ 'uuid',
428
+ 'guid',
429
+ 'hash',
430
+ 'hashes',
431
+ 'sha',
432
+ 'sha1',
433
+ 'sha256',
434
+ 'sha512',
435
+ 'md5',
436
+ 'crc',
437
+ 'crc32c',
438
+ 'etag',
439
+ 'checksum',
440
+ 'digest',
441
+ 'fingerprint',
442
+ 'generation',
443
+ 'version',
444
+ 'revision',
445
+ 'slug',
446
+ 'path',
447
+ 'url',
448
+ 'uri',
449
+ 'href',
450
+ 'src',
451
+ 'color',
452
+ 'colour',
453
+ 'hex'
454
+ ]);
455
+ /** Vendor credential prefixes — unambiguous, and worth naming explicitly. */ const CREDENTIAL_PREFIXES = [
456
+ 'sk_live_',
457
+ 'sk_test_',
458
+ 'rk_live_',
459
+ 'rk_test_',
460
+ 'whsec_',
461
+ 'ghp_',
462
+ 'gho_',
463
+ 'ghu_',
464
+ 'ghs_',
465
+ 'ghr_',
466
+ 'github_pat_',
467
+ 'xoxb-',
468
+ 'xoxp-',
469
+ 'xapp-',
470
+ 'xoxa-',
471
+ 'AKIA',
472
+ 'ASIA',
473
+ 'AIza',
474
+ 'ya29.',
475
+ 'SG.',
476
+ 're_',
477
+ 'shpat_',
478
+ 'shpss_',
479
+ 'npm_',
480
+ 'glpat-',
481
+ 'sq0csp-',
482
+ 'sq0atp-'
483
+ ];
484
+ /** A PEM block that carries a private key or a certificate request. */ const PEM_PATTERN = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/;
485
+ /** Modular crypt format: bcrypt, argon2, scrypt, PBKDF2, sha512-crypt. */ const CRYPT_HASH_PATTERN = /^\$(2[abxy]?|argon2[a-z]*|scrypt|pbkdf2[^$]*|[56])\$/;
486
+ /** This repo's own scrypt storage form: `{16+ hex salt}:{32+ hex hash}`. */ const SALTED_HASH_PATTERN = /^[0-9a-f]{16,}:[0-9a-f]{32,}$/i;
487
+ /** A JSON Web Token — three base64url segments. */ const JWT_PATTERN = /^[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}$/;
488
+ /** A UUID, which is an identifier far more often than it is a credential. */ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
489
+ /** Charset of an opaque credential: base64, base64url or hex, nothing else. */ const OPAQUE_CHARSET = /^[A-Za-z0-9_+/=-]+$/;
490
+ /**
491
+ * The window an opaque secret lives in.
492
+ *
493
+ * Below 32 characters the false-positive rate against ordinary identifiers is
494
+ * not worth it and the value is too short to be a modern credential anyway.
495
+ * Above 512 it is customer CONTENT — a base64 image, a compressed canvas
496
+ * blob, an encoded document — and blanking those is the "gutting the export"
497
+ * failure this file is explicitly trying not to commit.
498
+ */ const OPAQUE_MIN_LENGTH = 32;
499
+ const OPAQUE_MAX_LENGTH = 512;
500
+ /** Shannon entropy per character. Prose and slugs sit well below this. */ const OPAQUE_MIN_ENTROPY = 3.2;
501
+ function shannonEntropyPerChar(text) {
502
+ const counts = new Map();
503
+ for (const character of text){
504
+ var _counts_get;
505
+ counts.set(character, ((_counts_get = counts.get(character)) != null ? _counts_get : 0) + 1);
506
+ }
507
+ let entropy = 0;
508
+ for (const count of counts.values()){
509
+ const p = count / text.length;
510
+ entropy -= p * Math.log2(p);
511
+ }
512
+ return entropy;
513
+ }
514
+ /**
515
+ * Does this STRING look like a credential, ignoring what it is called?
516
+ *
517
+ * Exported because the guard spec asserts each limb independently — a shape
518
+ * detector tested only through the redactor is a detector whose gaps are
519
+ * invisible.
520
+ */ export function looksLikeCredential(value) {
521
+ const text = value.trim();
522
+ if (!text) return false;
523
+ if (PEM_PATTERN.test(text)) return true;
524
+ if (CRYPT_HASH_PATTERN.test(text)) return true;
525
+ if (SALTED_HASH_PATTERN.test(text)) return true;
526
+ if (CREDENTIAL_PREFIXES.some((prefix)=>text.startsWith(prefix))) return true;
527
+ if (JWT_PATTERN.test(text)) return true;
528
+ // Anything with whitespace is prose, and `data:` is an inline asset. Both
529
+ // are customer content, and neither is a bearer credential.
530
+ if (/\s/.test(text)) return false;
531
+ if (/^data:/i.test(text)) return false;
532
+ if (UUID_PATTERN.test(text)) return false;
533
+ if (text.length < OPAQUE_MIN_LENGTH || text.length > OPAQUE_MAX_LENGTH) {
534
+ return false;
535
+ }
536
+ if (!OPAQUE_CHARSET.test(text)) return false;
537
+ return shannonEntropyPerChar(text) >= OPAQUE_MIN_ENTROPY;
538
+ }
539
+ /** What a stripped URL parameter reads as, so the removal is visible. */ export const REDACTED_URL_PARAM = 'REDACTED';
540
+ /** Query parameters that carry a bearer credential inside a URL. */ const URL_CREDENTIAL_PARAMS = new Set([
541
+ 'token',
542
+ 'access_token',
543
+ 'accesstoken',
544
+ 'id_token',
545
+ 'refresh_token',
546
+ 'auth',
547
+ 'authorization',
548
+ 'apikey',
549
+ 'api_key',
550
+ 'key',
551
+ 'sig',
552
+ 'signature',
553
+ 'secret',
554
+ 'password',
555
+ 'downloadtoken'
556
+ ]);
557
+ /**
558
+ * Strip the credential out of a URL, keeping the URL.
559
+ *
560
+ * A media object's `url` is `…/o/{path}?alt=media&token={uuid}` — a live,
561
+ * unauthenticated download credential, in a field an access request has a
562
+ * real interest in. Blanking the whole value would tell the customer nothing
563
+ * about where their own file is; keeping it whole hands out the credential.
564
+ * So the parameter is replaced and the rest survives. `user:pass@` userinfo
565
+ * goes the same way.
566
+ *
567
+ * Returns the input unchanged when it is not a URL — every non-URL string
568
+ * has already been decided by the shape gate.
569
+ */ export function scrubUrlCredentials(value) {
570
+ if (!/^https?:\/\//i.test(value)) return value;
571
+ let url;
572
+ try {
573
+ url = new URL(value);
574
+ } catch (unused) {
575
+ return value;
576
+ }
577
+ let changed = false;
578
+ if (url.username || url.password) {
579
+ url.username = '';
580
+ url.password = '';
581
+ changed = true;
582
+ }
583
+ for (const name of Array.from(url.searchParams.keys())){
584
+ if (!URL_CREDENTIAL_PARAMS.has(name.toLowerCase())) continue;
585
+ if (!url.searchParams.get(name)) continue;
586
+ url.searchParams.set(name, REDACTED_URL_PARAM);
587
+ changed = true;
588
+ }
589
+ return changed ? url.toString() : value;
590
+ }
591
+ /**
592
+ * Gate (A): does the NAME say credential?
593
+ *
594
+ * `value` is consulted only for the metering carve-out described in the
595
+ * header — a `tokensUsed: 14203` is a count, not a credential.
596
+ */ export function secretByName(key, value) {
597
+ const tokens = fieldNameTokens(key);
598
+ if (!tokens.length) return false;
599
+ const hits = tokens.filter((token)=>SECRET_WORDS.has(token));
600
+ for(let index = 0; index + 1 < tokens.length; index += 1){
601
+ if (SECRET_WORD_PAIRS.has(`${tokens[index]} ${tokens[index + 1]}`)) {
602
+ return true;
603
+ }
604
+ }
605
+ if (!hits.length) return false;
606
+ // Matched only on a metering word, over a number or a boolean: a count.
607
+ const metering = hits.every((token)=>METERING_WORDS.has(token));
608
+ if (metering && (typeof value === 'number' || typeof value === 'boolean')) {
609
+ return false;
610
+ }
611
+ return true;
612
+ }
613
+ /** Gate (B)'s defuser: does the NAME say this high-entropy value is an id? */ function nameMarksContentIdentifier(key) {
614
+ return fieldNameTokens(key).some((token)=>SAFE_VALUE_WORDS.has(token));
615
+ }
616
+ const redactedMarker = (present)=>({
617
+ redacted: true,
618
+ present,
619
+ reason: 'secret'
620
+ });
621
+ /**
622
+ * Deep-copy a document, replacing every credential-shaped value with a
623
+ * presence marker.
624
+ *
625
+ * Presence rather than deletion, because "we hold a webhook secret for you"
626
+ * is itself a true and disclosable fact, and a silently-absent key would make
627
+ * the export look like it holds less than it does — the under-disclosure this
628
+ * feature exists to stop, reintroduced by the safety measure.
629
+ *
630
+ * Firestore values that are not plain JSON (Timestamp, GeoPoint, DocumentRef,
631
+ * Buffer) are stringified rather than walked: they carry no nested keys to
632
+ * redact, and `JSON.stringify` on a Timestamp yields `{_seconds,_nanoseconds}`,
633
+ * which is not a date to a human reading the file.
634
+ *
635
+ * Three gates, any of which redacts — the name (A), the value's shape (B),
636
+ * and a credential embedded in a URL (C). See the header above
637
+ * {@link fieldNameTokens} for why the value half is the one that carries the
638
+ * "withhold unless known safe" default.
639
+ */ export function redactSecrets(value, key = '') {
640
+ // (A) The name, tokenized — convention-independent.
641
+ if (key && secretByName(key, value)) {
642
+ return redactedMarker(value !== undefined && value !== null && value !== '');
643
+ }
644
+ if (value === null || value === undefined) return value != null ? value : null;
645
+ if (typeof value === 'string') {
646
+ // (C) A URL that carries its credential in a parameter keeps the URL and
647
+ // loses the credential — see {@link scrubUrlCredentials}.
648
+ const scrubbed = scrubUrlCredentials(value);
649
+ if (scrubbed !== value) return scrubbed;
650
+ // (B) The shape, whatever the field is called. Withheld unless the name
651
+ // marks it as a content identifier.
652
+ if (looksLikeCredential(value) && !nameMarksContentIdentifier(key)) {
653
+ return redactedMarker(true);
654
+ }
655
+ return value;
656
+ }
657
+ if (Array.isArray(value)) return value.map((entry)=>redactSecrets(entry, key));
658
+ if (typeof value === 'object') {
659
+ const source = value;
660
+ // Firestore Timestamp — the only non-plain value common enough to be
661
+ // worth rendering rather than dumping.
662
+ if (typeof source.toDate === 'function') {
663
+ try {
664
+ return source.toDate().toISOString();
665
+ } catch (unused) {
666
+ return String(value);
667
+ }
668
+ }
669
+ if (typeof source.toBase64 === 'function') return '[binary]';
670
+ const out = {};
671
+ for (const [childKey, childValue] of Object.entries(source)){
672
+ out[childKey] = redactSecrets(childValue, childKey);
673
+ }
674
+ return out;
675
+ }
676
+ return value;
677
+ }
678
+ /** Data we hold outside Firestore — named, because a file that omits them reads as complete. */ const ELSEWHERE = [
679
+ 'Stripe — customer object, payment methods, invoices. Request from Stripe or ask us.',
680
+ 'Resend — transactional email delivery logs.',
681
+ 'Google Analytics — 14-month retention, not keyed to an account.',
682
+ 'Cloud Storage — uploaded media and avatars, under users/{uid}/, orgs/{orgId}/ and hosts/{hostId}/. Listed by path in this file; the bytes are served by their own URLs.'
683
+ ];
684
+ function sourcesFor(subject) {
685
+ return PERSONAL_DATA_SOURCES.filter((source)=>source.subjects.includes(subject));
686
+ }
687
+ /** Read a top-level collection bounded by a field equal to the subject id. */ async function readByField(db, collection, field, id, options = {}) {
688
+ const rows = await db.collection(collection).where(field, '==', id).get();
689
+ return rows.docs.map((doc)=>_extends({}, options.includeIds === false ? {} : {
690
+ id: doc.id
691
+ }, {
692
+ data: redactSecrets(doc.data())
693
+ }));
694
+ }
695
+ /** Read one document by id, or nothing when it does not exist. */ async function readDoc(db, collection, id) {
696
+ const snapshot = await db.collection(collection).doc(id).get();
697
+ if (!snapshot.exists) return [];
698
+ return [
699
+ {
700
+ id,
701
+ data: redactSecrets(snapshot.data())
702
+ }
703
+ ];
704
+ }
705
+ /**
706
+ * Walk a document and everything beneath it.
707
+ *
708
+ * `listCollections()` rather than a hard-coded subcollection list, for the
709
+ * same reason the erasure uses `recursiveDelete`: a subcollection added next
710
+ * quarter is reached without anybody remembering this file exists. Bounded by
711
+ * `maxDepth` because the tree is customer-shaped and an unbounded recursion
712
+ * inside a request is how AGL-1455 lost erasures to a 60-second timeout.
713
+ */ async function readSubtree(db, ref, depth = 0, maxDepth = 4) {
714
+ var _ref;
715
+ const snapshot = await ref.get();
716
+ const found = [];
717
+ if (snapshot.exists) {
718
+ found.push({
719
+ path: ref.path,
720
+ data: redactSecrets(snapshot.data())
721
+ });
722
+ }
723
+ if (depth >= maxDepth) return found;
724
+ const children = (_ref = await (ref.listCollections == null ? void 0 : ref.listCollections.call(ref))) != null ? _ref : [];
725
+ for (const child of children){
726
+ const rows = await child.get();
727
+ for (const doc of rows.docs){
728
+ found.push(...await readSubtree(db, doc.ref, depth + 1, maxDepth));
729
+ }
730
+ }
731
+ return found;
732
+ }
733
+ /**
734
+ * Everything we hold about a PERSON, as the person themselves may ask for it
735
+ * (GDPR Art. 15 and Art. 20).
736
+ *
737
+ * Scoped to them, never to the workspaces they belong to. Their membership row
738
+ * in each org is theirs; the roster around it is their colleagues'. An org
739
+ * export is a separate call with a separate authorization, which is the whole
740
+ * reason the two are different functions rather than one with a flag.
741
+ */ export async function exportUserData(uid, options = {}) {
742
+ var _options_firestore, _options_maxDepth;
743
+ const db = (_options_firestore = options.firestore) != null ? _options_firestore : firestoreApp();
744
+ const data = {};
745
+ const counts = {};
746
+ // The profile tree — `users/{uid}` and every subcollection under it.
747
+ data['users'] = await readSubtree(db, db.collection('users').doc(uid), 0, (_options_maxDepth = options.maxDepth) != null ? _options_maxDepth : 4);
748
+ // The public identity, which the user tree does not reach (AGL-1970).
749
+ data['profiles'] = await readDoc(db, 'profiles', uid);
750
+ // ONLY the messages this person wrote. The threads belong to a workspace
751
+ // and carry other people's words; handing a member the whole conversation
752
+ // to answer their own access request is a disclosure nobody consented to.
753
+ const messages = await db.collectionGroup('messages').where('authorId', '==', uid).get().catch(()=>({
754
+ docs: []
755
+ }));
756
+ data['supportTickets'] = messages.docs.map((doc)=>({
757
+ path: doc.ref.path,
758
+ data: redactSecrets(doc.data())
759
+ }));
760
+ return {
761
+ subject: {
762
+ type: 'user',
763
+ id: uid
764
+ },
765
+ generatedAt: new Date().toISOString(),
766
+ coverage: sourcesFor('user'),
767
+ data,
768
+ counts,
769
+ elsewhere: ELSEWHERE
770
+ };
771
+ }
772
+ /**
773
+ * Everything we hold about a WORKSPACE, for its owner (Art. 15 for the
774
+ * personal data in it, and the "export before you delete" the Delete tab,
775
+ * DPA §11 and Terms §13.3 have been promising).
776
+ *
777
+ * Every top-level collection the erasure sweeps by `orgId` is read here by the
778
+ * same bound, so the two see the same rows. Where a source is withheld, its
779
+ * COUNT is still reported: a collection that appears in neither the data nor
780
+ * the counts is indistinguishable from one that does not exist, and that
781
+ * ambiguity is what makes an incomplete export undetectable.
782
+ */ export async function exportOrgData(orgId, options = {}) {
783
+ var _options_firestore, _options_maxDepth;
784
+ const db = (_options_firestore = options.firestore) != null ? _options_firestore : firestoreApp();
785
+ const data = {};
786
+ const counts = {};
787
+ data['orgs'] = await readSubtree(db, db.collection('orgs').doc(orgId), 0, (_options_maxDepth = options.maxDepth) != null ? _options_maxDepth : 4);
788
+ // Sites: the routing entry and the whole tree for each.
789
+ const hosts = await db.collection('hosts').where('orgId', '==', orgId).get();
790
+ const hostDocs = [];
791
+ const routing = [];
792
+ const outbox = [];
793
+ for (const host of hosts.docs){
794
+ var _options_maxDepth1;
795
+ hostDocs.push(...await readSubtree(db, host.ref, 0, (_options_maxDepth1 = options.maxDepth) != null ? _options_maxDepth1 : 4));
796
+ routing.push(...await readDoc(db, 'hostIndex', host.id));
797
+ // Keyed by `hostId`, not `orgId`, so it is read here rather than in the
798
+ // field-keyed loop below — the same bound `eraseHostSupplierDeliveries`
799
+ // sweeps by, so the export and the erasure see the same rows.
800
+ outbox.push(...await readByField(db, 'supplierDeliveries', 'hostId', host.id));
801
+ }
802
+ data['hosts'] = hostDocs;
803
+ data['hostIndex'] = routing;
804
+ data['supplierDeliveries'] = outbox;
805
+ // The field-keyed collections a path cascade is blind to — the ones the
806
+ // erasure had to grow a sweep for, one issue at a time.
807
+ for (const collection of [
808
+ 'ssoDomains',
809
+ CONSOLE_DOMAINS_COLLECTION,
810
+ 'stripeCustomers',
811
+ 'orgSlugs',
812
+ 'publisherHandles',
813
+ 'platformRevenue',
814
+ 'storefrontTaxCollected'
815
+ ]){
816
+ data[collection] = await readByField(db, collection, 'orgId', orgId);
817
+ }
818
+ // Existence, never the credential — and not the id either, which is the
819
+ // token's SHA-256.
820
+ data['apiKeys'] = await readByField(db, 'apiKeys', 'orgId', orgId, {
821
+ includeIds: false
822
+ });
823
+ // Which mailboxes the reps connected, never the grant behind one: the
824
+ // tokens are withheld by `redactSecrets` like every other read here. The id
825
+ // is the mailbox's own, so unlike `apiKeys` it stays.
826
+ data['outreachMailboxCredentials'] = await readByField(db, 'outreachMailboxCredentials', 'orgId', orgId);
827
+ data['publisherProfiles'] = await readDoc(db, 'publisherProfiles', orgId);
828
+ data['marketplaceListings'] = await readByField(db, 'marketplaceListings', 'profileId', orgId);
829
+ // Support threads WITH their messages — for an org the whole thread is the
830
+ // org's own record, unlike the person-scoped export above.
831
+ const tickets = await db.collection('supportTickets').where('orgId', '==', orgId).get();
832
+ const ticketDocs = [];
833
+ for (const ticket of tickets.docs){
834
+ ticketDocs.push(...await readSubtree(db, ticket.ref, 0, 2));
835
+ }
836
+ data['supportTickets'] = ticketDocs;
837
+ // Withheld, but counted — see the note on this function.
838
+ const replayKeys = await db.collection('apiIdempotency').where('orgId', '==', orgId).get();
839
+ counts['apiIdempotency'] = replayKeys.docs.length;
840
+ return {
841
+ subject: {
842
+ type: 'org',
843
+ id: orgId
844
+ },
845
+ generatedAt: new Date().toISOString(),
846
+ coverage: sourcesFor('org'),
847
+ data,
848
+ counts,
849
+ elsewhere: ELSEWHERE
850
+ };
851
+ }
852
+ /** A filename a human can file: subject, id and the date it was produced. */ export function exportFilename(exported) {
853
+ const day = exported.generatedAt.slice(0, 10);
854
+ return `aglyn-${exported.subject.type}-data-${exported.subject.id}-${day}.json`;
855
+ }
856
+ export default exportUserData;
857
+
858
+ //# sourceMappingURL=personal-data-export.js.map