@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,95 @@
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
+ * Has an SSO domain stopped proving it belongs to the org routing it, and is
18
+ * that conclusion safe to act on? (AGL-1210)
19
+ *
20
+ * PURE, and in its own file with NO imports at all — not `dns`, not
21
+ * `firebase-admin`. `sso-provisioning.ts` re-exports everything here, so the
22
+ * public surface is unchanged, but the decision can be required by a test on
23
+ * either side of the app/lib boundary without dragging in the admin SDK. The
24
+ * console route's spec does exactly that: it fakes the I/O and runs the REAL
25
+ * decision, which is the only arrangement where "the route obeys the rule"
26
+ * means anything.
27
+ *
28
+ * The reasoning for the policy itself lives in `sso-provisioning.ts`, next to
29
+ * the probe it interprets. In one line: a DNS lookup cannot distinguish "the
30
+ * record is gone" from "nobody answered", so the third state below is what
31
+ * stops a resolver blip from being read as every customer deleting their TXT
32
+ * record at the same instant.
33
+ */ /**
34
+ * What a re-verification lookup actually established.
35
+ *
36
+ * The third state is the whole point. A resolver that did not answer has told
37
+ * us nothing, and "nothing" must not be counted as "gone".
38
+ */ /**
39
+ * Consecutive conclusive failures before a domain is REPORTED as drifted.
40
+ *
41
+ * Three, against a weekly sweep, is a three-week signal. Exported so the
42
+ * threshold is one named number rather than a literal buried in a loop, and so
43
+ * a test can state the boundary it is checking.
44
+ */ export const SSO_DRIFT_FAILURES_BEFORE_REPORT = 3;
45
+ /**
46
+ * And the wall-clock floor the same run must clear, independent of the count.
47
+ *
48
+ * Belt AND braces, because the two guard different mistakes. The count alone
49
+ * can be run up in three minutes by a staff member re-running the sweep by
50
+ * hand, or by a scheduler firing more often than anyone intended — and "we
51
+ * checked three times" reads as diligence while meaning nothing. The age alone
52
+ * would report a domain that failed once a month ago and has answered every
53
+ * time since. A report needs both.
54
+ */ export const SSO_DRIFT_MIN_AGE_MS = 14 * 24 * 60 * 60000;
55
+ /**
56
+ * Should this probe change anything, and has the domain drifted?
57
+ *
58
+ * `hold` is the load-bearing arm. An `unreachable` probe neither increments
59
+ * the count nor resets it — an outage must not manufacture evidence, and must
60
+ * not launder away evidence already gathered either. The run simply pauses,
61
+ * and the next conclusive answer continues it.
62
+ *
63
+ * Note what this function CANNOT return: there is no `revoke`. Detection is
64
+ * the whole of its vocabulary, and revocation stays a human act.
65
+ */ export function assessDomainDrift(probe, prior, nowMs, failuresBeforeReport = SSO_DRIFT_FAILURES_BEFORE_REPORT, minAgeMs = SSO_DRIFT_MIN_AGE_MS) {
66
+ // Read defensively: these come straight off a Firestore document, and with
67
+ // `strictNullChecks` off the compiler will not mention that they might be
68
+ // undefined, NaN, or a value written before this feature existed.
69
+ const priorCount = Number(prior == null ? void 0 : prior.consecutiveFailures) || 0;
70
+ const priorFirst = Number.isFinite(prior == null ? void 0 : prior.firstFailureAtMs) && prior.firstFailureAtMs > 0 ? prior.firstFailureAtMs : null;
71
+ if (probe.status === 'unreachable') {
72
+ return {
73
+ action: 'hold',
74
+ consecutiveFailures: priorCount,
75
+ firstFailureAtMs: priorFirst
76
+ };
77
+ }
78
+ if (probe.status === 'proven') {
79
+ return {
80
+ action: 'clear',
81
+ consecutiveFailures: 0,
82
+ firstFailureAtMs: null
83
+ };
84
+ }
85
+ const consecutiveFailures = priorCount + 1;
86
+ const firstFailureAtMs = priorFirst != null ? priorFirst : nowMs;
87
+ const oldEnough = nowMs - firstFailureAtMs >= minAgeMs;
88
+ return {
89
+ action: consecutiveFailures >= failuresBeforeReport && oldEnough ? 'report' : 'count',
90
+ consecutiveFailures,
91
+ firstFailureAtMs
92
+ };
93
+ }
94
+
95
+ //# sourceMappingURL=sso-drift-logic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/sso-drift-logic.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 * Has an SSO domain stopped proving it belongs to the org routing it, and is\n * that conclusion safe to act on? (AGL-1210)\n *\n * PURE, and in its own file with NO imports at all — not `dns`, not\n * `firebase-admin`. `sso-provisioning.ts` re-exports everything here, so the\n * public surface is unchanged, but the decision can be required by a test on\n * either side of the app/lib boundary without dragging in the admin SDK. The\n * console route's spec does exactly that: it fakes the I/O and runs the REAL\n * decision, which is the only arrangement where \"the route obeys the rule\"\n * means anything.\n *\n * The reasoning for the policy itself lives in `sso-provisioning.ts`, next to\n * the probe it interprets. In one line: a DNS lookup cannot distinguish \"the\n * record is gone\" from \"nobody answered\", so the third state below is what\n * stops a resolver blip from being read as every customer deleting their TXT\n * record at the same instant.\n */\n\n/**\n * What a re-verification lookup actually established.\n *\n * The third state is the whole point. A resolver that did not answer has told\n * us nothing, and \"nothing\" must not be counted as \"gone\".\n */\nexport type DomainProbeStatus =\n /** The record is there and matches this org's token. */\n | 'proven'\n /** We got an ANSWER, and the token is not in it. Conclusive. */\n | 'missing'\n /** Nobody answered. Not evidence of anything, in either direction. */\n | 'unreachable'\n\nexport interface DomainProbe {\n status: DomainProbeStatus\n /** What the lookup saw — the difference between \"gone\" and \"changed\". */\n records: string[]\n}\n\n/**\n * Consecutive conclusive failures before a domain is REPORTED as drifted.\n *\n * Three, against a weekly sweep, is a three-week signal. Exported so the\n * threshold is one named number rather than a literal buried in a loop, and so\n * a test can state the boundary it is checking.\n */\nexport const SSO_DRIFT_FAILURES_BEFORE_REPORT = 3\n\n/**\n * And the wall-clock floor the same run must clear, independent of the count.\n *\n * Belt AND braces, because the two guard different mistakes. The count alone\n * can be run up in three minutes by a staff member re-running the sweep by\n * hand, or by a scheduler firing more often than anyone intended — and \"we\n * checked three times\" reads as diligence while meaning nothing. The age alone\n * would report a domain that failed once a month ago and has answered every\n * time since. A report needs both.\n */\nexport const SSO_DRIFT_MIN_AGE_MS = 14 * 24 * 60 * 60_000\n\n/** The drift bookkeeping carried on a claim between sweeps. */\nexport interface DomainDriftState {\n /** Conclusive failures in a row. Reset by any single `proven`. */\n consecutiveFailures: number\n /** When the current run of failures began. Null when there is no run. */\n firstFailureAtMs: number | null\n}\n\nexport type DomainDriftAction =\n /** Proven. Any failure run ends here. */\n | 'clear'\n /** Unreachable. Change nothing at all — this is not evidence. */\n | 'hold'\n /** Conclusive failure, but not yet enough of them for long enough. */\n | 'count'\n /** Enough, for long enough. Tell people. Still revoke nothing. */\n | 'report'\n\nexport interface DomainDriftVerdict {\n action: DomainDriftAction\n /** The run length AFTER this probe. Unchanged on `hold`. */\n consecutiveFailures: number\n /** Start of the run after this probe, for the next sweep to carry. */\n firstFailureAtMs: number | null\n}\n\n/**\n * Should this probe change anything, and has the domain drifted?\n *\n * `hold` is the load-bearing arm. An `unreachable` probe neither increments\n * the count nor resets it — an outage must not manufacture evidence, and must\n * not launder away evidence already gathered either. The run simply pauses,\n * and the next conclusive answer continues it.\n *\n * Note what this function CANNOT return: there is no `revoke`. Detection is\n * the whole of its vocabulary, and revocation stays a human act.\n */\nexport function assessDomainDrift(\n probe: DomainProbe,\n prior: DomainDriftState,\n nowMs: number,\n failuresBeforeReport: number = SSO_DRIFT_FAILURES_BEFORE_REPORT,\n minAgeMs: number = SSO_DRIFT_MIN_AGE_MS,\n): DomainDriftVerdict {\n // Read defensively: these come straight off a Firestore document, and with\n // `strictNullChecks` off the compiler will not mention that they might be\n // undefined, NaN, or a value written before this feature existed.\n const priorCount = Number(prior?.consecutiveFailures) || 0\n const priorFirst =\n Number.isFinite(prior?.firstFailureAtMs) && prior.firstFailureAtMs > 0\n ? prior.firstFailureAtMs\n : null\n\n if (probe.status === 'unreachable') {\n return {\n action: 'hold',\n consecutiveFailures: priorCount,\n firstFailureAtMs: priorFirst,\n }\n }\n if (probe.status === 'proven') {\n return { action: 'clear', consecutiveFailures: 0, firstFailureAtMs: null }\n }\n\n const consecutiveFailures = priorCount + 1\n const firstFailureAtMs = priorFirst ?? nowMs\n const oldEnough = nowMs - firstFailureAtMs >= minAgeMs\n return {\n action:\n consecutiveFailures >= failuresBeforeReport && oldEnough\n ? 'report'\n : 'count',\n consecutiveFailures,\n firstFailureAtMs,\n }\n}\n"],"names":["SSO_DRIFT_FAILURES_BEFORE_REPORT","SSO_DRIFT_MIN_AGE_MS","assessDomainDrift","probe","prior","nowMs","failuresBeforeReport","minAgeMs","priorCount","Number","consecutiveFailures","priorFirst","isFinite","firstFailureAtMs","status","action","oldEnough"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;CAiBC,GAED;;;;;CAKC,GAeD;;;;;;CAMC,GACD,OAAO,MAAMA,mCAAmC,EAAC;AAEjD;;;;;;;;;CASC,GACD,OAAO,MAAMC,uBAAuB,KAAK,KAAK,KAAK,MAAM;AA4BzD;;;;;;;;;;CAUC,GACD,OAAO,SAASC,kBACdC,KAAkB,EAClBC,KAAuB,EACvBC,KAAa,EACbC,uBAA+BN,gCAAgC,EAC/DO,WAAmBN,oBAAoB;IAEvC,2EAA2E;IAC3E,0EAA0E;IAC1E,kEAAkE;IAClE,MAAMO,aAAaC,OAAOL,yBAAAA,MAAOM,mBAAmB,KAAK;IACzD,MAAMC,aACJF,OAAOG,QAAQ,CAACR,yBAAAA,MAAOS,gBAAgB,KAAKT,MAAMS,gBAAgB,GAAG,IACjET,MAAMS,gBAAgB,GACtB;IAEN,IAAIV,MAAMW,MAAM,KAAK,eAAe;QAClC,OAAO;YACLC,QAAQ;YACRL,qBAAqBF;YACrBK,kBAAkBF;QACpB;IACF;IACA,IAAIR,MAAMW,MAAM,KAAK,UAAU;QAC7B,OAAO;YAAEC,QAAQ;YAASL,qBAAqB;YAAGG,kBAAkB;QAAK;IAC3E;IAEA,MAAMH,sBAAsBF,aAAa;IACzC,MAAMK,mBAAmBF,qBAAAA,aAAcN;IACvC,MAAMW,YAAYX,QAAQQ,oBAAoBN;IAC9C,OAAO;QACLQ,QACEL,uBAAuBJ,wBAAwBU,YAC3C,WACA;QACNN;QACAG;IACF;AACF"}
@@ -0,0 +1,204 @@
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
+ * SSO enforcement sweep (AGL-1129): when an org turns on `sso.enforced`,
19
+ * every account in its GCIP tenant loses any sign-in method that is not the
20
+ * org's own IdP.
21
+ *
22
+ * AGL-1128 stopped SSO-governed accounts from linking NEW consumer providers.
23
+ * This is the grandfathering half: an account that linked Google before
24
+ * enforcement keeps a way in that the customer's IdP cannot see or revoke, and
25
+ * a bypass that predates the switch is still a bypass.
26
+ *
27
+ * Runs against the TENANT pool via `authForPool` — project-level Admin SDK
28
+ * calls cannot see, let alone mutate, these accounts (AGL-1122).
29
+ *
30
+ * Three properties this must have, in order of how badly they bite:
31
+ *
32
+ * 1. **Never unlink down to zero.** An account whose only provider is the one
33
+ * we are about to remove would be orphaned — reachable by nobody, not even
34
+ * the IdP. Skipped and reported, never assumed away: being in the tenant
35
+ * does not prove the SAML link is present.
36
+ * 2. **Revoke refresh tokens for accounts it actually changed.** Unlinking
37
+ * does not end a live session, so without this the bypass simply has a
38
+ * clock on it. Only for accounts that changed — a sweep that revoked
39
+ * everyone would sign out the org's whole staff on a no-op run.
40
+ * 3. **Idempotent.** Re-running is the natural way to catch accounts created
41
+ * between the flag flip and the sweep, so a second run must be a no-op
42
+ * rather than a second round of revocations.
43
+ * 4. **Never leave the ORG with no way in** (AGL-1888). Property 1 protects
44
+ * one account at a time and is blind to the org-level failure: strip every
45
+ * account down to the SAML link and the pool is perfectly consistent right
46
+ * up until the IdP stops answering, at which point nobody can sign in and
47
+ * we cannot let them back in either. The `aglyn-org-y5v14` tenant is in
48
+ * that state today. So enforcement REFUSES unless the org keeps a way in
49
+ * that does
50
+ * not depend on the IdP: either a DESIGNATED break-glass account inside
51
+ * the pool, or an org owner who lives OUTSIDE it
52
+ * ({@link findBreakGlassOrgOwners}). The second is what makes the first
53
+ * satisfiable at all — nothing inside a pool we provisioned can hold a
54
+ * password, so for a while this guard could only ever say no.
55
+ */
56
+ import type { UserRecord } from 'firebase-admin/auth';
57
+ import { type SsoBreakGlassOwner } from './sso-break-glass-owners';
58
+ export interface SsoEnforcementOptions {
59
+ /** Staff uid recorded on the audit rows; `system` for an automated run. */
60
+ actorUid?: string;
61
+ /** Report what would change without touching anything. */
62
+ dryRun?: boolean;
63
+ /**
64
+ * Run even when `sso.enforced` is false. ONLY for a rehearsal against a
65
+ * throwaway account — the flag is the customer's instruction, and acting
66
+ * without it removes sign-in methods nobody asked us to remove.
67
+ */
68
+ force?: boolean;
69
+ }
70
+ /** Why an account with removable providers was left alone. */
71
+ export type SsoEnforcementSkip = 'would-orphan' | 'break-glass';
72
+ export interface SsoEnforcementAccount {
73
+ uid: string;
74
+ email: string | null;
75
+ /** Providers removed (or that would be, in a dry run). */
76
+ unlinked: string[];
77
+ /** Providers left in place. */
78
+ kept: string[];
79
+ /** Why an account with removable providers was left alone. */
80
+ skipped?: SsoEnforcementSkip;
81
+ }
82
+ /**
83
+ * Whether this org would still have a way in if its IdP stopped answering
84
+ * (AGL-1888).
85
+ */
86
+ export interface SsoLockoutAssessment {
87
+ /**
88
+ * True when the org keeps at least one way in that its IdP does not
89
+ * mediate: a DESIGNATED break-glass account inside the pool, or an org
90
+ * owner outside it.
91
+ */
92
+ safe: boolean;
93
+ /** Designated break-glass uids that actually retain a non-IdP credential. */
94
+ retainedBy: string[];
95
+ /**
96
+ * Designated uids that do NOT protect anybody — either absent from the pool,
97
+ * or holding nothing but the IdP link. Named rather than silently ignored:
98
+ * a designation that protects nothing is worse than none, because it reads
99
+ * as protection.
100
+ */
101
+ ineffective: string[];
102
+ /**
103
+ * Org owners in the PROJECT pool (AGL-1888 option (a)). The sweep cannot
104
+ * see these accounts, so an IdP that stops answering cannot lock them out.
105
+ * Named, not counted: the admin has to recognise the person.
106
+ */
107
+ ownersOutsidePool: SsoBreakGlassOwner[];
108
+ /**
109
+ * The owner lookup could not be completed. `ownersOutsidePool` is then
110
+ * INCOMPLETE rather than empty, so the refusal must say we could not check
111
+ * instead of asserting the org has nobody. Never makes an org safe.
112
+ */
113
+ ownerLookupFailed: boolean;
114
+ }
115
+ export interface SsoEnforcementResult {
116
+ orgId: string;
117
+ tenantId: string;
118
+ providerId: string;
119
+ dryRun: boolean;
120
+ /** Accounts walked. */
121
+ scanned: number;
122
+ /** Accounts whose providers changed (0 on a converged re-run). */
123
+ changed: number;
124
+ accounts: SsoEnforcementAccount[];
125
+ /** True when the tenant holds more accounts than one sweep walks. */
126
+ truncated: boolean;
127
+ /**
128
+ * Whether the org retains a way in that does not depend on its IdP
129
+ * (AGL-1888). Present on the DRY RUN too — that is the point, since the
130
+ * rehearsal is where an org is supposed to discover it needs one.
131
+ */
132
+ lockout: SsoLockoutAssessment;
133
+ }
134
+ export declare class SsoEnforcementError extends Error {
135
+ }
136
+ /**
137
+ * Strip non-IdP sign-in methods from every account in an org's SSO tenant.
138
+ *
139
+ * Throws when the org has no active SSO config, or when `sso.enforced` is
140
+ * false without `force` — enforcement is the customer's switch, and running
141
+ * this without it would be us deciding to remove their people's sign-in
142
+ * methods on our own.
143
+ */
144
+ export declare function enforceSsoSignInMethods(orgId: string, options?: SsoEnforcementOptions): Promise<SsoEnforcementResult>;
145
+ /**
146
+ * What one account loses, decided without mutating anything so the dry run
147
+ * and the real run cannot disagree.
148
+ *
149
+ * `password` counts as a provider to strip: an email/password credential on a
150
+ * governed account is exactly the sort of standing bypass enforcement is
151
+ * bought to remove, and it is not the org's IdP.
152
+ *
153
+ * @param breakGlass uids the org has DESIGNATED to keep their other sign-in
154
+ * methods (AGL-1888). Deliberately a parameter rather than a lookup:
155
+ * this function is the whole decision and stays pure, so the rehearsal
156
+ * and the real run cannot diverge on it either.
157
+ */
158
+ export declare function planAccount(record: UserRecord, providerId: string, breakGlass?: ReadonlySet<string>): SsoEnforcementAccount;
159
+ /**
160
+ * Would this org still have a way in after the sweep? (AGL-1888)
161
+ *
162
+ * Pure, and over the PLANNED accounts rather than the raw records, so it
163
+ * answers the question about the state enforcement is actually about to
164
+ * create rather than the one it is in now.
165
+ *
166
+ * ONLY A DESIGNATED ACCOUNT COUNTS. An account that happens to retain a
167
+ * password because it is misconfigured — the `would-orphan` case, which has no
168
+ * IdP link at all — is not break-glass: nobody chose it, it may be a stale
169
+ * record or a service account, and an org protected by an accident it does not
170
+ * know about is not protected. Requiring the designation is what makes this a
171
+ * control rather than a coincidence.
172
+ *
173
+ * A designation only counts when the account actually holds something OTHER
174
+ * than the org's IdP. Naming an account whose sole credential is the SAML link
175
+ * is the most natural way to get this wrong — it looks like a break-glass
176
+ * account and provides nothing, because it fails in exactly the situation it
177
+ * exists for.
178
+ *
179
+ * THE SECOND WAY IN IS NOT IN THE POOL AT ALL (AGL-1888 option (a)). An org
180
+ * owner holding a project-level credential is invisible to the sweep — the
181
+ * enforcement pass lists `authForPool(tenantId)` and never touches the
182
+ * project pool — so a lapsed certificate cannot reach them. No designation is
183
+ * required for those: nobody has to be told to keep the key they already
184
+ * hold, and requiring a tick would put the requirement back out of reach for
185
+ * exactly the org that already satisfies it. Whether such an owner exists is
186
+ * decided by {@link findBreakGlassOrgOwners}, which is where the seven
187
+ * conditions live; this function only counts what it is handed, so that the
188
+ * rehearsal and the real run cannot disagree.
189
+ *
190
+ * `ownerLookupFailed` deliberately does NOT make an org safe. It exists so a
191
+ * refusal can distinguish "you have nobody" from "we could not check" — an
192
+ * error swallowed into an empty list renders as a measured zero, and nothing
193
+ * about it looks wrong.
194
+ */
195
+ export declare function assessSsoLockoutRisk(accounts: readonly SsoEnforcementAccount[], providerId: string, breakGlass: readonly string[], ownersOutsidePool?: readonly SsoBreakGlassOwner[], ownerLookupFailed?: boolean): SsoLockoutAssessment;
196
+ /**
197
+ * The refusal an org sees when enforcing would lock it out of itself.
198
+ *
199
+ * Says what to do, because a refusal that only says no sends someone to turn
200
+ * the guard off. Deliberately free of a product name — this string is read by
201
+ * self-host operators too.
202
+ */
203
+ export declare const SSO_LOCKOUT_REFUSAL: string;
204
+ export default enforceSsoSignInMethods;
@@ -0,0 +1,309 @@
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
+ * SSO enforcement sweep (AGL-1129): when an org turns on `sso.enforced`,
18
+ * every account in its GCIP tenant loses any sign-in method that is not the
19
+ * org's own IdP.
20
+ *
21
+ * AGL-1128 stopped SSO-governed accounts from linking NEW consumer providers.
22
+ * This is the grandfathering half: an account that linked Google before
23
+ * enforcement keeps a way in that the customer's IdP cannot see or revoke, and
24
+ * a bypass that predates the switch is still a bypass.
25
+ *
26
+ * Runs against the TENANT pool via `authForPool` — project-level Admin SDK
27
+ * calls cannot see, let alone mutate, these accounts (AGL-1122).
28
+ *
29
+ * Three properties this must have, in order of how badly they bite:
30
+ *
31
+ * 1. **Never unlink down to zero.** An account whose only provider is the one
32
+ * we are about to remove would be orphaned — reachable by nobody, not even
33
+ * the IdP. Skipped and reported, never assumed away: being in the tenant
34
+ * does not prove the SAML link is present.
35
+ * 2. **Revoke refresh tokens for accounts it actually changed.** Unlinking
36
+ * does not end a live session, so without this the bypass simply has a
37
+ * clock on it. Only for accounts that changed — a sweep that revoked
38
+ * everyone would sign out the org's whole staff on a no-op run.
39
+ * 3. **Idempotent.** Re-running is the natural way to catch accounts created
40
+ * between the flag flip and the sweep, so a second run must be a no-op
41
+ * rather than a second round of revocations.
42
+ * 4. **Never leave the ORG with no way in** (AGL-1888). Property 1 protects
43
+ * one account at a time and is blind to the org-level failure: strip every
44
+ * account down to the SAML link and the pool is perfectly consistent right
45
+ * up until the IdP stops answering, at which point nobody can sign in and
46
+ * we cannot let them back in either. The `aglyn-org-y5v14` tenant is in
47
+ * that state today. So enforcement REFUSES unless the org keeps a way in
48
+ * that does
49
+ * not depend on the IdP: either a DESIGNATED break-glass account inside
50
+ * the pool, or an org owner who lives OUTSIDE it
51
+ * ({@link findBreakGlassOrgOwners}). The second is what makes the first
52
+ * satisfiable at all — nothing inside a pool we provisioned can hold a
53
+ * password, so for a while this guard could only ever say no.
54
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
55
+ import { FieldValue } from "firebase-admin/firestore";
56
+ import { authForPool } from "./auth-pools.js";
57
+ import { findBreakGlassOrgOwners } from "./sso-break-glass-owners.js";
58
+ import { invalidateTokenRevocationCache } from "./token-revocation.js";
59
+ import firebaseAdmin from "./firebase-admin.js";
60
+ import { notifyUsers } from "./notifications.js";
61
+ const firestore = ()=>firebaseAdmin.app().firestore();
62
+ /** How many tenant accounts one sweep will walk. Enterprise pools are small. */ const TENANT_USER_CAP = 1000;
63
+ export class SsoEnforcementError extends Error {
64
+ }
65
+ /**
66
+ * Strip non-IdP sign-in methods from every account in an org's SSO tenant.
67
+ *
68
+ * Throws when the org has no active SSO config, or when `sso.enforced` is
69
+ * false without `force` — enforcement is the customer's switch, and running
70
+ * this without it would be us deciding to remove their people's sign-in
71
+ * methods on our own.
72
+ */ export async function enforceSsoSignInMethods(orgId, options = {}) {
73
+ const { actorUid = 'system', dryRun = false, force = false } = options;
74
+ const db = firestore();
75
+ const orgSnapshot = await db.collection('orgs').doc(orgId).get();
76
+ if (!orgSnapshot.exists) {
77
+ throw new SsoEnforcementError(`No such organization: ${orgId}`);
78
+ }
79
+ const sso = orgSnapshot.get('sso');
80
+ const tenantId = sso == null ? void 0 : sso.tenantId;
81
+ const providerId = sso == null ? void 0 : sso.providerId;
82
+ if (!tenantId || !providerId) {
83
+ throw new SsoEnforcementError(`${orgId} has no SSO tenant configured`);
84
+ }
85
+ if ((sso == null ? void 0 : sso.status) !== 'active') {
86
+ var _ref;
87
+ throw new SsoEnforcementError(`${orgId} SSO status is "${(_ref = sso == null ? void 0 : sso.status) != null ? _ref : 'unset'}", not active`);
88
+ }
89
+ if (!(sso == null ? void 0 : sso.enforced) && !force) {
90
+ throw new SsoEnforcementError(`${orgId} has sso.enforced = false — nothing to enforce`);
91
+ }
92
+ const breakGlassUids = Array.isArray(sso == null ? void 0 : sso.breakGlassUids) ? sso.breakGlassUids.map(String).filter(Boolean) : [];
93
+ const pool = authForPool(tenantId);
94
+ const page = await pool.listUsers(TENANT_USER_CAP);
95
+ const accounts = [];
96
+ const changedUids = [];
97
+ const breakGlass = new Set(breakGlassUids);
98
+ for (const record of page.users){
99
+ const summary = planAccount(record, providerId, breakGlass);
100
+ accounts.push(summary);
101
+ }
102
+ // The half the pool cannot supply (AGL-1888 option (a)). Run for the DRY
103
+ // RUN too: the rehearsal is where an org learns whether it may enforce, and
104
+ // a rehearsal that skipped this would report a lockout risk the real run
105
+ // then disagrees with.
106
+ const ownerLookup = await findBreakGlassOrgOwners(orgId, providerId, tenantId);
107
+ const lockout = assessSsoLockoutRisk(accounts, providerId, breakGlassUids, ownerLookup.owners, ownerLookup.unavailable);
108
+ // THE PRE-FLIGHT (AGL-1888). Planned first, refused before a single
109
+ // `updateUser` — this is the whole reason the plan is computed separately
110
+ // from being carried out. Half a sweep is the worst outcome available: the
111
+ // accounts already stripped are stripped, and the org is locked out with no
112
+ // record of how far it got.
113
+ //
114
+ // The dry run is exempt, and has to be. The rehearsal is exactly how an org
115
+ // FINDS OUT it needs a break-glass account, so refusing to rehearse would
116
+ // leave them guessing at the requirement they are being held to.
117
+ //
118
+ // No override parameter. An `acknowledgeLockoutRisk: true` a caller can pass
119
+ // is not a guard, it is a spelling of the guard being off — and the caller
120
+ // that would pass it is the one already convinced enforcement is fine.
121
+ if (!dryRun && !lockout.safe) {
122
+ throw new SsoEnforcementError(SSO_LOCKOUT_REFUSAL);
123
+ }
124
+ for (const summary of accounts){
125
+ const record = page.users.find((user)=>user.uid === summary.uid);
126
+ if (!record) continue;
127
+ if (!summary.unlinked.length || summary.skipped) continue;
128
+ if (!dryRun) {
129
+ await pool.updateUser(record.uid, {
130
+ providersToUnlink: summary.unlinked
131
+ });
132
+ // Only for accounts that actually changed (property 2 above). An
133
+ // existing session outlives the unlink otherwise.
134
+ await pool.revokeRefreshTokens(record.uid);
135
+ // This process refuses the old token NOW (AGL-1881); every other one
136
+ // converges within TOKEN_REVOCATION_TTL_MS.
137
+ invalidateTokenRevocationCache(record.uid, tenantId);
138
+ await db.collection('adminAudit').add({
139
+ actorUid,
140
+ action: 'org.sso.enforceSignInMethods',
141
+ target: `users/${record.uid}`,
142
+ before: {
143
+ providers: providerIdsOf(record)
144
+ },
145
+ after: {
146
+ providers: summary.kept,
147
+ orgId,
148
+ tenantId,
149
+ unlinked: summary.unlinked,
150
+ tokensRevoked: true
151
+ },
152
+ at: FieldValue.serverTimestamp()
153
+ });
154
+ }
155
+ changedUids.push(record.uid);
156
+ }
157
+ if (!dryRun && changedUids.length) {
158
+ // Explaining it beats a mysteriously missing button on their next visit.
159
+ await notifyUsers(changedUids, {
160
+ type: 'system.signInMethodRemoved',
161
+ title: 'Your sign-in methods changed',
162
+ body: 'Your organization now requires single sign-on, so other sign-in ' + 'methods have been removed from your account. Sign in through your ' + 'organization instead.',
163
+ orgId,
164
+ link: '/manage/user'
165
+ });
166
+ }
167
+ return {
168
+ orgId,
169
+ tenantId,
170
+ providerId,
171
+ dryRun,
172
+ scanned: page.users.length,
173
+ changed: changedUids.length,
174
+ accounts,
175
+ truncated: Boolean(page.pageToken),
176
+ lockout
177
+ };
178
+ }
179
+ const providerIdsOf = (record)=>{
180
+ var _record_providerData;
181
+ return ((_record_providerData = record.providerData) != null ? _record_providerData : []).map((info)=>info == null ? void 0 : info.providerId).filter((id)=>Boolean(id));
182
+ };
183
+ /**
184
+ * What one account loses, decided without mutating anything so the dry run
185
+ * and the real run cannot disagree.
186
+ *
187
+ * `password` counts as a provider to strip: an email/password credential on a
188
+ * governed account is exactly the sort of standing bypass enforcement is
189
+ * bought to remove, and it is not the org's IdP.
190
+ *
191
+ * @param breakGlass uids the org has DESIGNATED to keep their other sign-in
192
+ * methods (AGL-1888). Deliberately a parameter rather than a lookup:
193
+ * this function is the whole decision and stays pure, so the rehearsal
194
+ * and the real run cannot diverge on it either.
195
+ */ export function planAccount(record, providerId, breakGlass = new Set()) {
196
+ var _record_email;
197
+ const providers = providerIdsOf(record);
198
+ const unlinked = providers.filter((id)=>id !== providerId);
199
+ const kept = providers.filter((id)=>id === providerId);
200
+ const summary = {
201
+ uid: record.uid,
202
+ email: (_record_email = record.email) != null ? _record_email : null,
203
+ unlinked,
204
+ kept
205
+ };
206
+ // Property 1: an account with nothing but removable providers keeps them.
207
+ // It is misconfigured, not a bypass to close, and orphaning it would turn a
208
+ // security tightening into a lockout with no way back.
209
+ if (unlinked.length && !kept.length) {
210
+ return _extends({}, summary, {
211
+ unlinked: [],
212
+ kept: providers,
213
+ skipped: 'would-orphan'
214
+ });
215
+ }
216
+ // Property 4 (AGL-1888): a designated break-glass account keeps everything.
217
+ //
218
+ // This IS a standing bypass of SSO enforcement, and it is meant to be —
219
+ // that is what makes it a way back in when the IdP stops answering. The
220
+ // alternative is the state the `aglyn-org-y5v14` tenant is in today: every
221
+ // account in the pool holds nothing but a SAML link, and a lapsed
222
+ // certificate or a deleted SAML app locks the organization out of itself
223
+ // permanently.
224
+ //
225
+ // Checked AFTER the orphan rule so the two cannot disagree about an account
226
+ // that is both, and it changes nothing for a break-glass account that has
227
+ // no removable provider anyway — `skipped` marks a decision, so it is set
228
+ // only where there was something to decide.
229
+ if (unlinked.length && breakGlass.has(record.uid)) {
230
+ return _extends({}, summary, {
231
+ unlinked: [],
232
+ kept: providers,
233
+ skipped: 'break-glass'
234
+ });
235
+ }
236
+ return summary;
237
+ }
238
+ /**
239
+ * Would this org still have a way in after the sweep? (AGL-1888)
240
+ *
241
+ * Pure, and over the PLANNED accounts rather than the raw records, so it
242
+ * answers the question about the state enforcement is actually about to
243
+ * create rather than the one it is in now.
244
+ *
245
+ * ONLY A DESIGNATED ACCOUNT COUNTS. An account that happens to retain a
246
+ * password because it is misconfigured — the `would-orphan` case, which has no
247
+ * IdP link at all — is not break-glass: nobody chose it, it may be a stale
248
+ * record or a service account, and an org protected by an accident it does not
249
+ * know about is not protected. Requiring the designation is what makes this a
250
+ * control rather than a coincidence.
251
+ *
252
+ * A designation only counts when the account actually holds something OTHER
253
+ * than the org's IdP. Naming an account whose sole credential is the SAML link
254
+ * is the most natural way to get this wrong — it looks like a break-glass
255
+ * account and provides nothing, because it fails in exactly the situation it
256
+ * exists for.
257
+ *
258
+ * THE SECOND WAY IN IS NOT IN THE POOL AT ALL (AGL-1888 option (a)). An org
259
+ * owner holding a project-level credential is invisible to the sweep — the
260
+ * enforcement pass lists `authForPool(tenantId)` and never touches the
261
+ * project pool — so a lapsed certificate cannot reach them. No designation is
262
+ * required for those: nobody has to be told to keep the key they already
263
+ * hold, and requiring a tick would put the requirement back out of reach for
264
+ * exactly the org that already satisfies it. Whether such an owner exists is
265
+ * decided by {@link findBreakGlassOrgOwners}, which is where the seven
266
+ * conditions live; this function only counts what it is handed, so that the
267
+ * rehearsal and the real run cannot disagree.
268
+ *
269
+ * `ownerLookupFailed` deliberately does NOT make an org safe. It exists so a
270
+ * refusal can distinguish "you have nobody" from "we could not check" — an
271
+ * error swallowed into an empty list renders as a measured zero, and nothing
272
+ * about it looks wrong.
273
+ */ export function assessSsoLockoutRisk(accounts, providerId, breakGlass, ownersOutsidePool = [], ownerLookupFailed = false) {
274
+ const designated = [
275
+ ...new Set(breakGlass.filter(Boolean))
276
+ ];
277
+ const byUid = new Map(accounts.map((account)=>[
278
+ account.uid,
279
+ account
280
+ ]));
281
+ const retainedBy = [];
282
+ const ineffective = [];
283
+ for (const uid of designated){
284
+ const account = byUid.get(uid);
285
+ const protects = Boolean(account == null ? void 0 : account.kept.some((id)=>id !== providerId));
286
+ if (protects) retainedBy.push(uid);
287
+ else ineffective.push(uid);
288
+ }
289
+ const owners = [
290
+ ...ownersOutsidePool
291
+ ];
292
+ return {
293
+ safe: retainedBy.length > 0 || owners.length > 0,
294
+ retainedBy,
295
+ ineffective,
296
+ ownersOutsidePool: owners,
297
+ ownerLookupFailed
298
+ };
299
+ }
300
+ /**
301
+ * The refusal an org sees when enforcing would lock it out of itself.
302
+ *
303
+ * Says what to do, because a refusal that only says no sends someone to turn
304
+ * the guard off. Deliberately free of a product name — this string is read by
305
+ * self-host operators too.
306
+ */ export const SSO_LOCKOUT_REFUSAL = 'Enforcing single sign-on would remove every sign-in method that does not ' + 'go through your identity provider, and this organization would be left ' + 'with no way in that survives your identity provider failing. If its ' + 'certificate lapses or its application is removed, nobody would be able ' + 'to sign in and we could not let you back in either. Give the ' + 'organization one of two things first: an owner who signs in outside your ' + 'identity pool, or a designated break-glass account inside it that keeps ' + 'a password.';
307
+ export default enforceSsoSignInMethods;
308
+
309
+ //# sourceMappingURL=sso-enforcement.js.map