@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,520 @@
1
+ /**
2
+ * Auth — Session management, token verification, and middleware.
3
+ *
4
+ * Provides:
5
+ * - `requireRole` middleware for role-based access control
6
+ * - `scopedBearerAuth` middleware for operation-scoped API tokens
7
+ */
8
+
9
+ import { timingSafeEqual } from "node:crypto";
10
+ import type { Context, Next } from "hono";
11
+ import type { IOrgMemberRepository } from "../tenancy/org-member-repository.js";
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Types
15
+ // ---------------------------------------------------------------------------
16
+
17
+ export interface AuthUser {
18
+ id: string;
19
+ roles: string[];
20
+ }
21
+
22
+ export interface AuthEnv {
23
+ Variables: {
24
+ user: AuthUser;
25
+ authMethod: "session" | "api_key";
26
+ tokenTenantId?: string;
27
+ isOperatorToken?: boolean;
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Extract the bearer token from an Authorization header value.
33
+ * Returns `null` if the header is missing, empty, or not a Bearer scheme.
34
+ */
35
+ export function extractBearerToken(header: string | undefined): string | null {
36
+ if (!header) return null;
37
+ const trimmed = header.trim();
38
+ if (!trimmed.toLowerCase().startsWith("bearer ")) return null;
39
+ const token = trimmed.slice(7).trim();
40
+ return token || null;
41
+ }
42
+
43
+ /**
44
+ * Timing-safe lookup of a string key in a Map.
45
+ *
46
+ * Iterates all entries and compares each key using `crypto.timingSafeEqual`
47
+ * to prevent timing side-channel attacks. Returns the value of the first
48
+ * matching key, or `undefined` if no key matches.
49
+ */
50
+ export function timingSafeMapLookup<V>(map: Map<string, V>, candidate: string): V | undefined {
51
+ const candidateBuf = Buffer.from(candidate);
52
+ let found: V | undefined;
53
+ for (const [key, value] of map) {
54
+ const keyBuf = Buffer.from(key);
55
+ if (candidateBuf.length === keyBuf.length && timingSafeEqual(candidateBuf, keyBuf)) {
56
+ found = value;
57
+ }
58
+ }
59
+ return found;
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Middleware
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /**
67
+ * Create a `requireRole` middleware that rejects users without the specified role.
68
+ * Must be used after `requireAuth`.
69
+ */
70
+ export function requireRole(role: string) {
71
+ return async (c: Context<AuthEnv>, next: Next) => {
72
+ let user: AuthUser | undefined;
73
+ try {
74
+ user = c.get("user");
75
+ } catch {
76
+ return c.json({ error: "Authentication required" }, 401);
77
+ }
78
+
79
+ if (!user) {
80
+ return c.json({ error: "Authentication required" }, 401);
81
+ }
82
+
83
+ if (!user.roles.includes(role)) {
84
+ return c.json({ error: "Insufficient permissions", required: role }, 403);
85
+ }
86
+
87
+ return next();
88
+ };
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Scoped API Tokens
93
+ // ---------------------------------------------------------------------------
94
+
95
+ /** Operation scopes ordered by privilege level. */
96
+ export type TokenScope = "read" | "write" | "admin";
97
+
98
+ /** Privilege hierarchy: admin > write > read. */
99
+ const SCOPE_LEVEL: Record<TokenScope, number> = {
100
+ read: 0,
101
+ write: 1,
102
+ admin: 2,
103
+ };
104
+
105
+ const VALID_SCOPES = new Set<string>(["read", "write", "admin"]);
106
+
107
+ /**
108
+ * Parse a token's scope from the `wopr_<scope>_<random>` format.
109
+ *
110
+ * - `wopr_read_abc123` -> `"read"`
111
+ * - `wopr_write_abc123` -> `"write"`
112
+ * - `wopr_admin_abc123` -> `"admin"`
113
+ * - Any other format -> `null` (not a scoped token)
114
+ */
115
+ export function parseTokenScope(token: string): TokenScope | null {
116
+ if (!token.startsWith("wopr_")) return null;
117
+ const parts = token.split("_");
118
+ // Must be at least 3 parts: "wopr", scope, random
119
+ if (parts.length < 3) return null;
120
+ const scope = parts[1];
121
+ if (!VALID_SCOPES.has(scope)) return null;
122
+ // The random portion (everything after wopr_<scope>_) must be non-empty
123
+ const random = parts.slice(2).join("_");
124
+ if (!random) return null;
125
+ return scope as TokenScope;
126
+ }
127
+
128
+ /**
129
+ * Check whether a token's scope satisfies the required minimum scope.
130
+ * admin >= write >= read.
131
+ */
132
+ export function scopeSatisfies(tokenScope: TokenScope, requiredScope: TokenScope): boolean {
133
+ return SCOPE_LEVEL[tokenScope] >= SCOPE_LEVEL[requiredScope];
134
+ }
135
+
136
+ export interface ScopedTokenConfig {
137
+ /**
138
+ * Map of token string -> its scope.
139
+ * Tokens can be in `wopr_<scope>_<random>` format (scope parsed automatically)
140
+ * or plain strings mapped explicitly.
141
+ */
142
+ tokens: Map<string, TokenScope>;
143
+ }
144
+
145
+ /** Token metadata including scope and tenant association. */
146
+ export interface TokenMetadata {
147
+ scope: TokenScope;
148
+ tenantId?: string;
149
+ }
150
+
151
+ /**
152
+ * Build a token-to-scope map from environment variables.
153
+ *
154
+ * Accepts:
155
+ * - `FLEET_API_TOKEN` (legacy single token, treated as admin)
156
+ * - `FLEET_API_TOKEN_READ` (read-scoped token)
157
+ * - `FLEET_API_TOKEN_WRITE` (write-scoped token)
158
+ * - `FLEET_API_TOKEN_ADMIN` (admin-scoped token)
159
+ * - Any `wopr_<scope>_<random>` formatted token has its scope inferred.
160
+ */
161
+ export function buildTokenMap(
162
+ env: Record<string, string | undefined> = process.env as Record<string, string | undefined>,
163
+ ): Map<string, TokenScope> {
164
+ const tokens = new Map<string, TokenScope>();
165
+
166
+ // Scoped env vars
167
+ const scopedVars: [string, TokenScope][] = [
168
+ ["FLEET_API_TOKEN_READ", "read"],
169
+ ["FLEET_API_TOKEN_WRITE", "write"],
170
+ ["FLEET_API_TOKEN_ADMIN", "admin"],
171
+ ];
172
+
173
+ for (const [envVar, scope] of scopedVars) {
174
+ const val = env[envVar]?.trim();
175
+ if (val) tokens.set(val, scope);
176
+ }
177
+
178
+ // Legacy single token -- admin scope for backwards compatibility
179
+ const legacyToken = env.FLEET_API_TOKEN?.trim();
180
+ if (legacyToken && !tokens.has(legacyToken)) {
181
+ // If the token is in wopr_ format, parse its actual scope
182
+ const parsed = parseTokenScope(legacyToken);
183
+ tokens.set(legacyToken, parsed ?? "admin");
184
+ }
185
+
186
+ return tokens;
187
+ }
188
+
189
+ /**
190
+ * Build a token-to-metadata map from environment variables.
191
+ *
192
+ * Accepts:
193
+ * - `FLEET_TOKEN_<TENANT>=<scope>:<token>` (tenant-scoped tokens)
194
+ * - Fallback to legacy token map for backwards compatibility (no tenant constraint)
195
+ *
196
+ * Example: `FLEET_TOKEN_ACME=write:wopr_write_abc123`
197
+ */
198
+ export function buildTokenMetadataMap(
199
+ env: Record<string, string | undefined> = process.env as Record<string, string | undefined>,
200
+ ): Map<string, TokenMetadata> {
201
+ const metadataMap = new Map<string, TokenMetadata>();
202
+
203
+ // Parse FLEET_TOKEN_<TENANT>=<scope>:<token> format
204
+ for (const [key, value] of Object.entries(env)) {
205
+ if (key.startsWith("FLEET_TOKEN_") && value) {
206
+ const tenantId = key.slice("FLEET_TOKEN_".length);
207
+ if (!tenantId) continue;
208
+
209
+ const trimmed = value.trim();
210
+ const colonIdx = trimmed.indexOf(":");
211
+ if (colonIdx === -1) continue;
212
+
213
+ const scopeStr = trimmed.slice(0, colonIdx);
214
+ const token = trimmed.slice(colonIdx + 1);
215
+
216
+ if (!VALID_SCOPES.has(scopeStr) || !token) continue;
217
+
218
+ metadataMap.set(token, {
219
+ scope: scopeStr as TokenScope,
220
+ tenantId,
221
+ });
222
+ }
223
+ }
224
+
225
+ // Fallback: add legacy tokens without tenant constraint
226
+ const legacyTokenMap = buildTokenMap(env);
227
+ for (const [token, scope] of legacyTokenMap) {
228
+ if (!metadataMap.has(token)) {
229
+ metadataMap.set(token, { scope });
230
+ }
231
+ }
232
+
233
+ return metadataMap;
234
+ }
235
+
236
+ /**
237
+ * Create a scoped bearer auth middleware.
238
+ *
239
+ * Validates the bearer token against the token map and checks that the
240
+ * token's scope satisfies the required minimum scope for the route.
241
+ *
242
+ * @param tokenMap - Map of token -> scope (use `buildTokenMap()` to create)
243
+ * @param requiredScope - Minimum scope required for this route/group
244
+ */
245
+ export function scopedBearerAuth(tokenMap: Map<string, TokenScope>, requiredScope: TokenScope) {
246
+ return async (c: Context, next: Next) => {
247
+ const authHeader = c.req.header("Authorization");
248
+ const token = extractBearerToken(authHeader);
249
+
250
+ if (!token) {
251
+ return c.json({ error: "Authentication required" }, 401);
252
+ }
253
+
254
+ // Look up the token in the map
255
+ const scope = timingSafeMapLookup(tokenMap, token);
256
+
257
+ // If not in map, try to parse scope from token format for dynamic tokens
258
+ if (scope === undefined) {
259
+ return c.json({ error: "Invalid or expired token" }, 401);
260
+ }
261
+
262
+ // Check scope hierarchy
263
+ if (!scopeSatisfies(scope, requiredScope)) {
264
+ return c.json({ error: "Insufficient scope", required: requiredScope, provided: scope }, 403);
265
+ }
266
+
267
+ // Set user context for downstream middleware (audit, etc.)
268
+ c.set("user", { id: `token:${scope}`, roles: [scope] } satisfies AuthUser);
269
+ c.set("authMethod", "api_key" as const);
270
+
271
+ return next();
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Create a tenant-aware scoped bearer auth middleware.
277
+ *
278
+ * Validates the bearer token against the metadata map, checks scope,
279
+ * and stores the token's associated tenantId for downstream ownership checks.
280
+ *
281
+ * @param metadataMap - Map of token -> metadata (use `buildTokenMetadataMap()` to create)
282
+ * @param requiredScope - Minimum scope required for this route/group
283
+ */
284
+ export function scopedBearerAuthWithTenant(metadataMap: Map<string, TokenMetadata>, requiredScope: TokenScope) {
285
+ return async (c: Context<AuthEnv>, next: Next) => {
286
+ const authHeader = c.req.header("Authorization");
287
+ const token = extractBearerToken(authHeader);
288
+
289
+ if (!token) {
290
+ return c.json({ error: "Authentication required" }, 401);
291
+ }
292
+
293
+ // Look up the token in the metadata map
294
+ const metadata = timingSafeMapLookup(metadataMap, token);
295
+
296
+ if (!metadata) {
297
+ return c.json({ error: "Invalid or expired token" }, 401);
298
+ }
299
+
300
+ // Check scope hierarchy
301
+ if (!scopeSatisfies(metadata.scope, requiredScope)) {
302
+ return c.json({ error: "Insufficient scope", required: requiredScope, provided: metadata.scope }, 403);
303
+ }
304
+
305
+ // Set user context for downstream middleware (audit, etc.)
306
+ c.set("user", { id: `token:${metadata.scope}`, roles: [metadata.scope] } satisfies AuthUser);
307
+ c.set("authMethod", "api_key" as const);
308
+
309
+ // Store tenant constraint if present; absence is allowed here — admin/legacy
310
+ // tokens have no tenantId and must still reach admin routes.
311
+ // Tenant-scoped routes enforce ownership via requireTenantOwnership /
312
+ // validateTenantOwnership (WOP-1264).
313
+ if (metadata.tenantId) {
314
+ c.set("tokenTenantId", metadata.tenantId);
315
+ } else {
316
+ // Operator/admin tokens have no tenant scope — mark them so ownership
317
+ // middlewares can pass them through without a tenantId check.
318
+ c.set("isOperatorToken", true);
319
+ }
320
+
321
+ return next();
322
+ };
323
+ }
324
+
325
+ // ---------------------------------------------------------------------------
326
+ // Session Resolution (better-auth)
327
+ // ---------------------------------------------------------------------------
328
+
329
+ /**
330
+ * Middleware that resolves the current user from a better-auth session cookie.
331
+ *
332
+ * On success, sets:
333
+ * - `c.set("user", { id, roles })`
334
+ * - `c.set("authMethod", "session")`
335
+ *
336
+ * If no session cookie is present (or session is invalid), the request
337
+ * continues without a user — downstream middleware like `scopedBearerAuth`
338
+ * or `requireAuth` will handle enforcement.
339
+ *
340
+ * Uses lazy `getAuth()` to avoid initializing the DB at import time.
341
+ */
342
+ export function resolveSessionUser() {
343
+ return async (c: Context, next: Next) => {
344
+ // Skip if user is already set (e.g., by scopedBearerAuth)
345
+ try {
346
+ if (c.get("user")) return next();
347
+ } catch {
348
+ // c.get throws if variable not set — that's fine, continue
349
+ }
350
+
351
+ try {
352
+ const { getAuth } = await import("./better-auth.js");
353
+ const auth = getAuth();
354
+ const session = await auth.api.getSession({ headers: c.req.raw.headers });
355
+ if (session?.user) {
356
+ const user = session.user as { id: string; role?: string };
357
+ const roles: string[] = [];
358
+ if (user.role) roles.push(user.role);
359
+ c.set("user", { id: user.id, roles } satisfies AuthUser);
360
+ c.set("authMethod", "session" as const);
361
+ }
362
+ } catch {
363
+ // Session resolution failed — continue without user
364
+ }
365
+
366
+ return next();
367
+ };
368
+ }
369
+
370
+ /**
371
+ * Middleware that requires either a valid session or a scoped API token.
372
+ *
373
+ * Tries session first, then falls back to scoped bearer auth.
374
+ * Returns 401 if neither is present.
375
+ */
376
+ export function requireSessionOrToken(tokenMap: Map<string, TokenScope>, requiredScope: TokenScope) {
377
+ return async (c: Context, next: Next) => {
378
+ // Check if user was already resolved by resolveSessionUser
379
+ try {
380
+ if (c.get("user")) return next();
381
+ } catch {
382
+ // Not set — continue to check bearer token
383
+ }
384
+
385
+ // Fall back to scoped bearer auth
386
+ const authHeader = c.req.header("Authorization");
387
+ const token = extractBearerToken(authHeader);
388
+
389
+ if (!token) {
390
+ return c.json({ error: "Authentication required" }, 401);
391
+ }
392
+
393
+ const scope = timingSafeMapLookup(tokenMap, token);
394
+ if (scope === undefined) {
395
+ return c.json({ error: "Invalid or expired token" }, 401);
396
+ }
397
+
398
+ if (!scopeSatisfies(scope, requiredScope)) {
399
+ return c.json({ error: "Insufficient scope", required: requiredScope, provided: scope }, 403);
400
+ }
401
+
402
+ c.set("user", { id: `token:${scope}`, roles: [scope] } satisfies AuthUser);
403
+ c.set("authMethod", "api_key" as const);
404
+
405
+ return next();
406
+ };
407
+ }
408
+
409
+ // ---------------------------------------------------------------------------
410
+ // Tenant Ownership Validation
411
+ // ---------------------------------------------------------------------------
412
+
413
+ /**
414
+ * Middleware that enforces tenant ownership on tenant-scoped resources.
415
+ *
416
+ * Must be used after `scopedBearerAuthWithTenant` middleware.
417
+ * Compares the resource's tenantId against the token's tenantId.
418
+ * - If token has no tenantId (legacy/admin tokens), returns 403 Forbidden.
419
+ * - If resource tenantId matches token tenantId, passes through.
420
+ * - Otherwise, returns 403 Forbidden.
421
+ *
422
+ * @param _getResourceTenantId - Function that extracts tenantId from the resource (reserved for future use)
423
+ */
424
+ export function requireTenantOwnership<T>(_getResourceTenantId: (resource: T) => string | undefined) {
425
+ return async (c: Context<AuthEnv>, next: Next) => {
426
+ // Operator/admin tokens (fleet env var tokens) have no tenantId but must
427
+ // still access tenant-scoped routes — they are trusted at the operator level.
428
+ const isOperatorToken = c.get("isOperatorToken");
429
+ if (isOperatorToken) {
430
+ return next();
431
+ }
432
+
433
+ const tokenTenantId = c.get("tokenTenantId");
434
+
435
+ // If token has no tenant constraint and is not an operator token, reject.
436
+ if (!tokenTenantId) {
437
+ return c.json({ error: "Token lacks tenant scope" }, 403);
438
+ }
439
+
440
+ // Resource tenantId will be validated by route handler
441
+ // Store tokenTenantId for route to check
442
+ return next();
443
+ };
444
+ }
445
+
446
+ /**
447
+ * Validate that a resource belongs to the authenticated tenant.
448
+ * Returns a response if validation fails (404 for not found or tenant mismatch).
449
+ *
450
+ * @param c - Hono context
451
+ * @param resource - The resource to check (null/undefined = not found)
452
+ * @param resourceTenantId - The resource's tenantId
453
+ * @returns Response if validation fails, undefined if validation passes
454
+ */
455
+ export function validateTenantOwnership<T>(
456
+ c: Context,
457
+ resource: T | null | undefined,
458
+ resourceTenantId: string | undefined,
459
+ ): Response | undefined {
460
+ // Resource not found
461
+ if (resource == null) {
462
+ return c.json({ error: "Resource not found" }, 404);
463
+ }
464
+
465
+ // Get token's tenant constraint
466
+ let tokenTenantId: string | undefined;
467
+ try {
468
+ tokenTenantId = c.get("tokenTenantId");
469
+ } catch {
470
+ // No tokenTenantId set — this is a legacy/admin token
471
+ tokenTenantId = undefined;
472
+ }
473
+
474
+ // Operator/admin tokens (fleet env var tokens) have no tenantId but must
475
+ // still access tenant-scoped resources — they are trusted at the operator level.
476
+ let isOperatorToken: boolean | undefined;
477
+ try {
478
+ isOperatorToken = c.get("isOperatorToken");
479
+ } catch {
480
+ isOperatorToken = undefined;
481
+ }
482
+ if (isOperatorToken) {
483
+ return undefined;
484
+ }
485
+
486
+ // No tenant constraint and not an operator token — reject (WOP-1264)
487
+ if (!tokenTenantId) {
488
+ return c.json({ error: "Token lacks tenant scope" }, 403);
489
+ }
490
+
491
+ // Validate tenant match
492
+ if (resourceTenantId !== tokenTenantId) {
493
+ return c.json({ error: "Resource not found" }, 404);
494
+ }
495
+
496
+ return undefined;
497
+ }
498
+
499
+ /**
500
+ * Validate that a user has access to the requested tenant.
501
+ *
502
+ * - If requestedTenantId is falsy or matches userId, access is allowed (personal tenant).
503
+ * - Otherwise, checks org membership via IOrgMemberRepository.
504
+ *
505
+ * @returns true if the user has access, false otherwise.
506
+ */
507
+ export async function validateTenantAccess(
508
+ userId: string,
509
+ requestedTenantId: string | undefined,
510
+ orgMemberRepo: IOrgMemberRepository,
511
+ ): Promise<boolean> {
512
+ // No tenant requested or personal tenant — always allowed
513
+ if (!requestedTenantId || requestedTenantId === userId) {
514
+ return true;
515
+ }
516
+
517
+ // Check org membership
518
+ const member = await orgMemberRepo.findMember(requestedTenantId, userId);
519
+ return member !== null;
520
+ }
@@ -0,0 +1,54 @@
1
+ import type { Pool, QueryResult } from "pg";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { BetterAuthLoginHistoryRepository } from "./login-history-repository.js";
4
+
5
+ function makePool(rows: Record<string, unknown>[]): Pool {
6
+ return {
7
+ query: vi.fn().mockResolvedValue({ rows } as unknown as QueryResult),
8
+ } as unknown as Pool;
9
+ }
10
+
11
+ describe("BetterAuthLoginHistoryRepository", () => {
12
+ beforeEach(() => {
13
+ vi.clearAllMocks();
14
+ });
15
+
16
+ it("returns empty array when user has no sessions", async () => {
17
+ const pool = makePool([]);
18
+ const repo = new BetterAuthLoginHistoryRepository(pool);
19
+ const result = await repo.findByUserId("no-such-user");
20
+ expect(result).toEqual([]);
21
+ });
22
+
23
+ it("returns sessions ordered by createdAt DESC", async () => {
24
+ const pool = makePool([
25
+ { ipAddress: "5.6.7.8", userAgent: "Chrome/120", createdAt: new Date("2026-01-02") },
26
+ { ipAddress: "1.2.3.4", userAgent: "Mozilla/5.0", createdAt: new Date("2026-01-01") },
27
+ ]);
28
+ const repo = new BetterAuthLoginHistoryRepository(pool);
29
+ const result = await repo.findByUserId("user-1");
30
+ expect(result).toHaveLength(2);
31
+ expect(result[0].ip).toBe("5.6.7.8");
32
+ expect(result[1].ip).toBe("1.2.3.4");
33
+ });
34
+
35
+ it("respects the limit parameter", async () => {
36
+ const pool = makePool([
37
+ { ipAddress: "1.1.1.1", userAgent: null, createdAt: new Date() },
38
+ { ipAddress: "2.2.2.2", userAgent: null, createdAt: new Date() },
39
+ { ipAddress: "3.3.3.3", userAgent: null, createdAt: new Date() },
40
+ ]);
41
+ const repo = new BetterAuthLoginHistoryRepository(pool);
42
+ const result = await repo.findByUserId("user-1", 3);
43
+ expect(pool.query).toHaveBeenCalledWith(expect.any(String), ["user-1", 3]);
44
+ expect(result).toHaveLength(3);
45
+ });
46
+
47
+ it("does not return sessions for other users", async () => {
48
+ const pool = makePool([]);
49
+ const repo = new BetterAuthLoginHistoryRepository(pool);
50
+ const result = await repo.findByUserId("user-1");
51
+ expect(result).toEqual([]);
52
+ expect(pool.query).toHaveBeenCalledWith(expect.any(String), ["user-1", 20]);
53
+ });
54
+ });
@@ -0,0 +1,28 @@
1
+ import type { Pool } from "pg";
2
+
3
+ export interface LoginHistoryEntry {
4
+ ip: string | null;
5
+ userAgent: string | null;
6
+ createdAt: string;
7
+ }
8
+
9
+ export interface ILoginHistoryRepository {
10
+ findByUserId(userId: string, limit?: number): Promise<LoginHistoryEntry[]>;
11
+ }
12
+
13
+ export class BetterAuthLoginHistoryRepository implements ILoginHistoryRepository {
14
+ constructor(private readonly pool: Pool) {}
15
+
16
+ async findByUserId(userId: string, limit = 20): Promise<LoginHistoryEntry[]> {
17
+ // raw SQL: better-auth manages its own schema outside Drizzle
18
+ const { rows } = await this.pool.query(
19
+ `SELECT "ipAddress", "userAgent", "createdAt" FROM "session" WHERE "userId" = $1 ORDER BY "createdAt" DESC LIMIT $2`,
20
+ [userId, Math.min(Math.max(1, limit), 100)],
21
+ );
22
+ return rows.map((r: { ipAddress: string | null; userAgent: string | null; createdAt: Date }) => ({
23
+ ip: r.ipAddress,
24
+ userAgent: r.userAgent,
25
+ createdAt: r.createdAt instanceof Date ? r.createdAt.toISOString() : String(r.createdAt),
26
+ }));
27
+ }
28
+ }