@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,161 @@
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
+ /** The custom-metadata key Firebase reads the raw download token from. */
18
+ export declare const DOWNLOAD_TOKEN_METADATA_KEY = "firebaseStorageDownloadTokens";
19
+ /**
20
+ * Per-prefix object cap. Well past any real library; a scope somehow larger
21
+ * rotates this many and reports `truncated` rather than running unbounded
22
+ * inside a request.
23
+ */
24
+ export declare const MAX_ROTATED_OBJECTS = 5000;
25
+ /** Wall-clock budget per prefix, comfortably inside the route's own limit. */
26
+ export declare const ROTATION_BUDGET_MS = 20000;
27
+ export interface DownloadTokenRotationResult {
28
+ /** Storage prefix scanned, e.g. `orgs/acme/`. */
29
+ prefix: string;
30
+ /** Objects listed under the prefix. */
31
+ scanned: number;
32
+ /** Objects that carried a token and got a new one. */
33
+ rotated: number;
34
+ /** Objects that carried a token and whose rewrite failed. */
35
+ failed: number;
36
+ /** Whether a bound stopped the scan before the prefix was exhausted. */
37
+ truncated: boolean;
38
+ ok: boolean;
39
+ reason: string;
40
+ }
41
+ /**
42
+ * Does this lock revoke raw download URLs?
43
+ *
44
+ * Deliberately a pure predicate over the lock's own two fields, exported so
45
+ * the lockdown core and its specs test the SAME rule rather than two copies
46
+ * that can drift. See narrowing (1) above for why the answer is this narrow.
47
+ */
48
+ export declare function lockRotatesDownloadTokens(lock: {
49
+ reason?: string;
50
+ mode?: 'full' | 'read-only';
51
+ }): boolean;
52
+ /**
53
+ * Rewrite the download token on every already-tokened object under one
54
+ * Storage prefix.
55
+ *
56
+ * Fail-soft per object: one 403 on one file must not abandon the rest of a
57
+ * security revocation. Failures are counted and reported, never thrown —
58
+ * the caller (a lock that has already been applied) has nothing useful to
59
+ * do with an exception, but the audit row wants the number.
60
+ */
61
+ export declare function rotateDownloadTokensUnderPrefix(options: {
62
+ prefix: string;
63
+ /** Injectable for tests; defaults to the configured media bucket. */
64
+ bucket?: any;
65
+ maxObjects?: number;
66
+ budgetMs?: number;
67
+ now?: () => number;
68
+ }): Promise<DownloadTokenRotationResult>;
69
+ /** What happened to ONE object's token. See {@link rotateDownloadTokenForObject}. */
70
+ export interface ObjectTokenRotation {
71
+ /** The token was rewritten, so the published raw URL is now dead. */
72
+ rotated: boolean;
73
+ /**
74
+ * Why not, when it was not. `no-token` is the common and CORRECT case —
75
+ * the object had no raw URL to revoke. `error` and `not-configured` are
76
+ * infrastructure; `no-path` is a document with no object behind it.
77
+ */
78
+ reason: 'rotated' | 'no-token' | 'no-path' | 'not-configured' | 'error';
79
+ }
80
+ /**
81
+ * Revoke ONE object's raw download URL (AGL-1615).
82
+ *
83
+ * The per-asset counterpart to the prefix scan above, for asset quarantine
84
+ * rather than a scope lockdown — and it closes a hole that is not a caching
85
+ * residual but an un-gated origin.
86
+ *
87
+ * `serveMediaCdn` consults the quarantine deny list because that route is
88
+ * ours. The media document's `url` field is not: it points at
89
+ * `firebasestorage.googleapis.com`, where no code of ours runs, and the
90
+ * console DAM and `GET /api/v1/media` both fall back to it whenever an asset
91
+ * has no `cdnPath` — every free-tier workspace, every private asset, every
92
+ * upload predating AGL-829. For those, a quarantine was not slow; it was
93
+ * never consulted, and a DMCA'd file kept serving to anyone holding the link.
94
+ *
95
+ * Same three narrowings as the prefix scan, for the same reasons, and one
96
+ * addition:
97
+ *
98
+ * * **Only an object that ALREADY carries a token is touched.** Writing one
99
+ * where none existed would MINT a public raw URL for a file that never
100
+ * had one — creating the exposure this exists to remove.
101
+ * * **The existing custom-metadata map is read and respread**, because
102
+ * `setMetadata` replaces the whole map and a takedown must not also be a
103
+ * silent erasure of customer-set metadata.
104
+ * * **Fail soft.** A Storage outage must not become a takedown outage: the
105
+ * deny-list entry is the primary control and lands regardless. The caller
106
+ * reports what actually happened rather than assuming success.
107
+ * * **There is no un-rotate, and none is offered.** Releasing a quarantine
108
+ * does not mint a fresh token: that would hand back a WORKING raw URL
109
+ * under a new value while every embed of the old one stays broken — it
110
+ * helps nobody and re-publishes the bytes. The irreversibility is real
111
+ * and is stated to the operator before they act, not discovered after.
112
+ */
113
+ export declare function rotateDownloadTokenForObject(options: {
114
+ storagePath?: string | null;
115
+ /** Injectable for tests; defaults to the configured media bucket. */
116
+ bucket?: any;
117
+ }): Promise<ObjectTokenRotation>;
118
+ /**
119
+ * The object's CURRENT raw download URL, rebuilt from the token it already
120
+ * carries (AGL-1881).
121
+ *
122
+ * The publish half of `POST /api/media/folders {action:'set-private'}`.
123
+ * Turning an asset private now rotates its download token and drops the
124
+ * media document's `url`, so publishing it again has to put a working `url`
125
+ * back — for a free-tier workspace, or any asset predating AGL-829, that
126
+ * field is the ONLY delivery path, and leaving it deleted would make
127
+ * "Publish" a one-way door.
128
+ *
129
+ * **This mints nothing.** It reads the token the object already has and
130
+ * reassembles the URL from it, so:
131
+ *
132
+ * * every URL handed out before the asset went private stays dead — the
133
+ * rotation is not undone, only re-read;
134
+ * * an object that never carried a token still does not get one, which is
135
+ * the same narrowing {@link rotateDownloadTokenForObject} makes and for
136
+ * the same reason: writing one would MINT a public URL for a file that
137
+ * never had one.
138
+ *
139
+ * Returns `null` when there is nothing to rebuild, so the caller writes no
140
+ * `url` rather than an empty one.
141
+ */
142
+ export declare function currentDownloadUrlForObject(options: {
143
+ storagePath?: string | null;
144
+ /** Injectable for tests; defaults to the configured media bucket. */
145
+ bucket?: any;
146
+ }): Promise<string | null>;
147
+ /**
148
+ * Rotate across every prefix a lock scope owns.
149
+ *
150
+ * An ORG lock passes `orgs/{orgId}/` AND `hosts/{hostId}/` for each of its
151
+ * sites: org-scope and host-scope libraries are separate Storage trees
152
+ * (`MediaScope.base`), and a lock that cleaned only the org tree would
153
+ * leave every site's own assets serving — which is most of them.
154
+ */
155
+ export declare function rotateScopeDownloadTokens(options: {
156
+ prefixes: readonly string[];
157
+ bucket?: any;
158
+ maxObjects?: number;
159
+ budgetMs?: number;
160
+ now?: () => number;
161
+ }): Promise<DownloadTokenRotationResult[]>;
@@ -0,0 +1,386 @@
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
+ * Raw-download-URL revocation for a security lockdown (AGL-1526).
19
+ *
20
+ * THE HOLE THIS CLOSES. AGL-1520 put a lockdown gate on the media CDN
21
+ * (`mediaCdnServeBlock`), and that gate works because the CDN path runs OUR
22
+ * code: a locked scope gets a 410. But a media object can also be addressed
23
+ * directly at Google's edge:
24
+ *
25
+ * https://firebasestorage.googleapis.com/v0/b/{bucket}/o/{path}
26
+ * ?alt=media&token={firebaseStorageDownloadTokens}
27
+ *
28
+ * That URL is the free-tier / legacy delivery path — every upload route
29
+ * still mints one and stores it on the media doc's `url`. **No code of ours
30
+ * runs on that origin.** A lock cannot refuse it, a revalidation cannot
31
+ * evict it, and the tenant middleware never sees it. It serves the locked
32
+ * org's bytes to anyone holding the link until the token dies.
33
+ *
34
+ * The only thing that kills it is the token itself, which lives in the
35
+ * object's custom metadata. Rewrite that entry and Google's edge starts
36
+ * answering the old URL with 403 — immediately, with no cache of ours in
37
+ * the path and nothing to revalidate.
38
+ *
39
+ * KNOWN CONSEQUENCE, not a bug: the media doc's own `url` field still holds
40
+ * the OLD raw URL after rotation, so it is stale-by-design for the duration
41
+ * of the lock — which is the intent — and stays stale after an unlock. The
42
+ * console DAM and the public `GET /api/v1/media` both fall back to `url`
43
+ * when an asset has no `cdnPath` (free tier, private assets, pre-AGL-829
44
+ * uploads), so those surfaces will show broken images for a rotated org
45
+ * once it is unlocked. Rewriting every media doc's `url` is a second,
46
+ * Firestore-wide pass that deliberately is NOT done here: during the lock
47
+ * the dead link is the point, and a repair pass that ran at lock time would
48
+ * be handing back a working URL. See the AGL-1526 thread.
49
+ *
50
+ * WHAT THIS DOES NOT DO (AGL-1615, honestly). Rotation kills the URL, not
51
+ * the bytes. Anything that already fetched them — a browser cache, a
52
+ * downstream CDN, a scraper's disk, an archive.org snapshot — keeps what it
53
+ * has. Like quarantine and like the CDN gate, this stops only NEW origin
54
+ * fetches. It is the strongest control available on a path we do not serve,
55
+ * and it is not a recall.
56
+ *
57
+ * THREE DELIBERATE NARROWINGS:
58
+ *
59
+ * 1. **`security` reason only, `full` mode only.** Rotation is
60
+ * IRREVERSIBLE for embeds: the new token is a new URL, so every raw URL
61
+ * already pasted into a screen, an email or a customer's site stays
62
+ * dead after the lock is lifted. The stable CDN URL (AGL-829) survives
63
+ * and is the supported reference, but pre-AGL-829 content that still
64
+ * embeds raw URLs will visibly break. That price is worth paying to cut
65
+ * off an org serving malware or CSAM; it is NOT worth paying because an
66
+ * invoice failed. `billing` and `maintenance` therefore never rotate,
67
+ * and neither does a read-only lock — a read-only lock's whole promise
68
+ * is that the sites keep serving.
69
+ *
70
+ * 2. **Only objects that ALREADY carry a token are touched.** An object
71
+ * with no `firebaseStorageDownloadTokens` entry has no raw URL to kill,
72
+ * so rotating it would be pure cost — and worse, WRITING a token where
73
+ * none existed would MINT a public raw URL for an object that never had
74
+ * one. The scan skips them. This is why the result reports `scanned`
75
+ * and `rotated` separately.
76
+ *
77
+ * 3. **Bounded, and awaited anyway.** AGL-1526 suggested firing this off
78
+ * async so the lock write does not wait on it. It is awaited instead,
79
+ * under an object cap and a wall-clock budget, because an unawaited
80
+ * promise in a serverless invocation is frozen the moment the response
81
+ * is returned — the classic shape of a control that exists and never
82
+ * runs. A bounded rotation that provably ran beats an unbounded one
83
+ * that provably did not; when a bound is hit the result says
84
+ * `truncated` so the audit row records an incomplete revocation rather
85
+ * than implying a clean one.
86
+ */ import { randomUUID } from "node:crypto";
87
+ import { firebaseAdmin } from "./firebase-admin.js";
88
+ /** The custom-metadata key Firebase reads the raw download token from. */ export const DOWNLOAD_TOKEN_METADATA_KEY = 'firebaseStorageDownloadTokens';
89
+ /**
90
+ * Per-prefix object cap. Well past any real library; a scope somehow larger
91
+ * rotates this many and reports `truncated` rather than running unbounded
92
+ * inside a request.
93
+ */ export const MAX_ROTATED_OBJECTS = 5000;
94
+ /** Wall-clock budget per prefix, comfortably inside the route's own limit. */ export const ROTATION_BUDGET_MS = 20000;
95
+ /** Objects listed per Storage page. */ const PAGE_SIZE = 500;
96
+ /** In-flight `setMetadata` calls; the bucket is the bottleneck, not us. */ const ROTATE_CONCURRENCY = 8;
97
+ /**
98
+ * The admin app is initialized without a default storageBucket, so every
99
+ * bucket access must name it explicitly (same as `erase.ts` and the media
100
+ * routes).
101
+ */ function storageBucket() {
102
+ const name = process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET'];
103
+ return firebaseAdmin.app().storage().bucket(name || undefined);
104
+ }
105
+ /**
106
+ * Does this lock revoke raw download URLs?
107
+ *
108
+ * Deliberately a pure predicate over the lock's own two fields, exported so
109
+ * the lockdown core and its specs test the SAME rule rather than two copies
110
+ * that can drift. See narrowing (1) above for why the answer is this narrow.
111
+ */ export function lockRotatesDownloadTokens(lock) {
112
+ if (!lock) return false;
113
+ if (lock.mode === 'read-only') return false;
114
+ return lock.reason === 'security';
115
+ }
116
+ /**
117
+ * Rewrite the download token on every already-tokened object under one
118
+ * Storage prefix.
119
+ *
120
+ * Fail-soft per object: one 403 on one file must not abandon the rest of a
121
+ * security revocation. Failures are counted and reported, never thrown —
122
+ * the caller (a lock that has already been applied) has nothing useful to
123
+ * do with an exception, but the audit row wants the number.
124
+ */ export async function rotateDownloadTokensUnderPrefix(options) {
125
+ var _options_maxObjects, _options_budgetMs, _options_now;
126
+ const { prefix } = options;
127
+ const maxObjects = (_options_maxObjects = options.maxObjects) != null ? _options_maxObjects : MAX_ROTATED_OBJECTS;
128
+ const budgetMs = (_options_budgetMs = options.budgetMs) != null ? _options_budgetMs : ROTATION_BUDGET_MS;
129
+ const now = (_options_now = options.now) != null ? _options_now : ()=>Date.now();
130
+ const startedAt = now();
131
+ const result = {
132
+ prefix,
133
+ scanned: 0,
134
+ rotated: 0,
135
+ failed: 0,
136
+ truncated: false,
137
+ ok: true,
138
+ reason: 'ok'
139
+ };
140
+ if (!prefix) return _extends({}, result, {
141
+ ok: false,
142
+ reason: 'no-prefix'
143
+ });
144
+ let bucket;
145
+ try {
146
+ var _options_bucket;
147
+ bucket = (_options_bucket = options.bucket) != null ? _options_bucket : storageBucket();
148
+ } catch (error) {
149
+ console.error('[lockdown] storage bucket unavailable', prefix, error);
150
+ return _extends({}, result, {
151
+ ok: false,
152
+ reason: 'not-configured'
153
+ });
154
+ }
155
+ if (!bucket) return _extends({}, result, {
156
+ ok: false,
157
+ reason: 'not-configured'
158
+ });
159
+ let pageToken;
160
+ try {
161
+ do {
162
+ const [files, nextQuery] = await bucket.getFiles(_extends({
163
+ prefix,
164
+ maxResults: PAGE_SIZE,
165
+ autoPaginate: false
166
+ }, pageToken ? {
167
+ pageToken
168
+ } : {}));
169
+ const batch = Array.isArray(files) ? files : [];
170
+ result.scanned += batch.length;
171
+ for(let index = 0; index < batch.length; index += ROTATE_CONCURRENCY){
172
+ await Promise.all(batch.slice(index, index + ROTATE_CONCURRENCY).map((file)=>rotateOne(file, result)));
173
+ }
174
+ pageToken = nextQuery == null ? void 0 : nextQuery.pageToken;
175
+ if (result.scanned >= maxObjects && pageToken) {
176
+ result.truncated = true;
177
+ result.reason = 'object-cap';
178
+ break;
179
+ }
180
+ if (now() - startedAt >= budgetMs && pageToken) {
181
+ result.truncated = true;
182
+ result.reason = 'time-budget';
183
+ break;
184
+ }
185
+ }while (pageToken)
186
+ } catch (error) {
187
+ console.error('[lockdown] download-token rotation failed', prefix, error);
188
+ return _extends({}, result, {
189
+ ok: false,
190
+ reason: 'error'
191
+ });
192
+ }
193
+ return result;
194
+ }
195
+ /**
196
+ * One object's rewrite.
197
+ *
198
+ * The existing custom-metadata map is READ AND RESPREAD rather than
199
+ * replaced with a lone token key, because `setMetadata` overwrites the
200
+ * whole custom map (the same trap `POST /api/media/folders` documents at
201
+ * its move path). Dropping the map here would silently erase every
202
+ * customer-set metadata pair on a locked org's library — a lock is not a
203
+ * data-loss event.
204
+ */ async function rotateOne(file, result) {
205
+ var _ref;
206
+ var _file_metadata;
207
+ const existing = (_ref = file == null ? void 0 : (_file_metadata = file.metadata) == null ? void 0 : _file_metadata.metadata) != null ? _ref : {};
208
+ // Narrowing (2): no token, no raw URL, nothing to revoke — and minting
209
+ // one here would CREATE the exposure this function exists to remove.
210
+ if (!existing[DOWNLOAD_TOKEN_METADATA_KEY]) return;
211
+ try {
212
+ await file.setMetadata({
213
+ metadata: _extends({}, existing, {
214
+ [DOWNLOAD_TOKEN_METADATA_KEY]: randomUUID()
215
+ })
216
+ });
217
+ result.rotated += 1;
218
+ } catch (error) {
219
+ console.error('[lockdown] token rewrite failed', file == null ? void 0 : file.name, error);
220
+ result.failed += 1;
221
+ }
222
+ }
223
+ /**
224
+ * Revoke ONE object's raw download URL (AGL-1615).
225
+ *
226
+ * The per-asset counterpart to the prefix scan above, for asset quarantine
227
+ * rather than a scope lockdown — and it closes a hole that is not a caching
228
+ * residual but an un-gated origin.
229
+ *
230
+ * `serveMediaCdn` consults the quarantine deny list because that route is
231
+ * ours. The media document's `url` field is not: it points at
232
+ * `firebasestorage.googleapis.com`, where no code of ours runs, and the
233
+ * console DAM and `GET /api/v1/media` both fall back to it whenever an asset
234
+ * has no `cdnPath` — every free-tier workspace, every private asset, every
235
+ * upload predating AGL-829. For those, a quarantine was not slow; it was
236
+ * never consulted, and a DMCA'd file kept serving to anyone holding the link.
237
+ *
238
+ * Same three narrowings as the prefix scan, for the same reasons, and one
239
+ * addition:
240
+ *
241
+ * * **Only an object that ALREADY carries a token is touched.** Writing one
242
+ * where none existed would MINT a public raw URL for a file that never
243
+ * had one — creating the exposure this exists to remove.
244
+ * * **The existing custom-metadata map is read and respread**, because
245
+ * `setMetadata` replaces the whole map and a takedown must not also be a
246
+ * silent erasure of customer-set metadata.
247
+ * * **Fail soft.** A Storage outage must not become a takedown outage: the
248
+ * deny-list entry is the primary control and lands regardless. The caller
249
+ * reports what actually happened rather than assuming success.
250
+ * * **There is no un-rotate, and none is offered.** Releasing a quarantine
251
+ * does not mint a fresh token: that would hand back a WORKING raw URL
252
+ * under a new value while every embed of the old one stays broken — it
253
+ * helps nobody and re-publishes the bytes. The irreversibility is real
254
+ * and is stated to the operator before they act, not discovered after.
255
+ */ export async function rotateDownloadTokenForObject(options) {
256
+ var _ref;
257
+ const storagePath = String((_ref = options == null ? void 0 : options.storagePath) != null ? _ref : '').trim();
258
+ if (!storagePath) return {
259
+ rotated: false,
260
+ reason: 'no-path'
261
+ };
262
+ let bucket;
263
+ try {
264
+ var _options_bucket;
265
+ bucket = (_options_bucket = options.bucket) != null ? _options_bucket : storageBucket();
266
+ } catch (error) {
267
+ console.error('[quarantine] storage bucket unavailable', storagePath, error);
268
+ return {
269
+ rotated: false,
270
+ reason: 'not-configured'
271
+ };
272
+ }
273
+ if (!bucket) return {
274
+ rotated: false,
275
+ reason: 'not-configured'
276
+ };
277
+ try {
278
+ var _ref1;
279
+ const file = bucket.file(storagePath);
280
+ // FETCHED, not read off the handle (AGL-1881). `bucket.file(path)`
281
+ // constructs a File whose `.metadata` is `{}` until something populates
282
+ // it — the prefix scan above gets it free because `bucket.getFiles()`
283
+ // returns hydrated handles, and this function never called anything that
284
+ // does. So `existing[DOWNLOAD_TOKEN_METADATA_KEY]` was always undefined
285
+ // and this returned `no-token` for every object, including the ones that
286
+ // carried a live raw URL. `no-token` is also the CORRECT benign answer,
287
+ // which is why nothing looked wrong: a takedown reported "no raw URL to
288
+ // revoke" and the operator had no way to tell that apart from a
289
+ // revocation that ran. A control that exists, is wired, and silently
290
+ // never fires.
291
+ const [fetched] = await file.getMetadata();
292
+ const existing = (_ref1 = fetched == null ? void 0 : fetched.metadata) != null ? _ref1 : {};
293
+ if (!existing[DOWNLOAD_TOKEN_METADATA_KEY]) {
294
+ return {
295
+ rotated: false,
296
+ reason: 'no-token'
297
+ };
298
+ }
299
+ await file.setMetadata({
300
+ metadata: _extends({}, existing, {
301
+ [DOWNLOAD_TOKEN_METADATA_KEY]: randomUUID()
302
+ })
303
+ });
304
+ return {
305
+ rotated: true,
306
+ reason: 'rotated'
307
+ };
308
+ } catch (error) {
309
+ console.error('[quarantine] token revocation failed', storagePath, error);
310
+ return {
311
+ rotated: false,
312
+ reason: 'error'
313
+ };
314
+ }
315
+ }
316
+ /**
317
+ * The object's CURRENT raw download URL, rebuilt from the token it already
318
+ * carries (AGL-1881).
319
+ *
320
+ * The publish half of `POST /api/media/folders {action:'set-private'}`.
321
+ * Turning an asset private now rotates its download token and drops the
322
+ * media document's `url`, so publishing it again has to put a working `url`
323
+ * back — for a free-tier workspace, or any asset predating AGL-829, that
324
+ * field is the ONLY delivery path, and leaving it deleted would make
325
+ * "Publish" a one-way door.
326
+ *
327
+ * **This mints nothing.** It reads the token the object already has and
328
+ * reassembles the URL from it, so:
329
+ *
330
+ * * every URL handed out before the asset went private stays dead — the
331
+ * rotation is not undone, only re-read;
332
+ * * an object that never carried a token still does not get one, which is
333
+ * the same narrowing {@link rotateDownloadTokenForObject} makes and for
334
+ * the same reason: writing one would MINT a public URL for a file that
335
+ * never had one.
336
+ *
337
+ * Returns `null` when there is nothing to rebuild, so the caller writes no
338
+ * `url` rather than an empty one.
339
+ */ export async function currentDownloadUrlForObject(options) {
340
+ var _ref;
341
+ const storagePath = String((_ref = options == null ? void 0 : options.storagePath) != null ? _ref : '').trim();
342
+ if (!storagePath) return null;
343
+ let bucket;
344
+ try {
345
+ var _options_bucket;
346
+ bucket = (_options_bucket = options.bucket) != null ? _options_bucket : storageBucket();
347
+ } catch (error) {
348
+ console.error('[media] storage bucket unavailable', storagePath, error);
349
+ return null;
350
+ }
351
+ if (!bucket) return null;
352
+ try {
353
+ var _ref1;
354
+ const file = bucket.file(storagePath);
355
+ const [fetched] = await file.getMetadata();
356
+ const token = ((_ref1 = fetched == null ? void 0 : fetched.metadata) != null ? _ref1 : {})[DOWNLOAD_TOKEN_METADATA_KEY];
357
+ if (!token) return null;
358
+ return `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/` + `${encodeURIComponent(storagePath)}?alt=media&token=${String(token)}`;
359
+ } catch (error) {
360
+ console.error('[media] download url read failed', storagePath, error);
361
+ return null;
362
+ }
363
+ }
364
+ /**
365
+ * Rotate across every prefix a lock scope owns.
366
+ *
367
+ * An ORG lock passes `orgs/{orgId}/` AND `hosts/{hostId}/` for each of its
368
+ * sites: org-scope and host-scope libraries are separate Storage trees
369
+ * (`MediaScope.base`), and a lock that cleaned only the org tree would
370
+ * leave every site's own assets serving — which is most of them.
371
+ */ export async function rotateScopeDownloadTokens(options) {
372
+ var _options_prefixes;
373
+ const results = [];
374
+ for (const prefix of (_options_prefixes = options.prefixes) != null ? _options_prefixes : []){
375
+ results.push(await rotateDownloadTokensUnderPrefix({
376
+ prefix,
377
+ bucket: options.bucket,
378
+ maxObjects: options.maxObjects,
379
+ budgetMs: options.budgetMs,
380
+ now: options.now
381
+ }));
382
+ }
383
+ return results;
384
+ }
385
+
386
+ //# sourceMappingURL=media-download-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/media-download-tokens.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 * Raw-download-URL revocation for a security lockdown (AGL-1526).\n *\n * THE HOLE THIS CLOSES. AGL-1520 put a lockdown gate on the media CDN\n * (`mediaCdnServeBlock`), and that gate works because the CDN path runs OUR\n * code: a locked scope gets a 410. But a media object can also be addressed\n * directly at Google's edge:\n *\n * https://firebasestorage.googleapis.com/v0/b/{bucket}/o/{path}\n * ?alt=media&token={firebaseStorageDownloadTokens}\n *\n * That URL is the free-tier / legacy delivery path — every upload route\n * still mints one and stores it on the media doc's `url`. **No code of ours\n * runs on that origin.** A lock cannot refuse it, a revalidation cannot\n * evict it, and the tenant middleware never sees it. It serves the locked\n * org's bytes to anyone holding the link until the token dies.\n *\n * The only thing that kills it is the token itself, which lives in the\n * object's custom metadata. Rewrite that entry and Google's edge starts\n * answering the old URL with 403 — immediately, with no cache of ours in\n * the path and nothing to revalidate.\n *\n * KNOWN CONSEQUENCE, not a bug: the media doc's own `url` field still holds\n * the OLD raw URL after rotation, so it is stale-by-design for the duration\n * of the lock — which is the intent — and stays stale after an unlock. The\n * console DAM and the public `GET /api/v1/media` both fall back to `url`\n * when an asset has no `cdnPath` (free tier, private assets, pre-AGL-829\n * uploads), so those surfaces will show broken images for a rotated org\n * once it is unlocked. Rewriting every media doc's `url` is a second,\n * Firestore-wide pass that deliberately is NOT done here: during the lock\n * the dead link is the point, and a repair pass that ran at lock time would\n * be handing back a working URL. See the AGL-1526 thread.\n *\n * WHAT THIS DOES NOT DO (AGL-1615, honestly). Rotation kills the URL, not\n * the bytes. Anything that already fetched them — a browser cache, a\n * downstream CDN, a scraper's disk, an archive.org snapshot — keeps what it\n * has. Like quarantine and like the CDN gate, this stops only NEW origin\n * fetches. It is the strongest control available on a path we do not serve,\n * and it is not a recall.\n *\n * THREE DELIBERATE NARROWINGS:\n *\n * 1. **`security` reason only, `full` mode only.** Rotation is\n * IRREVERSIBLE for embeds: the new token is a new URL, so every raw URL\n * already pasted into a screen, an email or a customer's site stays\n * dead after the lock is lifted. The stable CDN URL (AGL-829) survives\n * and is the supported reference, but pre-AGL-829 content that still\n * embeds raw URLs will visibly break. That price is worth paying to cut\n * off an org serving malware or CSAM; it is NOT worth paying because an\n * invoice failed. `billing` and `maintenance` therefore never rotate,\n * and neither does a read-only lock — a read-only lock's whole promise\n * is that the sites keep serving.\n *\n * 2. **Only objects that ALREADY carry a token are touched.** An object\n * with no `firebaseStorageDownloadTokens` entry has no raw URL to kill,\n * so rotating it would be pure cost — and worse, WRITING a token where\n * none existed would MINT a public raw URL for an object that never had\n * one. The scan skips them. This is why the result reports `scanned`\n * and `rotated` separately.\n *\n * 3. **Bounded, and awaited anyway.** AGL-1526 suggested firing this off\n * async so the lock write does not wait on it. It is awaited instead,\n * under an object cap and a wall-clock budget, because an unawaited\n * promise in a serverless invocation is frozen the moment the response\n * is returned — the classic shape of a control that exists and never\n * runs. A bounded rotation that provably ran beats an unbounded one\n * that provably did not; when a bound is hit the result says\n * `truncated` so the audit row records an incomplete revocation rather\n * than implying a clean one.\n */\n\nimport { randomUUID } from 'node:crypto'\nimport { firebaseAdmin } from './firebase-admin'\n\n/** The custom-metadata key Firebase reads the raw download token from. */\nexport const DOWNLOAD_TOKEN_METADATA_KEY = 'firebaseStorageDownloadTokens'\n\n/**\n * Per-prefix object cap. Well past any real library; a scope somehow larger\n * rotates this many and reports `truncated` rather than running unbounded\n * inside a request.\n */\nexport const MAX_ROTATED_OBJECTS = 5000\n/** Wall-clock budget per prefix, comfortably inside the route's own limit. */\nexport const ROTATION_BUDGET_MS = 20_000\n/** Objects listed per Storage page. */\nconst PAGE_SIZE = 500\n/** In-flight `setMetadata` calls; the bucket is the bottleneck, not us. */\nconst ROTATE_CONCURRENCY = 8\n\nexport interface DownloadTokenRotationResult {\n /** Storage prefix scanned, e.g. `orgs/acme/`. */\n prefix: string\n /** Objects listed under the prefix. */\n scanned: number\n /** Objects that carried a token and got a new one. */\n rotated: number\n /** Objects that carried a token and whose rewrite failed. */\n failed: number\n /** Whether a bound stopped the scan before the prefix was exhausted. */\n truncated: boolean\n ok: boolean\n reason: string\n}\n\n/**\n * The admin app is initialized without a default storageBucket, so every\n * bucket access must name it explicitly (same as `erase.ts` and the media\n * routes).\n */\nfunction storageBucket(): any {\n const name = process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET']\n return firebaseAdmin.app().storage().bucket(name || undefined)\n}\n\n/**\n * Does this lock revoke raw download URLs?\n *\n * Deliberately a pure predicate over the lock's own two fields, exported so\n * the lockdown core and its specs test the SAME rule rather than two copies\n * that can drift. See narrowing (1) above for why the answer is this narrow.\n */\nexport function lockRotatesDownloadTokens(lock: {\n reason?: string\n mode?: 'full' | 'read-only'\n}): boolean {\n if (!lock) return false\n if (lock.mode === 'read-only') return false\n return lock.reason === 'security'\n}\n\n/**\n * Rewrite the download token on every already-tokened object under one\n * Storage prefix.\n *\n * Fail-soft per object: one 403 on one file must not abandon the rest of a\n * security revocation. Failures are counted and reported, never thrown —\n * the caller (a lock that has already been applied) has nothing useful to\n * do with an exception, but the audit row wants the number.\n */\nexport async function rotateDownloadTokensUnderPrefix(options: {\n prefix: string\n /** Injectable for tests; defaults to the configured media bucket. */\n bucket?: any\n maxObjects?: number\n budgetMs?: number\n now?: () => number\n}): Promise<DownloadTokenRotationResult> {\n const { prefix } = options\n const maxObjects = options.maxObjects ?? MAX_ROTATED_OBJECTS\n const budgetMs = options.budgetMs ?? ROTATION_BUDGET_MS\n const now = options.now ?? (() => Date.now())\n const startedAt = now()\n const result: DownloadTokenRotationResult = {\n prefix,\n scanned: 0,\n rotated: 0,\n failed: 0,\n truncated: false,\n ok: true,\n reason: 'ok',\n }\n if (!prefix) return { ...result, ok: false, reason: 'no-prefix' }\n\n let bucket: any\n try {\n bucket = options.bucket ?? storageBucket()\n } catch (error) {\n console.error('[lockdown] storage bucket unavailable', prefix, error)\n return { ...result, ok: false, reason: 'not-configured' }\n }\n if (!bucket) return { ...result, ok: false, reason: 'not-configured' }\n\n let pageToken: string | undefined\n try {\n do {\n const [files, nextQuery] = await bucket.getFiles({\n prefix,\n maxResults: PAGE_SIZE,\n autoPaginate: false,\n ...(pageToken ? { pageToken } : {}),\n })\n const batch: any[] = Array.isArray(files) ? files : []\n result.scanned += batch.length\n\n for (let index = 0; index < batch.length; index += ROTATE_CONCURRENCY) {\n await Promise.all(\n batch\n .slice(index, index + ROTATE_CONCURRENCY)\n .map((file: any) => rotateOne(file, result)),\n )\n }\n\n pageToken = nextQuery?.pageToken\n if (result.scanned >= maxObjects && pageToken) {\n result.truncated = true\n result.reason = 'object-cap'\n break\n }\n if (now() - startedAt >= budgetMs && pageToken) {\n result.truncated = true\n result.reason = 'time-budget'\n break\n }\n } while (pageToken)\n } catch (error) {\n console.error('[lockdown] download-token rotation failed', prefix, error)\n return { ...result, ok: false, reason: 'error' }\n }\n\n return result\n}\n\n/**\n * One object's rewrite.\n *\n * The existing custom-metadata map is READ AND RESPREAD rather than\n * replaced with a lone token key, because `setMetadata` overwrites the\n * whole custom map (the same trap `POST /api/media/folders` documents at\n * its move path). Dropping the map here would silently erase every\n * customer-set metadata pair on a locked org's library — a lock is not a\n * data-loss event.\n */\nasync function rotateOne(\n file: any,\n result: DownloadTokenRotationResult,\n): Promise<void> {\n const existing = (file?.metadata?.metadata ?? {}) as Record<string, unknown>\n // Narrowing (2): no token, no raw URL, nothing to revoke — and minting\n // one here would CREATE the exposure this function exists to remove.\n if (!existing[DOWNLOAD_TOKEN_METADATA_KEY]) return\n try {\n await file.setMetadata({\n metadata: { ...existing, [DOWNLOAD_TOKEN_METADATA_KEY]: randomUUID() },\n })\n result.rotated += 1\n } catch (error) {\n console.error('[lockdown] token rewrite failed', file?.name, error)\n result.failed += 1\n }\n}\n\n/** What happened to ONE object's token. See {@link rotateDownloadTokenForObject}. */\nexport interface ObjectTokenRotation {\n /** The token was rewritten, so the published raw URL is now dead. */\n rotated: boolean\n /**\n * Why not, when it was not. `no-token` is the common and CORRECT case —\n * the object had no raw URL to revoke. `error` and `not-configured` are\n * infrastructure; `no-path` is a document with no object behind it.\n */\n reason: 'rotated' | 'no-token' | 'no-path' | 'not-configured' | 'error'\n}\n\n/**\n * Revoke ONE object's raw download URL (AGL-1615).\n *\n * The per-asset counterpart to the prefix scan above, for asset quarantine\n * rather than a scope lockdown — and it closes a hole that is not a caching\n * residual but an un-gated origin.\n *\n * `serveMediaCdn` consults the quarantine deny list because that route is\n * ours. The media document's `url` field is not: it points at\n * `firebasestorage.googleapis.com`, where no code of ours runs, and the\n * console DAM and `GET /api/v1/media` both fall back to it whenever an asset\n * has no `cdnPath` — every free-tier workspace, every private asset, every\n * upload predating AGL-829. For those, a quarantine was not slow; it was\n * never consulted, and a DMCA'd file kept serving to anyone holding the link.\n *\n * Same three narrowings as the prefix scan, for the same reasons, and one\n * addition:\n *\n * * **Only an object that ALREADY carries a token is touched.** Writing one\n * where none existed would MINT a public raw URL for a file that never\n * had one — creating the exposure this exists to remove.\n * * **The existing custom-metadata map is read and respread**, because\n * `setMetadata` replaces the whole map and a takedown must not also be a\n * silent erasure of customer-set metadata.\n * * **Fail soft.** A Storage outage must not become a takedown outage: the\n * deny-list entry is the primary control and lands regardless. The caller\n * reports what actually happened rather than assuming success.\n * * **There is no un-rotate, and none is offered.** Releasing a quarantine\n * does not mint a fresh token: that would hand back a WORKING raw URL\n * under a new value while every embed of the old one stays broken — it\n * helps nobody and re-publishes the bytes. The irreversibility is real\n * and is stated to the operator before they act, not discovered after.\n */\nexport async function rotateDownloadTokenForObject(options: {\n storagePath?: string | null\n /** Injectable for tests; defaults to the configured media bucket. */\n bucket?: any\n}): Promise<ObjectTokenRotation> {\n const storagePath = String(options?.storagePath ?? '').trim()\n if (!storagePath) return { rotated: false, reason: 'no-path' }\n\n let bucket: any\n try {\n bucket = options.bucket ?? storageBucket()\n } catch (error) {\n console.error('[quarantine] storage bucket unavailable', storagePath, error)\n return { rotated: false, reason: 'not-configured' }\n }\n if (!bucket) return { rotated: false, reason: 'not-configured' }\n\n try {\n const file = bucket.file(storagePath)\n // FETCHED, not read off the handle (AGL-1881). `bucket.file(path)`\n // constructs a File whose `.metadata` is `{}` until something populates\n // it — the prefix scan above gets it free because `bucket.getFiles()`\n // returns hydrated handles, and this function never called anything that\n // does. So `existing[DOWNLOAD_TOKEN_METADATA_KEY]` was always undefined\n // and this returned `no-token` for every object, including the ones that\n // carried a live raw URL. `no-token` is also the CORRECT benign answer,\n // which is why nothing looked wrong: a takedown reported \"no raw URL to\n // revoke\" and the operator had no way to tell that apart from a\n // revocation that ran. A control that exists, is wired, and silently\n // never fires.\n const [fetched] = await file.getMetadata()\n const existing = (fetched?.metadata ?? {}) as Record<string, unknown>\n if (!existing[DOWNLOAD_TOKEN_METADATA_KEY]) {\n return { rotated: false, reason: 'no-token' }\n }\n await file.setMetadata({\n metadata: { ...existing, [DOWNLOAD_TOKEN_METADATA_KEY]: randomUUID() },\n })\n return { rotated: true, reason: 'rotated' }\n } catch (error) {\n console.error('[quarantine] token revocation failed', storagePath, error)\n return { rotated: false, reason: 'error' }\n }\n}\n\n/**\n * The object's CURRENT raw download URL, rebuilt from the token it already\n * carries (AGL-1881).\n *\n * The publish half of `POST /api/media/folders {action:'set-private'}`.\n * Turning an asset private now rotates its download token and drops the\n * media document's `url`, so publishing it again has to put a working `url`\n * back — for a free-tier workspace, or any asset predating AGL-829, that\n * field is the ONLY delivery path, and leaving it deleted would make\n * \"Publish\" a one-way door.\n *\n * **This mints nothing.** It reads the token the object already has and\n * reassembles the URL from it, so:\n *\n * * every URL handed out before the asset went private stays dead — the\n * rotation is not undone, only re-read;\n * * an object that never carried a token still does not get one, which is\n * the same narrowing {@link rotateDownloadTokenForObject} makes and for\n * the same reason: writing one would MINT a public URL for a file that\n * never had one.\n *\n * Returns `null` when there is nothing to rebuild, so the caller writes no\n * `url` rather than an empty one.\n */\nexport async function currentDownloadUrlForObject(options: {\n storagePath?: string | null\n /** Injectable for tests; defaults to the configured media bucket. */\n bucket?: any\n}): Promise<string | null> {\n const storagePath = String(options?.storagePath ?? '').trim()\n if (!storagePath) return null\n let bucket: any\n try {\n bucket = options.bucket ?? storageBucket()\n } catch (error) {\n console.error('[media] storage bucket unavailable', storagePath, error)\n return null\n }\n if (!bucket) return null\n try {\n const file = bucket.file(storagePath)\n const [fetched] = await file.getMetadata()\n const token = (fetched?.metadata ?? {})[DOWNLOAD_TOKEN_METADATA_KEY]\n if (!token) return null\n return (\n `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/` +\n `${encodeURIComponent(storagePath)}?alt=media&token=${String(token)}`\n )\n } catch (error) {\n console.error('[media] download url read failed', storagePath, error)\n return null\n }\n}\n\n/**\n * Rotate across every prefix a lock scope owns.\n *\n * An ORG lock passes `orgs/{orgId}/` AND `hosts/{hostId}/` for each of its\n * sites: org-scope and host-scope libraries are separate Storage trees\n * (`MediaScope.base`), and a lock that cleaned only the org tree would\n * leave every site's own assets serving — which is most of them.\n */\nexport async function rotateScopeDownloadTokens(options: {\n prefixes: readonly string[]\n bucket?: any\n maxObjects?: number\n budgetMs?: number\n now?: () => number\n}): Promise<DownloadTokenRotationResult[]> {\n const results: DownloadTokenRotationResult[] = []\n for (const prefix of options.prefixes ?? []) {\n results.push(\n await rotateDownloadTokensUnderPrefix({\n prefix,\n bucket: options.bucket,\n maxObjects: options.maxObjects,\n budgetMs: options.budgetMs,\n now: options.now,\n }),\n )\n }\n return results\n}\n"],"names":["randomUUID","firebaseAdmin","DOWNLOAD_TOKEN_METADATA_KEY","MAX_ROTATED_OBJECTS","ROTATION_BUDGET_MS","PAGE_SIZE","ROTATE_CONCURRENCY","storageBucket","name","process","env","app","storage","bucket","undefined","lockRotatesDownloadTokens","lock","mode","reason","rotateDownloadTokensUnderPrefix","options","prefix","maxObjects","budgetMs","now","Date","startedAt","result","scanned","rotated","failed","truncated","ok","error","console","pageToken","files","nextQuery","getFiles","maxResults","autoPaginate","batch","Array","isArray","length","index","Promise","all","slice","map","file","rotateOne","existing","metadata","setMetadata","rotateDownloadTokenForObject","storagePath","String","trim","fetched","getMetadata","currentDownloadUrlForObject","token","encodeURIComponent","rotateScopeDownloadTokens","results","prefixes","push"],"mappings":"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"}