@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,90 @@
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
+ * Published ceiling for `webSettings.allowedDomains`, quoted rather than
19
+ * measured: *"Each key supports a maximum of 250 domains."* An occupancy
20
+ * reading is NOT a limit — this repo already spent two weeks treating a
21
+ * snapshot of nine entries as a hard ceiling. Re-probe occupancy, never the
22
+ * limit.
23
+ */
24
+ export declare const MAX_ALLOWED_DOMAINS = 250;
25
+ export type RecaptchaAllowlistOutcome =
26
+ /** We wrote the entry and re-read the key to confirm it. */
27
+ 'listed'
28
+ /** The exact name was already on the key; nothing was written. */
29
+ | 'already-listed'
30
+ /** We removed the entry and re-read the key to confirm it. */
31
+ | 'removed'
32
+ /** Nothing to remove — the exact name was not on the key. */
33
+ | 'absent'
34
+ /**
35
+ * This deployment runs no App Check reCAPTCHA provider, so there is no
36
+ * allowlist to maintain and nothing is gated. Self-host, and local dev.
37
+ */
38
+ | 'unenforced'
39
+ /** The key is at {@link MAX_ALLOWED_DOMAINS}. A commercial event, not a bug. */
40
+ | 'full'
41
+ /** Anything else: misconfiguration, IAM, transport, or a suspicious write. */
42
+ | 'failed';
43
+ export interface RecaptchaAllowlistResult {
44
+ outcome: RecaptchaAllowlistOutcome;
45
+ domain: string;
46
+ /** Human-readable cause. Always set when the outcome is not a success. */
47
+ detail?: string;
48
+ /**
49
+ * Live `allowedDomains` after the operation, when we got far enough to read
50
+ * them. Occupancy is worth surfacing precisely because it must never be
51
+ * quoted from memory.
52
+ */
53
+ allowedDomains?: string[];
54
+ }
55
+ /**
56
+ * Does this outcome mean the origin can attest?
57
+ *
58
+ * The single decision point, so that no caller can invent a looser one. Note
59
+ * what is NOT here: `full` and `failed` both leave a domain that will 401 its
60
+ * customer at sign-in, and `full` is the easier of the two to mistake for a
61
+ * warning.
62
+ */
63
+ export declare function allowlistSatisfied(outcome: RecaptchaAllowlistOutcome): boolean;
64
+ /**
65
+ * Put a custom console domain on the key, so a browser there can attest.
66
+ *
67
+ * Called by `activateConsoleDomain` for the **serving** name only. Redirect
68
+ * twins (`www.acme.com` → `acme.com`) never execute the console, never load
69
+ * reCAPTCHA, and would spend a slot of the 250 for nothing.
70
+ */
71
+ export declare function allowConsoleOrigin(input: string): Promise<RecaptchaAllowlistResult>;
72
+ /**
73
+ * Take a domain back off the key when its claim is released.
74
+ *
75
+ * Removes the EXACT entry and nothing else. A filter that matched subdomains
76
+ * would take `aglyn.com` off the key while releasing `console.aglyn.com` —
77
+ * and the entry it deleted is the one every Aglyn origin attests against.
78
+ */
79
+ export declare function reclaimConsoleOrigin(input: string): Promise<RecaptchaAllowlistResult>;
80
+ /**
81
+ * Read-only occupancy, for an operator asking "how many of the 250 are gone?"
82
+ *
83
+ * Deliberately returns the live list rather than a count: a count is what got
84
+ * quoted as a ceiling for two weeks.
85
+ */
86
+ export declare function readConsoleOriginAllowlist(): Promise<{
87
+ domains: string[] | null;
88
+ limit: number;
89
+ detail?: string;
90
+ }>;
@@ -0,0 +1,409 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Aglyn LLC
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */ /**
19
+ * The App Check reCAPTCHA allowlist, written from the product (AGL-1378).
20
+ *
21
+ * ## Why this file has to exist
22
+ *
23
+ * The console reads Firestore **client-side**, and App Check gates those reads
24
+ * with a reCAPTCHA token that the key itself refuses to mint on an origin it
25
+ * has never heard of. Measured against the live key on 2026-08-23, with both
26
+ * controls, by requesting `recaptcha/api2/anchor` with a base64 `co` origin:
27
+ *
28
+ * ```
29
+ * https://app.aglyn.com:443 ACCEPTED (listed)
30
+ * https://never-registered-9f3x.aglyn.com:443 ACCEPTED (subtree of a listed entry)
31
+ * https://console.acme-agency.example:443 REJECTED "Invalid domain for site key"
32
+ * https://console.northwind-coffee.com:443 REJECTED "Invalid domain for site key"
33
+ * ```
34
+ *
35
+ * A rejected solve means `initializeAppCheck` → `getToken` fails with
36
+ * `appCheck/recaptcha-error`, and the very next Identity Platform call is
37
+ * refused `401 UNAUTHENTICATED — "Firebase App Check token is invalid"`
38
+ * **before** token validation (`docs/design/agl-1099a-poc-findings.md` §2).
39
+ * So a custom console domain that is attached and routed but not allowlisted
40
+ * renders a console that can never sign anyone in — and the symptom the
41
+ * customer reports is "Missing or insufficient permissions", which is the same
42
+ * string a Security Rules verdict produces. That is the misdiagnosis this
43
+ * whole area keeps generating; the fix is to never let the state exist.
44
+ *
45
+ * ## Why it was believed impossible, and why that is stale
46
+ *
47
+ * AGL-1378 concluded the key was a classic v3 key with no management API. The
48
+ * key was auto-migrated into a Google-created project, and every probe asked
49
+ * `aglyn-main` — where the reCAPTCHA API genuinely is disabled — rather than
50
+ * the project the key actually lives in. Measured 2026-08-23:
51
+ * `recaptchaenterprise.googleapis.com` is enabled there, two accounts we
52
+ * control are `roles/owner`, and `projects.keys.patch` on
53
+ * `webSettings.allowedDomains` answers `200`.
54
+ *
55
+ * ## The two hazards this code is shaped around
56
+ *
57
+ * 1. **A patch that drops sibling fields.** `webSettings` also carries
58
+ * `allowAllDomains`, `integrationType`, `allowAmpTraffic` and
59
+ * `challengeSecurityPreference`; a whole-object write that omitted
60
+ * `allowAllDomains` would read as `false`… or a future default. So the mask
61
+ * is `webSettings.allowedDomains` — the narrowest path the API accepts —
62
+ * **and** the response is re-read afterwards: every domain that was there
63
+ * before must still be there, and `allowAllDomains` must still be `false`.
64
+ * A write that silently widened the key to every origin is a worse outcome
65
+ * than the domain never being listed.
66
+ * 2. **Silent success.** Every outcome here is explicit, and
67
+ * {@link allowlistSatisfied} is the ONLY place that decides which ones mean
68
+ * "the customer can actually sign in". `activateConsoleDomain` refuses to
69
+ * mark a claim `active` unless it says yes.
70
+ *
71
+ * ## Exact names, never subtree cover
72
+ *
73
+ * A listed entry covers its whole subtree but never its parents (proved
74
+ * 2026-08-10). It is therefore tempting to skip the write when some existing
75
+ * entry already covers the new name. Deliberately not done: if org A holds
76
+ * `acme.com` and org B later claims `console.acme.com`, B would be silently
77
+ * covered by A's entry — and A detaching would break B with no trace linking
78
+ * the two. One entry per claimed apex is what the documented 250-domain
79
+ * ceiling is denominated in anyway (250 distinct customer apexes, occupancy 5
80
+ * on 2026-08-23), so exactness costs nothing we were not already counting.
81
+ */ import { getApp } from "firebase-admin/app";
82
+ const RECAPTCHA_API = 'https://recaptchaenterprise.googleapis.com/v1';
83
+ /**
84
+ * Published ceiling for `webSettings.allowedDomains`, quoted rather than
85
+ * measured: *"Each key supports a maximum of 250 domains."* An occupancy
86
+ * reading is NOT a limit — this repo already spent two weeks treating a
87
+ * snapshot of nine entries as a hard ceiling. Re-probe occupancy, never the
88
+ * limit.
89
+ */ export const MAX_ALLOWED_DOMAINS = 250;
90
+ /**
91
+ * Ceiling on the whole exchange, matching `workspace-domains.ts`.
92
+ *
93
+ * This one is awaited by an attach the operator is watching, so an
94
+ * unresponsive Google API must lose quickly rather than hang the activation
95
+ * until the platform kills the function.
96
+ */ const REQUEST_TIMEOUT_MS = 8000;
97
+ /**
98
+ * Does this outcome mean the origin can attest?
99
+ *
100
+ * The single decision point, so that no caller can invent a looser one. Note
101
+ * what is NOT here: `full` and `failed` both leave a domain that will 401 its
102
+ * customer at sign-in, and `full` is the easier of the two to mistake for a
103
+ * warning.
104
+ */ export function allowlistSatisfied(outcome) {
105
+ return outcome === 'listed' || outcome === 'already-listed' || outcome === 'removed' || outcome === 'absent' || outcome === 'unenforced';
106
+ }
107
+ /**
108
+ * `unenforced` is the ONLY safe way to skip, and it is gated on the client's
109
+ * own site key rather than on this file's credentials.
110
+ *
111
+ * The failure this shape exists to prevent: a production deployment that runs
112
+ * App Check but has no `RECAPTCHA_ADMIN_KEY_NAME` would, under a
113
+ * "missing config → skip" rule, report every custom domain ready while listing
114
+ * none of them. Configuration absence must therefore mean two different things
115
+ * depending on whether App Check is running at all — and the tell is
116
+ * `NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY`, which is exactly what
117
+ * `new ReCaptchaV3Provider(...)` is handed.
118
+ *
119
+ * The admin key name must also END with that site key. A resource name
120
+ * pointing at a different key would be written happily and attest nothing —
121
+ * the one misconfiguration whose symptom is indistinguishable from success.
122
+ */ function config() {
123
+ var _process_env_NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY, _process_env_RECAPTCHA_ADMIN_KEY_NAME;
124
+ const siteKey = String((_process_env_NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY = process.env.NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY) != null ? _process_env_NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY : '').trim();
125
+ const keyName = String((_process_env_RECAPTCHA_ADMIN_KEY_NAME = process.env.RECAPTCHA_ADMIN_KEY_NAME) != null ? _process_env_RECAPTCHA_ADMIN_KEY_NAME : '').trim();
126
+ if (!siteKey) return {
127
+ kind: 'unenforced'
128
+ };
129
+ if (!keyName) {
130
+ return {
131
+ kind: 'misconfigured',
132
+ detail: 'App Check runs here (NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY is set) but RECAPTCHA_ADMIN_KEY_NAME is not, so the allowlist cannot be written'
133
+ };
134
+ }
135
+ if (!/^projects\/[^/]+\/keys\/[^/]+$/.test(keyName)) {
136
+ return {
137
+ kind: 'misconfigured',
138
+ detail: `RECAPTCHA_ADMIN_KEY_NAME must be projects/{project}/keys/{siteKey}, got "${keyName}"`
139
+ };
140
+ }
141
+ if (!keyName.endsWith(`/${siteKey}`)) {
142
+ return {
143
+ kind: 'misconfigured',
144
+ detail: 'RECAPTCHA_ADMIN_KEY_NAME names a different key than NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY — writing it would allowlist a key nothing uses'
145
+ };
146
+ }
147
+ return {
148
+ kind: 'ready',
149
+ keyName
150
+ };
151
+ }
152
+ /**
153
+ * An OAuth token for the same service account firebase-admin already runs as.
154
+ *
155
+ * Not a second credential: `cert()`'s `getAccessToken()` requests
156
+ * `https://www.googleapis.com/auth/cloud-platform` among its scopes, which is
157
+ * the scope `recaptchaenterprise.googleapis.com` asks for. The account needs
158
+ * `recaptchaenterprise.keys.get` + `.update` on the project the KEY lives in —
159
+ * which is not `aglyn-main`; the key was migrated into its own project and
160
+ * that is the whole reason this was thought unautomatable.
161
+ */ async function accessToken() {
162
+ const credential = getApp().options.credential;
163
+ if (!credential) throw new Error('firebase-admin app has no credential');
164
+ const token = await credential.getAccessToken();
165
+ const value = token == null ? void 0 : token.access_token;
166
+ if (!value) throw new Error('credential returned no access token');
167
+ return value;
168
+ }
169
+ async function call(url, init) {
170
+ var _rest_headers;
171
+ const { token } = init, rest = _object_without_properties_loose(init, [
172
+ "token"
173
+ ]);
174
+ const response = await fetch(url, _extends({}, rest, {
175
+ headers: _extends({}, (_rest_headers = rest.headers) != null ? _rest_headers : {}, {
176
+ Authorization: `Bearer ${token}`,
177
+ 'Content-Type': 'application/json'
178
+ }),
179
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
180
+ }));
181
+ const body = await response.json().catch(()=>({}));
182
+ return {
183
+ ok: response.ok,
184
+ status: response.status,
185
+ body
186
+ };
187
+ }
188
+ function domainsOf(key) {
189
+ var _key_webSettings;
190
+ const domains = key == null ? void 0 : (_key_webSettings = key.webSettings) == null ? void 0 : _key_webSettings.allowedDomains;
191
+ return Array.isArray(domains) ? domains.map((entry)=>String(entry)) : [];
192
+ }
193
+ function normalize(input) {
194
+ return String(input != null ? input : '').trim().toLowerCase().replace(/\.$/, '');
195
+ }
196
+ /**
197
+ * Read the key, apply `mutate` to its domains, write back, and prove the write.
198
+ *
199
+ * `mutate` returning `null` means "no write is needed" and is a success.
200
+ */ async function rewrite(domain, mutate) {
201
+ var _after_webSettings;
202
+ const settings = config();
203
+ if (settings.kind === 'unenforced') return {
204
+ outcome: 'unenforced',
205
+ domain
206
+ };
207
+ if (settings.kind === 'misconfigured') {
208
+ return {
209
+ outcome: 'failed',
210
+ domain,
211
+ detail: settings.detail
212
+ };
213
+ }
214
+ let token;
215
+ try {
216
+ token = await accessToken();
217
+ } catch (error) {
218
+ return {
219
+ outcome: 'failed',
220
+ domain,
221
+ detail: `credential: ${String(error)}`
222
+ };
223
+ }
224
+ const url = `${RECAPTCHA_API}/${settings.keyName}`;
225
+ let before;
226
+ try {
227
+ const read = await call(url, {
228
+ method: 'GET',
229
+ token
230
+ });
231
+ if (!read.ok) {
232
+ var _ref;
233
+ var _read_body_error, _read_body;
234
+ return {
235
+ outcome: 'failed',
236
+ domain,
237
+ detail: `read ${read.status}: ${(_ref = (_read_body = read.body) == null ? void 0 : (_read_body_error = _read_body.error) == null ? void 0 : _read_body_error.message) != null ? _ref : 'unknown'}`
238
+ };
239
+ }
240
+ before = read.body;
241
+ } catch (error) {
242
+ return {
243
+ outcome: 'failed',
244
+ domain,
245
+ detail: `read: ${String(error)}`
246
+ };
247
+ }
248
+ const current = domainsOf(before);
249
+ const plan = mutate(current);
250
+ if ('skip' in plan) {
251
+ return {
252
+ outcome: plan.skip,
253
+ domain,
254
+ detail: plan.detail,
255
+ allowedDomains: current
256
+ };
257
+ }
258
+ let after;
259
+ try {
260
+ const written = await call(`${url}?updateMask=webSettings.allowedDomains`, {
261
+ method: 'PATCH',
262
+ token,
263
+ body: JSON.stringify({
264
+ webSettings: {
265
+ allowedDomains: plan.next
266
+ }
267
+ })
268
+ });
269
+ if (!written.ok) {
270
+ var _ref1;
271
+ var _written_body_error, _written_body;
272
+ return {
273
+ outcome: 'failed',
274
+ domain,
275
+ detail: `write ${written.status}: ${(_ref1 = (_written_body = written.body) == null ? void 0 : (_written_body_error = _written_body.error) == null ? void 0 : _written_body_error.message) != null ? _ref1 : 'unknown'}`,
276
+ allowedDomains: current
277
+ };
278
+ }
279
+ after = written.body;
280
+ } catch (error) {
281
+ return {
282
+ outcome: 'failed',
283
+ domain,
284
+ detail: `write: ${String(error)}`,
285
+ allowedDomains: current
286
+ };
287
+ }
288
+ // Hazard 1. The response is the authoritative post-state, so believe it
289
+ // rather than the request body. A mask that the API interpreted more
290
+ // broadly than intended shows up here as a missing sibling field or a
291
+ // vanished entry — and `allowAllDomains: true` would be a key open to every
292
+ // origin on the internet, which must never be a side effect of adding one.
293
+ const applied = domainsOf(after);
294
+ const lost = plan.next.filter((entry)=>!applied.includes(entry));
295
+ if (lost.length > 0) {
296
+ return {
297
+ outcome: 'failed',
298
+ domain,
299
+ detail: `the key did not keep every domain the write sent (missing: ${lost.join(', ')})`,
300
+ allowedDomains: applied
301
+ };
302
+ }
303
+ if ((after == null ? void 0 : (_after_webSettings = after.webSettings) == null ? void 0 : _after_webSettings.allowAllDomains) === true) {
304
+ return {
305
+ outcome: 'failed',
306
+ domain,
307
+ detail: 'the write left allowAllDomains true — the key would accept every origin',
308
+ allowedDomains: applied
309
+ };
310
+ }
311
+ return {
312
+ outcome: plan.outcome,
313
+ domain,
314
+ allowedDomains: applied
315
+ };
316
+ }
317
+ /**
318
+ * Put a custom console domain on the key, so a browser there can attest.
319
+ *
320
+ * Called by `activateConsoleDomain` for the **serving** name only. Redirect
321
+ * twins (`www.acme.com` → `acme.com`) never execute the console, never load
322
+ * reCAPTCHA, and would spend a slot of the 250 for nothing.
323
+ */ export async function allowConsoleOrigin(input) {
324
+ const domain = normalize(input);
325
+ if (!domain) return {
326
+ outcome: 'failed',
327
+ domain: '',
328
+ detail: 'No domain given'
329
+ };
330
+ return rewrite(domain, (current)=>{
331
+ if (current.includes(domain)) return {
332
+ skip: 'already-listed'
333
+ };
334
+ if (current.length >= MAX_ALLOWED_DOMAINS) {
335
+ return {
336
+ skip: 'full',
337
+ detail: `the key holds ${current.length} of ${MAX_ALLOWED_DOMAINS} domains`
338
+ };
339
+ }
340
+ return {
341
+ next: [
342
+ ...current,
343
+ domain
344
+ ],
345
+ outcome: 'listed'
346
+ };
347
+ });
348
+ }
349
+ /**
350
+ * Take a domain back off the key when its claim is released.
351
+ *
352
+ * Removes the EXACT entry and nothing else. A filter that matched subdomains
353
+ * would take `aglyn.com` off the key while releasing `console.aglyn.com` —
354
+ * and the entry it deleted is the one every Aglyn origin attests against.
355
+ */ export async function reclaimConsoleOrigin(input) {
356
+ const domain = normalize(input);
357
+ if (!domain) return {
358
+ outcome: 'failed',
359
+ domain: '',
360
+ detail: 'No domain given'
361
+ };
362
+ return rewrite(domain, (current)=>{
363
+ if (!current.includes(domain)) return {
364
+ skip: 'absent'
365
+ };
366
+ const next = current.filter((entry)=>entry !== domain);
367
+ if (next.length !== current.length - 1) {
368
+ return {
369
+ skip: 'failed',
370
+ detail: `removing ${domain} would have removed ${current.length - next.length} entries`
371
+ };
372
+ }
373
+ return {
374
+ next,
375
+ outcome: 'removed'
376
+ };
377
+ });
378
+ }
379
+ /**
380
+ * Read-only occupancy, for an operator asking "how many of the 250 are gone?"
381
+ *
382
+ * Deliberately returns the live list rather than a count: a count is what got
383
+ * quoted as a ceiling for two weeks.
384
+ */ export async function readConsoleOriginAllowlist() {
385
+ var _result_allowedDomains;
386
+ const result = await rewrite('', ()=>({
387
+ skip: 'absent'
388
+ }));
389
+ if (result.outcome === 'unenforced') {
390
+ return {
391
+ domains: null,
392
+ limit: MAX_ALLOWED_DOMAINS,
393
+ detail: 'App Check is not configured here'
394
+ };
395
+ }
396
+ if (result.outcome === 'failed') {
397
+ return {
398
+ domains: null,
399
+ limit: MAX_ALLOWED_DOMAINS,
400
+ detail: result.detail
401
+ };
402
+ }
403
+ return {
404
+ domains: (_result_allowedDomains = result.allowedDomains) != null ? _result_allowedDomains : [],
405
+ limit: MAX_ALLOWED_DOMAINS
406
+ };
407
+ }
408
+
409
+ //# sourceMappingURL=recaptcha-allowlist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/recaptcha-allowlist.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 * The App Check reCAPTCHA allowlist, written from the product (AGL-1378).\n *\n * ## Why this file has to exist\n *\n * The console reads Firestore **client-side**, and App Check gates those reads\n * with a reCAPTCHA token that the key itself refuses to mint on an origin it\n * has never heard of. Measured against the live key on 2026-08-23, with both\n * controls, by requesting `recaptcha/api2/anchor` with a base64 `co` origin:\n *\n * ```\n * https://app.aglyn.com:443 ACCEPTED (listed)\n * https://never-registered-9f3x.aglyn.com:443 ACCEPTED (subtree of a listed entry)\n * https://console.acme-agency.example:443 REJECTED \"Invalid domain for site key\"\n * https://console.northwind-coffee.com:443 REJECTED \"Invalid domain for site key\"\n * ```\n *\n * A rejected solve means `initializeAppCheck` → `getToken` fails with\n * `appCheck/recaptcha-error`, and the very next Identity Platform call is\n * refused `401 UNAUTHENTICATED — \"Firebase App Check token is invalid\"`\n * **before** token validation (`docs/design/agl-1099a-poc-findings.md` §2).\n * So a custom console domain that is attached and routed but not allowlisted\n * renders a console that can never sign anyone in — and the symptom the\n * customer reports is \"Missing or insufficient permissions\", which is the same\n * string a Security Rules verdict produces. That is the misdiagnosis this\n * whole area keeps generating; the fix is to never let the state exist.\n *\n * ## Why it was believed impossible, and why that is stale\n *\n * AGL-1378 concluded the key was a classic v3 key with no management API. The\n * key was auto-migrated into a Google-created project, and every probe asked\n * `aglyn-main` — where the reCAPTCHA API genuinely is disabled — rather than\n * the project the key actually lives in. Measured 2026-08-23:\n * `recaptchaenterprise.googleapis.com` is enabled there, two accounts we\n * control are `roles/owner`, and `projects.keys.patch` on\n * `webSettings.allowedDomains` answers `200`.\n *\n * ## The two hazards this code is shaped around\n *\n * 1. **A patch that drops sibling fields.** `webSettings` also carries\n * `allowAllDomains`, `integrationType`, `allowAmpTraffic` and\n * `challengeSecurityPreference`; a whole-object write that omitted\n * `allowAllDomains` would read as `false`… or a future default. So the mask\n * is `webSettings.allowedDomains` — the narrowest path the API accepts —\n * **and** the response is re-read afterwards: every domain that was there\n * before must still be there, and `allowAllDomains` must still be `false`.\n * A write that silently widened the key to every origin is a worse outcome\n * than the domain never being listed.\n * 2. **Silent success.** Every outcome here is explicit, and\n * {@link allowlistSatisfied} is the ONLY place that decides which ones mean\n * \"the customer can actually sign in\". `activateConsoleDomain` refuses to\n * mark a claim `active` unless it says yes.\n *\n * ## Exact names, never subtree cover\n *\n * A listed entry covers its whole subtree but never its parents (proved\n * 2026-08-10). It is therefore tempting to skip the write when some existing\n * entry already covers the new name. Deliberately not done: if org A holds\n * `acme.com` and org B later claims `console.acme.com`, B would be silently\n * covered by A's entry — and A detaching would break B with no trace linking\n * the two. One entry per claimed apex is what the documented 250-domain\n * ceiling is denominated in anyway (250 distinct customer apexes, occupancy 5\n * on 2026-08-23), so exactness costs nothing we were not already counting.\n */\n\nimport { getApp } from 'firebase-admin/app'\n\nconst RECAPTCHA_API = 'https://recaptchaenterprise.googleapis.com/v1'\n\n/**\n * Published ceiling for `webSettings.allowedDomains`, quoted rather than\n * measured: *\"Each key supports a maximum of 250 domains.\"* An occupancy\n * reading is NOT a limit — this repo already spent two weeks treating a\n * snapshot of nine entries as a hard ceiling. Re-probe occupancy, never the\n * limit.\n */\nexport const MAX_ALLOWED_DOMAINS = 250\n\n/**\n * Ceiling on the whole exchange, matching `workspace-domains.ts`.\n *\n * This one is awaited by an attach the operator is watching, so an\n * unresponsive Google API must lose quickly rather than hang the activation\n * until the platform kills the function.\n */\nconst REQUEST_TIMEOUT_MS = 8_000\n\nexport type RecaptchaAllowlistOutcome =\n /** We wrote the entry and re-read the key to confirm it. */\n | 'listed'\n /** The exact name was already on the key; nothing was written. */\n | 'already-listed'\n /** We removed the entry and re-read the key to confirm it. */\n | 'removed'\n /** Nothing to remove — the exact name was not on the key. */\n | 'absent'\n /**\n * This deployment runs no App Check reCAPTCHA provider, so there is no\n * allowlist to maintain and nothing is gated. Self-host, and local dev.\n */\n | 'unenforced'\n /** The key is at {@link MAX_ALLOWED_DOMAINS}. A commercial event, not a bug. */\n | 'full'\n /** Anything else: misconfiguration, IAM, transport, or a suspicious write. */\n | 'failed'\n\nexport interface RecaptchaAllowlistResult {\n outcome: RecaptchaAllowlistOutcome\n domain: string\n /** Human-readable cause. Always set when the outcome is not a success. */\n detail?: string\n /**\n * Live `allowedDomains` after the operation, when we got far enough to read\n * them. Occupancy is worth surfacing precisely because it must never be\n * quoted from memory.\n */\n allowedDomains?: string[]\n}\n\n/**\n * Does this outcome mean the origin can attest?\n *\n * The single decision point, so that no caller can invent a looser one. Note\n * what is NOT here: `full` and `failed` both leave a domain that will 401 its\n * customer at sign-in, and `full` is the easier of the two to mistake for a\n * warning.\n */\nexport function allowlistSatisfied(outcome: RecaptchaAllowlistOutcome): boolean {\n return (\n outcome === 'listed' ||\n outcome === 'already-listed' ||\n outcome === 'removed' ||\n outcome === 'absent' ||\n outcome === 'unenforced'\n )\n}\n\ntype Config =\n | { kind: 'unenforced' }\n | { kind: 'misconfigured'; detail: string }\n | { kind: 'ready'; keyName: string }\n\n/**\n * `unenforced` is the ONLY safe way to skip, and it is gated on the client's\n * own site key rather than on this file's credentials.\n *\n * The failure this shape exists to prevent: a production deployment that runs\n * App Check but has no `RECAPTCHA_ADMIN_KEY_NAME` would, under a\n * \"missing config → skip\" rule, report every custom domain ready while listing\n * none of them. Configuration absence must therefore mean two different things\n * depending on whether App Check is running at all — and the tell is\n * `NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY`, which is exactly what\n * `new ReCaptchaV3Provider(...)` is handed.\n *\n * The admin key name must also END with that site key. A resource name\n * pointing at a different key would be written happily and attest nothing —\n * the one misconfiguration whose symptom is indistinguishable from success.\n */\nfunction config(): Config {\n const siteKey = String(process.env.NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY ?? '').trim()\n const keyName = String(process.env.RECAPTCHA_ADMIN_KEY_NAME ?? '').trim()\n if (!siteKey) return { kind: 'unenforced' }\n if (!keyName) {\n return {\n kind: 'misconfigured',\n detail:\n 'App Check runs here (NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY is set) but RECAPTCHA_ADMIN_KEY_NAME is not, so the allowlist cannot be written',\n }\n }\n if (!/^projects\\/[^/]+\\/keys\\/[^/]+$/.test(keyName)) {\n return {\n kind: 'misconfigured',\n detail: `RECAPTCHA_ADMIN_KEY_NAME must be projects/{project}/keys/{siteKey}, got \"${keyName}\"`,\n }\n }\n if (!keyName.endsWith(`/${siteKey}`)) {\n return {\n kind: 'misconfigured',\n detail:\n 'RECAPTCHA_ADMIN_KEY_NAME names a different key than NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY — writing it would allowlist a key nothing uses',\n }\n }\n return { kind: 'ready', keyName }\n}\n\n/**\n * An OAuth token for the same service account firebase-admin already runs as.\n *\n * Not a second credential: `cert()`'s `getAccessToken()` requests\n * `https://www.googleapis.com/auth/cloud-platform` among its scopes, which is\n * the scope `recaptchaenterprise.googleapis.com` asks for. The account needs\n * `recaptchaenterprise.keys.get` + `.update` on the project the KEY lives in —\n * which is not `aglyn-main`; the key was migrated into its own project and\n * that is the whole reason this was thought unautomatable.\n */\nasync function accessToken(): Promise<string> {\n const credential = getApp().options.credential\n if (!credential) throw new Error('firebase-admin app has no credential')\n const token = await credential.getAccessToken()\n const value = token?.access_token\n if (!value) throw new Error('credential returned no access token')\n return value\n}\n\nasync function call(\n url: string,\n init: RequestInit & { token: string },\n): Promise<{ ok: boolean; status: number; body: any }> {\n const { token, ...rest } = init\n const response = await fetch(url, {\n ...rest,\n headers: {\n ...(rest.headers ?? {}),\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n })\n const body = await response.json().catch(() => ({}))\n return { ok: response.ok, status: response.status, body }\n}\n\nfunction domainsOf(key: any): string[] {\n const domains = key?.webSettings?.allowedDomains\n return Array.isArray(domains) ? domains.map((entry: unknown) => String(entry)) : []\n}\n\nfunction normalize(input: string): string {\n return String(input ?? '')\n .trim()\n .toLowerCase()\n .replace(/\\.$/, '')\n}\n\n/**\n * Read the key, apply `mutate` to its domains, write back, and prove the write.\n *\n * `mutate` returning `null` means \"no write is needed\" and is a success.\n */\nasync function rewrite(\n domain: string,\n mutate: (\n current: string[],\n ) =>\n | { next: string[]; outcome: RecaptchaAllowlistOutcome }\n | { skip: RecaptchaAllowlistOutcome; detail?: string },\n): Promise<RecaptchaAllowlistResult> {\n const settings = config()\n if (settings.kind === 'unenforced') return { outcome: 'unenforced', domain }\n if (settings.kind === 'misconfigured') {\n return { outcome: 'failed', domain, detail: settings.detail }\n }\n\n let token: string\n try {\n token = await accessToken()\n } catch (error) {\n return { outcome: 'failed', domain, detail: `credential: ${String(error)}` }\n }\n\n const url = `${RECAPTCHA_API}/${settings.keyName}`\n let before: any\n try {\n const read = await call(url, { method: 'GET', token })\n if (!read.ok) {\n return {\n outcome: 'failed',\n domain,\n detail: `read ${read.status}: ${read.body?.error?.message ?? 'unknown'}`,\n }\n }\n before = read.body\n } catch (error) {\n return { outcome: 'failed', domain, detail: `read: ${String(error)}` }\n }\n\n const current = domainsOf(before)\n const plan = mutate(current)\n if ('skip' in plan) {\n return {\n outcome: plan.skip,\n domain,\n detail: plan.detail,\n allowedDomains: current,\n }\n }\n\n let after: any\n try {\n const written = await call(`${url}?updateMask=webSettings.allowedDomains`, {\n method: 'PATCH',\n token,\n body: JSON.stringify({ webSettings: { allowedDomains: plan.next } }),\n })\n if (!written.ok) {\n return {\n outcome: 'failed',\n domain,\n detail: `write ${written.status}: ${written.body?.error?.message ?? 'unknown'}`,\n allowedDomains: current,\n }\n }\n after = written.body\n } catch (error) {\n return { outcome: 'failed', domain, detail: `write: ${String(error)}`, allowedDomains: current }\n }\n\n // Hazard 1. The response is the authoritative post-state, so believe it\n // rather than the request body. A mask that the API interpreted more\n // broadly than intended shows up here as a missing sibling field or a\n // vanished entry — and `allowAllDomains: true` would be a key open to every\n // origin on the internet, which must never be a side effect of adding one.\n const applied = domainsOf(after)\n const lost = plan.next.filter((entry) => !applied.includes(entry))\n if (lost.length > 0) {\n return {\n outcome: 'failed',\n domain,\n detail: `the key did not keep every domain the write sent (missing: ${lost.join(', ')})`,\n allowedDomains: applied,\n }\n }\n if (after?.webSettings?.allowAllDomains === true) {\n return {\n outcome: 'failed',\n domain,\n detail: 'the write left allowAllDomains true — the key would accept every origin',\n allowedDomains: applied,\n }\n }\n\n return { outcome: plan.outcome, domain, allowedDomains: applied }\n}\n\n/**\n * Put a custom console domain on the key, so a browser there can attest.\n *\n * Called by `activateConsoleDomain` for the **serving** name only. Redirect\n * twins (`www.acme.com` → `acme.com`) never execute the console, never load\n * reCAPTCHA, and would spend a slot of the 250 for nothing.\n */\nexport async function allowConsoleOrigin(\n input: string,\n): Promise<RecaptchaAllowlistResult> {\n const domain = normalize(input)\n if (!domain) return { outcome: 'failed', domain: '', detail: 'No domain given' }\n return rewrite(domain, (current) => {\n if (current.includes(domain)) return { skip: 'already-listed' as const }\n if (current.length >= MAX_ALLOWED_DOMAINS) {\n return {\n skip: 'full' as const,\n detail: `the key holds ${current.length} of ${MAX_ALLOWED_DOMAINS} domains`,\n }\n }\n return { next: [...current, domain], outcome: 'listed' as const }\n })\n}\n\n/**\n * Take a domain back off the key when its claim is released.\n *\n * Removes the EXACT entry and nothing else. A filter that matched subdomains\n * would take `aglyn.com` off the key while releasing `console.aglyn.com` —\n * and the entry it deleted is the one every Aglyn origin attests against.\n */\nexport async function reclaimConsoleOrigin(\n input: string,\n): Promise<RecaptchaAllowlistResult> {\n const domain = normalize(input)\n if (!domain) return { outcome: 'failed', domain: '', detail: 'No domain given' }\n return rewrite(domain, (current) => {\n if (!current.includes(domain)) return { skip: 'absent' as const }\n const next = current.filter((entry) => entry !== domain)\n if (next.length !== current.length - 1) {\n return {\n skip: 'failed' as const,\n detail: `removing ${domain} would have removed ${current.length - next.length} entries`,\n }\n }\n return { next, outcome: 'removed' as const }\n })\n}\n\n/**\n * Read-only occupancy, for an operator asking \"how many of the 250 are gone?\"\n *\n * Deliberately returns the live list rather than a count: a count is what got\n * quoted as a ceiling for two weeks.\n */\nexport async function readConsoleOriginAllowlist(): Promise<{\n domains: string[] | null\n limit: number\n detail?: string\n}> {\n const result = await rewrite('', () => ({ skip: 'absent' as const }))\n if (result.outcome === 'unenforced') {\n return { domains: null, limit: MAX_ALLOWED_DOMAINS, detail: 'App Check is not configured here' }\n }\n if (result.outcome === 'failed') {\n return { domains: null, limit: MAX_ALLOWED_DOMAINS, detail: result.detail }\n }\n return { domains: result.allowedDomains ?? [], limit: MAX_ALLOWED_DOMAINS }\n}\n"],"names":["getApp","RECAPTCHA_API","MAX_ALLOWED_DOMAINS","REQUEST_TIMEOUT_MS","allowlistSatisfied","outcome","config","process","siteKey","String","env","NEXT_PUBLIC_RECAPTCHA_PUBLIC_KEY","trim","keyName","RECAPTCHA_ADMIN_KEY_NAME","kind","detail","test","endsWith","accessToken","credential","options","Error","token","getAccessToken","value","access_token","call","url","init","rest","response","fetch","headers","Authorization","signal","AbortSignal","timeout","body","json","catch","ok","status","domainsOf","key","domains","webSettings","allowedDomains","Array","isArray","map","entry","normalize","input","toLowerCase","replace","rewrite","domain","mutate","after","settings","error","before","read","method","message","current","plan","skip","written","JSON","stringify","next","applied","lost","filter","includes","length","join","allowAllDomains","allowConsoleOrigin","reclaimConsoleOrigin","readConsoleOriginAllowlist","result","limit"],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type OrgPlan, type OrgReleaseFlagOverrides, type ReleaseFlagKey, type ReleaseFlagValue } from '@aglyn/aglyn/server';
18
+ export declare function getServerReleaseFlagValues(): Promise<Record<ReleaseFlagKey, ReleaseFlagValue>>;
19
+ /**
20
+ * Per-org release-flag overrides (AGL-1635), read off `orgs/{orgId}`.
21
+ *
22
+ * Cached on the same TTL as the template read above, and for the same
23
+ * reason: these are consulted on every published page render and every API
24
+ * dispatch, so an uncached read would add a Firestore round trip to the hot
25
+ * path. A grant therefore takes up to a minute to land, matching how long a
26
+ * flag flip already takes — one propagation story, not two.
27
+ *
28
+ * Keyed by orgId because a subject may be a uid or a hostId, and only an
29
+ * org can carry overrides. Failure is silent and yields `{}`: a missing org
30
+ * doc or a denied read must gate exactly as "no override set", never as an
31
+ * error that takes a published page down.
32
+ */
33
+ /**
34
+ * What one org read answers about release flags: the staff overrides, and
35
+ * the org's EFFECTIVE plan for tier targeting (AGL-2486).
36
+ *
37
+ * `plan` is null when the read did not produce an org document — missing,
38
+ * denied, or no Firestore at all. Deliberately not `'free'`: an org whose
39
+ * doc could not be read has an UNKNOWN tier, and
40
+ * `resolveEffectivePlan(undefined)` answers `'free'`, which would hand a
41
+ * Free-targeted flag to every org the read failed for. The two cases have to
42
+ * stay distinguishable at the boundary, because the evaluator treats an
43
+ * unknown plan as a refusal and a known `'free'` as a match.
44
+ */
45
+ interface OrgReleaseFlagTargeting {
46
+ overrides: OrgReleaseFlagOverrides;
47
+ plan: OrgPlan | null;
48
+ }
49
+ /**
50
+ * Both halves of the per-org answer from ONE cached document read.
51
+ *
52
+ * Sharing the read is the point: `plan` and `releaseFlags` live on the same
53
+ * org document, this runs on every published page render and every API
54
+ * dispatch, and fetching the tier separately would have doubled the hot-path
55
+ * Firestore traffic to learn something already in hand.
56
+ */
57
+ export declare function getOrgReleaseFlagTargeting(orgId: string | null | undefined): Promise<OrgReleaseFlagTargeting>;
58
+ export declare function getOrgReleaseFlagOverrides(orgId: string | null | undefined): Promise<OrgReleaseFlagOverrides>;
59
+ /**
60
+ * The verdict for ONE flag and ONE org, overrides applied (AGL-1635).
61
+ *
62
+ * The surfaces that gate a single flag server-side — the tenant admin bar
63
+ * slot, edit-context redemption, edit-access minting — resolve through here
64
+ * rather than reading `getServerReleaseFlagValues()` and calling
65
+ * `isReleaseFlagOn` themselves, so a per-org grant reaches all of them at
66
+ * once. A gate that kept the old two-step would silently ignore overrides,
67
+ * which is the failure this issue exists to fix.
68
+ */
69
+ export declare function isServerReleaseFlagOnForOrg(flagKey: ReleaseFlagKey, orgId: string | null | undefined): Promise<boolean>;
70
+ /** Test seam: drops both caches so a spec can change a verdict. */
71
+ export declare function __resetReleaseFlagCaches(): void;
72
+ /**
73
+ * The server half of the plugin release gate (AGL-422): subtracts
74
+ * flagged-off first-party plugins from an effective set. The staff bypass is
75
+ * only paid for when something was actually subtracted AND the request
76
+ * carries a bearer token — the common all-flags-on path never verifies.
77
+ *
78
+ * `orgId` is the ONLY subject: it selects the per-org overrides (AGL-1635)
79
+ * and it is the rollout bucket. AGL-1635 took a separate `subjectId` here
80
+ * because callers fell back to a hostId when the org lookup missed, and a
81
+ * hostId must never be read as an org id. AGL-1656 removed that fallback
82
+ * instead of accommodating it — a hostId and an orgId hash to different
83
+ * buckets, so a mid-rollout flag could land the published site and the
84
+ * console on opposite sides of the same percentage for the same workspace.
85
+ * With one subject the two cannot disagree by construction, and there is no
86
+ * longer a parameter through which a non-org subject could be reintroduced.
87
+ */
88
+ export declare function filterEnabledPluginsByReleaseFlags(pluginIds: readonly string[], options: {
89
+ /**
90
+ * The org this request belongs to: rollout bucket AND override key.
91
+ * Null when no org could be resolved — a subject-less request gets the
92
+ * fully-enabled flags only, never a partial rollout.
93
+ */
94
+ orgId?: string | null;
95
+ /** Raw Authorization header, when the caller has one (API dispatch). */
96
+ authorization?: string | null;
97
+ /**
98
+ * The account a TOKENLESS request acts for, as its route proved it
99
+ * (AGL-2978) — see `PluginApiRequestSubject.uid`. Consulted for the staff
100
+ * preview only, and only when the request carries no bearer token: a
101
+ * request with a token is the token's, whatever else it names.
102
+ */
103
+ subjectUid?: string | null;
104
+ }): Promise<string[]>;
105
+ export {};