@wopr-network/platform-core 0.1.0

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 (694) hide show
  1. package/biome.json +61 -0
  2. package/dist/admin/admin-audit-log-repository.d.ts +33 -0
  3. package/dist/admin/admin-audit-log-repository.js +102 -0
  4. package/dist/admin/audit-log.d.ts +49 -0
  5. package/dist/admin/audit-log.js +63 -0
  6. package/dist/admin/index.d.ts +6 -0
  7. package/dist/admin/index.js +3 -0
  8. package/dist/admin/role-store.d.ts +37 -0
  9. package/dist/admin/role-store.js +106 -0
  10. package/dist/auth/api-key-repository.d.ts +11 -0
  11. package/dist/auth/api-key-repository.js +33 -0
  12. package/dist/auth/api-key-repository.test.d.ts +1 -0
  13. package/dist/auth/api-key-repository.test.js +46 -0
  14. package/dist/auth/auth.test.d.ts +1 -0
  15. package/dist/auth/auth.test.js +140 -0
  16. package/dist/auth/better-auth.d.ts +42 -0
  17. package/dist/auth/better-auth.js +196 -0
  18. package/dist/auth/index.d.ts +186 -0
  19. package/dist/auth/index.js +422 -0
  20. package/dist/auth/login-history-repository.d.ts +14 -0
  21. package/dist/auth/login-history-repository.js +15 -0
  22. package/dist/auth/login-history-repository.test.d.ts +1 -0
  23. package/dist/auth/login-history-repository.test.js +47 -0
  24. package/dist/auth/middleware.d.ts +55 -0
  25. package/dist/auth/middleware.js +101 -0
  26. package/dist/auth/middleware.test.d.ts +1 -0
  27. package/dist/auth/middleware.test.js +213 -0
  28. package/dist/auth/scoped-tokens.test.d.ts +1 -0
  29. package/dist/auth/scoped-tokens.test.js +306 -0
  30. package/dist/auth/tenant-access.test.d.ts +1 -0
  31. package/dist/auth/tenant-access.test.js +62 -0
  32. package/dist/auth/user-creator.d.ts +9 -0
  33. package/dist/auth/user-creator.js +47 -0
  34. package/dist/auth/user-creator.test.d.ts +1 -0
  35. package/dist/auth/user-creator.test.js +78 -0
  36. package/dist/auth/user-role-repository.d.ts +31 -0
  37. package/dist/auth/user-role-repository.js +53 -0
  38. package/dist/auth/user-role-repository.test.d.ts +1 -0
  39. package/dist/auth/user-role-repository.test.js +122 -0
  40. package/dist/billing/drizzle-webhook-seen-repository.d.ts +10 -0
  41. package/dist/billing/drizzle-webhook-seen-repository.js +28 -0
  42. package/dist/billing/index.d.ts +7 -0
  43. package/dist/billing/index.js +7 -0
  44. package/dist/billing/payment-processor.d.ts +127 -0
  45. package/dist/billing/payment-processor.js +8 -0
  46. package/dist/billing/payment-processor.test.d.ts +1 -0
  47. package/dist/billing/payment-processor.test.js +71 -0
  48. package/dist/billing/payram/cents-credits-boundary.test.d.ts +1 -0
  49. package/dist/billing/payram/cents-credits-boundary.test.js +75 -0
  50. package/dist/billing/payram/charge-store.d.ts +41 -0
  51. package/dist/billing/payram/charge-store.js +72 -0
  52. package/dist/billing/payram/charge-store.test.d.ts +1 -0
  53. package/dist/billing/payram/charge-store.test.js +64 -0
  54. package/dist/billing/payram/checkout.d.ts +15 -0
  55. package/dist/billing/payram/checkout.js +24 -0
  56. package/dist/billing/payram/checkout.test.d.ts +1 -0
  57. package/dist/billing/payram/checkout.test.js +74 -0
  58. package/dist/billing/payram/client.d.ts +7 -0
  59. package/dist/billing/payram/client.js +15 -0
  60. package/dist/billing/payram/client.test.d.ts +1 -0
  61. package/dist/billing/payram/client.test.js +52 -0
  62. package/dist/billing/payram/index.d.ts +8 -0
  63. package/dist/billing/payram/index.js +4 -0
  64. package/dist/billing/payram/types.d.ts +40 -0
  65. package/dist/billing/payram/types.js +1 -0
  66. package/dist/billing/payram/webhook.d.ts +19 -0
  67. package/dist/billing/payram/webhook.js +67 -0
  68. package/dist/billing/payram/webhook.test.d.ts +7 -0
  69. package/dist/billing/payram/webhook.test.js +248 -0
  70. package/dist/billing/stripe/cents-credits-boundary.test.d.ts +1 -0
  71. package/dist/billing/stripe/cents-credits-boundary.test.js +62 -0
  72. package/dist/billing/stripe/checkout.d.ts +20 -0
  73. package/dist/billing/stripe/checkout.js +63 -0
  74. package/dist/billing/stripe/checkout.test.d.ts +1 -0
  75. package/dist/billing/stripe/checkout.test.js +148 -0
  76. package/dist/billing/stripe/client.d.ts +14 -0
  77. package/dist/billing/stripe/client.js +33 -0
  78. package/dist/billing/stripe/client.test.d.ts +1 -0
  79. package/dist/billing/stripe/client.test.js +58 -0
  80. package/dist/billing/stripe/credit-prices.d.ts +63 -0
  81. package/dist/billing/stripe/credit-prices.js +81 -0
  82. package/dist/billing/stripe/credit-prices.test.d.ts +1 -0
  83. package/dist/billing/stripe/credit-prices.test.js +87 -0
  84. package/dist/billing/stripe/index.d.ts +14 -0
  85. package/dist/billing/stripe/index.js +8 -0
  86. package/dist/billing/stripe/payment-methods-detach-all.test.d.ts +1 -0
  87. package/dist/billing/stripe/payment-methods-detach-all.test.js +40 -0
  88. package/dist/billing/stripe/payment-methods.d.ts +25 -0
  89. package/dist/billing/stripe/payment-methods.js +53 -0
  90. package/dist/billing/stripe/payment-methods.test.d.ts +1 -0
  91. package/dist/billing/stripe/payment-methods.test.js +122 -0
  92. package/dist/billing/stripe/portal.d.ts +10 -0
  93. package/dist/billing/stripe/portal.js +16 -0
  94. package/dist/billing/stripe/portal.test.d.ts +1 -0
  95. package/dist/billing/stripe/portal.test.js +48 -0
  96. package/dist/billing/stripe/setup-intent.d.ts +16 -0
  97. package/dist/billing/stripe/setup-intent.js +22 -0
  98. package/dist/billing/stripe/setup-intent.test.d.ts +1 -0
  99. package/dist/billing/stripe/setup-intent.test.js +58 -0
  100. package/dist/billing/stripe/stripe-payment-processor.d.ts +49 -0
  101. package/dist/billing/stripe/stripe-payment-processor.js +166 -0
  102. package/dist/billing/stripe/stripe-payment-processor.test.d.ts +1 -0
  103. package/dist/billing/stripe/stripe-payment-processor.test.js +413 -0
  104. package/dist/billing/stripe/tenant-store.d.ts +56 -0
  105. package/dist/billing/stripe/tenant-store.js +119 -0
  106. package/dist/billing/stripe/tenant-store.test.d.ts +1 -0
  107. package/dist/billing/stripe/tenant-store.test.js +97 -0
  108. package/dist/billing/stripe/types.d.ts +49 -0
  109. package/dist/billing/stripe/types.js +1 -0
  110. package/dist/billing/webhook-seen-repository.d.ts +14 -0
  111. package/dist/billing/webhook-seen-repository.js +13 -0
  112. package/dist/config/billing-env.test.d.ts +1 -0
  113. package/dist/config/billing-env.test.js +48 -0
  114. package/dist/config/index.d.ts +46 -0
  115. package/dist/config/index.js +38 -0
  116. package/dist/config/logger.d.ts +2 -0
  117. package/dist/config/logger.js +11 -0
  118. package/dist/config/provider-endpoints.d.ts +6 -0
  119. package/dist/config/provider-endpoints.js +12 -0
  120. package/dist/credits/auto-topup-charge.d.ts +27 -0
  121. package/dist/credits/auto-topup-charge.js +139 -0
  122. package/dist/credits/auto-topup-charge.test.d.ts +1 -0
  123. package/dist/credits/auto-topup-charge.test.js +242 -0
  124. package/dist/credits/auto-topup-event-log-repository.d.ts +16 -0
  125. package/dist/credits/auto-topup-event-log-repository.js +18 -0
  126. package/dist/credits/auto-topup-event-log-repository.test.d.ts +1 -0
  127. package/dist/credits/auto-topup-event-log-repository.test.js +83 -0
  128. package/dist/credits/auto-topup-schedule.d.ts +27 -0
  129. package/dist/credits/auto-topup-schedule.js +66 -0
  130. package/dist/credits/auto-topup-schedule.test.d.ts +1 -0
  131. package/dist/credits/auto-topup-schedule.test.js +145 -0
  132. package/dist/credits/auto-topup-settings-repository.d.ts +54 -0
  133. package/dist/credits/auto-topup-settings-repository.js +184 -0
  134. package/dist/credits/auto-topup-settings-repository.test.d.ts +1 -0
  135. package/dist/credits/auto-topup-settings-repository.test.js +104 -0
  136. package/dist/credits/auto-topup-usage.d.ts +22 -0
  137. package/dist/credits/auto-topup-usage.js +56 -0
  138. package/dist/credits/auto-topup-usage.test.d.ts +1 -0
  139. package/dist/credits/auto-topup-usage.test.js +181 -0
  140. package/dist/credits/credit-expiry-cron.d.ts +19 -0
  141. package/dist/credits/credit-expiry-cron.js +50 -0
  142. package/dist/credits/credit-expiry-cron.test.d.ts +1 -0
  143. package/dist/credits/credit-expiry-cron.test.js +67 -0
  144. package/dist/credits/credit-ledger-extra.test.d.ts +1 -0
  145. package/dist/credits/credit-ledger-extra.test.js +40 -0
  146. package/dist/credits/credit-ledger.bench.d.ts +1 -0
  147. package/dist/credits/credit-ledger.bench.js +33 -0
  148. package/dist/credits/credit-ledger.d.ts +130 -0
  149. package/dist/credits/credit-ledger.js +293 -0
  150. package/dist/credits/credit-ledger.test.d.ts +4 -0
  151. package/dist/credits/credit-ledger.test.js +203 -0
  152. package/dist/credits/credit-transaction-repository.d.ts +17 -0
  153. package/dist/credits/credit-transaction-repository.js +35 -0
  154. package/dist/credits/credit-transaction-repository.test.d.ts +1 -0
  155. package/dist/credits/credit-transaction-repository.test.js +232 -0
  156. package/dist/credits/credit.d.ts +75 -0
  157. package/dist/credits/credit.js +139 -0
  158. package/dist/credits/credit.test.d.ts +1 -0
  159. package/dist/credits/credit.test.js +196 -0
  160. package/dist/credits/dividend-cron.d.ts +29 -0
  161. package/dist/credits/dividend-cron.js +88 -0
  162. package/dist/credits/dividend-cron.test.d.ts +1 -0
  163. package/dist/credits/dividend-cron.test.js +128 -0
  164. package/dist/credits/dividend-repository.d.ts +29 -0
  165. package/dist/credits/dividend-repository.js +126 -0
  166. package/dist/credits/dividend-repository.test.d.ts +1 -0
  167. package/dist/credits/dividend-repository.test.js +176 -0
  168. package/dist/credits/index.d.ts +9 -0
  169. package/dist/credits/index.js +5 -0
  170. package/dist/credits/repository-types.d.ts +29 -0
  171. package/dist/credits/repository-types.js +1 -0
  172. package/dist/credits/signup-grant.d.ts +12 -0
  173. package/dist/credits/signup-grant.js +35 -0
  174. package/dist/credits/signup-grant.test.d.ts +1 -0
  175. package/dist/credits/signup-grant.test.js +51 -0
  176. package/dist/credits/tenant-customer-repository.d.ts +30 -0
  177. package/dist/credits/tenant-customer-repository.js +5 -0
  178. package/dist/db/auth-user-repository.d.ts +46 -0
  179. package/dist/db/auth-user-repository.js +90 -0
  180. package/dist/db/credit-column.d.ts +27 -0
  181. package/dist/db/credit-column.js +13 -0
  182. package/dist/db/index.d.ts +14 -0
  183. package/dist/db/index.js +8 -0
  184. package/dist/db/schema/account-deletion-requests.d.ts +203 -0
  185. package/dist/db/schema/account-deletion-requests.js +36 -0
  186. package/dist/db/schema/account-export-requests.d.ts +148 -0
  187. package/dist/db/schema/account-export-requests.js +19 -0
  188. package/dist/db/schema/admin-audit.d.ts +194 -0
  189. package/dist/db/schema/admin-audit.js +21 -0
  190. package/dist/db/schema/admin-users.d.ts +177 -0
  191. package/dist/db/schema/admin-users.js +23 -0
  192. package/dist/db/schema/affiliate-fraud.d.ts +160 -0
  193. package/dist/db/schema/affiliate-fraud.js +18 -0
  194. package/dist/db/schema/affiliate.d.ts +277 -0
  195. package/dist/db/schema/affiliate.js +32 -0
  196. package/dist/db/schema/coupon-codes.d.ts +143 -0
  197. package/dist/db/schema/coupon-codes.js +17 -0
  198. package/dist/db/schema/credit-auto-topup-settings.d.ts +232 -0
  199. package/dist/db/schema/credit-auto-topup-settings.js +27 -0
  200. package/dist/db/schema/credit-auto-topup.d.ts +130 -0
  201. package/dist/db/schema/credit-auto-topup.js +21 -0
  202. package/dist/db/schema/credits.d.ts +283 -0
  203. package/dist/db/schema/credits.js +38 -0
  204. package/dist/db/schema/dividend-distributions.d.ts +130 -0
  205. package/dist/db/schema/dividend-distributions.js +19 -0
  206. package/dist/db/schema/email-notifications.d.ts +99 -0
  207. package/dist/db/schema/email-notifications.js +21 -0
  208. package/dist/db/schema/index.d.ts +33 -0
  209. package/dist/db/schema/index.js +33 -0
  210. package/dist/db/schema/meter-events.d.ts +599 -0
  211. package/dist/db/schema/meter-events.js +55 -0
  212. package/dist/db/schema/notification-preferences.d.ts +165 -0
  213. package/dist/db/schema/notification-preferences.js +18 -0
  214. package/dist/db/schema/notification-queue.d.ts +236 -0
  215. package/dist/db/schema/notification-queue.js +40 -0
  216. package/dist/db/schema/org-memberships.d.ts +63 -0
  217. package/dist/db/schema/org-memberships.js +15 -0
  218. package/dist/db/schema/organization-members.d.ts +235 -0
  219. package/dist/db/schema/organization-members.js +27 -0
  220. package/dist/db/schema/payram.d.ts +164 -0
  221. package/dist/db/schema/payram.js +21 -0
  222. package/dist/db/schema/platform-api-keys.d.ts +143 -0
  223. package/dist/db/schema/platform-api-keys.js +20 -0
  224. package/dist/db/schema/promotion-redemptions.d.ts +143 -0
  225. package/dist/db/schema/promotion-redemptions.js +18 -0
  226. package/dist/db/schema/promotions.d.ts +445 -0
  227. package/dist/db/schema/promotions.js +48 -0
  228. package/dist/db/schema/provider-credentials.d.ts +201 -0
  229. package/dist/db/schema/provider-credentials.js +36 -0
  230. package/dist/db/schema/rate-limit-entries.d.ts +75 -0
  231. package/dist/db/schema/rate-limit-entries.js +7 -0
  232. package/dist/db/schema/secret-audit-log.d.ts +109 -0
  233. package/dist/db/schema/secret-audit-log.js +15 -0
  234. package/dist/db/schema/session-usage.d.ts +194 -0
  235. package/dist/db/schema/session-usage.js +19 -0
  236. package/dist/db/schema/spending-limits.d.ts +92 -0
  237. package/dist/db/schema/spending-limits.js +8 -0
  238. package/dist/db/schema/tenant-addons.d.ts +58 -0
  239. package/dist/db/schema/tenant-addons.js +9 -0
  240. package/dist/db/schema/tenant-api-keys.d.ts +131 -0
  241. package/dist/db/schema/tenant-api-keys.js +21 -0
  242. package/dist/db/schema/tenant-capability-settings.d.ts +79 -0
  243. package/dist/db/schema/tenant-capability-settings.js +12 -0
  244. package/dist/db/schema/tenant-customers.d.ts +303 -0
  245. package/dist/db/schema/tenant-customers.js +25 -0
  246. package/dist/db/schema/tenants.d.ts +126 -0
  247. package/dist/db/schema/tenants.js +18 -0
  248. package/dist/db/schema/user-roles.d.ts +98 -0
  249. package/dist/db/schema/user-roles.js +18 -0
  250. package/dist/db/schema/webhook-seen-events.d.ts +58 -0
  251. package/dist/db/schema/webhook-seen-events.js +9 -0
  252. package/dist/email/billing-emails.d.ts +51 -0
  253. package/dist/email/billing-emails.js +163 -0
  254. package/dist/email/billing-emails.test.d.ts +1 -0
  255. package/dist/email/billing-emails.test.js +162 -0
  256. package/dist/email/client.d.ts +51 -0
  257. package/dist/email/client.js +102 -0
  258. package/dist/email/client.test.d.ts +1 -0
  259. package/dist/email/client.test.js +120 -0
  260. package/dist/email/drizzle-billing-email-repository.d.ts +21 -0
  261. package/dist/email/drizzle-billing-email-repository.js +36 -0
  262. package/dist/email/drizzle-billing-email-repository.test.d.ts +1 -0
  263. package/dist/email/drizzle-billing-email-repository.test.js +42 -0
  264. package/dist/email/index.d.ts +33 -0
  265. package/dist/email/index.js +22 -0
  266. package/dist/email/notification-preferences-store.d.ts +12 -0
  267. package/dist/email/notification-preferences-store.js +82 -0
  268. package/dist/email/notification-preferences-store.test.d.ts +1 -0
  269. package/dist/email/notification-preferences-store.test.js +86 -0
  270. package/dist/email/notification-queue-store.d.ts +25 -0
  271. package/dist/email/notification-queue-store.js +97 -0
  272. package/dist/email/notification-queue-store.test.d.ts +1 -0
  273. package/dist/email/notification-queue-store.test.js +177 -0
  274. package/dist/email/notification-repository-types.d.ts +70 -0
  275. package/dist/email/notification-repository-types.js +6 -0
  276. package/dist/email/notification-service.d.ts +41 -0
  277. package/dist/email/notification-service.js +196 -0
  278. package/dist/email/notification-service.test.d.ts +1 -0
  279. package/dist/email/notification-service.test.js +160 -0
  280. package/dist/email/notification-templates.d.ts +18 -0
  281. package/dist/email/notification-templates.js +574 -0
  282. package/dist/email/notification-templates.test.d.ts +1 -0
  283. package/dist/email/notification-templates.test.js +238 -0
  284. package/dist/email/notification-worker.d.ts +24 -0
  285. package/dist/email/notification-worker.js +109 -0
  286. package/dist/email/notification-worker.test.d.ts +1 -0
  287. package/dist/email/notification-worker.test.js +153 -0
  288. package/dist/email/require-verified.d.ts +25 -0
  289. package/dist/email/require-verified.js +52 -0
  290. package/dist/email/require-verified.test.d.ts +1 -0
  291. package/dist/email/require-verified.test.js +62 -0
  292. package/dist/email/resend-adapter.d.ts +47 -0
  293. package/dist/email/resend-adapter.js +137 -0
  294. package/dist/email/resend-adapter.test.d.ts +1 -0
  295. package/dist/email/resend-adapter.test.js +190 -0
  296. package/dist/email/templates.d.ts +22 -0
  297. package/dist/email/templates.js +359 -0
  298. package/dist/email/templates.test.d.ts +1 -0
  299. package/dist/email/templates.test.js +170 -0
  300. package/dist/email/verification.d.ts +42 -0
  301. package/dist/email/verification.js +83 -0
  302. package/dist/email/verification.test.d.ts +1 -0
  303. package/dist/email/verification.test.js +141 -0
  304. package/dist/index.d.ts +13 -0
  305. package/dist/index.js +23 -0
  306. package/dist/metering/aggregator.d.ts +54 -0
  307. package/dist/metering/aggregator.js +123 -0
  308. package/dist/metering/aggregator.test.d.ts +1 -0
  309. package/dist/metering/aggregator.test.js +179 -0
  310. package/dist/metering/dlq.d.ts +31 -0
  311. package/dist/metering/dlq.js +82 -0
  312. package/dist/metering/dlq.test.d.ts +1 -0
  313. package/dist/metering/dlq.test.js +117 -0
  314. package/dist/metering/drizzle-usage-summary-repository.d.ts +67 -0
  315. package/dist/metering/drizzle-usage-summary-repository.js +98 -0
  316. package/dist/metering/emitter.d.ts +66 -0
  317. package/dist/metering/emitter.js +185 -0
  318. package/dist/metering/emitter.test.d.ts +1 -0
  319. package/dist/metering/emitter.test.js +171 -0
  320. package/dist/metering/index.d.ts +11 -0
  321. package/dist/metering/index.js +5 -0
  322. package/dist/metering/load-test.bench.d.ts +1 -0
  323. package/dist/metering/load-test.bench.js +103 -0
  324. package/dist/metering/meter-event-repository.d.ts +33 -0
  325. package/dist/metering/meter-event-repository.js +58 -0
  326. package/dist/metering/meter-repositories.test.d.ts +1 -0
  327. package/dist/metering/meter-repositories.test.js +419 -0
  328. package/dist/metering/metering.test.d.ts +1 -0
  329. package/dist/metering/metering.test.js +1046 -0
  330. package/dist/metering/reconciliation-cron.d.ts +37 -0
  331. package/dist/metering/reconciliation-cron.js +85 -0
  332. package/dist/metering/reconciliation-cron.test.d.ts +1 -0
  333. package/dist/metering/reconciliation-cron.test.js +162 -0
  334. package/dist/metering/reconciliation-repository.d.ts +27 -0
  335. package/dist/metering/reconciliation-repository.js +43 -0
  336. package/dist/metering/reconciliation-repository.test.d.ts +1 -0
  337. package/dist/metering/reconciliation-repository.test.js +160 -0
  338. package/dist/metering/types.d.ts +88 -0
  339. package/dist/metering/types.js +1 -0
  340. package/dist/metering/wal.d.ts +49 -0
  341. package/dist/metering/wal.js +124 -0
  342. package/dist/metering/wal.test.d.ts +1 -0
  343. package/dist/metering/wal.test.js +175 -0
  344. package/dist/middleware/csrf.d.ts +24 -0
  345. package/dist/middleware/csrf.js +80 -0
  346. package/dist/middleware/csrf.test.d.ts +1 -0
  347. package/dist/middleware/csrf.test.js +152 -0
  348. package/dist/middleware/drizzle-rate-limit-repository.d.ts +9 -0
  349. package/dist/middleware/drizzle-rate-limit-repository.js +52 -0
  350. package/dist/middleware/drizzle-rate-limit-repository.test.d.ts +1 -0
  351. package/dist/middleware/drizzle-rate-limit-repository.test.js +74 -0
  352. package/dist/middleware/get-client-ip.d.ts +22 -0
  353. package/dist/middleware/get-client-ip.js +51 -0
  354. package/dist/middleware/get-client-ip.test.d.ts +1 -0
  355. package/dist/middleware/get-client-ip.test.js +40 -0
  356. package/dist/middleware/index.d.ts +5 -0
  357. package/dist/middleware/index.js +4 -0
  358. package/dist/middleware/rate-limit-repository.d.ts +19 -0
  359. package/dist/middleware/rate-limit-repository.js +1 -0
  360. package/dist/middleware/rate-limit.d.ts +57 -0
  361. package/dist/middleware/rate-limit.js +109 -0
  362. package/dist/middleware/rate-limit.test.d.ts +1 -0
  363. package/dist/middleware/rate-limit.test.js +247 -0
  364. package/dist/security/credential-vault/audit-repository.d.ts +27 -0
  365. package/dist/security/credential-vault/audit-repository.js +42 -0
  366. package/dist/security/credential-vault/audit-repository.test.d.ts +1 -0
  367. package/dist/security/credential-vault/audit-repository.test.js +78 -0
  368. package/dist/security/credential-vault/credential-repository.d.ts +94 -0
  369. package/dist/security/credential-vault/credential-repository.js +145 -0
  370. package/dist/security/credential-vault/credential-repository.test.d.ts +1 -0
  371. package/dist/security/credential-vault/credential-repository.test.js +206 -0
  372. package/dist/security/credential-vault/index.d.ts +12 -0
  373. package/dist/security/credential-vault/index.js +6 -0
  374. package/dist/security/credential-vault/key-rotation.d.ts +18 -0
  375. package/dist/security/credential-vault/key-rotation.js +52 -0
  376. package/dist/security/credential-vault/key-rotation.test.d.ts +1 -0
  377. package/dist/security/credential-vault/key-rotation.test.js +95 -0
  378. package/dist/security/credential-vault/migrate-plaintext.d.ts +15 -0
  379. package/dist/security/credential-vault/migrate-plaintext.js +80 -0
  380. package/dist/security/credential-vault/migrate-plaintext.test.d.ts +1 -0
  381. package/dist/security/credential-vault/migrate-plaintext.test.js +111 -0
  382. package/dist/security/credential-vault/migration-check.d.ts +15 -0
  383. package/dist/security/credential-vault/migration-check.js +71 -0
  384. package/dist/security/credential-vault/migration-check.test.d.ts +1 -0
  385. package/dist/security/credential-vault/migration-check.test.js +457 -0
  386. package/dist/security/credential-vault/store.d.ts +106 -0
  387. package/dist/security/credential-vault/store.js +181 -0
  388. package/dist/security/credential-vault/store.test.d.ts +1 -0
  389. package/dist/security/credential-vault/store.test.js +482 -0
  390. package/dist/security/encryption.d.ts +22 -0
  391. package/dist/security/encryption.js +53 -0
  392. package/dist/security/encryption.test.d.ts +1 -0
  393. package/dist/security/encryption.test.js +95 -0
  394. package/dist/security/host-validation.d.ts +11 -0
  395. package/dist/security/host-validation.js +108 -0
  396. package/dist/security/host-validation.test.d.ts +1 -0
  397. package/dist/security/host-validation.test.js +106 -0
  398. package/dist/security/index.d.ts +11 -0
  399. package/dist/security/index.js +11 -0
  400. package/dist/security/key-audit.d.ts +16 -0
  401. package/dist/security/key-audit.js +35 -0
  402. package/dist/security/key-audit.test.d.ts +1 -0
  403. package/dist/security/key-audit.test.js +50 -0
  404. package/dist/security/key-injection.d.ts +28 -0
  405. package/dist/security/key-injection.js +57 -0
  406. package/dist/security/key-injection.test.d.ts +1 -0
  407. package/dist/security/key-injection.test.js +97 -0
  408. package/dist/security/key-validation.d.ts +16 -0
  409. package/dist/security/key-validation.js +78 -0
  410. package/dist/security/key-validation.test.d.ts +1 -0
  411. package/dist/security/key-validation.test.js +87 -0
  412. package/dist/security/redirect-allowlist.d.ts +6 -0
  413. package/dist/security/redirect-allowlist.js +36 -0
  414. package/dist/security/redirect-allowlist.test.d.ts +1 -0
  415. package/dist/security/redirect-allowlist.test.js +55 -0
  416. package/dist/security/tenant-keys/capability-settings-store.d.ts +22 -0
  417. package/dist/security/tenant-keys/capability-settings-store.js +33 -0
  418. package/dist/security/tenant-keys/capability-settings-store.test.d.ts +1 -0
  419. package/dist/security/tenant-keys/capability-settings-store.test.js +77 -0
  420. package/dist/security/tenant-keys/index.d.ts +10 -0
  421. package/dist/security/tenant-keys/index.js +5 -0
  422. package/dist/security/tenant-keys/key-resolution-repository.d.ts +15 -0
  423. package/dist/security/tenant-keys/key-resolution-repository.js +18 -0
  424. package/dist/security/tenant-keys/key-resolution-repository.test.d.ts +1 -0
  425. package/dist/security/tenant-keys/key-resolution-repository.test.js +72 -0
  426. package/dist/security/tenant-keys/key-resolution.d.ts +39 -0
  427. package/dist/security/tenant-keys/key-resolution.js +59 -0
  428. package/dist/security/tenant-keys/key-resolution.test.d.ts +1 -0
  429. package/dist/security/tenant-keys/key-resolution.test.js +97 -0
  430. package/dist/security/tenant-keys/org-key-resolution.d.ts +30 -0
  431. package/dist/security/tenant-keys/org-key-resolution.js +50 -0
  432. package/dist/security/tenant-keys/org-key-resolution.test.d.ts +1 -0
  433. package/dist/security/tenant-keys/org-key-resolution.test.js +103 -0
  434. package/dist/security/tenant-keys/tenant-key-repository.d.ts +36 -0
  435. package/dist/security/tenant-keys/tenant-key-repository.js +96 -0
  436. package/dist/security/tenant-keys/tenant-key-repository.test.d.ts +1 -0
  437. package/dist/security/tenant-keys/tenant-key-repository.test.js +114 -0
  438. package/dist/security/types.d.ts +35 -0
  439. package/dist/security/types.js +15 -0
  440. package/dist/tenancy/drizzle-org-repository.d.ts +40 -0
  441. package/dist/tenancy/drizzle-org-repository.js +126 -0
  442. package/dist/tenancy/index.d.ts +6 -0
  443. package/dist/tenancy/index.js +3 -0
  444. package/dist/tenancy/org-member-repository.d.ts +57 -0
  445. package/dist/tenancy/org-member-repository.js +99 -0
  446. package/dist/tenancy/org-repository.test.d.ts +1 -0
  447. package/dist/tenancy/org-repository.test.js +143 -0
  448. package/dist/tenancy/org-service.d.ts +70 -0
  449. package/dist/tenancy/org-service.js +223 -0
  450. package/dist/tenancy/org-service.test.d.ts +1 -0
  451. package/dist/tenancy/org-service.test.js +550 -0
  452. package/dist/test/db.d.ts +33 -0
  453. package/dist/test/db.js +65 -0
  454. package/dist/trpc/index.d.ts +1 -0
  455. package/dist/trpc/index.js +1 -0
  456. package/dist/trpc/init.d.ts +49 -0
  457. package/dist/trpc/init.js +108 -0
  458. package/dist/trpc/init.test.d.ts +1 -0
  459. package/dist/trpc/init.test.js +154 -0
  460. package/drizzle/migrations/0000_slippery_mandrill.sql +559 -0
  461. package/drizzle/migrations/meta/0000_snapshot.json +4374 -0
  462. package/drizzle/migrations/meta/_journal.json +13 -0
  463. package/drizzle.config.ts +41 -0
  464. package/package.json +64 -0
  465. package/src/admin/admin-audit-log-repository.ts +135 -0
  466. package/src/admin/audit-log.ts +111 -0
  467. package/src/admin/index.ts +6 -0
  468. package/src/admin/role-store.ts +134 -0
  469. package/src/auth/api-key-repository.test.ts +63 -0
  470. package/src/auth/api-key-repository.ts +46 -0
  471. package/src/auth/auth.test.ts +166 -0
  472. package/src/auth/better-auth.ts +216 -0
  473. package/src/auth/index.ts +520 -0
  474. package/src/auth/login-history-repository.test.ts +54 -0
  475. package/src/auth/login-history-repository.ts +28 -0
  476. package/src/auth/middleware.test.ts +264 -0
  477. package/src/auth/middleware.ts +117 -0
  478. package/src/auth/scoped-tokens.test.ts +362 -0
  479. package/src/auth/tenant-access.test.ts +69 -0
  480. package/src/auth/user-creator.test.ts +98 -0
  481. package/src/auth/user-creator.ts +54 -0
  482. package/src/auth/user-role-repository.test.ts +149 -0
  483. package/src/auth/user-role-repository.ts +67 -0
  484. package/src/billing/drizzle-webhook-seen-repository.ts +34 -0
  485. package/src/billing/index.ts +22 -0
  486. package/src/billing/payment-processor.test.ts +93 -0
  487. package/src/billing/payment-processor.ts +150 -0
  488. package/src/billing/payram/cents-credits-boundary.test.ts +84 -0
  489. package/src/billing/payram/charge-store.test.ts +84 -0
  490. package/src/billing/payram/charge-store.ts +109 -0
  491. package/src/billing/payram/checkout.test.ts +99 -0
  492. package/src/billing/payram/checkout.ts +40 -0
  493. package/src/billing/payram/client.test.ts +62 -0
  494. package/src/billing/payram/client.ts +21 -0
  495. package/src/billing/payram/index.ts +14 -0
  496. package/src/billing/payram/types.ts +44 -0
  497. package/src/billing/payram/webhook.test.ts +318 -0
  498. package/src/billing/payram/webhook.ts +97 -0
  499. package/src/billing/stripe/cents-credits-boundary.test.ts +70 -0
  500. package/src/billing/stripe/checkout.test.ts +186 -0
  501. package/src/billing/stripe/checkout.ts +82 -0
  502. package/src/billing/stripe/client.test.ts +64 -0
  503. package/src/billing/stripe/client.ts +39 -0
  504. package/src/billing/stripe/credit-prices.test.ts +114 -0
  505. package/src/billing/stripe/credit-prices.ts +113 -0
  506. package/src/billing/stripe/index.ts +14 -0
  507. package/src/billing/stripe/payment-methods-detach-all.test.ts +53 -0
  508. package/src/billing/stripe/payment-methods.test.ts +157 -0
  509. package/src/billing/stripe/payment-methods.ts +76 -0
  510. package/src/billing/stripe/portal.test.ts +63 -0
  511. package/src/billing/stripe/portal.ts +25 -0
  512. package/src/billing/stripe/setup-intent.test.ts +78 -0
  513. package/src/billing/stripe/setup-intent.ts +34 -0
  514. package/src/billing/stripe/stripe-payment-processor.test.ts +517 -0
  515. package/src/billing/stripe/stripe-payment-processor.ts +255 -0
  516. package/src/billing/stripe/tenant-store.test.ts +124 -0
  517. package/src/billing/stripe/tenant-store.ts +151 -0
  518. package/src/billing/stripe/types.ts +53 -0
  519. package/src/billing/webhook-seen-repository.ts +24 -0
  520. package/src/config/billing-env.test.ts +54 -0
  521. package/src/config/index.ts +44 -0
  522. package/src/config/logger.ts +12 -0
  523. package/src/config/provider-endpoints.ts +14 -0
  524. package/src/credits/auto-topup-charge.test.ts +292 -0
  525. package/src/credits/auto-topup-charge.ts +171 -0
  526. package/src/credits/auto-topup-event-log-repository.test.ts +99 -0
  527. package/src/credits/auto-topup-event-log-repository.ts +30 -0
  528. package/src/credits/auto-topup-schedule.test.ts +179 -0
  529. package/src/credits/auto-topup-schedule.ts +93 -0
  530. package/src/credits/auto-topup-settings-repository.test.ts +123 -0
  531. package/src/credits/auto-topup-settings-repository.ts +245 -0
  532. package/src/credits/auto-topup-usage.test.ts +220 -0
  533. package/src/credits/auto-topup-usage.ts +68 -0
  534. package/src/credits/credit-expiry-cron.test.ts +125 -0
  535. package/src/credits/credit-expiry-cron.ts +76 -0
  536. package/src/credits/credit-ledger-extra.test.ts +57 -0
  537. package/src/credits/credit-ledger.bench.ts +56 -0
  538. package/src/credits/credit-ledger.test.ts +276 -0
  539. package/src/credits/credit-ledger.ts +450 -0
  540. package/src/credits/credit-transaction-repository.test.ts +274 -0
  541. package/src/credits/credit-transaction-repository.ts +62 -0
  542. package/src/credits/credit.test.ts +234 -0
  543. package/src/credits/credit.ts +160 -0
  544. package/src/credits/dividend-cron.test.ts +158 -0
  545. package/src/credits/dividend-cron.ts +127 -0
  546. package/src/credits/dividend-repository.test.ts +223 -0
  547. package/src/credits/dividend-repository.ts +182 -0
  548. package/src/credits/index.ts +25 -0
  549. package/src/credits/repository-types.ts +33 -0
  550. package/src/credits/signup-grant.test.ts +63 -0
  551. package/src/credits/signup-grant.ts +44 -0
  552. package/src/credits/tenant-customer-repository.ts +28 -0
  553. package/src/db/auth-user-repository.ts +124 -0
  554. package/src/db/credit-column.ts +17 -0
  555. package/src/db/index.ts +21 -0
  556. package/src/db/schema/account-deletion-requests.ts +41 -0
  557. package/src/db/schema/account-export-requests.ts +24 -0
  558. package/src/db/schema/admin-audit.ts +26 -0
  559. package/src/db/schema/admin-users.ts +31 -0
  560. package/src/db/schema/affiliate-fraud.ts +23 -0
  561. package/src/db/schema/affiliate.ts +38 -0
  562. package/src/db/schema/coupon-codes.ts +22 -0
  563. package/src/db/schema/credit-auto-topup-settings.ts +32 -0
  564. package/src/db/schema/credit-auto-topup.ts +26 -0
  565. package/src/db/schema/credits.ts +44 -0
  566. package/src/db/schema/dividend-distributions.ts +24 -0
  567. package/src/db/schema/email-notifications.ts +26 -0
  568. package/src/db/schema/index.ts +33 -0
  569. package/src/db/schema/meter-events.ts +70 -0
  570. package/src/db/schema/notification-preferences.ts +19 -0
  571. package/src/db/schema/notification-queue.ts +45 -0
  572. package/src/db/schema/org-memberships.ts +20 -0
  573. package/src/db/schema/organization-members.ts +37 -0
  574. package/src/db/schema/payram.ts +26 -0
  575. package/src/db/schema/platform-api-keys.ts +25 -0
  576. package/src/db/schema/promotion-redemptions.ts +23 -0
  577. package/src/db/schema/promotions.ts +57 -0
  578. package/src/db/schema/provider-credentials.ts +41 -0
  579. package/src/db/schema/rate-limit-entries.ts +12 -0
  580. package/src/db/schema/secret-audit-log.ts +20 -0
  581. package/src/db/schema/session-usage.ts +24 -0
  582. package/src/db/schema/spending-limits.ts +9 -0
  583. package/src/db/schema/tenant-addons.ts +14 -0
  584. package/src/db/schema/tenant-api-keys.ts +26 -0
  585. package/src/db/schema/tenant-capability-settings.ts +17 -0
  586. package/src/db/schema/tenant-customers.ts +35 -0
  587. package/src/db/schema/tenants.ts +23 -0
  588. package/src/db/schema/user-roles.ts +23 -0
  589. package/src/db/schema/webhook-seen-events.ts +14 -0
  590. package/src/email/billing-emails.test.ts +198 -0
  591. package/src/email/billing-emails.ts +211 -0
  592. package/src/email/client.test.ts +149 -0
  593. package/src/email/client.ts +137 -0
  594. package/src/email/drizzle-billing-email-repository.test.ts +52 -0
  595. package/src/email/drizzle-billing-email-repository.ts +59 -0
  596. package/src/email/index.ts +57 -0
  597. package/src/email/notification-preferences-store.test.ts +102 -0
  598. package/src/email/notification-preferences-store.ts +90 -0
  599. package/src/email/notification-queue-store.test.ts +215 -0
  600. package/src/email/notification-queue-store.ts +127 -0
  601. package/src/email/notification-repository-types.ts +101 -0
  602. package/src/email/notification-service.test.ts +178 -0
  603. package/src/email/notification-service.ts +265 -0
  604. package/src/email/notification-templates.test.ts +261 -0
  605. package/src/email/notification-templates.ts +727 -0
  606. package/src/email/notification-worker.test.ts +189 -0
  607. package/src/email/notification-worker.ts +133 -0
  608. package/src/email/require-verified.ts +65 -0
  609. package/src/email/resend-adapter.test.ts +253 -0
  610. package/src/email/resend-adapter.ts +157 -0
  611. package/src/email/templates.test.ts +217 -0
  612. package/src/email/templates.ts +469 -0
  613. package/src/email/verification.test.ts +185 -0
  614. package/src/email/verification.ts +110 -0
  615. package/src/index.ts +51 -0
  616. package/src/metering/aggregator.test.ts +239 -0
  617. package/src/metering/aggregator.ts +160 -0
  618. package/src/metering/dlq.test.ts +134 -0
  619. package/src/metering/dlq.ts +102 -0
  620. package/src/metering/drizzle-usage-summary-repository.ts +167 -0
  621. package/src/metering/emitter.test.ts +202 -0
  622. package/src/metering/emitter.ts +227 -0
  623. package/src/metering/index.ts +21 -0
  624. package/src/metering/load-test.bench.ts +130 -0
  625. package/src/metering/meter-event-repository.ts +87 -0
  626. package/src/metering/meter-repositories.test.ts +491 -0
  627. package/src/metering/metering.test.ts +1317 -0
  628. package/src/metering/reconciliation-cron.test.ts +202 -0
  629. package/src/metering/reconciliation-cron.ts +134 -0
  630. package/src/metering/reconciliation-repository.test.ts +196 -0
  631. package/src/metering/reconciliation-repository.ts +83 -0
  632. package/src/metering/types.ts +93 -0
  633. package/src/metering/wal.test.ts +222 -0
  634. package/src/metering/wal.ts +139 -0
  635. package/src/middleware/csrf.test.ts +178 -0
  636. package/src/middleware/csrf.ts +101 -0
  637. package/src/middleware/drizzle-rate-limit-repository.test.ts +97 -0
  638. package/src/middleware/drizzle-rate-limit-repository.ts +57 -0
  639. package/src/middleware/get-client-ip.test.ts +49 -0
  640. package/src/middleware/get-client-ip.ts +62 -0
  641. package/src/middleware/index.ts +12 -0
  642. package/src/middleware/rate-limit-repository.ts +22 -0
  643. package/src/middleware/rate-limit.test.ts +338 -0
  644. package/src/middleware/rate-limit.ts +169 -0
  645. package/src/security/credential-vault/audit-repository.test.ts +91 -0
  646. package/src/security/credential-vault/audit-repository.ts +64 -0
  647. package/src/security/credential-vault/credential-repository.test.ts +264 -0
  648. package/src/security/credential-vault/credential-repository.ts +233 -0
  649. package/src/security/credential-vault/index.ts +26 -0
  650. package/src/security/credential-vault/key-rotation.test.ts +139 -0
  651. package/src/security/credential-vault/key-rotation.ts +70 -0
  652. package/src/security/credential-vault/migrate-plaintext.test.ts +138 -0
  653. package/src/security/credential-vault/migrate-plaintext.ts +101 -0
  654. package/src/security/credential-vault/migration-check.test.ts +533 -0
  655. package/src/security/credential-vault/migration-check.ts +88 -0
  656. package/src/security/credential-vault/store.test.ts +569 -0
  657. package/src/security/credential-vault/store.ts +284 -0
  658. package/src/security/encryption.test.ts +114 -0
  659. package/src/security/encryption.ts +65 -0
  660. package/src/security/host-validation.test.ts +136 -0
  661. package/src/security/host-validation.ts +116 -0
  662. package/src/security/index.ts +59 -0
  663. package/src/security/key-audit.test.ts +57 -0
  664. package/src/security/key-audit.ts +45 -0
  665. package/src/security/key-injection.test.ts +131 -0
  666. package/src/security/key-injection.ts +71 -0
  667. package/src/security/key-validation.test.ts +111 -0
  668. package/src/security/key-validation.ts +84 -0
  669. package/src/security/redirect-allowlist.test.ts +70 -0
  670. package/src/security/redirect-allowlist.ts +35 -0
  671. package/src/security/tenant-keys/capability-settings-store.test.ts +98 -0
  672. package/src/security/tenant-keys/capability-settings-store.ts +53 -0
  673. package/src/security/tenant-keys/index.ts +10 -0
  674. package/src/security/tenant-keys/key-resolution-repository.test.ts +95 -0
  675. package/src/security/tenant-keys/key-resolution-repository.ts +31 -0
  676. package/src/security/tenant-keys/key-resolution.test.ts +173 -0
  677. package/src/security/tenant-keys/key-resolution.ts +87 -0
  678. package/src/security/tenant-keys/org-key-resolution.test.ts +217 -0
  679. package/src/security/tenant-keys/org-key-resolution.ts +76 -0
  680. package/src/security/tenant-keys/tenant-key-repository.test.ts +143 -0
  681. package/src/security/tenant-keys/tenant-key-repository.ts +130 -0
  682. package/src/security/types.ts +43 -0
  683. package/src/tenancy/drizzle-org-repository.ts +169 -0
  684. package/src/tenancy/index.ts +6 -0
  685. package/src/tenancy/org-member-repository.ts +159 -0
  686. package/src/tenancy/org-repository.test.ts +172 -0
  687. package/src/tenancy/org-service.test.ts +634 -0
  688. package/src/tenancy/org-service.ts +290 -0
  689. package/src/test/db.ts +97 -0
  690. package/src/trpc/index.ts +11 -0
  691. package/src/trpc/init.test.ts +196 -0
  692. package/src/trpc/init.ts +138 -0
  693. package/tsconfig.json +20 -0
  694. package/vitest.config.ts +8 -0
