@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,715 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ /**
18
+ * Self-serve enterprise SSO provisioning (AGL-1210).
19
+ *
20
+ * Before this, `tenantManager()` appeared six times in the repo and every one
21
+ * was `authForTenant(...)` — authenticating against a pool somebody had made by
22
+ * hand in the Firebase console. There was no `createTenant` and no
23
+ * `createProviderConfig` anywhere, so onboarding an SSO org meant a person
24
+ * clicking through GCIP and then writing `tenantId`/`providerId` onto the org
25
+ * doc. This module is the write half that removes that step.
26
+ *
27
+ * ## Domain verification is the security gate, not a formality
28
+ *
29
+ * `ssoDomains/{domain}` routes every sign-in for a domain to whichever org
30
+ * claims it. Letting an org self-assert that claim would be an
31
+ * account-takeover vector: claim `competitor.com`, and their employees' sign-in
32
+ * attempts arrive at your IdP. `domainVerified` used to be staff-attested,
33
+ * which is exactly the human step self-serve removes — so the attestation has
34
+ * to be replaced by proof, not deleted.
35
+ *
36
+ * The proof is a DNS TXT record at `_aglyn-challenge.<domain>` carrying a token
37
+ * we generated for THAT org and THAT domain. Only someone who controls the
38
+ * domain's zone can publish it, and a token issued to one org is worthless to
39
+ * another. Nothing reaches `sso.domains` — the array sign-in actually consults
40
+ * — until a lookup has seen the record.
41
+ *
42
+ * DNS resolution mirrors `/api/domains/verify` (AGL-734): pinned public
43
+ * resolvers rather than the runtime's default, because a stale zone once made
44
+ * Vercel's own resolver return NXDOMAIN for records every public resolver could
45
+ * see. The fallback is deliberately narrow — a genuine "no such record" answer
46
+ * must stay a failure, and only an unreachable resolver falls through.
47
+ */ import { promises as dns, Resolver as CallbackResolver } from "dns";
48
+ import { randomBytes } from "crypto";
49
+ import firebaseAdmin from "./firebase-admin.js";
50
+ const auth = ()=>firebaseAdmin.app().auth();
51
+ const firestore = ()=>firebaseAdmin.app().firestore();
52
+ /** Subdomain the challenge record is published at. */ export const SSO_CHALLENGE_PREFIX = '_aglyn-challenge';
53
+ /** Prefix on the TXT value, so an unrelated record cannot pass by accident. */ export const SSO_TXT_PREFIX = 'aglyn-domain-verification=';
54
+ const DOMAIN_PATTERN = /^(?!-)[a-z0-9-]{1,63}(\.[a-z0-9-]{1,63})+$/;
55
+ /** Same pinned resolvers as the custom-domain check — see the header note. */ const PUBLIC_RESOLVERS = [
56
+ '1.1.1.1',
57
+ '8.8.8.8'
58
+ ];
59
+ /**
60
+ * Domains nobody may claim for SSO, however well they prove ownership.
61
+ *
62
+ * A consumer mailbox provider is shared by millions of unrelated people, so
63
+ * "proves control of the zone" and "is entitled to govern every account on it"
64
+ * come apart completely. Verifying `gmail.com` is possible for exactly one
65
+ * party, and routing every Gmail user's sign-in to their IdP is not a thing
66
+ * that party should be able to do either.
67
+ */ const PUBLIC_EMAIL_DOMAINS = new Set([
68
+ 'gmail.com',
69
+ 'googlemail.com',
70
+ 'outlook.com',
71
+ 'hotmail.com',
72
+ 'live.com',
73
+ 'msn.com',
74
+ 'yahoo.com',
75
+ 'ymail.com',
76
+ 'aol.com',
77
+ 'icloud.com',
78
+ 'me.com',
79
+ 'mac.com',
80
+ 'proton.me',
81
+ 'protonmail.com',
82
+ 'pm.me',
83
+ 'gmx.com',
84
+ 'gmx.net',
85
+ 'mail.com',
86
+ 'zoho.com',
87
+ 'yandex.com',
88
+ 'fastmail.com',
89
+ 'hey.com',
90
+ 'qq.com',
91
+ '163.com',
92
+ '126.com'
93
+ ]);
94
+ export function normalizeSsoDomain(input) {
95
+ const raw = String(input != null ? input : '').trim().toLowerCase();
96
+ const at = raw.lastIndexOf('@');
97
+ return (at >= 0 ? raw.slice(at + 1) : raw).replace(/^@+/, '').replace(/\.$/, '');
98
+ }
99
+ export function validateSsoDomain(input) {
100
+ const domain = normalizeSsoDomain(input);
101
+ if (!domain || !DOMAIN_PATTERN.test(domain)) {
102
+ return {
103
+ domain: null,
104
+ error: 'Enter a valid domain, for example acme.com'
105
+ };
106
+ }
107
+ if (PUBLIC_EMAIL_DOMAINS.has(domain)) {
108
+ return {
109
+ domain: null,
110
+ error: 'Public email domains cannot be used for SSO — they are shared by ' + 'people outside your organization. Use a domain your organization owns.'
111
+ };
112
+ }
113
+ return {
114
+ domain,
115
+ error: null
116
+ };
117
+ }
118
+ /**
119
+ * TXT records at the challenge host, lowercased and unwrapped.
120
+ *
121
+ * A TXT answer arrives as an array of string CHUNKS per record (DNS splits
122
+ * strings over 255 bytes), so each record is joined before comparison —
123
+ * otherwise a long token would never match.
124
+ */ export async function resolveChallengeTxt(domain) {
125
+ const host = `${SSO_CHALLENGE_PREFIX}.${domain}`;
126
+ const flatten = (records)=>records.map((chunks)=>chunks.join('').trim());
127
+ try {
128
+ const resolver = new CallbackResolver();
129
+ resolver.setServers(PUBLIC_RESOLVERS);
130
+ const records = await new Promise((resolve, reject)=>{
131
+ resolver.resolveTxt(host, (error, addresses)=>error ? reject(error) : resolve(addresses));
132
+ });
133
+ return flatten(records);
134
+ } catch (error) {
135
+ // ENOTFOUND/ENODATA are real answers — the name has no TXT record. Only an
136
+ // unreachable resolver falls through, so a genuine miss stays a failure.
137
+ const code = error == null ? void 0 : error.code;
138
+ if (code === 'ENOTFOUND' || code === 'ENODATA' || code === 'NXDOMAIN') {
139
+ return [];
140
+ }
141
+ try {
142
+ return flatten(await dns.resolveTxt(host));
143
+ } catch (unused) {
144
+ return [];
145
+ }
146
+ }
147
+ }
148
+ /** The exact TXT value an org must publish for a domain. */ export function challengeValue(token) {
149
+ return `${SSO_TXT_PREFIX}${token}`;
150
+ }
151
+ /**
152
+ * Does this set of TXT records prove control of the zone for THIS token?
153
+ *
154
+ * Extracted so the comparison that domain verification rests on is reachable
155
+ * from a test without standing up Firestore. It is deliberately an exact match
156
+ * on a whole record: a substring test would accept
157
+ * `aglyn-domain-verification=<token>-and-more`, and a prefix test would accept
158
+ * any record that merely starts the same way. Neither proves anything.
159
+ */ export function recordsProveOwnership(records, token) {
160
+ if (!token) return false;
161
+ return records.includes(challengeValue(token));
162
+ }
163
+ /*==========================================
164
+ * PERIODIC RE-VERIFICATION (AGL-1210, the residual the 2026-08-23 audit named).
165
+ *
166
+ * A domain is proven ONCE, when an admin presses Verify, and then routes that
167
+ * domain's sign-ins forever. A company that lets its domain lapse — or sells
168
+ * it — keeps the routing until a human revokes it. The audit called that
169
+ * "the account-takeover shape in slow motion", and it is the last residual on
170
+ * an otherwise strong proof.
171
+ *
172
+ * ## Why this REPORTS and never revokes
173
+ *
174
+ * The obvious fix — re-run the lookup, drop the domain when it fails — is a
175
+ * worse bug than the one it closes. `resolveChallengeTxt` cannot tell "the
176
+ * record is gone" from "the resolver did not answer", because BOTH return an
177
+ * empty array. So a resolver blip during the sweep looks exactly like every
178
+ * customer simultaneously deleting their TXT record, and an automated revoke
179
+ * would log out every enterprise on the platform at once. That is the
180
+ * `reverify-plugin-versions` argument — a lint that can stop a plugin in every
181
+ * workspace is a kill switch with no human in it — and the lockdown argument,
182
+ * where an unreachable Firestore is an outage rather than a verdict.
183
+ *
184
+ * So this half adds two things and deliberately nothing else:
185
+ *
186
+ * 1. {@link probeChallengeTxt} — a THIRD state. `unreachable` is not
187
+ * evidence, and is never counted as a failure.
188
+ * 2. {@link assessDomainDrift} — consecutive CONCLUSIVE failures over a
189
+ * minimum wall-clock age before a domain is even reported as drifted.
190
+ *
191
+ * Revocation stays exactly where it was: {@link revokeDomain}, an explicit act
192
+ * by a person. Detection with a human decision beats an automated action that
193
+ * can log out a paying customer.
194
+ *
195
+ * ## Why the risk tolerates a slow answer
196
+ *
197
+ * The threat needs a domain to lapse, clear ~30 days of registrar grace and
198
+ * ~30 days of redemption, drop, and be re-bought. That is months, not days.
199
+ * And the first harm is not silent credential theft: routing still points at
200
+ * the ORIGINAL org's IdP, where the new owner's staff have no account. The
201
+ * concrete harm is that the new legitimate owner cannot claim their own domain
202
+ * (`issueDomainClaim` refuses a domain another org already routes) and their
203
+ * users are misdirected. Weeks of grace cost that risk almost nothing, and buy
204
+ * immunity from the resolver blip that would otherwise be self-inflicted.
205
+ *=========================================*/ /**
206
+ * The pure half of re-verification — the probe STATES and the decision made
207
+ * from them — lives in `sso-drift-logic.ts` and is re-exported here so the
208
+ * public surface is one module. It has no imports at all, which is what lets
209
+ * the console route's spec run the REAL decision against faked I/O.
210
+ */ export { assessDomainDrift, SSO_DRIFT_FAILURES_BEFORE_REPORT, SSO_DRIFT_MIN_AGE_MS } from "./sso-drift-logic.js";
211
+ /**
212
+ * Re-check a claim's TXT record, distinguishing silence from absence.
213
+ *
214
+ * Same pinned public resolvers and same fallback as {@link resolveChallengeTxt}
215
+ * — this is not a second opinion about DNS, it is the same lookup reporting one
216
+ * more bit. NXDOMAIN/ENOTFOUND/ENODATA are ANSWERS ("that name has no TXT
217
+ * record") and stay conclusive; anything else from both the pinned resolvers
218
+ * and the runtime fallback is `unreachable`.
219
+ *
220
+ * Never throws: a sweep across every org must not stop at the first bad zone.
221
+ */ export async function probeChallengeTxt(domain, token) {
222
+ const host = `${SSO_CHALLENGE_PREFIX}.${domain}`;
223
+ const flatten = (records)=>records.map((chunks)=>chunks.join('').trim());
224
+ const settle = (records)=>({
225
+ status: recordsProveOwnership(records, token) ? 'proven' : 'missing',
226
+ records: records.slice(0, 5)
227
+ });
228
+ /** Codes that are a real answer rather than a failure to get one. */ const conclusive = (code)=>code === 'ENOTFOUND' || code === 'ENODATA' || code === 'NXDOMAIN';
229
+ try {
230
+ const resolver = new CallbackResolver();
231
+ resolver.setServers(PUBLIC_RESOLVERS);
232
+ const records = await new Promise((resolve, reject)=>{
233
+ resolver.resolveTxt(host, (error, addresses)=>error ? reject(error) : resolve(addresses));
234
+ });
235
+ return settle(flatten(records));
236
+ } catch (error) {
237
+ const code = error == null ? void 0 : error.code;
238
+ if (conclusive(code)) return settle([]);
239
+ try {
240
+ return settle(flatten(await dns.resolveTxt(host)));
241
+ } catch (fallbackError) {
242
+ const fallbackCode = fallbackError == null ? void 0 : fallbackError.code;
243
+ // The pinned resolvers gave us nothing usable. Only the fallback's own
244
+ // answer can still make this conclusive; otherwise we genuinely do not
245
+ // know, and saying so is the entire feature.
246
+ if (conclusive(fallbackCode)) return settle([]);
247
+ return {
248
+ status: 'unreachable',
249
+ records: []
250
+ };
251
+ }
252
+ }
253
+ }
254
+ const claimRef = (orgId, domain)=>firestore().collection('orgs').doc(orgId).collection('ssoDomains').doc(domain);
255
+ /**
256
+ * Start (or re-read) a claim on a domain. Idempotent: an existing claim keeps
257
+ * its token, because reissuing would invalidate a record the customer may have
258
+ * already published and turn a working setup into a mysterious failure.
259
+ */ export async function issueDomainClaim(orgId, rawDomain) {
260
+ const { domain, error } = validateSsoDomain(rawDomain);
261
+ if (!domain) return {
262
+ claim: null,
263
+ error: error != null ? error : 'Invalid domain'
264
+ };
265
+ // One domain, one org. Without this a second org could hold a live claim on
266
+ // a domain already governed elsewhere and race it at verification time.
267
+ const existing = await firestore().collection('ssoDomains').doc(domain).get();
268
+ if (existing.exists && existing.get('orgId') !== orgId) {
269
+ return {
270
+ claim: null,
271
+ error: 'That domain is already verified by another organization.'
272
+ };
273
+ }
274
+ const ref = claimRef(orgId, domain);
275
+ const snapshot = await ref.get();
276
+ const stored = snapshot.exists ? snapshot.get('token') : undefined;
277
+ const token = stored || randomBytes(24).toString('base64url');
278
+ if (!snapshot.exists) {
279
+ await ref.set({
280
+ domain,
281
+ token,
282
+ verified: false,
283
+ createdAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
284
+ });
285
+ } else if (!stored) {
286
+ // A claim document that EXISTS but carries NO token is a shape AGL-1887
287
+ // introduced: {@link attestSsoDomain} writes the attestation marker and
288
+ // nothing else, because a staff attestation has no DNS challenge behind
289
+ // it. Before self-serve, every claim document was created here and so
290
+ // always had a token — `exists` and `has a token` were the same fact.
291
+ //
292
+ // They are not any more, and conflating them broke the upgrade path for
293
+ // exactly the orgs AGL-1887 exists to serve. `exists` alone decided
294
+ // whether to persist, so "Set up DNS proof" on an attested domain minted
295
+ // a token, showed the admin a TXT record built from it, and stored it
296
+ // NOWHERE — a different value on every click. `verifyDomainClaim` re-reads
297
+ // `snapshot.get('token')`, finds `undefined`, and could never match the
298
+ // record they had just published. An attested domain could never become a
299
+ // verified one.
300
+ //
301
+ // MERGE, and `verified` is deliberately NOT written: the attestation has
302
+ // to survive being upgraded, and a claim already midway through DNS is not
303
+ // this branch's to demote.
304
+ await ref.set({
305
+ domain,
306
+ token
307
+ }, {
308
+ merge: true
309
+ });
310
+ }
311
+ return {
312
+ error: null,
313
+ claim: {
314
+ domain,
315
+ token,
316
+ verified: Boolean(snapshot.get('verified')),
317
+ recordHost: `${SSO_CHALLENGE_PREFIX}.${domain}`,
318
+ recordValue: challengeValue(token),
319
+ lastRecords: snapshot.get('lastRecords')
320
+ }
321
+ };
322
+ }
323
+ /**
324
+ * Check a claim against live DNS and, on success, promote the domain into
325
+ * `sso.domains` — the array that sign-in consults.
326
+ *
327
+ * Failure is recorded but never destructive: a transient DNS outage must not
328
+ * silently un-govern a domain mid-flight. Removal is `revokeDomain`, which is
329
+ * an explicit act.
330
+ */ export async function verifyDomainClaim(orgId, rawDomain) {
331
+ const { domain, error } = validateSsoDomain(rawDomain);
332
+ if (!domain) return {
333
+ result: null,
334
+ error: error != null ? error : 'Invalid domain'
335
+ };
336
+ const ref = claimRef(orgId, domain);
337
+ const snapshot = await ref.get();
338
+ if (!snapshot.exists) {
339
+ return {
340
+ result: null,
341
+ error: 'No claim on that domain — add it first.'
342
+ };
343
+ }
344
+ const token = snapshot.get('token');
345
+ const expected = challengeValue(token);
346
+ const records = await resolveChallengeTxt(domain);
347
+ const verified = recordsProveOwnership(records, token);
348
+ await ref.set(_extends({
349
+ verified,
350
+ lastCheckedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
351
+ }, verified ? {
352
+ verifiedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),
353
+ lastRecords: firebaseAdmin.firestore.FieldValue.delete()
354
+ } : {
355
+ lastRecords: records.slice(0, 5)
356
+ }), {
357
+ merge: true
358
+ });
359
+ if (verified) {
360
+ await firestore().collection('orgs').doc(orgId).set({
361
+ sso: {
362
+ domains: firebaseAdmin.firestore.FieldValue.arrayUnion(domain),
363
+ domainVerified: true
364
+ }
365
+ }, {
366
+ merge: true
367
+ });
368
+ }
369
+ return {
370
+ error: null,
371
+ result: {
372
+ domain,
373
+ verified,
374
+ records,
375
+ expected
376
+ }
377
+ };
378
+ }
379
+ /**
380
+ * Drop a domain: out of `sso.domains`, and its public routing doc deactivated.
381
+ *
382
+ * Order matters. The public doc goes first, because it is the one an attacker
383
+ * would benefit from: while it says `active`, sign-ins for the domain still
384
+ * route to this org's IdP regardless of what the org doc says.
385
+ */ export async function revokeDomain(orgId, rawDomain) {
386
+ const domain = normalizeSsoDomain(rawDomain);
387
+ if (!domain) return;
388
+ const publicRef = firestore().collection('ssoDomains').doc(domain);
389
+ const publicSnapshot = await publicRef.get();
390
+ if (publicSnapshot.exists && publicSnapshot.get('orgId') === orgId) {
391
+ await publicRef.set({
392
+ active: false
393
+ }, {
394
+ merge: true
395
+ });
396
+ }
397
+ await firestore().collection('orgs').doc(orgId).set({
398
+ sso: {
399
+ domains: firebaseAdmin.firestore.FieldValue.arrayRemove(domain)
400
+ }
401
+ }, {
402
+ merge: true
403
+ });
404
+ await claimRef(orgId, domain).delete().catch(()=>undefined);
405
+ }
406
+ /**
407
+ * Thrown by `ssoServiceMetadata()` when this deployment has no auth origin at
408
+ * all (AGL-2020).
409
+ *
410
+ * Deliberately names the variables and NOT a value: handing the operator
411
+ * a `*.firebaseapp.com` example here is how they end up pasting somebody
412
+ * else's project id into their IdP. `sso-provisioning.spec.ts` asserts this
413
+ * string mentions no Aglyn host.
414
+ */ export const SSO_AUTH_ORIGIN_UNCONFIGURED_MESSAGE = 'Single sign-on is not configured for this deployment: no auth origin ' + 'could be resolved. Set NEXT_PUBLIC_FIREBASE_PROJECT_ID — or one of ' + 'NEXT_PUBLIC_FIREBASE_AUTH_HANDLER_HOST, NEXT_PUBLIC_WORKSPACE_DOMAIN or ' + 'NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN — and rebuild.';
415
+ /**
416
+ * A HOST, never a URL.
417
+ *
418
+ * These strings are compared byte for byte — by GCIP against the assertion's
419
+ * Audience, and by the IdP against the Reply URL it posts to. An operator who
420
+ * pastes `https://auth.aglyn.com/` into the env var would otherwise produce
421
+ * `https://https://auth.aglyn.com//__/auth/handler` and reject every
422
+ * assertion, which presents as "SSO stopped working" with nothing in the
423
+ * config that looks wrong.
424
+ */ function normalizeAuthHost(value) {
425
+ return String(value != null ? value : '').trim().replace(/^https?:\/\//i, '').replace(/\/.*$/, '').toLowerCase();
426
+ }
427
+ /**
428
+ * The auth origin this deployment actually serves (AGL-1381).
429
+ *
430
+ * Mirrors `resolveFirebaseAuthDomain()` in the client's `firebase-config`: on a
431
+ * workspace deployment the whole OAuth/SAML handshake is funnelled through one
432
+ * dedicated same-site origin, `auth.<workspaceDomain>`, which reverse-proxies
433
+ * Firebase's `/__/auth/*` helpers (AGL-462). The IdP has to post the assertion
434
+ * to THAT origin — the one holding the pending-redirect state — or browser
435
+ * storage partitioning severs the two halves of the flow.
436
+ *
437
+ * The order is load-bearing:
438
+ *
439
+ * 1. `NEXT_PUBLIC_FIREBASE_AUTH_HANDLER_HOST` — an explicit override wins.
440
+ * 2. `auth.<NEXT_PUBLIC_WORKSPACE_DOMAIN>` — the deployed workspace origin.
441
+ * Derived only when the variable is actually set, never defaulted: falling
442
+ * back to `auth.aglyn.com` would point a self-hosted install at OUR auth
443
+ * origin, and it would also make the `firebaseapp.com` arm unreachable.
444
+ * It sits ABOVE `AUTH_DOMAIN` because production sets both, and the
445
+ * branded host is the one production serves.
446
+ * 3–4. the project's `*.firebaseapp.com` domain — localhost, previews that
447
+ * are not on the workspace domain, the emulator, and self-host installs
448
+ * that have not stood up an auth subdomain.
449
+ *
450
+ * When all four are absent this THROWS (AGL-2020). It used to default the
451
+ * project id to `aglyn-main` — ours — which is the anti-pattern AGL-1919
452
+ * exists to prevent, and it was silent in both directions that matter:
453
+ * `provisionSsoPool()` wrote our origin into the operator's OWN GCIP provider
454
+ * as `rpEntityId`/`callbackURL`, and the console printed it as the Reply URL
455
+ * for their customer to paste into their IdP. A third party's SAML assertions
456
+ * would then be posted at infrastructure the operator does not run.
457
+ *
458
+ * Throwing rather than degrading is deliberate, and this is the one place in
459
+ * the self-host audit where "run with the feature off" is not available: there
460
+ * is no empty value to fall back to. `authDomain: ''` emits
461
+ * `https:///__/auth/handler`, which GCIP accepts on write and which then
462
+ * rejects every assertion afterwards with nothing in the config that looks
463
+ * wrong — the same "looks configured, works never" shape as an App Check
464
+ * provider built on `undefined`. SSO is opt-in and deliberately configured, so
465
+ * an operator reaching this has asked for it; a named error telling them which
466
+ * variable to set is strictly better than either wrong origin.
467
+ *
468
+ * The READ path degrades instead of 500ing: `/api/orgs/sso?action=status`
469
+ * catches this and returns `metadata: null` with the message, so the settings
470
+ * page renders the reason rather than a wrong instruction. The WRITE path
471
+ * (`provisionSsoPool`) lets it fly.
472
+ *
473
+ * `entityId` carries the scheme. It is not cosmetic: live GCIP for the first
474
+ * SSO org holds `https://auth.aglyn.com`, and Google Workspace is already
475
+ * sending that exact Audience. Emitting the bare host here would be written
476
+ * over the working config by the next `provisionSsoPool()` save and lock out
477
+ * every SSO-only user in the org. `sso-provisioning.spec.ts` pins both strings
478
+ * character for character for that reason.
479
+ */ export function ssoServiceMetadata() {
480
+ var _process_env_NEXT_PUBLIC_FIREBASE_PROJECT_ID;
481
+ const workspaceDomain = normalizeAuthHost(process.env['NEXT_PUBLIC_WORKSPACE_DOMAIN']);
482
+ const projectId = String((_process_env_NEXT_PUBLIC_FIREBASE_PROJECT_ID = process.env['NEXT_PUBLIC_FIREBASE_PROJECT_ID']) != null ? _process_env_NEXT_PUBLIC_FIREBASE_PROJECT_ID : '').trim();
483
+ const authDomain = normalizeAuthHost(process.env['NEXT_PUBLIC_FIREBASE_AUTH_HANDLER_HOST']) || (workspaceDomain ? `auth.${workspaceDomain}` : '') || normalizeAuthHost(process.env['NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN']) || (projectId ? `${projectId}.firebaseapp.com` : '');
484
+ if (!authDomain) throw new Error(SSO_AUTH_ORIGIN_UNCONFIGURED_MESSAGE);
485
+ return {
486
+ authDomain,
487
+ acsUrl: `https://${authDomain}/__/auth/handler`,
488
+ entityId: `https://${authDomain}`
489
+ };
490
+ }
491
+ /**
492
+ * GCIP display names are constrained (4–20 chars, must start with a letter,
493
+ * letters/digits/hyphens only). An org slug can violate every one of those, so
494
+ * derive rather than pass through — a rejected display name would surface as an
495
+ * opaque provisioning failure at the worst moment.
496
+ */ export function poolDisplayName(orgSlug, orgId) {
497
+ const base = `${orgSlug || orgId}`.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-+|-+$/g, '').replace(/^[^a-z]+/, '');
498
+ const padded = (base || 'org').slice(0, 20);
499
+ return padded.length >= 4 ? padded : `${padded}-org`.slice(0, 20);
500
+ }
501
+ /**
502
+ * Create (or update) the org's GCIP pool and its SAML provider.
503
+ *
504
+ * Idempotent by construction: an org that already has a `tenantId` reuses it,
505
+ * and a provider that already exists is UPDATED rather than recreated. Creating
506
+ * a second pool would strand every user in the first one — they are separate
507
+ * user namespaces, so "just make a new one" silently locks out everybody who
508
+ * has already signed in.
509
+ */ export async function provisionSsoPool(options) {
510
+ const { orgId, orgSlug, existingTenantId, existingProviderId, idp } = options;
511
+ const manager = auth().tenantManager();
512
+ const metadata = ssoServiceMetadata();
513
+ let tenantId = existingTenantId;
514
+ let createdPool = false;
515
+ if (!tenantId) {
516
+ const tenant = await manager.createTenant({
517
+ displayName: poolDisplayName(orgSlug, orgId),
518
+ emailSignInConfig: {
519
+ enabled: false,
520
+ passwordRequired: false
521
+ }
522
+ });
523
+ tenantId = tenant.tenantId;
524
+ createdPool = true;
525
+ }
526
+ const tenantAuth = manager.authForTenant(tenantId);
527
+ const providerId = existingProviderId || `saml.${poolDisplayName(orgSlug, orgId)}`;
528
+ const config = {
529
+ displayName: options.displayName || 'Single sign-on',
530
+ enabled: true,
531
+ idpEntityId: idp.entityId,
532
+ ssoURL: idp.ssoUrl,
533
+ x509Certificates: idp.certificates,
534
+ rpEntityId: metadata.entityId,
535
+ callbackURL: metadata.acsUrl
536
+ };
537
+ let createdProvider = false;
538
+ try {
539
+ await tenantAuth.getProviderConfig(providerId);
540
+ await tenantAuth.updateProviderConfig(providerId, config);
541
+ } catch (unused) {
542
+ await tenantAuth.createProviderConfig(_extends({
543
+ providerId
544
+ }, config));
545
+ createdProvider = true;
546
+ }
547
+ return {
548
+ tenantId,
549
+ providerId,
550
+ createdPool,
551
+ createdProvider
552
+ };
553
+ }
554
+ /**
555
+ * Is this claim a STAFF ATTESTATION that the org owns the domain? (AGL-1887)
556
+ *
557
+ * The second of exactly two ways a domain can be proven, and the one that
558
+ * exists for orgs onboarded before self-serve. Their domain was written onto
559
+ * `sso.domains` by a person who checked, with no claim document behind it — so
560
+ * `unpublishSsoDomains` deactivated their routing happily and
561
+ * `publishSsoDomains` then refused to bring it back. Off worked, on did not,
562
+ * and for `aglyn-org` the owner's only credential lives inside the pool that
563
+ * stops answering (AGL-1888).
564
+ *
565
+ * A POSITIVE MARKER, never an absence. The rejected alternative was to treat
566
+ * "a routing doc already exists naming this org" as permission to re-publish,
567
+ * which makes `unpublish` non-final and would let a domain whose claim was
568
+ * revoked come back. What is trusted here is a field a staff member wrote on
569
+ * purpose, and it says WHO, so the trust is attributable.
570
+ *
571
+ * WHY THIS IS NOT A CLIENT-REACHABLE HOLE, which is the only question that
572
+ * matters about it:
573
+ *
574
+ * - `orgs/{orgId}/ssoDomains/{domain}` is `allow read, write: if false` in
575
+ * `cloud/firebase-firestore.rules` — deny-all for every client, reads
576
+ * included. Only the Admin SDK, which bypasses rules, can write here.
577
+ * - No customer-reachable route writes this field. `issueDomainClaim` is the
578
+ * only claim writer on the org's own path and it writes `domain`, `token`,
579
+ * `verified: false` and `createdAt` — never `attestedBy`.
580
+ * - {@link attestSsoDomain} is the only writer, and it is staff-only by
581
+ * construction: it lives here, server-side, and takes the attesting uid.
582
+ *
583
+ * That is why AGL-1887's proposed rules change is NOT part of this: the
584
+ * subcollection is already deny-all, and adding a rule permitting a narrower
585
+ * write than "none" would loosen it, not tighten it.
586
+ *
587
+ * A non-empty STRING, checked strictly. `true`, `1` or `{}` are not an
588
+ * attestation by anybody, and a field that can be satisfied by any truthy
589
+ * value is one a future careless write can satisfy by accident.
590
+ */ export function isStaffAttestedClaim(attestedBy) {
591
+ return typeof attestedBy === 'string' && attestedBy.trim().length > 0;
592
+ }
593
+ /**
594
+ * Record that staff have verified, out of band, that an org owns a domain
595
+ * (AGL-1887).
596
+ *
597
+ * This is the write half of the attestation {@link isStaffAttestedClaim}
598
+ * reads, and it exists so there is exactly ONE way the marker can be created
599
+ * and it is a named function with an actor on it — rather than a field somebody
600
+ * sets by hand in the Firebase console with no record of who or why.
601
+ *
602
+ * `verified` is left FALSE and untouched. The two facts are different: DNS
603
+ * verification is a proof we re-ran ourselves, an attestation is a person
604
+ * vouching. Collapsing them would make an attested domain indistinguishable
605
+ * from a DNS-proven one in the data, and the `verified` flag is what
606
+ * `verifyDomainClaim` owns.
607
+ *
608
+ * Not exposed through `/api/orgs/sso`, deliberately. An org admin proving
609
+ * their own domain has DNS for that; an attestation is us saying we checked,
610
+ * and a customer-reachable path that wrote it would be a customer-reachable
611
+ * path to publishing any domain.
612
+ */ export async function attestSsoDomain(options) {
613
+ var _options_note;
614
+ const { domain, error } = validateSsoDomain(options.domain);
615
+ if (!domain) return {
616
+ domain: null,
617
+ error: error != null ? error : 'Invalid domain'
618
+ };
619
+ if (!isStaffAttestedClaim(options.attestedByUid)) {
620
+ return {
621
+ domain: null,
622
+ error: 'An attestation needs the staff uid making it.'
623
+ };
624
+ }
625
+ // Same uniqueness rule `issueDomainClaim` enforces. An attestation must not
626
+ // be a way around "one domain, one org" — if another org already holds live
627
+ // routing for this domain, that conflict is resolved by a human before
628
+ // anything is written, not by whoever attests last.
629
+ const existing = await firestore().collection('ssoDomains').doc(domain).get();
630
+ if (existing.exists && existing.get('orgId') !== options.orgId) {
631
+ return {
632
+ domain: null,
633
+ error: 'That domain is already verified by another organization.'
634
+ };
635
+ }
636
+ await claimRef(options.orgId, domain).set({
637
+ domain,
638
+ attestedBy: options.attestedByUid.trim(),
639
+ attestedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),
640
+ attestationNote: ((_options_note = options.note) == null ? void 0 : _options_note.trim()) || null
641
+ }, // MERGE, so an existing claim keeps its token and its `verified` state. A
642
+ // fresh document is created for the pre-self-serve orgs that have none,
643
+ // which is the whole point, but an attestation must never reset a claim
644
+ // the org is midway through proving by DNS.
645
+ {
646
+ merge: true
647
+ });
648
+ return {
649
+ domain,
650
+ error: null
651
+ };
652
+ }
653
+ /**
654
+ * Publish the public routing docs — the last step, and the one that actually
655
+ * turns SSO on for a domain.
656
+ *
657
+ * Only PROVEN domains are written — DNS-verified, or staff-attested per
658
+ * {@link isStaffAttestedClaim}. This function is the single writer of
659
+ * `ssoDomains`, so the invariant "a live routing doc implies proven ownership"
660
+ * is enforceable by reading one place.
661
+ */ export async function publishSsoDomains(options) {
662
+ const { orgId, tenantId, providerId, protocol, displayName } = options;
663
+ const published = [];
664
+ const batch = firestore().batch();
665
+ for (const raw of options.domains){
666
+ const domain = normalizeSsoDomain(raw);
667
+ if (!domain) continue;
668
+ // Re-read the claim rather than trusting the caller's list: this is the
669
+ // boundary where an unverified domain would become live, so the check
670
+ // belongs here and not only at the call site.
671
+ //
672
+ // TWO ways to be proven, and no third (AGL-1887). `verified === true` is
673
+ // DNS, re-checked by us; `attestedBy` is a named staff member vouching for
674
+ // an org onboarded before self-serve existed. Both are POSITIVE markers on
675
+ // the org's own claim document, which no client can write.
676
+ //
677
+ // What this must never become is an unconditional accept, or an inference
678
+ // from the routing doc already existing. That check is what stops an org
679
+ // publishing routing for a domain it does not own and intercepting another
680
+ // company's sign-ins — see `sso-publish-gate.emulator.spec.ts`, which
681
+ // exercises every refusal branch, and `sso-attested-restore.spec.ts`,
682
+ // which pins the boundary this loosening must not cross.
683
+ const claim = await claimRef(orgId, domain).get();
684
+ if (!claim.exists) continue;
685
+ const proven = claim.get('verified') === true || isStaffAttestedClaim(claim.get('attestedBy'));
686
+ if (!proven) continue;
687
+ batch.set(firestore().collection('ssoDomains').doc(domain), {
688
+ orgId,
689
+ tenantId,
690
+ providerId,
691
+ protocol,
692
+ displayName: displayName != null ? displayName : null,
693
+ active: true,
694
+ updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
695
+ }, {
696
+ merge: true
697
+ });
698
+ published.push(domain);
699
+ }
700
+ await batch.commit();
701
+ return published;
702
+ }
703
+ /** Deactivate every routing doc this org owns (disable, not delete). */ export async function unpublishSsoDomains(orgId) {
704
+ const owned = await firestore().collection('ssoDomains').where('orgId', '==', orgId).get();
705
+ if (owned.empty) return;
706
+ const batch = firestore().batch();
707
+ owned.docs.forEach((doc)=>batch.set(doc.ref, {
708
+ active: false
709
+ }, {
710
+ merge: true
711
+ }));
712
+ await batch.commit();
713
+ }
714
+
715
+ //# sourceMappingURL=sso-provisioning.js.map