@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,482 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { createHash, randomBytes } from "crypto";
17
+ import { safeSameOriginPath } from "@aglyn/shared-util-http/safe-redirect";
18
+ import firebaseAdmin from "./firebase-admin.js";
19
+ import { consumeOnce } from "./consume-once.js";
20
+ import { CONSOLE_DOMAINS_COLLECTION, normalizeConsoleDomain, resolveConsoleDomain } from "./console-domains.js";
21
+ import { safeEqual } from "./safe-equal.js";
22
+ /**
23
+ * Cross-domain console session handoff — the 1099e mechanism (AGL-1902),
24
+ * built to `docs/design/agl-1099a-cross-domain-session-handoff.md`.
25
+ *
26
+ * ## The problem
27
+ *
28
+ * The console's session cookie is minted with `Domain=.aglyn.com`, which is
29
+ * why workspace subdomains can delegate interactive sign-in to
30
+ * `auth.aglyn.com` and pick the session back up. `console.acme-agency.com`
31
+ * shares no parent domain, so that mechanism is not merely inconvenient there,
32
+ * it is unavailable. A custom console domain needs its own first-party
33
+ * session, bootstrapped by a hand-off from the auth host.
34
+ *
35
+ * ## Threat model
36
+ *
37
+ * The credential in flight would, if stolen, be a full session on someone
38
+ * else's account. Four attacks, and what refuses each:
39
+ *
40
+ * 1. **The return secret leaks** — our own edge access logs, a log drain, a
41
+ * `Referer` on a subresource, a shoulder. Useless alone: redemption also
42
+ * requires the VERIFIER, which lives only in an `HttpOnly` cookie the
43
+ * custom domain set on the browser that started the flow. (The secret is
44
+ * additionally carried in the URL FRAGMENT, which is never transmitted to
45
+ * any server — so the one channel we own and cannot audit never sees it.)
46
+ * 2. **The attacker starts a flow and phishes the victim into finishing it**,
47
+ * so the attacker holds the verifier. Useless: the secret is delivered only
48
+ * to the browser that completed sign-in — the victim's.
49
+ * 3. **The attacker replays a redemption.** Refused by {@link consumeOnce}: the
50
+ * record is read and mutated in one serializable transaction, so a second
51
+ * consume finds `redeemed` whatever the outcome of the first, and two
52
+ * concurrent redemptions cannot both commit.
53
+ * 4. **The attacker redeems at their own origin**, or points a domain they
54
+ * genuinely own at us. Refused twice over: the record names its
55
+ * `targetHost` at initiation and redemption requires the request's own
56
+ * `Host` to match, and — the load-bearing one —
57
+ * {@link authorizeConsoleHandoff} refuses unless the signed-in user is a
58
+ * MEMBER of the org that owns the target host. Verified-domain status is
59
+ * not sufficient. A victim phished onto `console.aglyn-support.com` signs
60
+ * in on the genuine auth host, their credential never touches the
61
+ * attacker's origin, and they are told they have no access to that
62
+ * workspace.
63
+ *
64
+ * The token itself is opaque random bytes with a stored SHA-256, following
65
+ * `hashApiKey`/`generateApiKeyToken` — not a signed claims blob. Single use
66
+ * requires a server-side record, and once the record exists a signature buys
67
+ * nothing and adds a key to keep in step across two Vercel projects.
68
+ *
69
+ * ## Two windows, because they answer different questions
70
+ *
71
+ * `pending` gets 15 minutes: the user may type a password, do MFA, verify an
72
+ * email, and a pending record grants nothing. `authorized` gets 120 seconds:
73
+ * one `location.replace` and one same-origin POST. Anything slower is a broken
74
+ * flow, not a slow user.
75
+ *
76
+ * **Clock skew is not a threat here and the reason is structural: no timestamp
77
+ * originates in a browser.** Both legs run on our own serverless runtime and
78
+ * compare against a value another instance of the same runtime wrote. There is
79
+ * no skew-tolerance parameter to tune, and adding one would only widen the
80
+ * replay window.
81
+ *
82
+ * ## Not built here, and deliberately
83
+ *
84
+ * Staff impersonation is refused at authorize. A staff session on
85
+ * customer-controlled infrastructure is a credential sitting on someone else's
86
+ * server, and support can use `{slug}.aglyn.com`, which works today.
87
+ */ const firestore = ()=>firebaseAdmin.app().firestore();
88
+ /** Firestore collection holding pending/authorized handoffs. */ export const AUTH_HANDOFFS_COLLECTION = 'authHandoffs';
89
+ /** Name of the host-only verifier cookie the custom domain sets. */ export const HANDOFF_VERIFIER_COOKIE = '__aglyn_handoff';
90
+ /** initiate → authorize. Generous; a pending record grants nothing. */ export const HANDOFF_PENDING_TTL_MS = 15 * 60 * 1000;
91
+ /** authorize → redeem. One navigation and one same-origin POST. */ export const HANDOFF_AUTHORIZED_TTL_MS = 120 * 1000;
92
+ /** SHA-256 hex, the same shape `hashApiKey` stores. */ export function hashHandoffSecret(value) {
93
+ return createHash('sha256').update(value, 'utf8').digest('hex');
94
+ }
95
+ /** 32 random bytes, base64url — opaque, and carrying nothing to lie about. */ function newSecret() {
96
+ return randomBytes(32).toString('base64url');
97
+ }
98
+ /**
99
+ * A `continuePath` that cannot leave the custom domain.
100
+ *
101
+ * The handoff lands the user somewhere immediately after signing them in,
102
+ * which is the worst possible moment to hand control to an attacker-chosen
103
+ * origin — so this defers to {@link safeSameOriginPath}, which resolves the
104
+ * value and compares origins instead of testing its shape (AGL-1881).
105
+ *
106
+ * This function used to reject `//evil.example` and `/\evil.example` by
107
+ * inspecting the characters. That list read as complete and was not: the URL
108
+ * parser deletes tab, LF and CR before parsing, so `/<TAB>/evil.example`
109
+ * contains neither `//` nor `\` and still resolves to `https://evil.example/`.
110
+ * Enumerating tricks is the bug; asking the parser is the fix.
111
+ */ export function safeContinuePath(input) {
112
+ return safeSameOriginPath(input, '/');
113
+ }
114
+ /**
115
+ * Create a `pending` handoff for `targetHost`, returning the id and verifier.
116
+ *
117
+ * Called on the custom domain, unauthenticated: nothing about a pending record
118
+ * is a capability. It names the host the request actually arrived on — never a
119
+ * host from a parameter — which is what makes the `targetHost` check at
120
+ * redemption mean anything.
121
+ */ export async function startConsoleHandoff(options) {
122
+ var _options_nowMs, _options_orgSlug;
123
+ const targetHost = normalizeConsoleDomain(options.targetHost);
124
+ if (!targetHost) return null;
125
+ const nowMs = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.now();
126
+ const requestId = crypto.randomUUID();
127
+ const verifier = newSecret();
128
+ const expiresAtMs = nowMs + HANDOFF_PENDING_TTL_MS;
129
+ await firestore().collection(AUTH_HANDOFFS_COLLECTION).doc(requestId).set({
130
+ targetHost,
131
+ orgSlug: (_options_orgSlug = options.orgSlug) != null ? _options_orgSlug : null,
132
+ continuePath: safeContinuePath(options.continuePath),
133
+ verifierHash: hashHandoffSecret(verifier),
134
+ secretHash: null,
135
+ status: 'pending',
136
+ uid: null,
137
+ tenantId: null,
138
+ createdAt: nowMs,
139
+ // A Firestore `Timestamp`, matching the idiom in `passkeys.ts` and
140
+ // `rate-limit-store.ts`. TTL deletion is best-effort within ~72h, so
141
+ // EXPIRY IS ENFORCED IN CODE and the TTL policy is hygiene only —
142
+ // never the other way round.
143
+ expiresAt: new Date(expiresAtMs)
144
+ });
145
+ return {
146
+ requestId,
147
+ verifier,
148
+ expiresAtMs
149
+ };
150
+ }
151
+ /**
152
+ * Authorize a pending handoff, on the AUTH HOST, for a signed-in user.
153
+ *
154
+ * `isMember` is injected rather than resolved here so this module stays free
155
+ * of the membership model, and so the test can exercise the refusal without a
156
+ * membership fixture. It receives the target host as well as the slug because
157
+ * the ORG ID lives on the claim and `resolveConsoleDomain` deliberately never
158
+ * returns it — a verdict is read by an unauthenticated route. It is called
159
+ * only after the domain has been shown to be live, so a refusal never reveals
160
+ * whether an org exists.
161
+ *
162
+ * The write flips `pending → authorized` inside {@link consumeOnce}, so a
163
+ * second authorize of the same id finds `not-pending`. That matters: without
164
+ * it, an attacker who obtained a request id could keep re-authorizing it and
165
+ * mint a fresh secret each time the victim signed in.
166
+ */ export async function authorizeConsoleHandoff(options) {
167
+ var _options_nowMs, _peek_get, _peek_get1, _ref;
168
+ var _consumed_value;
169
+ const nowMs = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.now();
170
+ const requestId = options.requestId;
171
+ if (!requestId || requestId.includes('/')) {
172
+ return {
173
+ ok: false,
174
+ reason: 'unknown-request',
175
+ orgSlug: null
176
+ };
177
+ }
178
+ const ref = firestore().collection(AUTH_HANDOFFS_COLLECTION).doc(requestId);
179
+ // Read first, only to answer the questions that need an AWAIT — domain
180
+ // liveness and membership — because neither can be performed inside a
181
+ // transaction. Every check that can race is re-made inside `consumeOnce`
182
+ // below, against the state it reads for itself.
183
+ let peek;
184
+ try {
185
+ peek = await ref.get();
186
+ } catch (unused) {
187
+ return {
188
+ ok: false,
189
+ reason: 'unknown-request',
190
+ orgSlug: null
191
+ };
192
+ }
193
+ if (!peek.exists) return {
194
+ ok: false,
195
+ reason: 'unknown-request',
196
+ orgSlug: null
197
+ };
198
+ const targetHost = String((_peek_get = peek.get('targetHost')) != null ? _peek_get : '');
199
+ const orgSlug = (_peek_get1 = peek.get('orgSlug')) != null ? _peek_get1 : null;
200
+ // Staff impersonation over a custom console domain is refused outright
201
+ // (AGL-1353 §8.15). A staff session on customer-controlled infrastructure is
202
+ // a credential sitting on someone else's server; support uses the workspace
203
+ // subdomain, which works today.
204
+ if (options.impersonated) {
205
+ return {
206
+ ok: false,
207
+ reason: 'impersonation',
208
+ orgSlug
209
+ };
210
+ }
211
+ const verdict = await resolveConsoleDomain(targetHost);
212
+ // `degraded` refuses here, unlike the routing gate. Routing fails OPEN
213
+ // because a console going dark on a timeout is worse than the residual
214
+ // exposure; MINTING A SESSION on a domain we could not confirm is live is
215
+ // the opposite trade, and the user still has a working
216
+ // `{slug}.aglyn.com` to fall back to.
217
+ if (!verdict.servable) {
218
+ return {
219
+ ok: false,
220
+ reason: 'domain-inactive',
221
+ orgSlug: verdict.orgSlug
222
+ };
223
+ }
224
+ // THE check. Verified custom-domain status is not sufficient — an attacker
225
+ // can verify a domain they genuinely own. Membership in the org that owns
226
+ // the target host is what makes the feature safe to sell, and it is the
227
+ // first thing a reviewer should look for.
228
+ if (!await options.isMember({
229
+ targetHost,
230
+ orgSlug: verdict.orgSlug
231
+ })) {
232
+ return {
233
+ ok: false,
234
+ reason: 'not-a-member',
235
+ orgSlug: verdict.orgSlug
236
+ };
237
+ }
238
+ const secret = newSecret();
239
+ const consumed = await consumeOnce(firestore(), ref, (data)=>{
240
+ var _data_targetHost, _data_createdAt, _data_continuePath, _options_tenantId;
241
+ if (data['status'] !== 'pending') {
242
+ return {
243
+ accept: false,
244
+ reason: 'not-pending'
245
+ };
246
+ }
247
+ if (String((_data_targetHost = data['targetHost']) != null ? _data_targetHost : '') !== targetHost) {
248
+ return {
249
+ accept: false,
250
+ reason: 'host-mismatch'
251
+ };
252
+ }
253
+ const createdAt = Number((_data_createdAt = data['createdAt']) != null ? _data_createdAt : 0);
254
+ if (!createdAt || nowMs - createdAt > HANDOFF_PENDING_TTL_MS) {
255
+ return {
256
+ accept: false,
257
+ reason: 'expired'
258
+ };
259
+ }
260
+ return {
261
+ accept: true,
262
+ value: {
263
+ continuePath: String((_data_continuePath = data['continuePath']) != null ? _data_continuePath : '/')
264
+ },
265
+ patch: {
266
+ status: 'authorized',
267
+ secretHash: hashHandoffSecret(secret),
268
+ uid: options.uid,
269
+ tenantId: (_options_tenantId = options.tenantId) != null ? _options_tenantId : null,
270
+ authorizedAt: nowMs,
271
+ // The window shortens the moment it is authorized: 120 s, not the
272
+ // remainder of the pending 15 minutes.
273
+ expiresAt: new Date(nowMs + HANDOFF_AUTHORIZED_TTL_MS)
274
+ }
275
+ };
276
+ });
277
+ if (!consumed.ok) {
278
+ const reason = consumed.reason;
279
+ return {
280
+ ok: false,
281
+ reason: reason === 'absent' ? 'unknown-request' : reason != null ? reason : 'unknown-request',
282
+ orgSlug: verdict.orgSlug
283
+ };
284
+ }
285
+ return {
286
+ ok: true,
287
+ secret,
288
+ targetHost,
289
+ continuePath: (_ref = (_consumed_value = consumed.value) == null ? void 0 : _consumed_value.continuePath) != null ? _ref : '/'
290
+ };
291
+ }
292
+ /**
293
+ * Consume an authorized handoff, on the CUSTOM DOMAIN.
294
+ *
295
+ * Both secrets are required and both are checked INSIDE the transaction, so
296
+ * the checks cannot be raced against the consume.
297
+ *
298
+ * `verifiers` is every value of the `__aglyn_handoff` cookie on the request,
299
+ * not one. A compromised sibling host under the customer's own apex can set a
300
+ * `Domain=.acme-agency.com` cookie of the same name, which SHADOWS the
301
+ * host-only one in the `Cookie` header with no way to tell them apart — the
302
+ * precise failure AGL-1259 hit with duplicate `__session` cookies. Hashing
303
+ * every value and accepting if any matches is safe by construction (only the
304
+ * real `V` hashes to `verifierHash`) and turns a hijack attempt into a no-op
305
+ * rather than a denial of service.
306
+ */ export async function redeemConsoleHandoff(options) {
307
+ var _options_nowMs;
308
+ var _consumed_value;
309
+ const nowMs = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.now();
310
+ const requestId = options.requestId;
311
+ if (!requestId || requestId.includes('/')) {
312
+ return {
313
+ ok: false,
314
+ reason: 'unknown-request'
315
+ };
316
+ }
317
+ const requestHost = normalizeConsoleDomain(options.requestHost);
318
+ if (!requestHost) return {
319
+ ok: false,
320
+ reason: 'host-mismatch'
321
+ };
322
+ // Liveness and revocation, both of which need an AWAIT and so cannot live
323
+ // inside the transaction. D6 requires BOTH legs of the session to honour the
324
+ // epoch — "the redemption endpoint and the `GET` exchange" — and this leg is
325
+ // the one that had neither check.
326
+ //
327
+ // It matters because `/api/*` sits OUTSIDE the middleware matcher, so
328
+ // `serveConsoleDomain` never sees this route: a suspended or detached domain
329
+ // is refused a console to render and was still handed a working redemption.
330
+ // And the `GET` exchange cannot cover for it, because the credential a
331
+ // redemption mints is NEWER than the bump that was supposed to kill it — an
332
+ // epoch can only refuse a cookie it predates.
333
+ const verdict = await resolveConsoleDomain(requestHost);
334
+ // `degraded` refuses here, for `authorize`'s reason: routing fails OPEN
335
+ // because a console going dark on a timeout is worse than the residual
336
+ // exposure, but MINTING a session on a domain we could not confirm is live is
337
+ // the opposite trade, and the user still has `{slug}.aglyn.com` to fall back
338
+ // to.
339
+ if (!verdict.servable) return {
340
+ ok: false,
341
+ reason: 'domain-inactive'
342
+ };
343
+ const epoch = await readConsoleSessionEpoch(requestHost);
344
+ const ref = firestore().collection(AUTH_HANDOFFS_COLLECTION).doc(requestId);
345
+ const consumed = await consumeOnce(firestore(), ref, (data)=>{
346
+ var _data_targetHost, _data_authorizedAt, _data_secretHash, _data_verifierHash, _data_uid, _data_tenantId, _data_continuePath;
347
+ const status = data['status'];
348
+ if (status === 'redeemed') {
349
+ return {
350
+ accept: false,
351
+ reason: 'already-redeemed'
352
+ };
353
+ }
354
+ if (status !== 'authorized') {
355
+ return {
356
+ accept: false,
357
+ reason: 'not-authorized'
358
+ };
359
+ }
360
+ // Origin binding, half one: the record names its target and redemption
361
+ // requires the request's OWN host to match. Half two — the verifier below
362
+ // — is the load-bearing one, because an unbound token is a theft primitive
363
+ // even when the attacker controls no domain at all: they would simply POST
364
+ // it to our own redemption endpoint from their own machine.
365
+ if (String((_data_targetHost = data['targetHost']) != null ? _data_targetHost : '') !== requestHost) {
366
+ return {
367
+ accept: false,
368
+ reason: 'host-mismatch'
369
+ };
370
+ }
371
+ const authorizedAt = Number((_data_authorizedAt = data['authorizedAt']) != null ? _data_authorizedAt : 0);
372
+ if (!authorizedAt || nowMs - authorizedAt > HANDOFF_AUTHORIZED_TTL_MS) {
373
+ return {
374
+ accept: false,
375
+ reason: 'expired'
376
+ };
377
+ }
378
+ // D7 orders a detach `sessionEpoch = now` FIRST, then the Vercel delete,
379
+ // precisely so sessions die while we still control the host. An
380
+ // authorization that predates the bump is one of those sessions, and it is
381
+ // refused WITHOUT a write: `consumeOnce` does not destroy a refused record
382
+ // by default, so this cannot be used to knock out a concurrent legitimate
383
+ // redemption.
384
+ if (epoch > 0 && authorizedAt < epoch) {
385
+ return {
386
+ accept: false,
387
+ reason: 'revoked'
388
+ };
389
+ }
390
+ if (!safeEqual(hashHandoffSecret(options.secret), String((_data_secretHash = data['secretHash']) != null ? _data_secretHash : ''))) {
391
+ return {
392
+ accept: false,
393
+ reason: 'bad-secret'
394
+ };
395
+ }
396
+ const verifierHash = String((_data_verifierHash = data['verifierHash']) != null ? _data_verifierHash : '');
397
+ const verifierOk = options.verifiers.some((candidate)=>safeEqual(hashHandoffSecret(candidate), verifierHash));
398
+ if (!verifierOk) return {
399
+ accept: false,
400
+ reason: 'bad-verifier'
401
+ };
402
+ return {
403
+ accept: true,
404
+ value: {
405
+ uid: String((_data_uid = data['uid']) != null ? _data_uid : ''),
406
+ tenantId: (_data_tenantId = data['tenantId']) != null ? _data_tenantId : null,
407
+ continuePath: String((_data_continuePath = data['continuePath']) != null ? _data_continuePath : '/')
408
+ },
409
+ patch: {
410
+ status: 'redeemed',
411
+ redeemedAt: nowMs,
412
+ // Nothing that could re-authorize it survives the consume.
413
+ secretHash: null,
414
+ verifierHash: null
415
+ }
416
+ };
417
+ });
418
+ if (!consumed.ok || !((_consumed_value = consumed.value) == null ? void 0 : _consumed_value.uid)) {
419
+ const reason = consumed.reason;
420
+ return {
421
+ ok: false,
422
+ reason: reason === 'absent' ? 'unknown-request' : reason != null ? reason : 'unknown-request'
423
+ };
424
+ }
425
+ return {
426
+ ok: true,
427
+ uid: consumed.value.uid,
428
+ tenantId: consumed.value.tenantId,
429
+ continuePath: consumed.value.continuePath
430
+ };
431
+ }
432
+ /**
433
+ * `consoleDomains/{host}.sessionEpoch`, or `0` when there is no usable one.
434
+ *
435
+ * `0` is returned for every unreadable shape — an unnormalizable host, an
436
+ * absent document, a Firestore error, a non-finite or non-positive value — and
437
+ * every caller reads `0` as "this control has nothing to say", i.e. FAILS OPEN.
438
+ * That is deliberate and matches every other console-domain lookup: the Vercel
439
+ * allowlist is the boundary and this is defence in depth, so a Firestore blip
440
+ * must not lock every custom domain out of its own session.
441
+ *
442
+ * Note the split with {@link consoleSessionEpochRefuses}: failing open on a
443
+ * missing EPOCH is not the same as failing open on a missing CREDENTIAL DATE.
444
+ * A cookie whose `iat` cannot be read is refused, because an undateable
445
+ * credential cannot be shown to postdate a revocation.
446
+ */ export async function readConsoleSessionEpoch(host) {
447
+ var _snapshot_get;
448
+ const domain = normalizeConsoleDomain(host);
449
+ if (!domain) return 0;
450
+ let snapshot;
451
+ try {
452
+ snapshot = await firestore().collection(CONSOLE_DOMAINS_COLLECTION).doc(domain).get();
453
+ } catch (unused) {
454
+ return 0;
455
+ }
456
+ if (!snapshot.exists) return 0;
457
+ const epoch = Number((_snapshot_get = snapshot.get('sessionEpoch')) != null ? _snapshot_get : 0);
458
+ return Number.isFinite(epoch) && epoch > 0 ? epoch : 0;
459
+ }
460
+ /**
461
+ * The revocation epoch on a console domain (AGL-1353 D6).
462
+ *
463
+ * A session cookie minted before the epoch is dead. Bumping `sessionEpoch` —
464
+ * which `suspendOnDowngrade` and `releaseConsoleDomain` already do — therefore
465
+ * invalidates every outstanding cookie for that host instantly, at our
466
+ * boundary. Our boundary is the ONLY place a Firebase session cookie has any
467
+ * value: it is opaque and can only be cashed by `verifySessionCookie` under
468
+ * our Admin credentials.
469
+ *
470
+ * Fails OPEN on a read error, matching every other console-domain lookup.
471
+ */ export async function consoleSessionEpochRefuses(options) {
472
+ var _options_cookieIssuedAtMs;
473
+ const epoch = await readConsoleSessionEpoch(options.host);
474
+ if (epoch <= 0) return false;
475
+ const issuedAt = Number((_options_cookieIssuedAtMs = options.cookieIssuedAtMs) != null ? _options_cookieIssuedAtMs : 0);
476
+ // An undateable cookie cannot be shown to postdate the epoch, and the safe
477
+ // direction for a revocation control is to refuse it.
478
+ if (!Number.isFinite(issuedAt) || issuedAt <= 0) return true;
479
+ return issuedAt < epoch;
480
+ }
481
+
482
+ //# sourceMappingURL=auth-handoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/tenant/data/admin/src/lib/server/auth-handoff.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { createHash, randomBytes } from 'crypto'\nimport { safeSameOriginPath } from '@aglyn/shared-util-http/safe-redirect'\nimport firebaseAdmin from './firebase-admin'\nimport { consumeOnce } from './consume-once'\nimport {\n CONSOLE_DOMAINS_COLLECTION,\n normalizeConsoleDomain,\n resolveConsoleDomain,\n} from './console-domains'\nimport { safeEqual } from './safe-equal'\n\n/**\n * Cross-domain console session handoff — the 1099e mechanism (AGL-1902),\n * built to `docs/design/agl-1099a-cross-domain-session-handoff.md`.\n *\n * ## The problem\n *\n * The console's session cookie is minted with `Domain=.aglyn.com`, which is\n * why workspace subdomains can delegate interactive sign-in to\n * `auth.aglyn.com` and pick the session back up. `console.acme-agency.com`\n * shares no parent domain, so that mechanism is not merely inconvenient there,\n * it is unavailable. A custom console domain needs its own first-party\n * session, bootstrapped by a hand-off from the auth host.\n *\n * ## Threat model\n *\n * The credential in flight would, if stolen, be a full session on someone\n * else's account. Four attacks, and what refuses each:\n *\n * 1. **The return secret leaks** — our own edge access logs, a log drain, a\n * `Referer` on a subresource, a shoulder. Useless alone: redemption also\n * requires the VERIFIER, which lives only in an `HttpOnly` cookie the\n * custom domain set on the browser that started the flow. (The secret is\n * additionally carried in the URL FRAGMENT, which is never transmitted to\n * any server — so the one channel we own and cannot audit never sees it.)\n * 2. **The attacker starts a flow and phishes the victim into finishing it**,\n * so the attacker holds the verifier. Useless: the secret is delivered only\n * to the browser that completed sign-in — the victim's.\n * 3. **The attacker replays a redemption.** Refused by {@link consumeOnce}: the\n * record is read and mutated in one serializable transaction, so a second\n * consume finds `redeemed` whatever the outcome of the first, and two\n * concurrent redemptions cannot both commit.\n * 4. **The attacker redeems at their own origin**, or points a domain they\n * genuinely own at us. Refused twice over: the record names its\n * `targetHost` at initiation and redemption requires the request's own\n * `Host` to match, and — the load-bearing one —\n * {@link authorizeConsoleHandoff} refuses unless the signed-in user is a\n * MEMBER of the org that owns the target host. Verified-domain status is\n * not sufficient. A victim phished onto `console.aglyn-support.com` signs\n * in on the genuine auth host, their credential never touches the\n * attacker's origin, and they are told they have no access to that\n * workspace.\n *\n * The token itself is opaque random bytes with a stored SHA-256, following\n * `hashApiKey`/`generateApiKeyToken` — not a signed claims blob. Single use\n * requires a server-side record, and once the record exists a signature buys\n * nothing and adds a key to keep in step across two Vercel projects.\n *\n * ## Two windows, because they answer different questions\n *\n * `pending` gets 15 minutes: the user may type a password, do MFA, verify an\n * email, and a pending record grants nothing. `authorized` gets 120 seconds:\n * one `location.replace` and one same-origin POST. Anything slower is a broken\n * flow, not a slow user.\n *\n * **Clock skew is not a threat here and the reason is structural: no timestamp\n * originates in a browser.** Both legs run on our own serverless runtime and\n * compare against a value another instance of the same runtime wrote. There is\n * no skew-tolerance parameter to tune, and adding one would only widen the\n * replay window.\n *\n * ## Not built here, and deliberately\n *\n * Staff impersonation is refused at authorize. A staff session on\n * customer-controlled infrastructure is a credential sitting on someone else's\n * server, and support can use `{slug}.aglyn.com`, which works today.\n */\n\nconst firestore = () => firebaseAdmin.app().firestore()\n\n/** Firestore collection holding pending/authorized handoffs. */\nexport const AUTH_HANDOFFS_COLLECTION = 'authHandoffs'\n\n/** Name of the host-only verifier cookie the custom domain sets. */\nexport const HANDOFF_VERIFIER_COOKIE = '__aglyn_handoff'\n\n/** initiate → authorize. Generous; a pending record grants nothing. */\nexport const HANDOFF_PENDING_TTL_MS = 15 * 60 * 1000\n\n/** authorize → redeem. One navigation and one same-origin POST. */\nexport const HANDOFF_AUTHORIZED_TTL_MS = 120 * 1000\n\nexport type HandoffStatus = 'pending' | 'authorized' | 'redeemed'\n\n/** SHA-256 hex, the same shape `hashApiKey` stores. */\nexport function hashHandoffSecret(value: string): string {\n return createHash('sha256').update(value, 'utf8').digest('hex')\n}\n\n/** 32 random bytes, base64url — opaque, and carrying nothing to lie about. */\nfunction newSecret(): string {\n return randomBytes(32).toString('base64url')\n}\n\n/**\n * A `continuePath` that cannot leave the custom domain.\n *\n * The handoff lands the user somewhere immediately after signing them in,\n * which is the worst possible moment to hand control to an attacker-chosen\n * origin — so this defers to {@link safeSameOriginPath}, which resolves the\n * value and compares origins instead of testing its shape (AGL-1881).\n *\n * This function used to reject `//evil.example` and `/\\evil.example` by\n * inspecting the characters. That list read as complete and was not: the URL\n * parser deletes tab, LF and CR before parsing, so `/<TAB>/evil.example`\n * contains neither `//` nor `\\` and still resolves to `https://evil.example/`.\n * Enumerating tricks is the bug; asking the parser is the fix.\n */\nexport function safeContinuePath(input: string | null | undefined): string {\n return safeSameOriginPath(input, '/')\n}\n\nexport interface StartedHandoff {\n requestId: string\n /** `V` — goes in the custom domain's own host-only cookie. */\n verifier: string\n expiresAtMs: number\n}\n\n/**\n * Create a `pending` handoff for `targetHost`, returning the id and verifier.\n *\n * Called on the custom domain, unauthenticated: nothing about a pending record\n * is a capability. It names the host the request actually arrived on — never a\n * host from a parameter — which is what makes the `targetHost` check at\n * redemption mean anything.\n */\nexport async function startConsoleHandoff(options: {\n targetHost: string\n orgSlug: string | null\n continuePath?: string | null\n nowMs?: number\n}): Promise<StartedHandoff | null> {\n const targetHost = normalizeConsoleDomain(options.targetHost)\n if (!targetHost) return null\n const nowMs = options.nowMs ?? Date.now()\n const requestId = crypto.randomUUID()\n const verifier = newSecret()\n const expiresAtMs = nowMs + HANDOFF_PENDING_TTL_MS\n await firestore()\n .collection(AUTH_HANDOFFS_COLLECTION)\n .doc(requestId)\n .set({\n targetHost,\n orgSlug: options.orgSlug ?? null,\n continuePath: safeContinuePath(options.continuePath),\n verifierHash: hashHandoffSecret(verifier),\n secretHash: null,\n status: 'pending' satisfies HandoffStatus,\n uid: null,\n tenantId: null,\n createdAt: nowMs,\n // A Firestore `Timestamp`, matching the idiom in `passkeys.ts` and\n // `rate-limit-store.ts`. TTL deletion is best-effort within ~72h, so\n // EXPIRY IS ENFORCED IN CODE and the TTL policy is hygiene only —\n // never the other way round.\n expiresAt: new Date(expiresAtMs),\n })\n return { requestId, verifier, expiresAtMs }\n}\n\nexport type AuthorizeRefusal =\n | 'unknown-request'\n | 'not-pending'\n | 'expired'\n | 'host-mismatch'\n | 'domain-inactive'\n | 'not-a-member'\n | 'impersonation'\n\nexport type AuthorizeResult =\n | {\n ok: true\n /** `S` — goes in the return URL's FRAGMENT, never its query. */\n secret: string\n targetHost: string\n continuePath: string\n }\n | { ok: false; reason: AuthorizeRefusal; orgSlug: string | null }\n\n/**\n * Authorize a pending handoff, on the AUTH HOST, for a signed-in user.\n *\n * `isMember` is injected rather than resolved here so this module stays free\n * of the membership model, and so the test can exercise the refusal without a\n * membership fixture. It receives the target host as well as the slug because\n * the ORG ID lives on the claim and `resolveConsoleDomain` deliberately never\n * returns it — a verdict is read by an unauthenticated route. It is called\n * only after the domain has been shown to be live, so a refusal never reveals\n * whether an org exists.\n *\n * The write flips `pending → authorized` inside {@link consumeOnce}, so a\n * second authorize of the same id finds `not-pending`. That matters: without\n * it, an attacker who obtained a request id could keep re-authorizing it and\n * mint a fresh secret each time the victim signed in.\n */\nexport async function authorizeConsoleHandoff(options: {\n requestId: string\n uid: string\n tenantId?: string | null\n impersonated?: boolean\n isMember: (context: {\n targetHost: string\n orgSlug: string | null\n }) => Promise<boolean>\n nowMs?: number\n}): Promise<AuthorizeResult> {\n const nowMs = options.nowMs ?? Date.now()\n const requestId = options.requestId\n if (!requestId || requestId.includes('/')) {\n return { ok: false, reason: 'unknown-request', orgSlug: null }\n }\n const ref = firestore().collection(AUTH_HANDOFFS_COLLECTION).doc(requestId)\n\n // Read first, only to answer the questions that need an AWAIT — domain\n // liveness and membership — because neither can be performed inside a\n // transaction. Every check that can race is re-made inside `consumeOnce`\n // below, against the state it reads for itself.\n let peek: FirebaseFirestore.DocumentSnapshot\n try {\n peek = await ref.get()\n } catch {\n return { ok: false, reason: 'unknown-request', orgSlug: null }\n }\n if (!peek.exists) return { ok: false, reason: 'unknown-request', orgSlug: null }\n const targetHost = String(peek.get('targetHost') ?? '')\n const orgSlug = (peek.get('orgSlug') as string | null) ?? null\n\n // Staff impersonation over a custom console domain is refused outright\n // (AGL-1353 §8.15). A staff session on customer-controlled infrastructure is\n // a credential sitting on someone else's server; support uses the workspace\n // subdomain, which works today.\n if (options.impersonated) {\n return { ok: false, reason: 'impersonation', orgSlug }\n }\n\n const verdict = await resolveConsoleDomain(targetHost)\n // `degraded` refuses here, unlike the routing gate. Routing fails OPEN\n // because a console going dark on a timeout is worse than the residual\n // exposure; MINTING A SESSION on a domain we could not confirm is live is\n // the opposite trade, and the user still has a working\n // `{slug}.aglyn.com` to fall back to.\n if (!verdict.servable) {\n return { ok: false, reason: 'domain-inactive', orgSlug: verdict.orgSlug }\n }\n\n // THE check. Verified custom-domain status is not sufficient — an attacker\n // can verify a domain they genuinely own. Membership in the org that owns\n // the target host is what makes the feature safe to sell, and it is the\n // first thing a reviewer should look for.\n if (!(await options.isMember({ targetHost, orgSlug: verdict.orgSlug }))) {\n return { ok: false, reason: 'not-a-member', orgSlug: verdict.orgSlug }\n }\n\n const secret = newSecret()\n const consumed = await consumeOnce<{ continuePath: string }>(\n firestore(),\n ref,\n (data) => {\n if (data['status'] !== 'pending') {\n return { accept: false, reason: 'not-pending' }\n }\n if (String(data['targetHost'] ?? '') !== targetHost) {\n return { accept: false, reason: 'host-mismatch' }\n }\n const createdAt = Number(data['createdAt'] ?? 0)\n if (!createdAt || nowMs - createdAt > HANDOFF_PENDING_TTL_MS) {\n return { accept: false, reason: 'expired' }\n }\n return {\n accept: true,\n value: { continuePath: String(data['continuePath'] ?? '/') },\n patch: {\n status: 'authorized' satisfies HandoffStatus,\n secretHash: hashHandoffSecret(secret),\n uid: options.uid,\n tenantId: options.tenantId ?? null,\n authorizedAt: nowMs,\n // The window shortens the moment it is authorized: 120 s, not the\n // remainder of the pending 15 minutes.\n expiresAt: new Date(nowMs + HANDOFF_AUTHORIZED_TTL_MS),\n },\n }\n },\n )\n if (!consumed.ok) {\n const reason = consumed.reason\n return {\n ok: false,\n reason:\n reason === 'absent'\n ? 'unknown-request'\n : (reason as AuthorizeRefusal) ?? 'unknown-request',\n orgSlug: verdict.orgSlug,\n }\n }\n return {\n ok: true,\n secret,\n targetHost,\n continuePath: consumed.value?.continuePath ?? '/',\n }\n}\n\nexport type RedeemRefusal =\n | 'unknown-request'\n | 'already-redeemed'\n | 'not-authorized'\n | 'expired'\n | 'host-mismatch'\n | 'bad-secret'\n | 'bad-verifier'\n | 'domain-inactive'\n | 'revoked'\n\nexport type RedeemResult =\n | { ok: true; uid: string; tenantId: string | null; continuePath: string }\n | { ok: false; reason: RedeemRefusal }\n\n/**\n * Consume an authorized handoff, on the CUSTOM DOMAIN.\n *\n * Both secrets are required and both are checked INSIDE the transaction, so\n * the checks cannot be raced against the consume.\n *\n * `verifiers` is every value of the `__aglyn_handoff` cookie on the request,\n * not one. A compromised sibling host under the customer's own apex can set a\n * `Domain=.acme-agency.com` cookie of the same name, which SHADOWS the\n * host-only one in the `Cookie` header with no way to tell them apart — the\n * precise failure AGL-1259 hit with duplicate `__session` cookies. Hashing\n * every value and accepting if any matches is safe by construction (only the\n * real `V` hashes to `verifierHash`) and turns a hijack attempt into a no-op\n * rather than a denial of service.\n */\nexport async function redeemConsoleHandoff(options: {\n requestId: string\n secret: string\n verifiers: string[]\n requestHost: string\n nowMs?: number\n}): Promise<RedeemResult> {\n const nowMs = options.nowMs ?? Date.now()\n const requestId = options.requestId\n if (!requestId || requestId.includes('/')) {\n return { ok: false, reason: 'unknown-request' }\n }\n const requestHost = normalizeConsoleDomain(options.requestHost)\n if (!requestHost) return { ok: false, reason: 'host-mismatch' }\n\n // Liveness and revocation, both of which need an AWAIT and so cannot live\n // inside the transaction. D6 requires BOTH legs of the session to honour the\n // epoch — \"the redemption endpoint and the `GET` exchange\" — and this leg is\n // the one that had neither check.\n //\n // It matters because `/api/*` sits OUTSIDE the middleware matcher, so\n // `serveConsoleDomain` never sees this route: a suspended or detached domain\n // is refused a console to render and was still handed a working redemption.\n // And the `GET` exchange cannot cover for it, because the credential a\n // redemption mints is NEWER than the bump that was supposed to kill it — an\n // epoch can only refuse a cookie it predates.\n const verdict = await resolveConsoleDomain(requestHost)\n // `degraded` refuses here, for `authorize`'s reason: routing fails OPEN\n // because a console going dark on a timeout is worse than the residual\n // exposure, but MINTING a session on a domain we could not confirm is live is\n // the opposite trade, and the user still has `{slug}.aglyn.com` to fall back\n // to.\n if (!verdict.servable) return { ok: false, reason: 'domain-inactive' }\n const epoch = await readConsoleSessionEpoch(requestHost)\n\n const ref = firestore().collection(AUTH_HANDOFFS_COLLECTION).doc(requestId)\n\n const consumed = await consumeOnce<{\n uid: string\n tenantId: string | null\n continuePath: string\n }>(firestore(), ref, (data) => {\n const status = data['status']\n if (status === 'redeemed') {\n return { accept: false, reason: 'already-redeemed' }\n }\n if (status !== 'authorized') {\n return { accept: false, reason: 'not-authorized' }\n }\n // Origin binding, half one: the record names its target and redemption\n // requires the request's OWN host to match. Half two — the verifier below\n // — is the load-bearing one, because an unbound token is a theft primitive\n // even when the attacker controls no domain at all: they would simply POST\n // it to our own redemption endpoint from their own machine.\n if (String(data['targetHost'] ?? '') !== requestHost) {\n return { accept: false, reason: 'host-mismatch' }\n }\n const authorizedAt = Number(data['authorizedAt'] ?? 0)\n if (!authorizedAt || nowMs - authorizedAt > HANDOFF_AUTHORIZED_TTL_MS) {\n return { accept: false, reason: 'expired' }\n }\n // D7 orders a detach `sessionEpoch = now` FIRST, then the Vercel delete,\n // precisely so sessions die while we still control the host. An\n // authorization that predates the bump is one of those sessions, and it is\n // refused WITHOUT a write: `consumeOnce` does not destroy a refused record\n // by default, so this cannot be used to knock out a concurrent legitimate\n // redemption.\n if (epoch > 0 && authorizedAt < epoch) {\n return { accept: false, reason: 'revoked' }\n }\n if (!safeEqual(hashHandoffSecret(options.secret), String(data['secretHash'] ?? ''))) {\n return { accept: false, reason: 'bad-secret' }\n }\n const verifierHash = String(data['verifierHash'] ?? '')\n const verifierOk = options.verifiers.some((candidate) =>\n safeEqual(hashHandoffSecret(candidate), verifierHash),\n )\n if (!verifierOk) return { accept: false, reason: 'bad-verifier' }\n return {\n accept: true,\n value: {\n uid: String(data['uid'] ?? ''),\n tenantId: (data['tenantId'] as string | null) ?? null,\n continuePath: String(data['continuePath'] ?? '/'),\n },\n patch: {\n status: 'redeemed' satisfies HandoffStatus,\n redeemedAt: nowMs,\n // Nothing that could re-authorize it survives the consume.\n secretHash: null,\n verifierHash: null,\n },\n }\n })\n\n if (!consumed.ok || !consumed.value?.uid) {\n const reason = consumed.reason\n return {\n ok: false,\n reason:\n reason === 'absent'\n ? 'unknown-request'\n : (reason as RedeemRefusal) ?? 'unknown-request',\n }\n }\n return {\n ok: true,\n uid: consumed.value.uid,\n tenantId: consumed.value.tenantId,\n continuePath: consumed.value.continuePath,\n }\n}\n\n/**\n * `consoleDomains/{host}.sessionEpoch`, or `0` when there is no usable one.\n *\n * `0` is returned for every unreadable shape — an unnormalizable host, an\n * absent document, a Firestore error, a non-finite or non-positive value — and\n * every caller reads `0` as \"this control has nothing to say\", i.e. FAILS OPEN.\n * That is deliberate and matches every other console-domain lookup: the Vercel\n * allowlist is the boundary and this is defence in depth, so a Firestore blip\n * must not lock every custom domain out of its own session.\n *\n * Note the split with {@link consoleSessionEpochRefuses}: failing open on a\n * missing EPOCH is not the same as failing open on a missing CREDENTIAL DATE.\n * A cookie whose `iat` cannot be read is refused, because an undateable\n * credential cannot be shown to postdate a revocation.\n */\nexport async function readConsoleSessionEpoch(host: string): Promise<number> {\n const domain = normalizeConsoleDomain(host)\n if (!domain) return 0\n let snapshot: FirebaseFirestore.DocumentSnapshot\n try {\n snapshot = await firestore()\n .collection(CONSOLE_DOMAINS_COLLECTION)\n .doc(domain)\n .get()\n } catch {\n return 0\n }\n if (!snapshot.exists) return 0\n const epoch = Number(snapshot.get('sessionEpoch') ?? 0)\n return Number.isFinite(epoch) && epoch > 0 ? epoch : 0\n}\n\n/**\n * The revocation epoch on a console domain (AGL-1353 D6).\n *\n * A session cookie minted before the epoch is dead. Bumping `sessionEpoch` —\n * which `suspendOnDowngrade` and `releaseConsoleDomain` already do — therefore\n * invalidates every outstanding cookie for that host instantly, at our\n * boundary. Our boundary is the ONLY place a Firebase session cookie has any\n * value: it is opaque and can only be cashed by `verifySessionCookie` under\n * our Admin credentials.\n *\n * Fails OPEN on a read error, matching every other console-domain lookup.\n */\nexport async function consoleSessionEpochRefuses(options: {\n host: string\n cookieIssuedAtMs: number | null\n}): Promise<boolean> {\n const epoch = await readConsoleSessionEpoch(options.host)\n if (epoch <= 0) return false\n const issuedAt = Number(options.cookieIssuedAtMs ?? 0)\n // An undateable cookie cannot be shown to postdate the epoch, and the safe\n // direction for a revocation control is to refuse it.\n if (!Number.isFinite(issuedAt) || issuedAt <= 0) return true\n return issuedAt < epoch\n}\n"],"names":["createHash","randomBytes","safeSameOriginPath","firebaseAdmin","consumeOnce","CONSOLE_DOMAINS_COLLECTION","normalizeConsoleDomain","resolveConsoleDomain","safeEqual","firestore","app","AUTH_HANDOFFS_COLLECTION","HANDOFF_VERIFIER_COOKIE","HANDOFF_PENDING_TTL_MS","HANDOFF_AUTHORIZED_TTL_MS","hashHandoffSecret","value","update","digest","newSecret","toString","safeContinuePath","input","startConsoleHandoff","options","targetHost","nowMs","Date","now","requestId","crypto","randomUUID","verifier","expiresAtMs","collection","doc","set","orgSlug","continuePath","verifierHash","secretHash","status","uid","tenantId","createdAt","expiresAt","authorizeConsoleHandoff","peek","consumed","includes","ok","reason","ref","get","exists","String","impersonated","verdict","servable","isMember","secret","data","accept","Number","patch","authorizedAt","redeemConsoleHandoff","requestHost","epoch","readConsoleSessionEpoch","verifierOk","verifiers","some","candidate","redeemedAt","host","snapshot","domain","isFinite","consoleSessionEpochRefuses","issuedAt","cookieIssuedAtMs"],"mappings":"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"}