@@ -0,0 +1,70 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { assertSafeRedirectUrl } from "./redirect-allowlist.js";
3
+
4
+ describe("assertSafeRedirectUrl", () => {
5
+ it("allows https://app.wopr.bot paths", () => {
6
+ expect(() => assertSafeRedirectUrl("https://app.wopr.bot/billing/success")).not.toThrow();
7
+ });
8
+
9
+ it("allows https://app.wopr.bot with query params", () => {
10
+ expect(() => assertSafeRedirectUrl("https://app.wopr.bot/dashboard?vps=activated")).not.toThrow();
11
+ });
12
+
13
+ it("allows https://wopr.network paths", () => {
14
+ expect(() => assertSafeRedirectUrl("https://wopr.network/welcome")).not.toThrow();
15
+ });
16
+
17
+ it("allows http://localhost:3000 in dev", () => {
18
+ expect(() => assertSafeRedirectUrl("http://localhost:3000/billing")).not.toThrow();
19
+ });
20
+
21
+ it("allows http://localhost:3001 in dev", () => {
22
+ expect(() => assertSafeRedirectUrl("http://localhost:3001/billing")).not.toThrow();
23
+ });
24
+
25
+ it("rejects external domains", () => {
26
+ expect(() => assertSafeRedirectUrl("https://evil.com/phishing")).toThrow("Invalid redirect URL");
27
+ });
28
+
29
+ it("rejects subdomain spoofing (app.wopr.bot.evil.com)", () => {
30
+ expect(() => assertSafeRedirectUrl("https://app.wopr.bot.evil.com/phishing")).toThrow("Invalid redirect URL");
31
+ });
32
+
33
+ it("rejects non-URL strings", () => {
34
+ expect(() => assertSafeRedirectUrl("not-a-url")).toThrow("Invalid redirect URL");
35
+ });
36
+
37
+ it("rejects javascript: URIs", () => {
38
+ expect(() => assertSafeRedirectUrl("javascript:alert(1)")).toThrow("Invalid redirect URL");
39
+ });
40
+
41
+ it("rejects data: URIs", () => {
42
+ expect(() => assertSafeRedirectUrl("data:text/html,<h1>pwned</h1>")).toThrow("Invalid redirect URL");
43
+ });
44
+
45
+ it("rejects empty string", () => {
46
+ expect(() => assertSafeRedirectUrl("")).toThrow("Invalid redirect URL");
47
+ });
48
+
49
+ describe("PLATFORM_UI_URL env-driven entry", () => {
50
+ beforeEach(() => {
51
+ process.env.PLATFORM_UI_URL = "https://platform.example.com";
52
+ vi.resetModules();
53
+ });
54
+
55
+ afterEach(() => {
56
+ delete process.env.PLATFORM_UI_URL;
57
+ vi.resetModules();
58
+ });
59
+
60
+ it("allows PLATFORM_UI_URL when set", async () => {
61
+ const { assertSafeRedirectUrl: assertSafe } = await import("./redirect-allowlist.js");
62
+ expect(() => assertSafe("https://platform.example.com/dashboard")).not.toThrow();
63
+ });
64
+
65
+ it("rejects URLs not matching PLATFORM_UI_URL", async () => {
66
+ const { assertSafeRedirectUrl: assertSafe } = await import("./redirect-allowlist.js");
67
+ expect(() => assertSafe("https://other.example.com/dashboard")).toThrow("Invalid redirect URL");
68
+ });
69
+ });
70
+ });
@@ -0,0 +1,35 @@
1
+ const ALLOWED_REDIRECT_ORIGINS: string[] = [
2
+ "https://app.wopr.bot",
3
+ "https://wopr.network",
4
+ ...(process.env.NODE_ENV !== "production" ? ["http://localhost:3000", "http://localhost:3001"] : []),
5
+ ...(process.env.PLATFORM_UI_URL ? [process.env.PLATFORM_UI_URL] : []),
6
+ ...(process.env.NODE_ENV !== "production" ? ["https://example.com"] : []),
7
+ ];
8
+
9
+ /**
10
+ * Throws if `url` is not rooted at one of the allowed origins.
11
+ * Comparison is scheme + host (origin), not prefix string match,
12
+ * to prevent bypasses like `https://app.wopr.bot.evil.com`.
13
+ */
14
+ export function assertSafeRedirectUrl(url: string): void {
15
+ let parsed: URL;
16
+ try {
17
+ parsed = new URL(url);
18
+ } catch {
19
+ throw new Error("Invalid redirect URL");
20
+ }
21
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
22
+ throw new Error("Invalid redirect URL");
23
+ }
24
+ const origin = parsed.origin;
25
+ const allowed = ALLOWED_REDIRECT_ORIGINS.some((o) => {
26
+ try {
27
+ return origin === new URL(o).origin;
28
+ } catch {
29
+ return false;
30
+ }
31
+ });
32
+ if (!allowed) {
33
+ throw new Error("Invalid redirect URL");
34
+ }
35
+ }
@@ -0,0 +1,98 @@
1
+ import type { PGlite } from "@electric-sql/pglite";
2
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
3
+ import type { PlatformDb } from "../../db/index.js";
4
+ import { createTestDb, truncateAllTables } from "../../test/db.js";
5
+ import { CapabilitySettingsStore } from "./capability-settings-store.js";
6
+
7
+ describe("CapabilitySettingsStore", () => {
8
+ let db: PlatformDb;
9
+ let pool: PGlite;
10
+ let store: CapabilitySettingsStore;
11
+
12
+ beforeAll(async () => {
13
+ ({ db, pool } = await createTestDb());
14
+ });
15
+
16
+ afterAll(async () => {
17
+ await pool.close();
18
+ });
19
+
20
+ beforeEach(async () => {
21
+ await truncateAllTables(pool);
22
+ store = new CapabilitySettingsStore(db);
23
+ });
24
+
25
+ it("returns empty array for tenant with no overrides (platform defaults apply)", async () => {
26
+ const settings = await store.listForTenant("tenant-no-overrides");
27
+ expect(settings).toEqual([]);
28
+ });
29
+
30
+ it("upsert sets per-tenant override that listForTenant returns", async () => {
31
+ await store.upsert("tenant-1", "transcription", "byok");
32
+
33
+ const settings = await store.listForTenant("tenant-1");
34
+ expect(settings).toHaveLength(1);
35
+ expect(settings[0]).toMatchObject({
36
+ tenant_id: "tenant-1",
37
+ capability: "transcription",
38
+ mode: "byok",
39
+ });
40
+ expect(settings[0].updated_at).toBeGreaterThan(0);
41
+ });
42
+
43
+ it("upsert can toggle mode between hosted and byok", async () => {
44
+ await store.upsert("tenant-2", "image-gen", "byok");
45
+ let settings = await store.listForTenant("tenant-2");
46
+ expect(settings[0].mode).toBe("byok");
47
+
48
+ await store.upsert("tenant-2", "image-gen", "hosted");
49
+ settings = await store.listForTenant("tenant-2");
50
+ expect(settings).toHaveLength(1);
51
+ expect(settings[0].mode).toBe("hosted");
52
+ });
53
+
54
+ it("upsert overwrites previous mode via onConflictDoUpdate", async () => {
55
+ await store.upsert("tenant-3", "text-gen", "hosted");
56
+ const before = await store.listForTenant("tenant-3");
57
+ const firstUpdatedAt = before[0].updated_at;
58
+
59
+ // Small delay so timestamp differs
60
+ await new Promise((r) => setTimeout(r, 5));
61
+
62
+ await store.upsert("tenant-3", "text-gen", "byok");
63
+ const after = await store.listForTenant("tenant-3");
64
+ expect(after).toHaveLength(1);
65
+ expect(after[0].mode).toBe("byok");
66
+ expect(after[0].updated_at).toBeGreaterThanOrEqual(firstUpdatedAt);
67
+ });
68
+
69
+ it("tenant A overrides do not appear in tenant B list", async () => {
70
+ await store.upsert("tenant-A", "transcription", "byok");
71
+ await store.upsert("tenant-A", "image-gen", "byok");
72
+ await store.upsert("tenant-B", "embeddings", "hosted");
73
+
74
+ const settingsA = await store.listForTenant("tenant-A");
75
+ const settingsB = await store.listForTenant("tenant-B");
76
+
77
+ expect(settingsA).toHaveLength(2);
78
+ expect(settingsA.every((s) => s.tenant_id === "tenant-A")).toBe(true);
79
+
80
+ expect(settingsB).toHaveLength(1);
81
+ expect(settingsB[0].tenant_id).toBe("tenant-B");
82
+ expect(settingsB[0].capability).toBe("embeddings");
83
+ });
84
+
85
+ it("upsert changes are immediately visible in subsequent reads (no stale cache)", async () => {
86
+ await store.upsert("tenant-C", "text-gen", "hosted");
87
+ const read1 = await store.listForTenant("tenant-C");
88
+ expect(read1[0].mode).toBe("hosted");
89
+
90
+ await store.upsert("tenant-C", "text-gen", "byok");
91
+ const read2 = await store.listForTenant("tenant-C");
92
+ expect(read2[0].mode).toBe("byok");
93
+
94
+ await store.upsert("tenant-C", "image-gen", "byok");
95
+ const read3 = await store.listForTenant("tenant-C");
96
+ expect(read3).toHaveLength(2);
97
+ });
98
+ });
@@ -0,0 +1,53 @@
1
+ import { eq } from "drizzle-orm";
2
+ import type { PlatformDb } from "../../db/index.js";
3
+ import { tenantCapabilitySettings } from "../../db/schema/index.js";
4
+
5
+ export const ALL_CAPABILITIES = ["transcription", "image-gen", "text-gen", "embeddings"] as const;
6
+ export type CapabilityName = (typeof ALL_CAPABILITIES)[number];
7
+
8
+ export interface TenantCapabilitySetting {
9
+ tenant_id: string;
10
+ capability: string;
11
+ mode: string;
12
+ updated_at: number;
13
+ }
14
+
15
+ /** Repository for tenant capability mode settings (hosted vs byok). */
16
+ export interface ICapabilitySettingsRepository {
17
+ listForTenant(tenantId: string): Promise<TenantCapabilitySetting[]>;
18
+ upsert(tenantId: string, capability: string, mode: string): Promise<void>;
19
+ }
20
+
21
+ export class CapabilitySettingsStore implements ICapabilitySettingsRepository {
22
+ private readonly db: PlatformDb;
23
+
24
+ constructor(db: PlatformDb) {
25
+ this.db = db;
26
+ }
27
+
28
+ /** Get all capability settings for a tenant. Returns empty array if none set (defaults to hosted). */
29
+ async listForTenant(tenantId: string): Promise<TenantCapabilitySetting[]> {
30
+ const rows = await this.db
31
+ .select()
32
+ .from(tenantCapabilitySettings)
33
+ .where(eq(tenantCapabilitySettings.tenantId, tenantId));
34
+ return rows.map((r) => ({
35
+ tenant_id: r.tenantId,
36
+ capability: r.capability,
37
+ mode: r.mode,
38
+ updated_at: r.updatedAt,
39
+ }));
40
+ }
41
+
42
+ /** Set the mode for a specific capability. Upserts. */
43
+ async upsert(tenantId: string, capability: string, mode: string): Promise<void> {
44
+ const now = Date.now();
45
+ await this.db
46
+ .insert(tenantCapabilitySettings)
47
+ .values({ tenantId, capability, mode, updatedAt: now })
48
+ .onConflictDoUpdate({
49
+ target: [tenantCapabilitySettings.tenantId, tenantCapabilitySettings.capability],
50
+ set: { mode, updatedAt: now },
51
+ });
52
+ }
53
+ }
@@ -0,0 +1,10 @@
1
+ export type { IKeyResolutionRepository } from "./key-resolution-repository.js";
2
+ export { DrizzleKeyResolutionRepository } from "./key-resolution-repository.js";
3
+ export type { ResolvedKey } from "./key-resolution.js";
4
+ export { resolveApiKey, buildPooledKeysMap } from "./key-resolution.js";
5
+ export type { TenantApiKey, ITenantKeyRepository } from "./tenant-key-repository.js";
6
+ export { TenantKeyRepository } from "./tenant-key-repository.js";
7
+ export type { CapabilityName, TenantCapabilitySetting, ICapabilitySettingsRepository } from "./capability-settings-store.js";
8
+ export { ALL_CAPABILITIES, CapabilitySettingsStore } from "./capability-settings-store.js";
9
+ export type { IOrgMembershipRepository, OrgResolvedKey } from "./org-key-resolution.js";
10
+ export { DrizzleOrgMembershipRepository, resolveApiKeyWithOrgFallback } from "./org-key-resolution.js";
@@ -0,0 +1,95 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { PGlite } from "@electric-sql/pglite";
3
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
4
+ import type { PlatformDb } from "../../db/index.js";
5
+ import { tenantApiKeys } from "../../db/schema/tenant-api-keys.js";
6
+ import { createTestDb, truncateAllTables } from "../../test/db.js";
7
+ import type { Provider } from "../types.js";
8
+ import { DrizzleKeyResolutionRepository } from "./key-resolution-repository.js";
9
+
10
+ let db: PlatformDb;
11
+ let pool: PGlite;
12
+
13
+ async function insertKey(tenantId: string, provider: Provider, encryptedKey: string): Promise<void> {
14
+ const now = Date.now();
15
+ await db
16
+ .insert(tenantApiKeys)
17
+ .values({ id: randomUUID(), tenantId, provider, label: "", encryptedKey, createdAt: now, updatedAt: now });
18
+ }
19
+
20
+ beforeAll(async () => {
21
+ ({ db, pool } = await createTestDb());
22
+ });
23
+
24
+ afterAll(async () => {
25
+ await pool.close();
26
+ });
27
+
28
+ describe("DrizzleKeyResolutionRepository", () => {
29
+ let repo: DrizzleKeyResolutionRepository;
30
+
31
+ beforeEach(async () => {
32
+ await truncateAllTables(pool);
33
+ repo = new DrizzleKeyResolutionRepository(db);
34
+ });
35
+
36
+ it("returns the encrypted key for a matching tenant and provider", async () => {
37
+ const payload = JSON.stringify({ iv: "aaa", authTag: "bbb", ciphertext: "ccc" });
38
+ await insertKey("tenant-1", "anthropic", payload);
39
+
40
+ const result = await repo.findEncryptedKey("tenant-1", "anthropic");
41
+
42
+ expect(result).not.toBeNull();
43
+ expect(result?.encryptedKey).toBe(payload);
44
+ });
45
+
46
+ it("returns null when no key exists for the tenant", async () => {
47
+ const result = await repo.findEncryptedKey("nonexistent-tenant", "anthropic");
48
+ expect(result).toBeNull();
49
+ });
50
+
51
+ it("returns null when no key exists for the provider", async () => {
52
+ const payload = JSON.stringify({ iv: "aaa", authTag: "bbb", ciphertext: "ccc" });
53
+ await insertKey("tenant-1", "anthropic", payload);
54
+
55
+ const result = await repo.findEncryptedKey("tenant-1", "openai");
56
+ expect(result).toBeNull();
57
+ });
58
+
59
+ it("isolates keys between tenants (cross-tenant isolation)", async () => {
60
+ const payloadA = JSON.stringify({ iv: "aaa", authTag: "bbb", ciphertext: "tenant-a-secret" });
61
+ const payloadB = JSON.stringify({ iv: "xxx", authTag: "yyy", ciphertext: "tenant-b-secret" });
62
+ await insertKey("tenant-a", "anthropic", payloadA);
63
+ await insertKey("tenant-b", "anthropic", payloadB);
64
+
65
+ const resultA = await repo.findEncryptedKey("tenant-a", "anthropic");
66
+ const resultB = await repo.findEncryptedKey("tenant-b", "anthropic");
67
+
68
+ expect(resultA).not.toBeNull();
69
+ expect(resultB).not.toBeNull();
70
+ expect(resultA?.encryptedKey).toBe(payloadA);
71
+ expect(resultB?.encryptedKey).toBe(payloadB);
72
+ expect(resultA?.encryptedKey).not.toBe(resultB?.encryptedKey);
73
+ });
74
+
75
+ it("tenant A cannot see tenant B keys", async () => {
76
+ const payload = JSON.stringify({ iv: "aaa", authTag: "bbb", ciphertext: "only-for-b" });
77
+ await insertKey("tenant-b", "openai", payload);
78
+
79
+ const result = await repo.findEncryptedKey("tenant-a", "openai");
80
+ expect(result).toBeNull();
81
+ });
82
+
83
+ it("returns correct key when tenant has multiple providers", async () => {
84
+ const anthropicPayload = JSON.stringify({ iv: "a1", authTag: "a2", ciphertext: "anthropic-key" });
85
+ const openaiPayload = JSON.stringify({ iv: "o1", authTag: "o2", ciphertext: "openai-key" });
86
+ await insertKey("tenant-1", "anthropic", anthropicPayload);
87
+ await insertKey("tenant-1", "openai", openaiPayload);
88
+
89
+ const anthropicResult = await repo.findEncryptedKey("tenant-1", "anthropic");
90
+ const openaiResult = await repo.findEncryptedKey("tenant-1", "openai");
91
+
92
+ expect(anthropicResult?.encryptedKey).toBe(anthropicPayload);
93
+ expect(openaiResult?.encryptedKey).toBe(openaiPayload);
94
+ });
95
+ });
@@ -0,0 +1,31 @@
1
+ import { and, eq } from "drizzle-orm";
2
+ import type { PlatformDb } from "../../db/index.js";
3
+ import { tenantApiKeys } from "../../db/schema/index.js";
4
+ import type { Provider } from "../types.js";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Interface
8
+ // ---------------------------------------------------------------------------
9
+
10
+ /** Repository for looking up tenant BYOK keys by tenant + provider. */
11
+ export interface IKeyResolutionRepository {
12
+ findEncryptedKey(tenantId: string, provider: Provider): Promise<{ encryptedKey: string } | null>;
13
+ }
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Implementation
17
+ // ---------------------------------------------------------------------------
18
+
19
+ export class DrizzleKeyResolutionRepository implements IKeyResolutionRepository {
20
+ constructor(private readonly db: PlatformDb) {}
21
+
22
+ async findEncryptedKey(tenantId: string, provider: Provider): Promise<{ encryptedKey: string } | null> {
23
+ const row = (
24
+ await this.db
25
+ .select({ encryptedKey: tenantApiKeys.encryptedKey })
26
+ .from(tenantApiKeys)
27
+ .where(and(eq(tenantApiKeys.tenantId, tenantId), eq(tenantApiKeys.provider, provider)))
28
+ )[0];
29
+ return row ?? null;
30
+ }
31
+ }
@@ -0,0 +1,173 @@
1
+ import type { PGlite } from "@electric-sql/pglite";
2
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
3
+ import type { PlatformDb } from "../../db/index.js";
4
+ import { createTestDb, truncateAllTables } from "../../test/db.js";
5
+ import { encrypt, generateInstanceKey } from "../encryption.js";
6
+ import type { EncryptedPayload, Provider } from "../types.js";
7
+ import { buildPooledKeysMap, resolveApiKey } from "./key-resolution.js";
8
+ import { DrizzleKeyResolutionRepository } from "./key-resolution-repository.js";
9
+
10
+ async function insertTenantKey(
11
+ pool: PGlite,
12
+ tenantId: string,
13
+ provider: string,
14
+ encryptedKey: EncryptedPayload,
15
+ ): Promise<void> {
16
+ const { randomUUID } = await import("node:crypto");
17
+ const now = Date.now();
18
+ await pool.query(
19
+ "INSERT INTO tenant_api_keys (id, tenant_id, provider, label, encrypted_key, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7)",
20
+ [randomUUID(), tenantId, provider, "", JSON.stringify(encryptedKey), now, now],
21
+ );
22
+ }
23
+
24
+ let db: PlatformDb;
25
+ let pool: PGlite;
26
+
27
+ beforeAll(async () => {
28
+ ({ db, pool } = await createTestDb());
29
+ });
30
+
31
+ afterAll(async () => {
32
+ await pool.close();
33
+ });
34
+
35
+ describe("resolveApiKey", () => {
36
+ let encryptionKey: Buffer;
37
+
38
+ beforeEach(async () => {
39
+ await truncateAllTables(pool);
40
+ encryptionKey = generateInstanceKey();
41
+ });
42
+
43
+ it("returns tenant BYOK key when one is stored", async () => {
44
+ const encrypted = encrypt("sk-ant-tenant-key", encryptionKey);
45
+ await insertTenantKey(pool, "t1", "anthropic", encrypted);
46
+
47
+ const pooled = new Map<Provider, string>([["anthropic", "sk-ant-pooled"]]);
48
+ const result = await resolveApiKey(
49
+ new DrizzleKeyResolutionRepository(db),
50
+ "t1",
51
+ "anthropic",
52
+ encryptionKey,
53
+ pooled,
54
+ );
55
+ expect(result).not.toBeNull();
56
+ expect(result?.key).toBe("sk-ant-tenant-key");
57
+ expect(result?.source).toBe("tenant");
58
+ expect(result?.provider).toBe("anthropic");
59
+ });
60
+
61
+ it("falls back to pooled key when no tenant key stored", async () => {
62
+ const pooled = new Map<Provider, string>([["openai", "sk-pooled-openai"]]);
63
+ const result = await resolveApiKey(new DrizzleKeyResolutionRepository(db), "t1", "openai", encryptionKey, pooled);
64
+ expect(result).not.toBeNull();
65
+ expect(result?.key).toBe("sk-pooled-openai");
66
+ expect(result?.source).toBe("pooled");
67
+ expect(result?.provider).toBe("openai");
68
+ });
69
+
70
+ it("returns null when neither tenant nor pooled key exists", async () => {
71
+ const pooled = new Map<Provider, string>();
72
+ const result = await resolveApiKey(
73
+ new DrizzleKeyResolutionRepository(db),
74
+ "t1",
75
+ "anthropic",
76
+ encryptionKey,
77
+ pooled,
78
+ );
79
+ expect(result).toBeNull();
80
+ });
81
+
82
+ it("resolves different providers independently", async () => {
83
+ const encrypted = encrypt("my-google-key", encryptionKey);
84
+ await insertTenantKey(pool, "t1", "google", encrypted);
85
+
86
+ const pooled = new Map<Provider, string>([["anthropic", "sk-ant-pooled"]]);
87
+
88
+ const googleResult = await resolveApiKey(
89
+ new DrizzleKeyResolutionRepository(db),
90
+ "t1",
91
+ "google",
92
+ encryptionKey,
93
+ pooled,
94
+ );
95
+ expect(googleResult?.source).toBe("tenant");
96
+ expect(googleResult?.key).toBe("my-google-key");
97
+
98
+ const anthropicResult = await resolveApiKey(
99
+ new DrizzleKeyResolutionRepository(db),
100
+ "t1",
101
+ "anthropic",
102
+ encryptionKey,
103
+ pooled,
104
+ );
105
+ expect(anthropicResult?.source).toBe("pooled");
106
+ expect(anthropicResult?.key).toBe("sk-ant-pooled");
107
+
108
+ const discordResult = await resolveApiKey(
109
+ new DrizzleKeyResolutionRepository(db),
110
+ "t1",
111
+ "discord",
112
+ encryptionKey,
113
+ pooled,
114
+ );
115
+ expect(discordResult).toBeNull();
116
+ });
117
+
118
+ it("isolates keys between tenants", async () => {
119
+ const encrypted = encrypt("t1-anthropic-key", encryptionKey);
120
+ await insertTenantKey(pool, "t1", "anthropic", encrypted);
121
+
122
+ const pooled = new Map<Provider, string>([["anthropic", "sk-ant-pooled"]]);
123
+
124
+ const t1Result = await resolveApiKey(
125
+ new DrizzleKeyResolutionRepository(db),
126
+ "t1",
127
+ "anthropic",
128
+ encryptionKey,
129
+ pooled,
130
+ );
131
+ expect(t1Result?.source).toBe("tenant");
132
+
133
+ const t2Result = await resolveApiKey(
134
+ new DrizzleKeyResolutionRepository(db),
135
+ "t2",
136
+ "anthropic",
137
+ encryptionKey,
138
+ pooled,
139
+ );
140
+ expect(t2Result?.source).toBe("pooled");
141
+ });
142
+ });
143
+
144
+ describe("buildPooledKeysMap", () => {
145
+ it("reads keys from environment variables", () => {
146
+ const env = {
147
+ ANTHROPIC_API_KEY: "sk-ant-test",
148
+ OPENAI_API_KEY: "sk-openai-test",
149
+ GOOGLE_API_KEY: "google-test",
150
+ DISCORD_BOT_TOKEN: "discord-test",
151
+ };
152
+ const keys = buildPooledKeysMap(env);
153
+ expect(keys.get("anthropic")).toBe("sk-ant-test");
154
+ expect(keys.get("openai")).toBe("sk-openai-test");
155
+ expect(keys.get("google")).toBe("google-test");
156
+ expect(keys.get("discord")).toBe("discord-test");
157
+ });
158
+
159
+ it("ignores missing env vars", () => {
160
+ const keys = buildPooledKeysMap({});
161
+ expect(keys.size).toBe(0);
162
+ });
163
+
164
+ it("trims whitespace from values", () => {
165
+ const keys = buildPooledKeysMap({ ANTHROPIC_API_KEY: " sk-ant-test " });
166
+ expect(keys.get("anthropic")).toBe("sk-ant-test");
167
+ });
168
+
169
+ it("ignores empty values", () => {
170
+ const keys = buildPooledKeysMap({ ANTHROPIC_API_KEY: " " });
171
+ expect(keys.size).toBe(0);
172
+ });
173
+ });
@@ -0,0 +1,87 @@
1
+ import { decrypt } from "../encryption.js";
2
+ import type { Provider } from "../types.js";
3
+ import type { IKeyResolutionRepository } from "./key-resolution-repository.js";
4
+
5
+ /** Result of resolving which API key to use. */
6
+ export interface ResolvedKey {
7
+ /** The plaintext API key. */
8
+ key: string;
9
+ /** Where the key came from. */
10
+ source: "tenant" | "pooled";
11
+ /** The provider the key is for. */
12
+ provider: Provider;
13
+ }
14
+
15
+ /**
16
+ * Resolve which API key to use for a given tenant and provider.
17
+ *
18
+ * Resolution order:
19
+ * 1. If the tenant has a BYOK key stored, decrypt and return it.
20
+ * 2. Otherwise, fall back to the pooled (platform-level) key from env vars.
21
+ * 3. If neither exists, return null.
22
+ *
23
+ * SECURITY: The decrypted key is returned to the caller and must be discarded
24
+ * after use. This function does not log, persist, or cache the plaintext key.
25
+ *
26
+ * @param repo - Repository for looking up tenant BYOK keys
27
+ * @param tenantId - The tenant requesting the key
28
+ * @param provider - The AI provider (anthropic, openai, google, discord)
29
+ * @param encryptionKey - The 32-byte key used to decrypt the stored BYOK key
30
+ * @param pooledKeys - Map of provider -> pooled API key (from env vars)
31
+ */
32
+ export async function resolveApiKey(
33
+ repo: IKeyResolutionRepository,
34
+ tenantId: string,
35
+ provider: Provider,
36
+ encryptionKey: Buffer,
37
+ pooledKeys: Map<Provider, string>,
38
+ ): Promise<ResolvedKey | null> {
39
+ // 1. Check for tenant BYOK key
40
+ const row = await repo.findEncryptedKey(tenantId, provider);
41
+
42
+ if (row) {
43
+ const payload = JSON.parse(row.encryptedKey);
44
+ const plaintext = decrypt(payload, encryptionKey);
45
+ return { key: plaintext, source: "tenant", provider };
46
+ }
47
+
48
+ // 2. Fall back to pooled key
49
+ const pooledKey = pooledKeys.get(provider);
50
+ if (pooledKey) {
51
+ return { key: pooledKey, source: "pooled", provider };
52
+ }
53
+
54
+ // 3. No key available
55
+ return null;
56
+ }
57
+
58
+ /**
59
+ * Build a pooled keys map from environment variables.
60
+ *
61
+ * Reads:
62
+ * - ANTHROPIC_API_KEY
63
+ * - OPENAI_API_KEY
64
+ * - GOOGLE_API_KEY
65
+ * - DISCORD_BOT_TOKEN
66
+ */
67
+ export function buildPooledKeysMap(
68
+ env: Record<string, string | undefined> = process.env as Record<string, string | undefined>,
69
+ ): Map<Provider, string> {
70
+ const keys = new Map<Provider, string>();
71
+
72
+ const mapping: [string, Provider][] = [
73
+ ["ANTHROPIC_API_KEY", "anthropic"],
74
+ ["OPENAI_API_KEY", "openai"],
75
+ ["GOOGLE_API_KEY", "google"],
76
+ ["DISCORD_BOT_TOKEN", "discord"],
77
+ ];
78
+
79
+ for (const [envVar, provider] of mapping) {
80
+ const val = env[envVar]?.trim();
81
+ if (val) {
82
+ keys.set(provider, val);
83
+ }
84
+ }
85
+
86
+ return keys;
87
+ }