@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,114 @@
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
+ */ import { consumeRateLimit } from "./rate-limit-store.js";
18
+ /**
19
+ * The cooldown on the AUTOMATIC verification send (AGL-2584).
20
+ *
21
+ * `/verify-email` asks for a link every time it mounts, so somebody who leaves
22
+ * the tab and comes back to see whether the mail arrived asks for a second
23
+ * one. Identity Platform throttles link minting per account, ahead of and
24
+ * independently of the route's own 5/hour budget, and that collision is what a
25
+ * returning visitor met: a mail that had genuinely been sent, reported to them
26
+ * as a send failure.
27
+ *
28
+ * Reopening the page is a request to know whether the first mail worked, not a
29
+ * request for another one — so an automatic send is skipped while a link
30
+ * minted for this uid is still young. The explicit "Resend verification email"
31
+ * button does not come through here: someone who says the mail never arrived
32
+ * is asking deliberately, and that affordance keeps the full per-uid budget.
33
+ *
34
+ * ## Why the state is server-side and not a `sentAt` in the browser
35
+ *
36
+ * The throttle this avoids is per ACCOUNT, so the memory of "a link was minted
37
+ * recently" has to be too. A marker held in the browser cannot see a link
38
+ * minted from a phone, a second browser, or a private window, and clearing
39
+ * site data erases it — each of which puts the collision straight back. It
40
+ * would also put the decision on the side of the wire that does not do the
41
+ * minting, which is where it went wrong the first time.
42
+ *
43
+ * ## Why the durable limiter, with a budget of one
44
+ *
45
+ * A cooldown *is* a limit of one per window, and the limiter already solves
46
+ * the parts that are easy to get wrong: it is global rather than per instance
47
+ * (a per-instance `Map` on Vercel is close to no limit at all, AGL-794), it
48
+ * hashes the key out of the document id, and it fails soft when Firestore is
49
+ * unreachable.
50
+ *
51
+ * Its window is fixed rather than rolling, so a link minted near a boundary
52
+ * can be followed by another as soon as the next window opens — the cooldown
53
+ * holds for somewhere between zero and its full length. Where a window does
54
+ * end early the outcome is one extra mint, which the route reports as the
55
+ * throttle it is rather than as a failure.
56
+ *
57
+ * ## It may only suppress on evidence, never on silence
58
+ *
59
+ * The limiter refuses for two unrelated reasons, and only one of them is a
60
+ * counted overage. `contended` means the counter could not be read and
61
+ * written inside its budget, and the budget is spent by any slowness at all —
62
+ * a cold instance paying for Admin SDK startup and two Firestore round trips
63
+ * reaches it without another caller in sight, which on a low-traffic
64
+ * deployment is the ordinary case rather than the rare one.
65
+ *
66
+ * That refusal carries no information about whether a link was recently
67
+ * minted, so it may not be read as one. The asymmetry is the whole argument:
68
+ * a duplicate mail is a nuisance the route already reports correctly, while a
69
+ * suppressed FIRST mail is an account that never receives the link it is
70
+ * waiting for — and because a suppressed send is answered 200, the account
71
+ * holder is told a mail is on its way that nobody ever sent. A cooldown whose
72
+ * purpose is politeness does not get to fail that way, so an unanswered gate
73
+ * sends.
74
+ */ /**
75
+ * How long an automatic send suppresses the next one. Long enough to cover the
76
+ * round trip this exists for — open the mail app, look, come back — and short
77
+ * enough that someone whose mail was eaten by a spam filter gets a fresh one
78
+ * from a later visit rather than only from the resend button.
79
+ */ export const VERIFY_EMAIL_AUTO_SEND_COOLDOWN_MS = 10 * 60 * 1000;
80
+ /**
81
+ * Asks whether the page's automatic send may mint a link for `uid`.
82
+ *
83
+ * Keyed on the uid alone, under its own prefix: the cooldown is one account's
84
+ * and never another's, and it counts separately from the per-uid hourly budget
85
+ * so a suppressed automatic send spends nothing a deliberate resend would want.
86
+ */ export async function consumeVerifyEmailAutoSend(uid, options) {
87
+ var _ref;
88
+ const at = (_ref = options == null ? void 0 : options.now) != null ? _ref : Date.now();
89
+ const result = await consumeRateLimit(`verify-email:auto:${uid}`, _extends({
90
+ limit: 1,
91
+ windowMs: VERIFY_EMAIL_AUTO_SEND_COOLDOWN_MS
92
+ }, (options == null ? void 0 : options.now) === undefined ? {} : {
93
+ now: options.now
94
+ }, (options == null ? void 0 : options.firestore) === undefined ? {} : {
95
+ firestore: options.firestore
96
+ }));
97
+ // A refusal the limiter could not count is silence, not evidence of a
98
+ // recent mail, and this cooldown suppresses only on evidence.
99
+ if (result.contended) {
100
+ return {
101
+ allowed: true,
102
+ retryAfterSeconds: 0,
103
+ degraded: result.degraded
104
+ };
105
+ }
106
+ return {
107
+ allowed: result.allowed,
108
+ retryAfterSeconds: result.allowed ? 0 : Math.max(1, Math.ceil((result.resetMs - at) / 1000)),
109
+ degraded: result.degraded
110
+ };
111
+ }
112
+ export default consumeVerifyEmailAutoSend;
113
+
114
+ //# sourceMappingURL=verify-email-cooldown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/verify-email-cooldown.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { consumeRateLimit } from './rate-limit-store'\n\n/**\n * The cooldown on the AUTOMATIC verification send (AGL-2584).\n *\n * `/verify-email` asks for a link every time it mounts, so somebody who leaves\n * the tab and comes back to see whether the mail arrived asks for a second\n * one. Identity Platform throttles link minting per account, ahead of and\n * independently of the route's own 5/hour budget, and that collision is what a\n * returning visitor met: a mail that had genuinely been sent, reported to them\n * as a send failure.\n *\n * Reopening the page is a request to know whether the first mail worked, not a\n * request for another one — so an automatic send is skipped while a link\n * minted for this uid is still young. The explicit \"Resend verification email\"\n * button does not come through here: someone who says the mail never arrived\n * is asking deliberately, and that affordance keeps the full per-uid budget.\n *\n * ## Why the state is server-side and not a `sentAt` in the browser\n *\n * The throttle this avoids is per ACCOUNT, so the memory of \"a link was minted\n * recently\" has to be too. A marker held in the browser cannot see a link\n * minted from a phone, a second browser, or a private window, and clearing\n * site data erases it — each of which puts the collision straight back. It\n * would also put the decision on the side of the wire that does not do the\n * minting, which is where it went wrong the first time.\n *\n * ## Why the durable limiter, with a budget of one\n *\n * A cooldown *is* a limit of one per window, and the limiter already solves\n * the parts that are easy to get wrong: it is global rather than per instance\n * (a per-instance `Map` on Vercel is close to no limit at all, AGL-794), it\n * hashes the key out of the document id, and it fails soft when Firestore is\n * unreachable.\n *\n * Its window is fixed rather than rolling, so a link minted near a boundary\n * can be followed by another as soon as the next window opens — the cooldown\n * holds for somewhere between zero and its full length. Where a window does\n * end early the outcome is one extra mint, which the route reports as the\n * throttle it is rather than as a failure.\n *\n * ## It may only suppress on evidence, never on silence\n *\n * The limiter refuses for two unrelated reasons, and only one of them is a\n * counted overage. `contended` means the counter could not be read and\n * written inside its budget, and the budget is spent by any slowness at all —\n * a cold instance paying for Admin SDK startup and two Firestore round trips\n * reaches it without another caller in sight, which on a low-traffic\n * deployment is the ordinary case rather than the rare one.\n *\n * That refusal carries no information about whether a link was recently\n * minted, so it may not be read as one. The asymmetry is the whole argument:\n * a duplicate mail is a nuisance the route already reports correctly, while a\n * suppressed FIRST mail is an account that never receives the link it is\n * waiting for — and because a suppressed send is answered 200, the account\n * holder is told a mail is on its way that nobody ever sent. A cooldown whose\n * purpose is politeness does not get to fail that way, so an unanswered gate\n * sends.\n */\n\n/**\n * How long an automatic send suppresses the next one. Long enough to cover the\n * round trip this exists for — open the mail app, look, come back — and short\n * enough that someone whose mail was eaten by a spam filter gets a fresh one\n * from a later visit rather than only from the resend button.\n */\nexport const VERIFY_EMAIL_AUTO_SEND_COOLDOWN_MS = 10 * 60 * 1000\n\nexport interface VerifyEmailAutoSendResult {\n /** True when this mount may mint a link. */\n allowed: boolean\n /** Seconds until the cooldown lifts; 0 when allowed. */\n retryAfterSeconds: number\n /** True when the durable store was unreachable and only a local cap applied. */\n degraded: boolean\n}\n\nexport interface VerifyEmailAutoSendOptions {\n now?: number\n /** Injectable for tests; defaults to the Admin SDK's Firestore. */\n firestore?: unknown\n}\n\n/**\n * Asks whether the page's automatic send may mint a link for `uid`.\n *\n * Keyed on the uid alone, under its own prefix: the cooldown is one account's\n * and never another's, and it counts separately from the per-uid hourly budget\n * so a suppressed automatic send spends nothing a deliberate resend would want.\n */\nexport async function consumeVerifyEmailAutoSend(\n uid: string,\n options?: VerifyEmailAutoSendOptions,\n): Promise<VerifyEmailAutoSendResult> {\n const at = options?.now ?? Date.now()\n const result = await consumeRateLimit(`verify-email:auto:${uid}`, {\n limit: 1,\n windowMs: VERIFY_EMAIL_AUTO_SEND_COOLDOWN_MS,\n ...(options?.now === undefined ? {} : { now: options.now }),\n ...(options?.firestore === undefined\n ? {}\n : { firestore: options.firestore }),\n })\n // A refusal the limiter could not count is silence, not evidence of a\n // recent mail, and this cooldown suppresses only on evidence.\n if (result.contended) {\n return { allowed: true, retryAfterSeconds: 0, degraded: result.degraded }\n }\n return {\n allowed: result.allowed,\n retryAfterSeconds: result.allowed\n ? 0\n : Math.max(1, Math.ceil((result.resetMs - at) / 1000)),\n degraded: result.degraded,\n }\n}\n\nexport default consumeVerifyEmailAutoSend\n"],"names":["consumeRateLimit","VERIFY_EMAIL_AUTO_SEND_COOLDOWN_MS","consumeVerifyEmailAutoSend","uid","options","at","now","Date","result","limit","windowMs","undefined","firestore","contended","allowed","retryAfterSeconds","degraded","Math","max","ceil","resetMs"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,gBAAgB,QAAQ,wBAAoB;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwDC,GAED;;;;;CAKC,GACD,OAAO,MAAMC,qCAAqC,KAAK,KAAK,KAAI;AAiBhE;;;;;;CAMC,GACD,OAAO,eAAeC,2BACpBC,GAAW,EACXC,OAAoC;;IAEpC,MAAMC,aAAKD,2BAAAA,QAASE,GAAG,mBAAIC,KAAKD,GAAG;IACnC,MAAME,SAAS,MAAMR,iBAAiB,CAAC,kBAAkB,EAAEG,KAAK,EAAE;QAChEM,OAAO;QACPC,UAAUT;OACNG,CAAAA,2BAAAA,QAASE,GAAG,MAAKK,YAAY,CAAC,IAAI;QAAEL,KAAKF,QAAQE,GAAG;IAAC,GACrDF,CAAAA,2BAAAA,QAASQ,SAAS,MAAKD,YACvB,CAAC,IACD;QAAEC,WAAWR,QAAQQ,SAAS;IAAC;IAErC,sEAAsE;IACtE,8DAA8D;IAC9D,IAAIJ,OAAOK,SAAS,EAAE;QACpB,OAAO;YAAEC,SAAS;YAAMC,mBAAmB;YAAGC,UAAUR,OAAOQ,QAAQ;QAAC;IAC1E;IACA,OAAO;QACLF,SAASN,OAAOM,OAAO;QACvBC,mBAAmBP,OAAOM,OAAO,GAC7B,IACAG,KAAKC,GAAG,CAAC,GAAGD,KAAKE,IAAI,CAAC,AAACX,CAAAA,OAAOY,OAAO,GAAGf,EAAC,IAAK;QAClDW,UAAUR,OAAOQ,QAAQ;IAC3B;AACF;AAEA,eAAed,2BAA0B"}
@@ -0,0 +1,87 @@
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
+ export interface VisitorWriteRateLimitOptions {
18
+ /** Dispatcher path, e.g. `commerce/cart`. Decides visitor vs machine. */
19
+ path: string;
20
+ /** The site the write targets; `''` when none resolved (still limited). */
21
+ hostId: string;
22
+ /** Read for its method and its client-address headers. */
23
+ request: {
24
+ method?: string;
25
+ headers?: {
26
+ get?: (name: string) => unknown;
27
+ };
28
+ };
29
+ limit?: number;
30
+ windowMs?: number;
31
+ nowMs?: number;
32
+ /** Injectable for tests; defaults to the Admin SDK's Firestore. */
33
+ firestore?: unknown;
34
+ }
35
+ /**
36
+ * Counts one visitor write and returns a 429 once the window is spent.
37
+ *
38
+ * ## Reads are not limited, and that is a decision rather than an omission
39
+ *
40
+ * AGL-1770 scopes this to visitor-facing *writes*, and the cost argument
41
+ * agrees. `RATE_LIMITING.md`'s standing rule is "`checkRateLimit` for volume,
42
+ * `consumeRateLimit` for consequence": each durable call is a Firestore
43
+ * transaction — one read plus one write — which is the right price for a cart
44
+ * write that itself creates a document, and the wrong price for a GET. The
45
+ * dispatcher's read paths "leave before any Firestore read" (the lockdown gate
46
+ * says so), so a durable limiter there would be the ONLY Firestore work in the
47
+ * request — precisely the analytics-beacon anti-pattern the doc names.
48
+ *
49
+ * The residual is real and worth stating: tenant reads are not free (~40
50
+ * Firestore reads per rendered page). It is a different defect with a
51
+ * different remedy — caching and the ISR layer, not a per-request
52
+ * transaction — and it is not this one.
53
+ *
54
+ * ## Fail SOFT, inherited from `consumeRateLimit` and re-argued here
55
+ *
56
+ * A Firestore outage lets traffic through, flagged `degraded`, rather than
57
+ * refusing it. CSRF fails closed; this deliberately does not, for three
58
+ * reasons, the third of which is specific to this limiter:
59
+ *
60
+ * 1. **The blast radius of the strict choice is every storefront at once.**
61
+ * A Firestore blip that 429s every add-to-cart on every site costs
62
+ * merchants sales during an outage they did not cause. The risk it guards
63
+ * is unbounded document creation — storage. Lost revenue is the worse of
64
+ * the two, and it is the one the merchant notices.
65
+ * 2. **The fallback still enforces something.** `consumeRateLimit` degrades to
66
+ * the in-process limiter at the same ceiling, so the cap becomes
67
+ * `limit × warm instances` rather than nothing.
68
+ * 3. **The store and the thing being protected are the same Firestore.** The
69
+ * counter lives in `rateLimits` and the cart document it guards lives in
70
+ * `hosts/…/carts` — one database. If the limiter cannot write, the write it
71
+ * exists to bound is almost certainly failing too. Failing closed would
72
+ * refuse requests that were going to fail anyway, and *additionally* refuse
73
+ * the ones that would have succeeded. It buys nothing.
74
+ *
75
+ * The degradation stays findable: AGL-1679's recovery marker and AGL-1693's
76
+ * `/api/health/rate-limits` already cover every `consumeRateLimit` caller, so
77
+ * this one is watched by construction and needs no record of its own.
78
+ *
79
+ * ## What the caller is told
80
+ *
81
+ * A bare 429 with `Retry-After`. No `X-RateLimit-*` headers and no `degraded`
82
+ * flag, following `forms/submit`: this surface is public and unauthenticated,
83
+ * and both "you have N requests left" and "the global limiter is currently a
84
+ * per-instance one" are sentences an abuser would spend the window on.
85
+ */
86
+ export declare function visitorWriteRateLimitRefusal(options: VisitorWriteRateLimitOptions): Promise<Response | null>;
87
+ export default visitorWriteRateLimitRefusal;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { isMachinePluginApiPath, lockdownIntentForMethod, VISITOR_WRITE_RATE_LIMIT, VISITOR_WRITE_RATE_WINDOW_MS, visitorWriteRateLimitKey } from "@aglyn/aglyn/server";
17
+ import { consumeRateLimit } from "./rate-limit-store.js";
18
+ import { NO_CLIENT_ADDRESS_BUCKET, readClientIp } from "@aglyn/aglyn/app-utils/request-ip";
19
+ /**
20
+ * The rate-limit refusal a visitor-facing plugin write returns, or null to
21
+ * proceed (AGL-1770):
22
+ *
23
+ * const limited = await visitorWriteRateLimitRefusal({ path, hostId, request })
24
+ * if (limited) return limited
25
+ *
26
+ * Sits beside `visitorWriteRefusal` on purpose — same shape, same chokepoint,
27
+ * same "return it or carry on" contract. The policy (ceiling, window, key,
28
+ * which paths are exempt) is pure and lives in `@aglyn/aglyn/server`; this is
29
+ * only the durable half, which needs Firestore.
30
+ */ /**
31
+ * The visitor's address, through the shared reader, or the no-address bucket.
32
+ *
33
+ * Kept counting rather than skipped when nothing readable arrives: this is a
34
+ * cost control over unauthenticated writes, and one that stops counting is an
35
+ * open write endpoint. The key already carries the site, so an unreadable
36
+ * address shares one budget per SITE and not one across the deployment.
37
+ */ function clientIp(request) {
38
+ var _readClientIp;
39
+ const headers = request == null ? void 0 : request.headers;
40
+ if (typeof (headers == null ? void 0 : headers.get) !== 'function') return NO_CLIENT_ADDRESS_BUCKET;
41
+ return (_readClientIp = readClientIp(headers)) != null ? _readClientIp : NO_CLIENT_ADDRESS_BUCKET;
42
+ }
43
+ /**
44
+ * Counts one visitor write and returns a 429 once the window is spent.
45
+ *
46
+ * ## Reads are not limited, and that is a decision rather than an omission
47
+ *
48
+ * AGL-1770 scopes this to visitor-facing *writes*, and the cost argument
49
+ * agrees. `RATE_LIMITING.md`'s standing rule is "`checkRateLimit` for volume,
50
+ * `consumeRateLimit` for consequence": each durable call is a Firestore
51
+ * transaction — one read plus one write — which is the right price for a cart
52
+ * write that itself creates a document, and the wrong price for a GET. The
53
+ * dispatcher's read paths "leave before any Firestore read" (the lockdown gate
54
+ * says so), so a durable limiter there would be the ONLY Firestore work in the
55
+ * request — precisely the analytics-beacon anti-pattern the doc names.
56
+ *
57
+ * The residual is real and worth stating: tenant reads are not free (~40
58
+ * Firestore reads per rendered page). It is a different defect with a
59
+ * different remedy — caching and the ISR layer, not a per-request
60
+ * transaction — and it is not this one.
61
+ *
62
+ * ## Fail SOFT, inherited from `consumeRateLimit` and re-argued here
63
+ *
64
+ * A Firestore outage lets traffic through, flagged `degraded`, rather than
65
+ * refusing it. CSRF fails closed; this deliberately does not, for three
66
+ * reasons, the third of which is specific to this limiter:
67
+ *
68
+ * 1. **The blast radius of the strict choice is every storefront at once.**
69
+ * A Firestore blip that 429s every add-to-cart on every site costs
70
+ * merchants sales during an outage they did not cause. The risk it guards
71
+ * is unbounded document creation — storage. Lost revenue is the worse of
72
+ * the two, and it is the one the merchant notices.
73
+ * 2. **The fallback still enforces something.** `consumeRateLimit` degrades to
74
+ * the in-process limiter at the same ceiling, so the cap becomes
75
+ * `limit × warm instances` rather than nothing.
76
+ * 3. **The store and the thing being protected are the same Firestore.** The
77
+ * counter lives in `rateLimits` and the cart document it guards lives in
78
+ * `hosts/…/carts` — one database. If the limiter cannot write, the write it
79
+ * exists to bound is almost certainly failing too. Failing closed would
80
+ * refuse requests that were going to fail anyway, and *additionally* refuse
81
+ * the ones that would have succeeded. It buys nothing.
82
+ *
83
+ * The degradation stays findable: AGL-1679's recovery marker and AGL-1693's
84
+ * `/api/health/rate-limits` already cover every `consumeRateLimit` caller, so
85
+ * this one is watched by construction and needs no record of its own.
86
+ *
87
+ * ## What the caller is told
88
+ *
89
+ * A bare 429 with `Retry-After`. No `X-RateLimit-*` headers and no `degraded`
90
+ * flag, following `forms/submit`: this surface is public and unauthenticated,
91
+ * and both "you have N requests left" and "the global limiter is currently a
92
+ * per-instance one" are sentences an abuser would spend the window on.
93
+ */ export async function visitorWriteRateLimitRefusal(options) {
94
+ var _options_nowMs, _options_limit, _options_windowMs;
95
+ var _options_request;
96
+ if (lockdownIntentForMethod((_options_request = options.request) == null ? void 0 : _options_request.method) === 'read') return null;
97
+ if (isMachinePluginApiPath(options.path)) return null;
98
+ const nowMs = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.now();
99
+ const rate = await consumeRateLimit(visitorWriteRateLimitKey(options.hostId, clientIp(options.request)), {
100
+ limit: (_options_limit = options.limit) != null ? _options_limit : VISITOR_WRITE_RATE_LIMIT,
101
+ windowMs: (_options_windowMs = options.windowMs) != null ? _options_windowMs : VISITOR_WRITE_RATE_WINDOW_MS,
102
+ now: nowMs,
103
+ firestore: options.firestore
104
+ });
105
+ if (rate.allowed) return null;
106
+ return Response.json({
107
+ error: 'Too many requests'
108
+ }, {
109
+ status: 429,
110
+ headers: {
111
+ 'Retry-After': String(Math.max(1, Math.ceil((rate.resetMs - nowMs) / 1000)))
112
+ }
113
+ });
114
+ }
115
+ export default visitorWriteRateLimitRefusal;
116
+
117
+ //# sourceMappingURL=visitor-write-rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/visitor-write-rate-limit.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n isMachinePluginApiPath,\n lockdownIntentForMethod,\n VISITOR_WRITE_RATE_LIMIT,\n VISITOR_WRITE_RATE_WINDOW_MS,\n visitorWriteRateLimitKey,\n} from '@aglyn/aglyn/server'\nimport { consumeRateLimit } from './rate-limit-store'\nimport {\n NO_CLIENT_ADDRESS_BUCKET,\n readClientIp,\n} from '@aglyn/aglyn/app-utils/request-ip'\n\n/**\n * The rate-limit refusal a visitor-facing plugin write returns, or null to\n * proceed (AGL-1770):\n *\n * const limited = await visitorWriteRateLimitRefusal({ path, hostId, request })\n * if (limited) return limited\n *\n * Sits beside `visitorWriteRefusal` on purpose — same shape, same chokepoint,\n * same \"return it or carry on\" contract. The policy (ceiling, window, key,\n * which paths are exempt) is pure and lives in `@aglyn/aglyn/server`; this is\n * only the durable half, which needs Firestore.\n */\n\n/**\n * The visitor's address, through the shared reader, or the no-address bucket.\n *\n * Kept counting rather than skipped when nothing readable arrives: this is a\n * cost control over unauthenticated writes, and one that stops counting is an\n * open write endpoint. The key already carries the site, so an unreadable\n * address shares one budget per SITE and not one across the deployment.\n */\nfunction clientIp(request: { headers?: { get?: (name: string) => unknown } }): string {\n const headers = request?.headers\n if (typeof headers?.get !== 'function') return NO_CLIENT_ADDRESS_BUCKET\n return (\n readClientIp(headers as { get(name: string): string | null }) ??\n NO_CLIENT_ADDRESS_BUCKET\n )\n}\n\nexport interface VisitorWriteRateLimitOptions {\n /** Dispatcher path, e.g. `commerce/cart`. Decides visitor vs machine. */\n path: string\n /** The site the write targets; `''` when none resolved (still limited). */\n hostId: string\n /** Read for its method and its client-address headers. */\n request: { method?: string; headers?: { get?: (name: string) => unknown } }\n limit?: number\n windowMs?: number\n nowMs?: number\n /** Injectable for tests; defaults to the Admin SDK's Firestore. */\n firestore?: unknown\n}\n\n/**\n * Counts one visitor write and returns a 429 once the window is spent.\n *\n * ## Reads are not limited, and that is a decision rather than an omission\n *\n * AGL-1770 scopes this to visitor-facing *writes*, and the cost argument\n * agrees. `RATE_LIMITING.md`'s standing rule is \"`checkRateLimit` for volume,\n * `consumeRateLimit` for consequence\": each durable call is a Firestore\n * transaction — one read plus one write — which is the right price for a cart\n * write that itself creates a document, and the wrong price for a GET. The\n * dispatcher's read paths \"leave before any Firestore read\" (the lockdown gate\n * says so), so a durable limiter there would be the ONLY Firestore work in the\n * request — precisely the analytics-beacon anti-pattern the doc names.\n *\n * The residual is real and worth stating: tenant reads are not free (~40\n * Firestore reads per rendered page). It is a different defect with a\n * different remedy — caching and the ISR layer, not a per-request\n * transaction — and it is not this one.\n *\n * ## Fail SOFT, inherited from `consumeRateLimit` and re-argued here\n *\n * A Firestore outage lets traffic through, flagged `degraded`, rather than\n * refusing it. CSRF fails closed; this deliberately does not, for three\n * reasons, the third of which is specific to this limiter:\n *\n * 1. **The blast radius of the strict choice is every storefront at once.**\n * A Firestore blip that 429s every add-to-cart on every site costs\n * merchants sales during an outage they did not cause. The risk it guards\n * is unbounded document creation — storage. Lost revenue is the worse of\n * the two, and it is the one the merchant notices.\n * 2. **The fallback still enforces something.** `consumeRateLimit` degrades to\n * the in-process limiter at the same ceiling, so the cap becomes\n * `limit × warm instances` rather than nothing.\n * 3. **The store and the thing being protected are the same Firestore.** The\n * counter lives in `rateLimits` and the cart document it guards lives in\n * `hosts/…/carts` — one database. If the limiter cannot write, the write it\n * exists to bound is almost certainly failing too. Failing closed would\n * refuse requests that were going to fail anyway, and *additionally* refuse\n * the ones that would have succeeded. It buys nothing.\n *\n * The degradation stays findable: AGL-1679's recovery marker and AGL-1693's\n * `/api/health/rate-limits` already cover every `consumeRateLimit` caller, so\n * this one is watched by construction and needs no record of its own.\n *\n * ## What the caller is told\n *\n * A bare 429 with `Retry-After`. No `X-RateLimit-*` headers and no `degraded`\n * flag, following `forms/submit`: this surface is public and unauthenticated,\n * and both \"you have N requests left\" and \"the global limiter is currently a\n * per-instance one\" are sentences an abuser would spend the window on.\n */\nexport async function visitorWriteRateLimitRefusal(\n options: VisitorWriteRateLimitOptions,\n): Promise<Response | null> {\n if (lockdownIntentForMethod(options.request?.method) === 'read') return null\n if (isMachinePluginApiPath(options.path)) return null\n\n const nowMs = options.nowMs ?? Date.now()\n const rate = await consumeRateLimit(\n visitorWriteRateLimitKey(options.hostId, clientIp(options.request)),\n {\n limit: options.limit ?? VISITOR_WRITE_RATE_LIMIT,\n windowMs: options.windowMs ?? VISITOR_WRITE_RATE_WINDOW_MS,\n now: nowMs,\n firestore: options.firestore,\n },\n )\n if (rate.allowed) return null\n\n return Response.json(\n { error: 'Too many requests' },\n {\n status: 429,\n headers: {\n 'Retry-After': String(\n Math.max(1, Math.ceil((rate.resetMs - nowMs) / 1000)),\n ),\n },\n },\n )\n}\n\nexport default visitorWriteRateLimitRefusal\n"],"names":["isMachinePluginApiPath","lockdownIntentForMethod","VISITOR_WRITE_RATE_LIMIT","VISITOR_WRITE_RATE_WINDOW_MS","visitorWriteRateLimitKey","consumeRateLimit","NO_CLIENT_ADDRESS_BUCKET","readClientIp","clientIp","request","headers","get","visitorWriteRateLimitRefusal","options","method","path","nowMs","Date","now","rate","hostId","limit","windowMs","firestore","allowed","Response","json","error","status","String","Math","max","ceil","resetMs"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,sBAAsB,EACtBC,uBAAuB,EACvBC,wBAAwB,EACxBC,4BAA4B,EAC5BC,wBAAwB,QACnB,sBAAqB;AAC5B,SAASC,gBAAgB,QAAQ,wBAAoB;AACrD,SACEC,wBAAwB,EACxBC,YAAY,QACP,oCAAmC;AAE1C;;;;;;;;;;;CAWC,GAED;;;;;;;CAOC,GACD,SAASC,SAASC,OAA0D;QAIxEF;IAHF,MAAMG,UAAUD,2BAAAA,QAASC,OAAO;IAChC,IAAI,QAAOA,2BAAAA,QAASC,GAAG,MAAK,YAAY,OAAOL;IAC/C,QACEC,gBAAAA,aAAaG,oBAAbH,gBACAD;AAEJ;AAgBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDC,GACD,OAAO,eAAeM,6BACpBC,OAAqC;QAKvBA,gBAIHA,gBACGA;QARcA;IAA5B,IAAIZ,yBAAwBY,mBAAAA,QAAQJ,OAAO,qBAAfI,iBAAiBC,MAAM,MAAM,QAAQ,OAAO;IACxE,IAAId,uBAAuBa,QAAQE,IAAI,GAAG,OAAO;IAEjD,MAAMC,SAAQH,iBAAAA,QAAQG,KAAK,YAAbH,iBAAiBI,KAAKC,GAAG;IACvC,MAAMC,OAAO,MAAMd,iBACjBD,yBAAyBS,QAAQO,MAAM,EAAEZ,SAASK,QAAQJ,OAAO,IACjE;QACEY,KAAK,GAAER,iBAAAA,QAAQQ,KAAK,YAAbR,iBAAiBX;QACxBoB,QAAQ,GAAET,oBAAAA,QAAQS,QAAQ,YAAhBT,oBAAoBV;QAC9Be,KAAKF;QACLO,WAAWV,QAAQU,SAAS;IAC9B;IAEF,IAAIJ,KAAKK,OAAO,EAAE,OAAO;IAEzB,OAAOC,SAASC,IAAI,CAClB;QAAEC,OAAO;IAAoB,GAC7B;QACEC,QAAQ;QACRlB,SAAS;YACP,eAAemB,OACbC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,IAAI,CAAC,AAACb,CAAAA,KAAKc,OAAO,GAAGjB,KAAI,IAAK;QAEnD;IACF;AAEJ;AAEA,eAAeJ,6BAA4B"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Attach and detach names for the console (AGL-1136).
19
+ *
20
+ * Two layers. The bottom one — `attachProjectDomain` / `detachProjectDomain` —
21
+ * takes a fully-qualified name and is what a custom **console** domain uses
22
+ * (AGL-1373). The top one is the workspace-subdomain pair below, which is that
23
+ * primitive with `{slug}.aglyn.com` built for it.
24
+ *
25
+ * The hosting vendor is no longer either of them. Registering a name goes
26
+ * through `domain-provider.ts`, so the same workspace subdomain is created by
27
+ * Vercel's API on Aglyn's own hosting, by a wildcard record on a Docker
28
+ * install, or by whatever an operator put behind the webhook driver. What
29
+ * stays here is everything that is Aglyn's own policy rather than a vendor's:
30
+ * the slug arithmetic, the upload-CORS reconcile, and the three properties
31
+ * below.
32
+ *
33
+ * AGL-1135 removed the `*.aglyn.com` wildcard, because it meant every
34
+ * hostname under the domain served a real console sign-in page —
35
+ * `billing-security-update.aglyn.com` returned 200 with a valid certificate.
36
+ * Removing it made unregistered names 404, and made **registered** names
37
+ * something that has to be registered: a slug only resolves if that exact
38
+ * domain is attached.
39
+ *
40
+ * Nothing did that, so every workspace created after AGL-1135 had a subdomain
41
+ * that 404s. AGL-1115 then made signup provision an org, which turned a latent
42
+ * gap into one every new account hits.
43
+ *
44
+ * ## Three properties, in the order they matter
45
+ *
46
+ * 1. **It can never fail an org.** Every entry point swallows its errors and
47
+ * returns a verdict instead of throwing. The console is path-routed
48
+ * (`app.aglyn.com/{slug}`) and that is the canonical form, so a workspace
49
+ * with no subdomain is fully usable — while an org creation that rolled
50
+ * back because a DNS API was slow would not be.
51
+ * 2. **Unconfigured is a no-op, not an error.** With no provider this reports
52
+ * `skipped`. A deployment that registers names some other way must not log
53
+ * an error per signup forever.
54
+ * 3. **A rename ADDS without removing.** The old slug keeps a tombstone that
55
+ * 308s to the new one (AGL-585/236), and a redirect can only run on a
56
+ * hostname that still resolves. Detaching the old domain would break the
57
+ * very redirect the tombstone exists to serve.
58
+ */
59
+ import { type DomainAttachOptions, type DomainOutcome, type DomainScope, type ProjectDomainStatus } from './domain-provider';
60
+ import { type UploadCorsRelease, type UploadCorsVerdict } from './upload-cors-reconcile';
61
+ export { domainStateServes, redirectHostname, type ProjectDomainState, type ProjectDomainStatus, type ProjectDomainVerification, } from './domain-provider';
62
+ export type WorkspaceDomainOutcome = DomainOutcome;
63
+ export interface WorkspaceDomainResult {
64
+ outcome: WorkspaceDomainOutcome;
65
+ domain: string;
66
+ detail?: string;
67
+ /**
68
+ * Whether a browser at this name can complete a signed direct-to-GCS upload
69
+ * (AGL-1452).
70
+ *
71
+ * Present only when the attach made the name a SERVING origin — a redirect
72
+ * never becomes one. `permitted: false` carries the exact command a human
73
+ * must run, so the gap is legible here rather than days later as a large
74
+ * upload that fails behind a generic snackbar.
75
+ */
76
+ uploadCors?: UploadCorsVerdict | null;
77
+ /**
78
+ * What a DETACH did about the origin the name leaves behind (AGL-1452).
79
+ *
80
+ * The attach half without this one only ever grows the allowlist. A stale
81
+ * origin is a standing permission to complete a signed upload, held by a host
82
+ * we no longer serve — and for a white-label console domain, one the customer
83
+ * still controls.
84
+ */
85
+ uploadCorsRelease?: UploadCorsRelease | null;
86
+ }
87
+ export type ProjectDomainOptions = DomainAttachOptions;
88
+ /**
89
+ * Attach one fully-qualified name to the console.
90
+ *
91
+ * Idempotent by contract: a name already registered comes back as
92
+ * `already-exists`, which is success — the reconcile script and the create
93
+ * path can both run without coordinating.
94
+ *
95
+ * ⚠️ Tolerating `already-exists` is only safe while **every** name this
96
+ * function is asked to attach is also claimed in Firestore. Without that, a
97
+ * second org attaches a name someone else already holds, reads it as success,
98
+ * and gets a console that looks healthy while its visitors are served — or
99
+ * redirected — somewhere else. That is the AGL-743 shape, and
100
+ * `console-domains.ts` is where the claim is kept in step.
101
+ */
102
+ export declare function attachProjectDomain(name: string, options?: ProjectDomainOptions, scope?: DomainScope): Promise<WorkspaceDomainResult>;
103
+ /**
104
+ * The command still owed after a set of attaches, or null when nothing is
105
+ * (AGL-1452).
106
+ *
107
+ * One remedy rather than a list: every verdict names the same bucket and the
108
+ * same command, and a caller that has to render five copies of it renders
109
+ * none.
110
+ */
111
+ export declare function pendingUploadCorsRemedy(results: WorkspaceDomainResult[]): string | null;
112
+ /** Detach one fully-qualified name from the console. */
113
+ export declare function detachProjectDomain(name: string, scope?: DomainScope): Promise<WorkspaceDomainResult>;
114
+ /**
115
+ * Attach `{slug}.<workspace domain>` so the workspace subdomain resolves.
116
+ *
117
+ * The `!slug` guard is not redundant with the one inside `attachProjectDomain`:
118
+ * an empty slug still produces the well-formed `.aglyn.com`, and on the detach
119
+ * side that is a request to delete the apex.
120
+ */
121
+ export declare function attachWorkspaceDomain(slug: string): Promise<WorkspaceDomainResult>;
122
+ /**
123
+ * Detach the domain, for org erasure.
124
+ *
125
+ * NOT called on rename: the previous slug keeps serving a 308 to the new one,
126
+ * and a redirect needs a hostname that still resolves.
127
+ */
128
+ export declare function detachWorkspaceDomain(slug: string): Promise<WorkspaceDomainResult>;
129
+ /**
130
+ * Whether the workspace-domain integration is configured at all.
131
+ *
132
+ * Exposed so a reconcile script can say "not configured" once instead of
133
+ * reporting every workspace as skipped, which reads like a failure.
134
+ */
135
+ export declare function workspaceDomainsConfigured(): boolean;
136
+ /**
137
+ * What a name is ACTUALLY doing, right now.
138
+ *
139
+ * `attachProjectDomain` answers "did the registration succeed", which is a
140
+ * different question and a much weaker one. A domain can be registered and
141
+ * still serve nothing — ownership unproven, DNS not yet pointed, certificate
142
+ * not yet issued — and all three return a perfectly successful attach.
143
+ *
144
+ * That is the gap this closes (AGL-1913): the site custom-domain wizard
145
+ * reported `attached: true` off the attach alone, so "certificate is still
146
+ * issuing" and "this will never work" rendered as the same green chip, and the
147
+ * only advice the docs could give was to press retry again.
148
+ *
149
+ * `scope` picks which app should be answering for the name — `tenant` for a
150
+ * published site's custom domain, `console` for a workspace subdomain or a
151
+ * white-label console domain. It was a raw project id before the provider
152
+ * seam, which only ever meant anything to one vendor.
153
+ */
154
+ export declare function projectDomainStatus(name: string, options?: {
155
+ scope?: DomainScope;
156
+ }): Promise<ProjectDomainStatus>;