alepha 0.21.2 → 0.22.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 (463) hide show
  1. package/README.md +0 -1
  2. package/dist/api/audits/index.browser.js.map +1 -1
  3. package/dist/api/audits/index.d.ts +393 -403
  4. package/dist/api/audits/index.d.ts.map +1 -1
  5. package/dist/api/audits/index.js +25 -56
  6. package/dist/api/audits/index.js.map +1 -1
  7. package/dist/api/files/index.browser.js +31 -1
  8. package/dist/api/files/index.browser.js.map +1 -1
  9. package/dist/api/files/index.d.ts +313 -208
  10. package/dist/api/files/index.d.ts.map +1 -1
  11. package/dist/api/files/index.js +152 -42
  12. package/dist/api/files/index.js.map +1 -1
  13. package/dist/api/jobs/index.browser.js +2 -2
  14. package/dist/api/jobs/index.browser.js.map +1 -1
  15. package/dist/api/jobs/index.d.ts +289 -292
  16. package/dist/api/jobs/index.d.ts.map +1 -1
  17. package/dist/api/jobs/index.js +39 -33
  18. package/dist/api/jobs/index.js.map +1 -1
  19. package/dist/api/keys/index.d.ts +211 -216
  20. package/dist/api/keys/index.d.ts.map +1 -1
  21. package/dist/api/keys/index.js.map +1 -1
  22. package/dist/api/notifications/index.browser.js.map +1 -1
  23. package/dist/api/notifications/index.d.ts +188 -195
  24. package/dist/api/notifications/index.d.ts.map +1 -1
  25. package/dist/api/notifications/index.js.map +1 -1
  26. package/dist/api/oauth/index.d.ts +71 -76
  27. package/dist/api/oauth/index.d.ts.map +1 -1
  28. package/dist/api/oauth/index.js.map +1 -1
  29. package/dist/api/organizations/index.browser.js.map +1 -1
  30. package/dist/api/organizations/index.d.ts +104 -109
  31. package/dist/api/organizations/index.d.ts.map +1 -1
  32. package/dist/api/organizations/index.js.map +1 -1
  33. package/dist/api/parameters/index.browser.js +43 -16
  34. package/dist/api/parameters/index.browser.js.map +1 -1
  35. package/dist/api/parameters/index.d.ts +488 -344
  36. package/dist/api/parameters/index.d.ts.map +1 -1
  37. package/dist/api/parameters/index.js +175 -35
  38. package/dist/api/parameters/index.js.map +1 -1
  39. package/dist/api/payments/index.d.ts +396 -402
  40. package/dist/api/payments/index.d.ts.map +1 -1
  41. package/dist/api/payments/index.js.map +1 -1
  42. package/dist/api/subscriptions/index.d.ts +644 -652
  43. package/dist/api/subscriptions/index.d.ts.map +1 -1
  44. package/dist/api/subscriptions/index.js +1 -1
  45. package/dist/api/subscriptions/index.js.map +1 -1
  46. package/dist/api/users/index.browser.js +7 -0
  47. package/dist/api/users/index.browser.js.map +1 -1
  48. package/dist/api/users/index.d.ts +1073 -1006
  49. package/dist/api/users/index.d.ts.map +1 -1
  50. package/dist/api/users/index.js +283 -61
  51. package/dist/api/users/index.js.map +1 -1
  52. package/dist/api/verifications/index.browser.js.map +1 -1
  53. package/dist/api/verifications/index.d.ts +134 -140
  54. package/dist/api/verifications/index.d.ts.map +1 -1
  55. package/dist/api/verifications/index.js.map +1 -1
  56. package/dist/background/index.d.ts +95 -0
  57. package/dist/background/index.d.ts.map +1 -0
  58. package/dist/background/index.js +121 -0
  59. package/dist/background/index.js.map +1 -0
  60. package/dist/background/index.workerd.js +110 -0
  61. package/dist/background/index.workerd.js.map +1 -0
  62. package/dist/batch/index.d.ts +5 -7
  63. package/dist/batch/index.d.ts.map +1 -1
  64. package/dist/batch/index.js.map +1 -1
  65. package/dist/bin/index.js.map +1 -1
  66. package/dist/bucket/index.d.ts +76 -54
  67. package/dist/bucket/index.d.ts.map +1 -1
  68. package/dist/bucket/index.js +58 -11
  69. package/dist/bucket/index.js.map +1 -1
  70. package/dist/bucket/index.workerd.js +200 -5
  71. package/dist/bucket/index.workerd.js.map +1 -1
  72. package/dist/cache/core/index.d.ts +7 -10
  73. package/dist/cache/core/index.d.ts.map +1 -1
  74. package/dist/cache/core/index.js.map +1 -1
  75. package/dist/cache/core/index.workerd.js.map +1 -1
  76. package/dist/cache/database/index.d.ts +22 -26
  77. package/dist/cache/database/index.d.ts.map +1 -1
  78. package/dist/cache/database/index.js.map +1 -1
  79. package/dist/cache/redis/index.d.ts +4 -7
  80. package/dist/cache/redis/index.d.ts.map +1 -1
  81. package/dist/cache/redis/index.js.map +1 -1
  82. package/dist/captcha/index.d.ts +3 -6
  83. package/dist/captcha/index.d.ts.map +1 -1
  84. package/dist/captcha/index.js.map +1 -1
  85. package/dist/cli/config/index.d.ts.map +1 -1
  86. package/dist/cli/config/index.js.map +1 -1
  87. package/dist/cli/core/index.d.ts +417 -214
  88. package/dist/cli/core/index.d.ts.map +1 -1
  89. package/dist/cli/core/index.js +325 -563
  90. package/dist/cli/core/index.js.map +1 -1
  91. package/dist/cli/devtools/index.d.ts +3 -5
  92. package/dist/cli/devtools/index.d.ts.map +1 -1
  93. package/dist/cli/devtools/index.js.map +1 -1
  94. package/dist/cli/i18n/index.d.ts +8 -12
  95. package/dist/cli/i18n/index.d.ts.map +1 -1
  96. package/dist/cli/i18n/index.js.map +1 -1
  97. package/dist/cli/platform/index.d.ts +126 -1342
  98. package/dist/cli/platform/index.d.ts.map +1 -1
  99. package/dist/cli/platform/index.js +136 -2374
  100. package/dist/cli/platform/index.js.map +1 -1
  101. package/dist/cli/platform-lib/index.d.ts +1446 -0
  102. package/dist/cli/platform-lib/index.d.ts.map +1 -0
  103. package/dist/cli/platform-lib/index.js +2597 -0
  104. package/dist/cli/platform-lib/index.js.map +1 -0
  105. package/dist/cli/vendor/index.d.ts +17 -21
  106. package/dist/cli/vendor/index.d.ts.map +1 -1
  107. package/dist/cli/vendor/index.js.map +1 -1
  108. package/dist/command/index.d.ts +21 -20
  109. package/dist/command/index.d.ts.map +1 -1
  110. package/dist/command/index.js +39 -10
  111. package/dist/command/index.js.map +1 -1
  112. package/dist/{containers → container}/core/index.d.ts +13 -15
  113. package/dist/container/core/index.d.ts.map +1 -0
  114. package/dist/{containers → container}/core/index.js +23 -14
  115. package/dist/container/core/index.js.map +1 -0
  116. package/dist/{containers → container}/core/index.workerd.js +37 -22
  117. package/dist/container/core/index.workerd.js.map +1 -0
  118. package/dist/core/index.browser.js +27 -1
  119. package/dist/core/index.browser.js.map +1 -1
  120. package/dist/core/index.d.ts +48 -24
  121. package/dist/core/index.d.ts.map +1 -1
  122. package/dist/core/index.js +27 -1
  123. package/dist/core/index.js.map +1 -1
  124. package/dist/core/index.native.js +27 -1
  125. package/dist/core/index.native.js.map +1 -1
  126. package/dist/core/index.workerd.js +27 -1
  127. package/dist/core/index.workerd.js.map +1 -1
  128. package/dist/crypto/index.browser.js.map +1 -1
  129. package/dist/crypto/index.d.ts +5 -8
  130. package/dist/crypto/index.d.ts.map +1 -1
  131. package/dist/crypto/index.js.map +1 -1
  132. package/dist/datetime/index.d.ts +3 -4
  133. package/dist/datetime/index.d.ts.map +1 -1
  134. package/dist/datetime/index.js.map +1 -1
  135. package/dist/email/brevo/index.d.ts +2 -4
  136. package/dist/email/brevo/index.d.ts.map +1 -1
  137. package/dist/email/brevo/index.js.map +1 -1
  138. package/dist/email/cloudflare/index.d.ts +20 -7
  139. package/dist/email/cloudflare/index.d.ts.map +1 -1
  140. package/dist/email/cloudflare/index.js +46 -9
  141. package/dist/email/cloudflare/index.js.map +1 -1
  142. package/dist/email/core/index.d.ts +6 -9
  143. package/dist/email/core/index.d.ts.map +1 -1
  144. package/dist/email/core/index.js.map +1 -1
  145. package/dist/email/core/index.workerd.js.map +1 -1
  146. package/dist/email/smtp/index.d.ts +10 -13
  147. package/dist/email/smtp/index.d.ts.map +1 -1
  148. package/dist/email/smtp/index.js +107 -32
  149. package/dist/email/smtp/index.js.map +1 -1
  150. package/dist/fake/index.d.ts +1 -2
  151. package/dist/fake/index.d.ts.map +1 -1
  152. package/dist/fake/index.js.map +1 -1
  153. package/dist/lock/core/index.d.ts +9 -14
  154. package/dist/lock/core/index.d.ts.map +1 -1
  155. package/dist/lock/core/index.js.map +1 -1
  156. package/dist/lock/redis/index.d.ts +2 -4
  157. package/dist/lock/redis/index.d.ts.map +1 -1
  158. package/dist/lock/redis/index.js.map +1 -1
  159. package/dist/logger/index.d.ts +105 -76
  160. package/dist/logger/index.d.ts.map +1 -1
  161. package/dist/logger/index.js +196 -174
  162. package/dist/logger/index.js.map +1 -1
  163. package/dist/mcp/index.d.ts +16 -20
  164. package/dist/mcp/index.d.ts.map +1 -1
  165. package/dist/mcp/index.js.map +1 -1
  166. package/dist/orm/core/index.browser.js.map +1 -1
  167. package/dist/orm/core/index.bun.js +19 -1
  168. package/dist/orm/core/index.bun.js.map +1 -1
  169. package/dist/orm/core/index.d.ts +76 -62
  170. package/dist/orm/core/index.d.ts.map +1 -1
  171. package/dist/orm/core/index.js +20 -2
  172. package/dist/orm/core/index.js.map +1 -1
  173. package/dist/orm/postgres/index.bun.js.map +1 -1
  174. package/dist/orm/postgres/index.d.ts +28 -20
  175. package/dist/orm/postgres/index.d.ts.map +1 -1
  176. package/dist/orm/postgres/index.js.map +1 -1
  177. package/dist/queue/core/index.d.ts +12 -15
  178. package/dist/queue/core/index.d.ts.map +1 -1
  179. package/dist/queue/core/index.js.map +1 -1
  180. package/dist/queue/core/index.workerd.js.map +1 -1
  181. package/dist/queue/redis/index.d.ts +3 -5
  182. package/dist/queue/redis/index.d.ts.map +1 -1
  183. package/dist/queue/redis/index.js.map +1 -1
  184. package/dist/react/auth/index.browser.js +9 -2
  185. package/dist/react/auth/index.browser.js.map +1 -1
  186. package/dist/react/auth/index.d.ts +14 -9
  187. package/dist/react/auth/index.d.ts.map +1 -1
  188. package/dist/react/auth/index.js +9 -2
  189. package/dist/react/auth/index.js.map +1 -1
  190. package/dist/react/core/index.d.ts +7 -8
  191. package/dist/react/core/index.d.ts.map +1 -1
  192. package/dist/react/core/index.js +6 -3
  193. package/dist/react/core/index.js.map +1 -1
  194. package/dist/react/form/index.d.ts +2 -4
  195. package/dist/react/form/index.d.ts.map +1 -1
  196. package/dist/react/form/index.js.map +1 -1
  197. package/dist/react/head/index.browser.js.map +1 -1
  198. package/dist/react/head/index.d.ts +2 -4
  199. package/dist/react/head/index.d.ts.map +1 -1
  200. package/dist/react/head/index.js.map +1 -1
  201. package/dist/react/i18n/index.d.ts +47 -11
  202. package/dist/react/i18n/index.d.ts.map +1 -1
  203. package/dist/react/i18n/index.js +33 -1
  204. package/dist/react/i18n/index.js.map +1 -1
  205. package/dist/react/intro/index.d.ts +1 -2
  206. package/dist/react/intro/index.d.ts.map +1 -1
  207. package/dist/react/intro/index.js +2 -2
  208. package/dist/react/intro/index.js.map +1 -1
  209. package/dist/react/router/index.browser.js +65 -19
  210. package/dist/react/router/index.browser.js.map +1 -1
  211. package/dist/react/router/index.d.ts +327 -222
  212. package/dist/react/router/index.d.ts.map +1 -1
  213. package/dist/react/router/index.js +65 -29
  214. package/dist/react/router/index.js.map +1 -1
  215. package/dist/react/testing/index.d.ts +1 -2
  216. package/dist/react/testing/index.d.ts.map +1 -1
  217. package/dist/react/testing/index.js +16 -17
  218. package/dist/react/testing/index.js.map +1 -1
  219. package/dist/react/ui/index.d.ts +20 -25
  220. package/dist/react/ui/index.d.ts.map +1 -1
  221. package/dist/react/ui/index.js.map +1 -1
  222. package/dist/redis/index.bun.js.map +1 -1
  223. package/dist/redis/index.d.ts +17 -19
  224. package/dist/redis/index.d.ts.map +1 -1
  225. package/dist/redis/index.js.map +1 -1
  226. package/dist/retry/index.d.ts +2 -4
  227. package/dist/retry/index.d.ts.map +1 -1
  228. package/dist/retry/index.js.map +1 -1
  229. package/dist/router/index.d.ts.map +1 -1
  230. package/dist/router/index.js.map +1 -1
  231. package/dist/scheduler/index.d.ts +10 -13
  232. package/dist/scheduler/index.d.ts.map +1 -1
  233. package/dist/scheduler/index.js.map +1 -1
  234. package/dist/scheduler/index.workerd.js.map +1 -1
  235. package/dist/security/index.browser.js.map +1 -1
  236. package/dist/security/index.d.ts +45 -48
  237. package/dist/security/index.d.ts.map +1 -1
  238. package/dist/security/index.js.map +1 -1
  239. package/dist/server/auth/index.browser.js.map +1 -1
  240. package/dist/server/auth/index.d.ts +167 -172
  241. package/dist/server/auth/index.d.ts.map +1 -1
  242. package/dist/server/auth/index.js +4 -8
  243. package/dist/server/auth/index.js.map +1 -1
  244. package/dist/server/cookies/index.browser.js.map +1 -1
  245. package/dist/server/cookies/index.d.ts +5 -7
  246. package/dist/server/cookies/index.d.ts.map +1 -1
  247. package/dist/server/cookies/index.js.map +1 -1
  248. package/dist/server/core/index.browser.js.map +1 -1
  249. package/dist/server/core/index.d.ts +88 -73
  250. package/dist/server/core/index.d.ts.map +1 -1
  251. package/dist/server/core/index.js +19 -0
  252. package/dist/server/core/index.js.map +1 -1
  253. package/dist/server/cors/index.d.ts +11 -14
  254. package/dist/server/cors/index.d.ts.map +1 -1
  255. package/dist/server/cors/index.js.map +1 -1
  256. package/dist/server/etag/index.d.ts +6 -9
  257. package/dist/server/etag/index.d.ts.map +1 -1
  258. package/dist/server/etag/index.js.map +1 -1
  259. package/dist/server/health/index.d.ts +18 -21
  260. package/dist/server/health/index.d.ts.map +1 -1
  261. package/dist/server/health/index.js.map +1 -1
  262. package/dist/server/links/index.browser.js +2 -0
  263. package/dist/server/links/index.browser.js.map +1 -1
  264. package/dist/server/links/index.d.ts +63 -67
  265. package/dist/server/links/index.d.ts.map +1 -1
  266. package/dist/server/links/index.js +2 -0
  267. package/dist/server/links/index.js.map +1 -1
  268. package/dist/server/metrics/index.d.ts +5 -7
  269. package/dist/server/metrics/index.d.ts.map +1 -1
  270. package/dist/server/metrics/index.js.map +1 -1
  271. package/dist/server/proxy/index.d.ts +3 -5
  272. package/dist/server/proxy/index.d.ts.map +1 -1
  273. package/dist/server/proxy/index.js.map +1 -1
  274. package/dist/server/rate-limit/index.d.ts +10 -13
  275. package/dist/server/rate-limit/index.d.ts.map +1 -1
  276. package/dist/server/rate-limit/index.js.map +1 -1
  277. package/dist/server/static/index.d.ts +3 -5
  278. package/dist/server/static/index.d.ts.map +1 -1
  279. package/dist/server/static/index.js.map +1 -1
  280. package/dist/server/swagger/index.d.ts +5 -8
  281. package/dist/server/swagger/index.d.ts.map +1 -1
  282. package/dist/server/swagger/index.js.map +1 -1
  283. package/dist/sms/index.d.ts +3 -5
  284. package/dist/sms/index.d.ts.map +1 -1
  285. package/dist/sms/index.js.map +1 -1
  286. package/dist/system/index.browser.js.map +1 -1
  287. package/dist/system/index.d.ts +2 -4
  288. package/dist/system/index.d.ts.map +1 -1
  289. package/dist/system/index.js.map +1 -1
  290. package/dist/system/index.workerd.js.map +1 -1
  291. package/dist/topic/core/index.d.ts +4 -6
  292. package/dist/topic/core/index.d.ts.map +1 -1
  293. package/dist/topic/core/index.js.map +1 -1
  294. package/dist/topic/redis/index.d.ts +5 -8
  295. package/dist/topic/redis/index.d.ts.map +1 -1
  296. package/dist/topic/redis/index.js.map +1 -1
  297. package/package.json +45 -22
  298. package/src/api/audits/__tests__/AuditService.spec.ts +18 -110
  299. package/src/api/audits/controllers/AdminAuditController.ts +14 -0
  300. package/src/api/audits/services/AuditService.ts +21 -88
  301. package/src/api/files/__tests__/FileService.spec.ts +207 -2
  302. package/src/api/files/index.ts +3 -0
  303. package/src/api/files/schemas/fileCreatorSummarySchema.ts +22 -0
  304. package/src/api/files/schemas/fileResourceSchema.ts +10 -1
  305. package/src/api/files/services/FileService.ts +170 -72
  306. package/src/api/jobs/__tests__/$job.spec.ts +24 -1
  307. package/src/api/jobs/index.ts +4 -3
  308. package/src/api/jobs/primitives/$job.ts +7 -3
  309. package/src/api/jobs/providers/DirectJobDispatcher.ts +17 -36
  310. package/src/api/jobs/providers/JobProvider.ts +53 -24
  311. package/src/api/jobs/schemas/jobConfigAtom.ts +1 -1
  312. package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +4 -1
  313. package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +3 -1
  314. package/src/api/parameters/__tests__/$parameter.spec.ts +19 -2
  315. package/src/api/parameters/audits/ParameterAudits.ts +17 -0
  316. package/src/api/parameters/controllers/AdminParameterController.ts +95 -19
  317. package/src/api/parameters/index.ts +3 -0
  318. package/src/api/parameters/schemas/activateParameterBodySchema.ts +3 -3
  319. package/src/api/parameters/schemas/createParameterVersionBodySchema.ts +3 -2
  320. package/src/api/parameters/schemas/parameterCreatorSummarySchema.ts +25 -0
  321. package/src/api/parameters/schemas/parameterResponseSchema.ts +5 -0
  322. package/src/api/parameters/schemas/rollbackParameterBodySchema.ts +4 -2
  323. package/src/api/parameters/services/ParameterProvider.ts +69 -6
  324. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +1 -1
  325. package/src/api/users/__tests__/AdminSessionController.spec.ts +37 -0
  326. package/src/api/users/audits/SessionAudits.ts +33 -0
  327. package/src/api/users/audits/UserAudits.ts +19 -43
  328. package/src/api/users/controllers/AdminUserController.ts +66 -1
  329. package/src/api/users/entities/sessions.ts +6 -0
  330. package/src/api/users/entities/users.ts +2 -0
  331. package/src/api/users/index.ts +9 -1
  332. package/src/api/users/primitives/$realm.ts +3 -0
  333. package/src/api/users/schemas/sessionResourceSchema.ts +16 -0
  334. package/src/api/users/schemas/updateUserSchema.ts +1 -8
  335. package/src/api/users/schemas/userQuerySchema.ts +7 -0
  336. package/src/api/users/services/CredentialService.ts +15 -6
  337. package/src/api/users/services/IdentityService.ts +2 -1
  338. package/src/api/users/services/RegistrationService.ts +2 -1
  339. package/src/api/users/services/SessionCrudService.ts +19 -2
  340. package/src/api/users/services/SessionService.ts +39 -19
  341. package/src/api/users/services/UserService.ts +106 -8
  342. package/src/background/__tests__/BackgroundTaskProvider.spec.ts +96 -0
  343. package/src/background/index.ts +37 -0
  344. package/src/background/index.workerd.ts +28 -0
  345. package/src/background/providers/BackgroundTaskProvider.ts +70 -0
  346. package/src/background/providers/WorkerdBackgroundTaskProvider.ts +43 -0
  347. package/src/bucket/__tests__/$bucket.spec.ts +18 -0
  348. package/src/bucket/__tests__/LocalFileStorageProvider.spec.ts +5 -0
  349. package/src/bucket/__tests__/MemoryFileStorageProvider.spec.ts +5 -0
  350. package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +23 -4
  351. package/src/bucket/__tests__/shared.ts +30 -0
  352. package/src/bucket/index.ts +5 -5
  353. package/src/bucket/index.workerd.ts +11 -4
  354. package/src/bucket/primitives/$bucket.ts +27 -0
  355. package/src/bucket/providers/FileStorageProvider.ts +13 -0
  356. package/src/bucket/providers/LocalFileStorageProvider.ts +17 -1
  357. package/src/bucket/providers/MemoryFileStorageProvider.ts +7 -0
  358. package/src/bucket/providers/{CloudflareR2Provider.ts → R2FileStorageProvider.ts} +10 -1
  359. package/src/bucket/providers/{NodeS3BucketProvider.ts → S3FileStorageProvider.ts} +27 -5
  360. package/src/cli/core/__tests__/BuildDockerTask.spec.ts +25 -1
  361. package/src/cli/core/__tests__/init.spec.ts +0 -219
  362. package/src/cli/core/commands/__tests__/BuildCommand.spec.ts +43 -0
  363. package/src/cli/core/commands/build.ts +108 -30
  364. package/src/cli/core/commands/init.ts +0 -12
  365. package/src/cli/core/commands/pack.ts +133 -0
  366. package/src/cli/core/index.ts +3 -0
  367. package/src/cli/core/providers/ViteDevServerProvider.ts +40 -16
  368. package/src/cli/core/services/PackageManagerUtils.ts +0 -16
  369. package/src/cli/core/services/ProjectScaffolder.ts +29 -291
  370. package/src/cli/core/tasks/BuildCloudflareTask.ts +353 -47
  371. package/src/cli/core/tasks/BuildDockerTask.ts +33 -3
  372. package/src/cli/core/tasks/BuildTask.ts +34 -0
  373. package/src/cli/core/templates/apiIndexTs.ts +1 -22
  374. package/src/cli/core/templates/mainCss.ts +0 -1
  375. package/src/cli/core/templates/webAppRouterTs.ts +0 -99
  376. package/src/cli/core/templates/webIndexTs.ts +1 -22
  377. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +5 -3
  378. package/src/cli/platform/commands/SecretsCommand.ts +8 -6
  379. package/src/cli/platform/commands/platform.ts +192 -46
  380. package/src/cli/platform/index.ts +12 -52
  381. package/src/cli/{platform → platform-lib}/__tests__/CloudflareAdapter.spec.ts +426 -169
  382. package/src/cli/{platform → platform-lib}/__tests__/NamingService.spec.ts +91 -4
  383. package/src/cli/{platform → platform-lib}/__tests__/VercelAdapter.spec.ts +56 -85
  384. package/src/cli/{platform → platform-lib}/adapters/CloudflareAdapter.ts +402 -165
  385. package/src/cli/{platform → platform-lib}/adapters/PlatformAdapter.ts +62 -35
  386. package/src/cli/{platform → platform-lib}/adapters/VercelAdapter.ts +6 -10
  387. package/src/cli/{platform → platform-lib}/atoms/platformOptions.ts +34 -1
  388. package/src/cli/platform-lib/index.ts +67 -0
  389. package/src/cli/platform-lib/services/NamingService.ts +136 -0
  390. package/src/cli/{platform → platform-lib}/services/PlatformInspector.ts +60 -13
  391. package/src/cli/{platform → platform-lib}/services/PlatformOrchestrator.ts +54 -43
  392. package/src/cli/{platform → platform-lib}/services/WranglerApi.ts +4 -2
  393. package/src/command/__tests__/Runner.spec.ts +20 -0
  394. package/src/command/helpers/EnvUtils.ts +19 -3
  395. package/src/command/helpers/Runner.ts +12 -2
  396. package/src/command/providers/CliProvider.ts +34 -1
  397. package/src/{containers → container}/core/__tests__/$container.spec.ts +5 -5
  398. package/src/{containers → container}/core/index.ts +4 -4
  399. package/src/{containers → container}/core/index.workerd.ts +19 -3
  400. package/src/{containers → container}/core/primitives/$container.ts +1 -1
  401. package/src/{containers → container}/core/providers/CloudflareContainerProvider.ts +17 -19
  402. package/src/{containers → container}/core/providers/ContainerProvider.ts +16 -2
  403. package/src/{containers → container}/core/providers/MockContainerProvider.ts +1 -1
  404. package/src/core/Alepha.ts +49 -1
  405. package/src/core/__tests__/$env.spec.ts +42 -0
  406. package/src/core/__tests__/dump.spec.ts +47 -0
  407. package/src/email/cloudflare/__tests__/CloudflareEmailProvider.spec.ts +42 -10
  408. package/src/email/cloudflare/index.ts +14 -5
  409. package/src/email/cloudflare/providers/CloudflareEmailProvider.ts +54 -9
  410. package/src/logger/__tests__/Logger.spec.ts +55 -0
  411. package/src/logger/index.ts +13 -0
  412. package/src/logger/services/Logger.ts +31 -1
  413. package/src/orm/__tests__/orm-showcase-tests.ts +27 -0
  414. package/src/orm/__tests__/orm-showcase.spec.ts +12 -0
  415. package/src/orm/core/interfaces/PgQuery.ts +4 -1
  416. package/src/orm/core/services/Repository.ts +27 -11
  417. package/src/react/auth/hooks/useAuth.ts +10 -5
  418. package/src/react/core/__tests__/useQuery.browser.spec.tsx +25 -0
  419. package/src/react/core/hooks/useAction.ts +14 -3
  420. package/src/react/core/hooks/useQuery.ts +24 -4
  421. package/src/react/i18n/components/Translate.tsx +47 -0
  422. package/src/react/i18n/index.ts +2 -0
  423. package/src/react/intro/components/GettingStartedAdminSlide.tsx +2 -2
  424. package/src/react/router/__tests__/$page.spec.tsx +3 -2
  425. package/src/react/router/__tests__/page-can.spec.ts +18 -13
  426. package/src/react/router/hooks/useQueryParams.ts +114 -14
  427. package/src/react/router/primitives/$page.ts +85 -4
  428. package/src/react/router/providers/ReactBrowserRouterProvider.ts +3 -7
  429. package/src/react/router/providers/ReactServerProvider.ts +4 -13
  430. package/src/react/ui/services/SchemaControl.ts +3 -4
  431. package/src/server/core/providers/ServerMultipartProvider.ts +19 -0
  432. package/src/server/links/providers/LinkProvider.ts +10 -0
  433. package/dist/containers/core/index.d.ts.map +0 -1
  434. package/dist/containers/core/index.js.map +0 -1
  435. package/dist/containers/core/index.workerd.js.map +0 -1
  436. package/src/cli/core/templates/componentsJsonTs.ts +0 -39
  437. package/src/cli/core/templates/saasAdminLayoutTsx.ts +0 -77
  438. package/src/cli/core/templates/saasAdminPagesTsx.ts +0 -26
  439. package/src/cli/core/templates/saasAuthLayoutTsx.ts +0 -22
  440. package/src/cli/core/templates/saasAuthPagesTsx.ts +0 -62
  441. package/src/cli/core/templates/saasRealmProviderTs.ts +0 -52
  442. package/src/cli/platform/services/NamingService.ts +0 -54
  443. /package/dist/orm/core/{chunk-o8xxKEmq.js → chunk-B4FMCO8f.js} +0 -0
  444. /package/dist/react/testing/{chunk-6Ep1yQYe.js → chunk-BpyX8vjI.js} +0 -0
  445. /package/src/cli/{platform → platform-lib}/__tests__/GitHubSecretStore.spec.ts +0 -0
  446. /package/src/cli/{platform → platform-lib}/__tests__/PlatformCacheProvider.spec.ts +0 -0
  447. /package/src/cli/{platform → platform-lib}/__tests__/PlatformInspector.spec.ts +0 -0
  448. /package/src/cli/{platform → platform-lib}/__tests__/PlatformOrchestrator.spec.ts +0 -0
  449. /package/src/cli/{platform → platform-lib}/__tests__/SecretFilterService.spec.ts +0 -0
  450. /package/src/cli/{platform → platform-lib}/__tests__/detectResources.spec.ts +0 -0
  451. /package/src/cli/{platform → platform-lib}/providers/GitHubSecretStore.ts +0 -0
  452. /package/src/cli/{platform → platform-lib}/providers/MemorySecretStore.ts +0 -0
  453. /package/src/cli/{platform → platform-lib}/providers/PlatformCacheProvider.ts +0 -0
  454. /package/src/cli/{platform → platform-lib}/providers/SecretStoreProvider.ts +0 -0
  455. /package/src/cli/{platform → platform-lib}/schemas/cloudflare.ts +0 -0
  456. /package/src/cli/{platform → platform-lib}/schemas/platform.ts +0 -0
  457. /package/src/cli/{platform → platform-lib}/schemas/vercel.ts +0 -0
  458. /package/src/cli/{platform → platform-lib}/services/CloudflareApi.ts +0 -0
  459. /package/src/cli/{platform → platform-lib}/services/SecretFilterService.ts +0 -0
  460. /package/src/cli/{platform → platform-lib}/services/VercelApi.ts +0 -0
  461. /package/src/cli/{platform → platform-lib}/services/VercelCli.ts +0 -0
  462. /package/src/{containers → container}/core/interfaces/ContainerOptions.ts +0 -0
  463. /package/src/{containers → container}/core/providers/NodeContainerProvider.ts +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/api/notifications/schemas/notificationPayloadSchema.ts","../../../src/api/notifications/primitives/$notification.ts","../../../src/api/notifications/services/NotificationSenderService.ts","../../../src/api/notifications/jobs/NotificationJobs.ts","../../../src/api/notifications/schemas/notificationResourceSchema.ts","../../../src/api/notifications/schemas/notificationDetailResourceSchema.ts","../../../src/api/notifications/schemas/notificationQuerySchema.ts","../../../src/api/notifications/controllers/AdminNotificationController.ts","../../../src/api/notifications/schemas/notificationContactPreferencesSchema.ts","../../../src/api/notifications/schemas/notificationContactSchema.ts","../../../src/api/notifications/index.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\n\nexport const notificationPayloadSchema = t.object({\n type: t.enum([\"email\", \"sms\"]),\n template: t.text(),\n contact: t.text(),\n variables: t.optional(t.record(t.text(), t.any())),\n category: t.optional(t.text()),\n critical: t.optional(t.boolean()),\n sensitive: t.optional(t.boolean()),\n});\n\nexport type NotificationPayload = Static<typeof notificationPayloadSchema>;\n","import {\n $inject,\n createPrimitive,\n KIND,\n Primitive,\n type Static,\n type StaticEncode,\n type TObject,\n} from \"alepha\";\nimport { NotificationJobs } from \"../jobs/NotificationJobs.ts\";\n\n/**\n * Creates a notification primitive for managing email/SMS notification templates.\n *\n * Provides type-safe, reusable notification templates with multi-language support,\n * variable substitution, and categorization for different notification channels.\n *\n * @example\n * ```ts\n * class NotificationTemplates {\n * welcomeEmail = $notification({\n * name: \"welcome-email\",\n * category: \"onboarding\",\n * schema: t.object({ username: t.text(), activationLink: t.text() }),\n * email: {\n * subject: \"Welcome to our platform!\",\n * body: (vars) => `Hello ${vars.username}, click: ${vars.activationLink}`\n * }\n * });\n *\n * async sendWelcome(user: User) {\n * await this.welcomeEmail.push({\n * variables: { username: user.name, activationLink: generateLink() },\n * contact: user.email\n * });\n * }\n * }\n * ```\n */\nexport const $notification = <T extends TObject>(\n options: NotificationPrimitiveOptions<T>,\n) => createPrimitive(NotificationPrimitive<T>, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface NotificationPrimitiveOptions<T extends TObject>\n extends NotificationMessage<T> {\n name?: string;\n description?: string;\n category?: string;\n critical?: boolean;\n sensitive?: boolean;\n translations?: {\n // e.g., \"en\", \"fr\", even \"en-US\"\n [lang: string]: NotificationMessage<T>;\n };\n schema: T;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class NotificationPrimitive<T extends TObject> extends Primitive<\n NotificationPrimitiveOptions<T>\n> {\n protected readonly notificationJobs = $inject(NotificationJobs);\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n public async push(options: NotificationPushOptions<T>) {\n const pushOpts = this.options.critical\n ? ({ priority: \"critical\" } as const)\n : undefined;\n\n if (this.options.email) {\n await this.notificationJobs.sendNotification.push(\n {\n type: \"email\",\n template: this.name,\n contact: options.contact,\n variables: options.variables as Record<string, unknown>,\n category: this.options.category,\n critical: this.options.critical,\n sensitive: this.options.sensitive,\n },\n pushOpts,\n );\n }\n\n if (this.options.sms) {\n await this.notificationJobs.sendNotification.push(\n {\n type: \"sms\",\n template: this.name,\n contact: options.contact,\n variables: options.variables as Record<string, unknown>,\n category: this.options.category,\n critical: this.options.critical,\n sensitive: this.options.sensitive,\n },\n pushOpts,\n );\n }\n }\n\n public configure(options: Partial<NotificationPrimitiveOptions<T>>) {\n Object.assign(this.options, options);\n }\n}\n\n$notification[KIND] = NotificationPrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface NotificationPushOptions<T extends TObject> {\n variables: StaticEncode<T>;\n contact: string;\n}\n\nexport interface NotificationMessage<T extends TObject> {\n email?: {\n subject: string;\n body: string | ((variables: Static<T>) => string);\n };\n sms?: {\n message: string | ((variables: Static<T>) => string);\n };\n}\n","import { $inject, Alepha, AlephaError } from \"alepha\";\nimport { EmailProvider } from \"alepha/email\";\nimport { $logger } from \"alepha/logger\";\nimport { SmsProvider } from \"alepha/sms\";\nimport { $notification } from \"../primitives/$notification.ts\";\nimport type { NotificationPayload } from \"../schemas/notificationPayloadSchema.ts\";\n\nexport class NotificationSenderService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly emailProvider = $inject(EmailProvider);\n protected readonly smsProvider = $inject(SmsProvider);\n\n public async send(payload: NotificationPayload) {\n this.log.debug(\"Processing notification\", {\n type: payload.type,\n template: payload.template,\n contact: payload.contact,\n });\n\n if (payload.type === \"email\") {\n const rendered = this.renderEmail(payload);\n await this.emailProvider.send(rendered);\n this.log.info(\"Email notification sent\", {\n template: payload.template,\n contact: payload.contact,\n });\n return {\n type: \"email\" as const,\n to: rendered.to,\n subject: rendered.subject,\n body: rendered.body,\n };\n }\n\n if (payload.type === \"sms\") {\n const rendered = this.renderSms(payload);\n await this.smsProvider.send(rendered);\n this.log.info(\"SMS notification sent\", {\n template: payload.template,\n contact: payload.contact,\n });\n return {\n type: \"sms\" as const,\n to: rendered.to,\n message: rendered.message,\n };\n }\n }\n\n public renderSms(payload: NotificationPayload) {\n const { variables, contact, template } = this.load(payload);\n\n const sms = template.options.sms;\n if (!sms) {\n throw new AlephaError(\n `Notification template ${payload.template} has no sms defined`,\n );\n }\n\n const message =\n typeof sms.message === \"function\"\n ? sms.message(variables as any)\n : sms.message;\n\n return { to: contact, message };\n }\n\n public renderEmail(payload: NotificationPayload) {\n const { variables, contact, template } = this.load(payload);\n\n const email = template.options.email;\n if (!email) {\n throw new AlephaError(\n `Notification template ${payload.template} has no email defined`,\n );\n }\n\n const subject = email.subject;\n const body =\n typeof email.body === \"function\"\n ? email.body(variables as any)\n : email.body;\n\n return { to: contact, subject, body };\n }\n\n protected load(payload: NotificationPayload) {\n const variables = payload.variables || {};\n const contact = payload.contact;\n const template = this.alepha\n .primitives($notification)\n .find((it) => it.name === payload.template);\n\n if (!template) {\n throw new AlephaError(\n `No notification template found for ${payload.template}`,\n );\n }\n\n return { template, variables, contact };\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $job, jobExecutionEntity } from \"alepha/api/jobs\";\nimport { $parameter } from \"alepha/api/parameters\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { notificationPayloadSchema } from \"../schemas/notificationPayloadSchema.ts\";\nimport { NotificationSenderService } from \"../services/NotificationSenderService.ts\";\n\n/**\n * Notification jobs + runtime-editable retention.\n *\n * - `settings` — a `$parameter` exposing `retentionDays` that admins can\n * update at runtime. Changes propagate across instances via the parameter\n * pub/sub; the next purge run picks up the new value with no restart.\n * - `sendNotification` — queue-mode, audit-oriented. Every execution is kept\n * (`record: \"all\"`, `keep: { ok: 0, error: 0 }` disables the ring-buffer\n * trim) so the audit trail survives even under heavy volume.\n * - `purgeOldNotifications` — cron sweep that deletes notification execution\n * rows whose `completedAt` is older than the current `retentionDays`.\n *\n * Cron expression note: the purge cron is declared statically (`0 3 * * *`)\n * because some runtimes (Cloudflare Workers) freeze cron triggers at deploy\n * time. The *retention window* is fully runtime-editable — that's the knob\n * that actually matters for operators.\n */\nexport class NotificationJobs {\n protected readonly log = $logger();\n protected readonly dt = $inject(DateTimeProvider);\n protected readonly notificationSenderService = $inject(\n NotificationSenderService,\n );\n protected readonly executions = $repository(jobExecutionEntity);\n\n /** Runtime-editable config. Admins can change retentionDays without deploy. */\n public readonly settings = $parameter({\n name: \"alepha.api.notifications\",\n description: \"Notification delivery & retention settings.\",\n schema: t.object({\n retentionDays: t.integer({\n description:\n \"Days to keep notification execution rows before the purge sweep removes them.\",\n minimum: 1,\n }),\n }),\n default: {\n retentionDays: 7,\n },\n });\n\n public readonly sendNotification = $job({\n name: \"api:notifications:sendNotification\",\n description:\n \"Sends a notification (email/SMS) and keeps every execution for audit.\",\n schema: notificationPayloadSchema,\n retry: {\n retries: 3,\n },\n timeout: [30, \"seconds\"],\n record: \"all\",\n keep: { ok: 0, error: 0 },\n handler: async ({ payload }) => {\n await this.notificationSenderService.send(payload);\n },\n });\n\n public readonly purgeOldNotifications = $job({\n name: \"api:notifications:purgeOldNotifications\",\n description:\n \"Hourly sweep that deletes notification execution rows older than the configured retention window.\",\n cron: \"0 * * * *\",\n handler: async ({ now }) => {\n const { retentionDays } = this.settings.cachedCurrentContent;\n const cutoff = now.subtract(retentionDays, \"day\").toISOString();\n const jobName = this.sendNotification.name;\n\n const expired = await this.executions.findMany({\n where: {\n jobName: { eq: jobName },\n status: { inArray: [\"ok\", \"error\", \"cancelled\"] },\n completedAt: { lt: cutoff },\n },\n columns: [\"id\"] as any,\n limit: 5_000,\n });\n\n if (expired.length === 0) {\n this.log.debug(\"Notification purge: nothing to delete\", {\n cutoff,\n retentionDays,\n });\n return;\n }\n\n await this.executions.deleteMany({\n id: { inArray: expired.map((r) => r.id) },\n });\n this.log.info(\n `Notification purge: deleted ${expired.length} row(s) older than ${retentionDays} days`,\n { cutoff },\n );\n },\n });\n}\n","import { type Static, t } from \"alepha\";\n\nexport const notificationResourceSchema = t.object({\n id: t.uuid(),\n createdAt: t.datetime(),\n status: t.text(),\n template: t.optional(t.text()),\n type: t.optional(t.text()),\n contact: t.optional(t.text()),\n category: t.optional(t.text()),\n critical: t.optional(t.boolean()),\n sensitive: t.optional(t.boolean()),\n startedAt: t.optional(t.datetime()),\n completedAt: t.optional(t.datetime()),\n error: t.optional(t.text()),\n});\n\nexport type NotificationResource = Static<typeof notificationResourceSchema>;\n","import { type Static, t } from \"alepha\";\nimport { logEntrySchema } from \"alepha/logger\";\nimport { notificationResourceSchema } from \"./notificationResourceSchema.ts\";\n\nexport const notificationDetailResourceSchema = t.extend(\n notificationResourceSchema,\n {\n variables: t.optional(t.record(t.text(), t.any())),\n rendered: t.optional(t.record(t.text(), t.any())),\n logs: t.optional(t.array(logEntrySchema)),\n },\n {\n title: \"NotificationDetailResource\",\n description: \"A notification resource with rendered content and logs.\",\n },\n);\n\nexport type NotificationDetailResource = Static<\n typeof notificationDetailResourceSchema\n>;\n","import { type Static, t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const notificationQuerySchema = t.extend(pageQuerySchema, {\n status: t.optional(\n t.enum([\n \"pending\",\n \"scheduled\",\n \"retrying\",\n \"running\",\n \"completed\",\n \"dead\",\n \"cancelled\",\n ]),\n ),\n});\n\nexport type NotificationQuery = Static<typeof notificationQuerySchema>;\n","import { $inject, t } from \"alepha\";\nimport { jobExecutionEntity } from \"alepha/api/jobs\";\nimport { $repository } from \"alepha/orm\";\nimport { $secure } from \"alepha/security\";\nimport { $action, NotFoundError, okSchema } from \"alepha/server\";\nimport { NotificationJobs } from \"../jobs/NotificationJobs.ts\";\nimport { notificationDetailResourceSchema } from \"../schemas/notificationDetailResourceSchema.ts\";\nimport { notificationQuerySchema } from \"../schemas/notificationQuerySchema.ts\";\nimport { notificationResourceSchema } from \"../schemas/notificationResourceSchema.ts\";\n\nexport class AdminNotificationController {\n protected readonly url: string = \"/notifications\";\n protected readonly group: string = \"admin:notifications\";\n protected readonly notificationJobs = $inject(NotificationJobs);\n protected readonly executions = $repository(jobExecutionEntity);\n\n protected get jobName(): string {\n return this.notificationJobs.sendNotification.name;\n }\n\n public readonly findNotifications = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:notification:read\"] })],\n schema: {\n query: notificationQuerySchema,\n response: t.page(notificationResourceSchema),\n },\n handler: async ({ query }) => {\n query.sort ??= \"-createdAt\";\n const where = this.executions.createQueryWhere();\n where.jobName = { eq: this.jobName };\n const page = await this.executions.paginate(\n query,\n { where },\n { count: true },\n );\n return {\n ...page,\n content: page.content.map((exec) => this.toResource(exec)),\n } as any;\n },\n });\n\n public readonly getNotification = $action({\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:notification:read\"] })],\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n response: notificationDetailResourceSchema,\n },\n handler: async ({ params }) => {\n const exec = await this.executions.findById(params.id);\n if (!exec || exec.jobName !== this.jobName) {\n throw new NotFoundError(`Notification not found: ${params.id}`);\n }\n return this.toDetailResource(exec) as any;\n },\n });\n\n public readonly deleteNotification = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:notification:delete\"] })],\n description: \"Delete a notification record\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n response: okSchema,\n },\n handler: async ({ params }) => {\n const exec = await this.executions.findById(params.id);\n if (!exec || exec.jobName !== this.jobName) {\n throw new NotFoundError(`Notification not found: ${params.id}`);\n }\n await this.executions.deleteById(params.id);\n return { ok: true, id: params.id };\n },\n });\n\n public readonly deleteNotifications = $action({\n method: \"POST\",\n path: `${this.url}/delete`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:notification:delete\"] })],\n description: \"Delete many notification records in one call\",\n schema: {\n body: t.object({\n ids: t.array(t.uuid(), { minItems: 1, maxItems: 1000 }),\n }),\n response: t.object({\n deleted: t.array(t.uuid()),\n }),\n },\n handler: async ({ body }) => {\n // Constrain to this job's executions so an admin can't delete arbitrary\n // job rows through this endpoint.\n const deleted = await this.executions.deleteMany({\n id: { inArray: body.ids },\n jobName: { eq: this.jobName },\n });\n return { deleted: deleted.map(String) };\n },\n });\n\n protected toResource(exec: Record<string, unknown>) {\n const payload = (exec.payload ?? {}) as Record<string, unknown>;\n return {\n id: exec.id,\n createdAt: exec.createdAt,\n status: exec.status,\n template: payload.template,\n type: payload.type,\n contact: payload.contact,\n category: payload.category,\n critical: payload.critical,\n sensitive: payload.sensitive,\n startedAt: exec.startedAt,\n completedAt: exec.completedAt,\n error: exec.error,\n };\n }\n\n protected toDetailResource(exec: Record<string, unknown>) {\n const payload = (exec.payload ?? {}) as Record<string, unknown>;\n return {\n ...this.toResource(exec),\n variables: payload.variables,\n logs: exec.logs,\n };\n }\n}\n","import { type Static, t } from \"alepha\";\n\nexport const notificationContactPreferencesSchema = t.object({\n language: t.optional(t.text()),\n exclude: t.array(t.text()),\n});\n\nexport type NotificationContactPreferences = Static<\n typeof notificationContactPreferencesSchema\n>;\n","import { type Static, t } from \"alepha\";\n\nexport const notificationContactSchema = t.object({\n email: t.optional(t.email()),\n phoneNumber: t.optional(t.e164()),\n firstName: t.optional(t.shortText()),\n lastName: t.optional(t.text({ size: \"short\" })),\n language: t.optional(t.bcp47()),\n});\n\nexport type NotificationContact = Static<typeof notificationContactSchema>;\n","import { $module } from \"alepha\";\nimport { AlephaApiJobs } from \"alepha/api/jobs\";\nimport { AlephaApiParameters } from \"alepha/api/parameters\";\nimport { AdminNotificationController } from \"./controllers/AdminNotificationController.ts\";\nimport { NotificationJobs } from \"./jobs/NotificationJobs.ts\";\nimport { $notification } from \"./primitives/$notification.ts\";\nimport { NotificationSenderService } from \"./services/NotificationSenderService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/AdminNotificationController.ts\";\nexport * from \"./jobs/NotificationJobs.ts\";\nexport * from \"./primitives/$notification.ts\";\nexport * from \"./schemas/notificationContactPreferencesSchema.ts\";\nexport * from \"./schemas/notificationContactSchema.ts\";\nexport * from \"./schemas/notificationDetailResourceSchema.ts\";\nexport * from \"./schemas/notificationPayloadSchema.ts\";\nexport * from \"./schemas/notificationQuerySchema.ts\";\nexport * from \"./schemas/notificationResourceSchema.ts\";\nexport * from \"./services/NotificationSenderService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * User notification management.\n *\n * **Features:**\n * - Notification definitions (email/SMS templates)\n * - Delivery via `$job` with retry and audit trail (`record: \"all\"` + no ring buffer trim)\n * - Runtime-editable retention window via `$parameter` — purge cron respects it live\n * - Admin API for inspecting sent notifications\n *\n * **Delivery mode** is decided at runtime by the `$job` system:\n * - If your app loads `AlephaApiJobsQueue` (and thus `AlephaQueue`), notifications\n * go through the queue (best for high-volume systems).\n * - Otherwise, notifications run in **direct** mode: pushed to the outbox table\n * and processed in the same process right after the HTTP response is returned.\n * The reconciliation sweep is the safety net for crashes / retries.\n *\n * Direct mode is the recommended default for small / cheap deployments\n * (Cloudflare Workers, single-instance Node) — no queue infrastructure required.\n *\n * @module alepha.api.notifications\n */\nexport const AlephaApiNotifications = $module({\n name: \"alepha.api.notifications\",\n imports: [AlephaApiJobs, AlephaApiParameters],\n primitives: [$notification],\n services: [\n NotificationSenderService,\n NotificationJobs,\n AdminNotificationController,\n ],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/api/notifications/schemas/notificationPayloadSchema.ts","../../../src/api/notifications/primitives/$notification.ts","../../../src/api/notifications/services/NotificationSenderService.ts","../../../src/api/notifications/jobs/NotificationJobs.ts","../../../src/api/notifications/schemas/notificationResourceSchema.ts","../../../src/api/notifications/schemas/notificationDetailResourceSchema.ts","../../../src/api/notifications/schemas/notificationQuerySchema.ts","../../../src/api/notifications/controllers/AdminNotificationController.ts","../../../src/api/notifications/schemas/notificationContactPreferencesSchema.ts","../../../src/api/notifications/schemas/notificationContactSchema.ts","../../../src/api/notifications/index.ts"],"sourcesContent":["import { type Static, t } from \"alepha\";\n\nexport const notificationPayloadSchema = t.object({\n type: t.enum([\"email\", \"sms\"]),\n template: t.text(),\n contact: t.text(),\n variables: t.optional(t.record(t.text(), t.any())),\n category: t.optional(t.text()),\n critical: t.optional(t.boolean()),\n sensitive: t.optional(t.boolean()),\n});\n\nexport type NotificationPayload = Static<typeof notificationPayloadSchema>;\n","import {\n $inject,\n createPrimitive,\n KIND,\n Primitive,\n type Static,\n type StaticEncode,\n type TObject,\n} from \"alepha\";\nimport { NotificationJobs } from \"../jobs/NotificationJobs.ts\";\n\n/**\n * Creates a notification primitive for managing email/SMS notification templates.\n *\n * Provides type-safe, reusable notification templates with multi-language support,\n * variable substitution, and categorization for different notification channels.\n *\n * @example\n * ```ts\n * class NotificationTemplates {\n * welcomeEmail = $notification({\n * name: \"welcome-email\",\n * category: \"onboarding\",\n * schema: t.object({ username: t.text(), activationLink: t.text() }),\n * email: {\n * subject: \"Welcome to our platform!\",\n * body: (vars) => `Hello ${vars.username}, click: ${vars.activationLink}`\n * }\n * });\n *\n * async sendWelcome(user: User) {\n * await this.welcomeEmail.push({\n * variables: { username: user.name, activationLink: generateLink() },\n * contact: user.email\n * });\n * }\n * }\n * ```\n */\nexport const $notification = <T extends TObject>(\n options: NotificationPrimitiveOptions<T>,\n) => createPrimitive(NotificationPrimitive<T>, options);\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface NotificationPrimitiveOptions<T extends TObject>\n extends NotificationMessage<T> {\n name?: string;\n description?: string;\n category?: string;\n critical?: boolean;\n sensitive?: boolean;\n translations?: {\n // e.g., \"en\", \"fr\", even \"en-US\"\n [lang: string]: NotificationMessage<T>;\n };\n schema: T;\n}\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport class NotificationPrimitive<T extends TObject> extends Primitive<\n NotificationPrimitiveOptions<T>\n> {\n protected readonly notificationJobs = $inject(NotificationJobs);\n\n public get name() {\n return this.options.name ?? `${this.config.propertyKey}`;\n }\n\n public async push(options: NotificationPushOptions<T>) {\n const pushOpts = this.options.critical\n ? ({ priority: \"critical\" } as const)\n : undefined;\n\n if (this.options.email) {\n await this.notificationJobs.sendNotification.push(\n {\n type: \"email\",\n template: this.name,\n contact: options.contact,\n variables: options.variables as Record<string, unknown>,\n category: this.options.category,\n critical: this.options.critical,\n sensitive: this.options.sensitive,\n },\n pushOpts,\n );\n }\n\n if (this.options.sms) {\n await this.notificationJobs.sendNotification.push(\n {\n type: \"sms\",\n template: this.name,\n contact: options.contact,\n variables: options.variables as Record<string, unknown>,\n category: this.options.category,\n critical: this.options.critical,\n sensitive: this.options.sensitive,\n },\n pushOpts,\n );\n }\n }\n\n public configure(options: Partial<NotificationPrimitiveOptions<T>>) {\n Object.assign(this.options, options);\n }\n}\n\n$notification[KIND] = NotificationPrimitive;\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport interface NotificationPushOptions<T extends TObject> {\n variables: StaticEncode<T>;\n contact: string;\n}\n\nexport interface NotificationMessage<T extends TObject> {\n email?: {\n subject: string;\n body: string | ((variables: Static<T>) => string);\n };\n sms?: {\n message: string | ((variables: Static<T>) => string);\n };\n}\n","import { $inject, Alepha, AlephaError } from \"alepha\";\nimport { EmailProvider } from \"alepha/email\";\nimport { $logger } from \"alepha/logger\";\nimport { SmsProvider } from \"alepha/sms\";\nimport { $notification } from \"../primitives/$notification.ts\";\nimport type { NotificationPayload } from \"../schemas/notificationPayloadSchema.ts\";\n\nexport class NotificationSenderService {\n protected readonly alepha = $inject(Alepha);\n protected readonly log = $logger();\n protected readonly emailProvider = $inject(EmailProvider);\n protected readonly smsProvider = $inject(SmsProvider);\n\n public async send(payload: NotificationPayload) {\n this.log.debug(\"Processing notification\", {\n type: payload.type,\n template: payload.template,\n contact: payload.contact,\n });\n\n if (payload.type === \"email\") {\n const rendered = this.renderEmail(payload);\n await this.emailProvider.send(rendered);\n this.log.info(\"Email notification sent\", {\n template: payload.template,\n contact: payload.contact,\n });\n return {\n type: \"email\" as const,\n to: rendered.to,\n subject: rendered.subject,\n body: rendered.body,\n };\n }\n\n if (payload.type === \"sms\") {\n const rendered = this.renderSms(payload);\n await this.smsProvider.send(rendered);\n this.log.info(\"SMS notification sent\", {\n template: payload.template,\n contact: payload.contact,\n });\n return {\n type: \"sms\" as const,\n to: rendered.to,\n message: rendered.message,\n };\n }\n }\n\n public renderSms(payload: NotificationPayload) {\n const { variables, contact, template } = this.load(payload);\n\n const sms = template.options.sms;\n if (!sms) {\n throw new AlephaError(\n `Notification template ${payload.template} has no sms defined`,\n );\n }\n\n const message =\n typeof sms.message === \"function\"\n ? sms.message(variables as any)\n : sms.message;\n\n return { to: contact, message };\n }\n\n public renderEmail(payload: NotificationPayload) {\n const { variables, contact, template } = this.load(payload);\n\n const email = template.options.email;\n if (!email) {\n throw new AlephaError(\n `Notification template ${payload.template} has no email defined`,\n );\n }\n\n const subject = email.subject;\n const body =\n typeof email.body === \"function\"\n ? email.body(variables as any)\n : email.body;\n\n return { to: contact, subject, body };\n }\n\n protected load(payload: NotificationPayload) {\n const variables = payload.variables || {};\n const contact = payload.contact;\n const template = this.alepha\n .primitives($notification)\n .find((it) => it.name === payload.template);\n\n if (!template) {\n throw new AlephaError(\n `No notification template found for ${payload.template}`,\n );\n }\n\n return { template, variables, contact };\n }\n}\n","import { $inject, t } from \"alepha\";\nimport { $job, jobExecutionEntity } from \"alepha/api/jobs\";\nimport { $parameter } from \"alepha/api/parameters\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport { notificationPayloadSchema } from \"../schemas/notificationPayloadSchema.ts\";\nimport { NotificationSenderService } from \"../services/NotificationSenderService.ts\";\n\n/**\n * Notification jobs + runtime-editable retention.\n *\n * - `settings` — a `$parameter` exposing `retentionDays` that admins can\n * update at runtime. Changes propagate across instances via the parameter\n * pub/sub; the next purge run picks up the new value with no restart.\n * - `sendNotification` — queue-mode, audit-oriented. Every execution is kept\n * (`record: \"all\"`, `keep: { ok: 0, error: 0 }` disables the ring-buffer\n * trim) so the audit trail survives even under heavy volume.\n * - `purgeOldNotifications` — cron sweep that deletes notification execution\n * rows whose `completedAt` is older than the current `retentionDays`.\n *\n * Cron expression note: the purge cron is declared statically (`0 3 * * *`)\n * because some runtimes (Cloudflare Workers) freeze cron triggers at deploy\n * time. The *retention window* is fully runtime-editable — that's the knob\n * that actually matters for operators.\n */\nexport class NotificationJobs {\n protected readonly log = $logger();\n protected readonly dt = $inject(DateTimeProvider);\n protected readonly notificationSenderService = $inject(\n NotificationSenderService,\n );\n protected readonly executions = $repository(jobExecutionEntity);\n\n /** Runtime-editable config. Admins can change retentionDays without deploy. */\n public readonly settings = $parameter({\n name: \"alepha.api.notifications\",\n description: \"Notification delivery & retention settings.\",\n schema: t.object({\n retentionDays: t.integer({\n description:\n \"Days to keep notification execution rows before the purge sweep removes them.\",\n minimum: 1,\n }),\n }),\n default: {\n retentionDays: 7,\n },\n });\n\n public readonly sendNotification = $job({\n name: \"api:notifications:sendNotification\",\n description:\n \"Sends a notification (email/SMS) and keeps every execution for audit.\",\n schema: notificationPayloadSchema,\n retry: {\n retries: 3,\n },\n timeout: [30, \"seconds\"],\n record: \"all\",\n keep: { ok: 0, error: 0 },\n handler: async ({ payload }) => {\n await this.notificationSenderService.send(payload);\n },\n });\n\n public readonly purgeOldNotifications = $job({\n name: \"api:notifications:purgeOldNotifications\",\n description:\n \"Hourly sweep that deletes notification execution rows older than the configured retention window.\",\n cron: \"0 * * * *\",\n handler: async ({ now }) => {\n const { retentionDays } = this.settings.cachedCurrentContent;\n const cutoff = now.subtract(retentionDays, \"day\").toISOString();\n const jobName = this.sendNotification.name;\n\n const expired = await this.executions.findMany({\n where: {\n jobName: { eq: jobName },\n status: { inArray: [\"ok\", \"error\", \"cancelled\"] },\n completedAt: { lt: cutoff },\n },\n columns: [\"id\"] as any,\n limit: 5_000,\n });\n\n if (expired.length === 0) {\n this.log.debug(\"Notification purge: nothing to delete\", {\n cutoff,\n retentionDays,\n });\n return;\n }\n\n await this.executions.deleteMany({\n id: { inArray: expired.map((r) => r.id) },\n });\n this.log.info(\n `Notification purge: deleted ${expired.length} row(s) older than ${retentionDays} days`,\n { cutoff },\n );\n },\n });\n}\n","import { type Static, t } from \"alepha\";\n\nexport const notificationResourceSchema = t.object({\n id: t.uuid(),\n createdAt: t.datetime(),\n status: t.text(),\n template: t.optional(t.text()),\n type: t.optional(t.text()),\n contact: t.optional(t.text()),\n category: t.optional(t.text()),\n critical: t.optional(t.boolean()),\n sensitive: t.optional(t.boolean()),\n startedAt: t.optional(t.datetime()),\n completedAt: t.optional(t.datetime()),\n error: t.optional(t.text()),\n});\n\nexport type NotificationResource = Static<typeof notificationResourceSchema>;\n","import { type Static, t } from \"alepha\";\nimport { logEntrySchema } from \"alepha/logger\";\nimport { notificationResourceSchema } from \"./notificationResourceSchema.ts\";\n\nexport const notificationDetailResourceSchema = t.extend(\n notificationResourceSchema,\n {\n variables: t.optional(t.record(t.text(), t.any())),\n rendered: t.optional(t.record(t.text(), t.any())),\n logs: t.optional(t.array(logEntrySchema)),\n },\n {\n title: \"NotificationDetailResource\",\n description: \"A notification resource with rendered content and logs.\",\n },\n);\n\nexport type NotificationDetailResource = Static<\n typeof notificationDetailResourceSchema\n>;\n","import { type Static, t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const notificationQuerySchema = t.extend(pageQuerySchema, {\n status: t.optional(\n t.enum([\n \"pending\",\n \"scheduled\",\n \"retrying\",\n \"running\",\n \"completed\",\n \"dead\",\n \"cancelled\",\n ]),\n ),\n});\n\nexport type NotificationQuery = Static<typeof notificationQuerySchema>;\n","import { $inject, t } from \"alepha\";\nimport { jobExecutionEntity } from \"alepha/api/jobs\";\nimport { $repository } from \"alepha/orm\";\nimport { $secure } from \"alepha/security\";\nimport { $action, NotFoundError, okSchema } from \"alepha/server\";\nimport { NotificationJobs } from \"../jobs/NotificationJobs.ts\";\nimport { notificationDetailResourceSchema } from \"../schemas/notificationDetailResourceSchema.ts\";\nimport { notificationQuerySchema } from \"../schemas/notificationQuerySchema.ts\";\nimport { notificationResourceSchema } from \"../schemas/notificationResourceSchema.ts\";\n\nexport class AdminNotificationController {\n protected readonly url: string = \"/notifications\";\n protected readonly group: string = \"admin:notifications\";\n protected readonly notificationJobs = $inject(NotificationJobs);\n protected readonly executions = $repository(jobExecutionEntity);\n\n protected get jobName(): string {\n return this.notificationJobs.sendNotification.name;\n }\n\n public readonly findNotifications = $action({\n path: this.url,\n group: this.group,\n use: [$secure({ permissions: [\"admin:notification:read\"] })],\n schema: {\n query: notificationQuerySchema,\n response: t.page(notificationResourceSchema),\n },\n handler: async ({ query }) => {\n query.sort ??= \"-createdAt\";\n const where = this.executions.createQueryWhere();\n where.jobName = { eq: this.jobName };\n const page = await this.executions.paginate(\n query,\n { where },\n { count: true },\n );\n return {\n ...page,\n content: page.content.map((exec) => this.toResource(exec)),\n } as any;\n },\n });\n\n public readonly getNotification = $action({\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:notification:read\"] })],\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n response: notificationDetailResourceSchema,\n },\n handler: async ({ params }) => {\n const exec = await this.executions.findById(params.id);\n if (!exec || exec.jobName !== this.jobName) {\n throw new NotFoundError(`Notification not found: ${params.id}`);\n }\n return this.toDetailResource(exec) as any;\n },\n });\n\n public readonly deleteNotification = $action({\n method: \"DELETE\",\n path: `${this.url}/:id`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:notification:delete\"] })],\n description: \"Delete a notification record\",\n schema: {\n params: t.object({\n id: t.uuid(),\n }),\n response: okSchema,\n },\n handler: async ({ params }) => {\n const exec = await this.executions.findById(params.id);\n if (!exec || exec.jobName !== this.jobName) {\n throw new NotFoundError(`Notification not found: ${params.id}`);\n }\n await this.executions.deleteById(params.id);\n return { ok: true, id: params.id };\n },\n });\n\n public readonly deleteNotifications = $action({\n method: \"POST\",\n path: `${this.url}/delete`,\n group: this.group,\n use: [$secure({ permissions: [\"admin:notification:delete\"] })],\n description: \"Delete many notification records in one call\",\n schema: {\n body: t.object({\n ids: t.array(t.uuid(), { minItems: 1, maxItems: 1000 }),\n }),\n response: t.object({\n deleted: t.array(t.uuid()),\n }),\n },\n handler: async ({ body }) => {\n // Constrain to this job's executions so an admin can't delete arbitrary\n // job rows through this endpoint.\n const deleted = await this.executions.deleteMany({\n id: { inArray: body.ids },\n jobName: { eq: this.jobName },\n });\n return { deleted: deleted.map(String) };\n },\n });\n\n protected toResource(exec: Record<string, unknown>) {\n const payload = (exec.payload ?? {}) as Record<string, unknown>;\n return {\n id: exec.id,\n createdAt: exec.createdAt,\n status: exec.status,\n template: payload.template,\n type: payload.type,\n contact: payload.contact,\n category: payload.category,\n critical: payload.critical,\n sensitive: payload.sensitive,\n startedAt: exec.startedAt,\n completedAt: exec.completedAt,\n error: exec.error,\n };\n }\n\n protected toDetailResource(exec: Record<string, unknown>) {\n const payload = (exec.payload ?? {}) as Record<string, unknown>;\n return {\n ...this.toResource(exec),\n variables: payload.variables,\n logs: exec.logs,\n };\n }\n}\n","import { type Static, t } from \"alepha\";\n\nexport const notificationContactPreferencesSchema = t.object({\n language: t.optional(t.text()),\n exclude: t.array(t.text()),\n});\n\nexport type NotificationContactPreferences = Static<\n typeof notificationContactPreferencesSchema\n>;\n","import { type Static, t } from \"alepha\";\n\nexport const notificationContactSchema = t.object({\n email: t.optional(t.email()),\n phoneNumber: t.optional(t.e164()),\n firstName: t.optional(t.shortText()),\n lastName: t.optional(t.text({ size: \"short\" })),\n language: t.optional(t.bcp47()),\n});\n\nexport type NotificationContact = Static<typeof notificationContactSchema>;\n","import { $module } from \"alepha\";\nimport { AlephaApiJobs } from \"alepha/api/jobs\";\nimport { AlephaApiParameters } from \"alepha/api/parameters\";\nimport { AdminNotificationController } from \"./controllers/AdminNotificationController.ts\";\nimport { NotificationJobs } from \"./jobs/NotificationJobs.ts\";\nimport { $notification } from \"./primitives/$notification.ts\";\nimport { NotificationSenderService } from \"./services/NotificationSenderService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\nexport * from \"./controllers/AdminNotificationController.ts\";\nexport * from \"./jobs/NotificationJobs.ts\";\nexport * from \"./primitives/$notification.ts\";\nexport * from \"./schemas/notificationContactPreferencesSchema.ts\";\nexport * from \"./schemas/notificationContactSchema.ts\";\nexport * from \"./schemas/notificationDetailResourceSchema.ts\";\nexport * from \"./schemas/notificationPayloadSchema.ts\";\nexport * from \"./schemas/notificationQuerySchema.ts\";\nexport * from \"./schemas/notificationResourceSchema.ts\";\nexport * from \"./services/NotificationSenderService.ts\";\n\n// ---------------------------------------------------------------------------------------------------------------------\n\n/**\n * User notification management.\n *\n * **Features:**\n * - Notification definitions (email/SMS templates)\n * - Delivery via `$job` with retry and audit trail (`record: \"all\"` + no ring buffer trim)\n * - Runtime-editable retention window via `$parameter` — purge cron respects it live\n * - Admin API for inspecting sent notifications\n *\n * **Delivery mode** is decided at runtime by the `$job` system:\n * - If your app loads `AlephaApiJobsQueue` (and thus `AlephaQueue`), notifications\n * go through the queue (best for high-volume systems).\n * - Otherwise, notifications run in **direct** mode: pushed to the outbox table\n * and processed in the same process right after the HTTP response is returned.\n * The reconciliation sweep is the safety net for crashes / retries.\n *\n * Direct mode is the recommended default for small / cheap deployments\n * (Cloudflare Workers, single-instance Node) — no queue infrastructure required.\n *\n * @module alepha.api.notifications\n */\nexport const AlephaApiNotifications = $module({\n name: \"alepha.api.notifications\",\n imports: [AlephaApiJobs, AlephaApiParameters],\n primitives: [$notification],\n services: [\n NotificationSenderService,\n NotificationJobs,\n AdminNotificationController,\n ],\n});\n"],"mappings":"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"}
@@ -1,11 +1,6 @@
1
- import * as _$alepha from "alepha";
2
1
  import { Alepha, Static } from "alepha";
3
- import * as _$alepha_logger0 from "alepha/logger";
4
- import * as _$alepha_server0 from "alepha/server";
5
2
  import { DateTimeProvider } from "alepha/datetime";
6
- import * as _$alepha_orm0 from "alepha/orm";
7
3
  import { IssuerPrimitive, JwtProvider, UserAccount } from "alepha/security";
8
- import * as _$typebox from "typebox";
9
4
 
10
5
  //#region ../../src/api/oauth/entities/oauthClientEntity.d.ts
11
6
  /**
@@ -16,19 +11,19 @@ import * as _$typebox from "typebox";
16
11
  * client; for DCR it is always `"dcr"` and `createdByUserId` is null until
17
12
  * a user completes an authorization.
18
13
  */
19
- declare const oauthClientEntity: _$alepha_orm0.EntityPrimitive<_$typebox.TObject<{
20
- id: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_PRIMARY_KEY>, typeof _$alepha_orm0.PG_DEFAULT>;
21
- createdAt: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_CREATED_AT>, typeof _$alepha_orm0.PG_DEFAULT>;
22
- updatedAt: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_UPDATED_AT>, typeof _$alepha_orm0.PG_DEFAULT>;
23
- clientId: _$typebox.TString;
24
- clientName: _$typebox.TString;
25
- redirectUris: _$alepha_orm0.PgAttr<_$typebox.TArray<_$typebox.TString>, typeof _$alepha_orm0.PG_DEFAULT>;
26
- scopes: _$alepha_orm0.PgAttr<_$typebox.TArray<_$typebox.TString>, typeof _$alepha_orm0.PG_DEFAULT>;
27
- realm: _$typebox.TString;
28
- source: _$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_DEFAULT>;
29
- createdByUserId: _$typebox.TOptional<_$typebox.TString>;
30
- lastUsedAt: _$typebox.TOptional<_$typebox.TString>;
31
- revokedAt: _$typebox.TOptional<_$typebox.TString>;
14
+ declare const oauthClientEntity: import("alepha/orm").EntityPrimitive<import("typebox").TObject<{
15
+ id: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_PRIMARY_KEY>, typeof import("alepha/orm").PG_DEFAULT>;
16
+ createdAt: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_CREATED_AT>, typeof import("alepha/orm").PG_DEFAULT>;
17
+ updatedAt: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_UPDATED_AT>, typeof import("alepha/orm").PG_DEFAULT>;
18
+ clientId: import("typebox").TString;
19
+ clientName: import("typebox").TString;
20
+ redirectUris: import("alepha/orm").PgAttr<import("typebox").TArray<import("typebox").TString>, typeof import("alepha/orm").PG_DEFAULT>;
21
+ scopes: import("alepha/orm").PgAttr<import("typebox").TArray<import("typebox").TString>, typeof import("alepha/orm").PG_DEFAULT>;
22
+ realm: import("typebox").TString;
23
+ source: import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_DEFAULT>;
24
+ createdByUserId: import("typebox").TOptional<import("typebox").TString>;
25
+ lastUsedAt: import("typebox").TOptional<import("typebox").TString>;
26
+ revokedAt: import("typebox").TOptional<import("typebox").TString>;
32
27
  }>>;
33
28
  type OAuthClientEntity = Static<typeof oauthClientEntity.schema>;
34
29
  //#endregion
@@ -56,20 +51,20 @@ interface RegisterClientOptions {
56
51
  declare class OAuthClientService {
57
52
  protected readonly alepha: Alepha;
58
53
  protected readonly dateTime: DateTimeProvider;
59
- protected readonly log: _$alepha_logger0.Logger;
60
- protected readonly repo: _$alepha_orm0.Repository<_$typebox.TObject<{
61
- id: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_PRIMARY_KEY>, typeof _$alepha_orm0.PG_DEFAULT>;
62
- createdAt: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_CREATED_AT>, typeof _$alepha_orm0.PG_DEFAULT>;
63
- updatedAt: _$alepha_orm0.PgAttr<_$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_UPDATED_AT>, typeof _$alepha_orm0.PG_DEFAULT>;
64
- clientId: _$typebox.TString;
65
- clientName: _$typebox.TString;
66
- redirectUris: _$alepha_orm0.PgAttr<_$typebox.TArray<_$typebox.TString>, typeof _$alepha_orm0.PG_DEFAULT>;
67
- scopes: _$alepha_orm0.PgAttr<_$typebox.TArray<_$typebox.TString>, typeof _$alepha_orm0.PG_DEFAULT>;
68
- realm: _$typebox.TString;
69
- source: _$alepha_orm0.PgAttr<_$typebox.TString, typeof _$alepha_orm0.PG_DEFAULT>;
70
- createdByUserId: _$typebox.TOptional<_$typebox.TString>;
71
- lastUsedAt: _$typebox.TOptional<_$typebox.TString>;
72
- revokedAt: _$typebox.TOptional<_$typebox.TString>;
54
+ protected readonly log: import("alepha/logger").Logger;
55
+ protected readonly repo: import("alepha/orm").Repository<import("typebox").TObject<{
56
+ id: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_PRIMARY_KEY>, typeof import("alepha/orm").PG_DEFAULT>;
57
+ createdAt: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_CREATED_AT>, typeof import("alepha/orm").PG_DEFAULT>;
58
+ updatedAt: import("alepha/orm").PgAttr<import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_UPDATED_AT>, typeof import("alepha/orm").PG_DEFAULT>;
59
+ clientId: import("typebox").TString;
60
+ clientName: import("typebox").TString;
61
+ redirectUris: import("alepha/orm").PgAttr<import("typebox").TArray<import("typebox").TString>, typeof import("alepha/orm").PG_DEFAULT>;
62
+ scopes: import("alepha/orm").PgAttr<import("typebox").TArray<import("typebox").TString>, typeof import("alepha/orm").PG_DEFAULT>;
63
+ realm: import("typebox").TString;
64
+ source: import("alepha/orm").PgAttr<import("typebox").TString, typeof import("alepha/orm").PG_DEFAULT>;
65
+ createdByUserId: import("typebox").TOptional<import("typebox").TString>;
66
+ lastUsedAt: import("typebox").TOptional<import("typebox").TString>;
67
+ revokedAt: import("typebox").TOptional<import("typebox").TString>;
73
68
  }>>;
74
69
  protected readonly jwt: JwtProvider;
75
70
  /**
@@ -168,10 +163,10 @@ declare class OAuthClientService {
168
163
  * `loginPath` is the app-level login page unauthenticated users are
169
164
  * redirected to from the authorize endpoint.
170
165
  */
171
- declare const oauthOptions: _$alepha.Atom<_$typebox.TObject<{
172
- realm: _$typebox.TString;
173
- resource: _$typebox.TString;
174
- loginPath: _$typebox.TString;
166
+ declare const oauthOptions: import("alepha").Atom<import("typebox").TObject<{
167
+ realm: import("typebox").TString;
168
+ resource: import("typebox").TString;
169
+ loginPath: import("typebox").TString;
175
170
  }>, "alepha.api.oauth.options">;
176
171
  /**
177
172
  * OAuth 2.1 authorization server endpoints: discovery metadata and
@@ -179,7 +174,7 @@ declare const oauthOptions: _$alepha.Atom<_$typebox.TObject<{
179
174
  * separately.
180
175
  */
181
176
  declare class OAuthController {
182
- protected readonly log: _$alepha_logger0.Logger;
177
+ protected readonly log: import("alepha/logger").Logger;
183
178
  protected readonly options: Readonly<{
184
179
  realm: string;
185
180
  resource: string;
@@ -190,15 +185,15 @@ declare class OAuthController {
190
185
  * Absolute origin of the current request, e.g. https://app.com.
191
186
  */
192
187
  protected baseUrl(url: URL): string;
193
- metadata: _$alepha_server0.RoutePrimitive<_$alepha_server0.RequestConfigSchema>;
194
- protectedResource: _$alepha_server0.RoutePrimitive<_$alepha_server0.RequestConfigSchema>;
195
- register: _$alepha_server0.RoutePrimitive<{
196
- body: _$typebox.TObject<{
197
- client_name: _$typebox.TOptional<_$typebox.TString>;
198
- redirect_uris: _$typebox.TArray<_$typebox.TString>;
199
- scope: _$typebox.TOptional<_$typebox.TString>;
200
- grant_types: _$typebox.TOptional<_$typebox.TArray<_$typebox.TString>>;
201
- token_endpoint_auth_method: _$typebox.TOptional<_$typebox.TString>;
188
+ metadata: import("alepha/server").RoutePrimitive<import("alepha/server").RequestConfigSchema>;
189
+ protectedResource: import("alepha/server").RoutePrimitive<import("alepha/server").RequestConfigSchema>;
190
+ register: import("alepha/server").RoutePrimitive<{
191
+ body: import("typebox").TObject<{
192
+ client_name: import("typebox").TOptional<import("typebox").TString>;
193
+ redirect_uris: import("typebox").TArray<import("typebox").TString>;
194
+ scope: import("typebox").TOptional<import("typebox").TString>;
195
+ grant_types: import("typebox").TOptional<import("typebox").TArray<import("typebox").TString>>;
196
+ token_endpoint_auth_method: import("typebox").TOptional<import("typebox").TString>;
202
197
  }>;
203
198
  }>;
204
199
  /**
@@ -206,16 +201,16 @@ declare class OAuthController {
206
201
  * has no session, redirect to the realm login page with a return URL.
207
202
  * If authenticated, render the consent screen.
208
203
  */
209
- authorize: _$alepha_server0.RoutePrimitive<{
210
- query: _$typebox.TObject<{
211
- response_type: _$typebox.TString;
212
- client_id: _$typebox.TString;
213
- redirect_uri: _$typebox.TString;
214
- code_challenge: _$typebox.TString;
215
- code_challenge_method: _$typebox.TString;
216
- scope: _$typebox.TOptional<_$typebox.TString>;
217
- state: _$typebox.TOptional<_$typebox.TString>;
218
- resource: _$typebox.TOptional<_$typebox.TString>;
204
+ authorize: import("alepha/server").RoutePrimitive<{
205
+ query: import("typebox").TObject<{
206
+ response_type: import("typebox").TString;
207
+ client_id: import("typebox").TString;
208
+ redirect_uri: import("typebox").TString;
209
+ code_challenge: import("typebox").TString;
210
+ code_challenge_method: import("typebox").TString;
211
+ scope: import("typebox").TOptional<import("typebox").TString>;
212
+ state: import("typebox").TOptional<import("typebox").TString>;
213
+ resource: import("typebox").TOptional<import("typebox").TString>;
219
214
  }>;
220
215
  }>;
221
216
  /**
@@ -228,17 +223,17 @@ declare class OAuthController {
228
223
  * already-registered client, and that code is bound by PKCE, so the
229
224
  * attacker cannot redeem it without the matching code_verifier.
230
225
  */
231
- authorizeDecision: _$alepha_server0.RoutePrimitive<{
232
- body: _$typebox.TObject<{
233
- decision: _$typebox.TString;
234
- response_type: _$typebox.TString;
235
- client_id: _$typebox.TString;
236
- redirect_uri: _$typebox.TString;
237
- code_challenge: _$typebox.TString;
238
- code_challenge_method: _$typebox.TString;
239
- scope: _$typebox.TOptional<_$typebox.TString>;
240
- state: _$typebox.TOptional<_$typebox.TString>;
241
- resource: _$typebox.TOptional<_$typebox.TString>;
226
+ authorizeDecision: import("alepha/server").RoutePrimitive<{
227
+ body: import("typebox").TObject<{
228
+ decision: import("typebox").TString;
229
+ response_type: import("typebox").TString;
230
+ client_id: import("typebox").TString;
231
+ redirect_uri: import("typebox").TString;
232
+ code_challenge: import("typebox").TString;
233
+ code_challenge_method: import("typebox").TString;
234
+ scope: import("typebox").TOptional<import("typebox").TString>;
235
+ state: import("typebox").TOptional<import("typebox").TString>;
236
+ resource: import("typebox").TOptional<import("typebox").TString>;
242
237
  }>;
243
238
  }>;
244
239
  /**
@@ -247,14 +242,14 @@ declare class OAuthController {
247
242
  * `refresh_token` grant (exchanges a refresh token for a fresh access
248
243
  * token, so a client stays connected without re-running the flow).
249
244
  */
250
- token: _$alepha_server0.RoutePrimitive<{
251
- body: _$typebox.TObject<{
252
- grant_type: _$typebox.TOptional<_$typebox.TString>;
253
- code: _$typebox.TOptional<_$typebox.TString>;
254
- client_id: _$typebox.TOptional<_$typebox.TString>;
255
- redirect_uri: _$typebox.TOptional<_$typebox.TString>;
256
- code_verifier: _$typebox.TOptional<_$typebox.TString>;
257
- refresh_token: _$typebox.TOptional<_$typebox.TString>;
245
+ token: import("alepha/server").RoutePrimitive<{
246
+ body: import("typebox").TObject<{
247
+ grant_type: import("typebox").TOptional<import("typebox").TString>;
248
+ code: import("typebox").TOptional<import("typebox").TString>;
249
+ client_id: import("typebox").TOptional<import("typebox").TString>;
250
+ redirect_uri: import("typebox").TOptional<import("typebox").TString>;
251
+ code_verifier: import("typebox").TOptional<import("typebox").TString>;
252
+ refresh_token: import("typebox").TOptional<import("typebox").TString>;
258
253
  }>;
259
254
  }>;
260
255
  }
@@ -281,7 +276,7 @@ declare class OAuthController {
281
276
  *
282
277
  * @module alepha.api.oauth
283
278
  */
284
- declare const AlephaOAuth: _$alepha.Service<_$alepha.Module>;
279
+ declare const AlephaOAuth: import("alepha").Service<import("alepha").Module>;
285
280
  //#endregion
286
281
  export { AlephaOAuth, type OAuthClientEntity, OAuthClientService, OAuthController, type RegisterClientOptions, oauthClientEntity, oauthOptions };
287
282
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/api/oauth/entities/oauthClientEntity.ts","../../../src/api/oauth/services/OAuthClientService.ts","../../../src/api/oauth/controllers/OAuthController.ts","../../../src/api/oauth/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;cAWa,iBAAA,EAAiB,aAAA,CAAA,eAAA,WAAA,OAAA;gDAiB5B,SAAA,CAAA,OAAA;;;;;;;;;;;;;KAEU,iBAAA,GAAoB,MAAA,QAAc,iBAAA,CAAkB,MAAA;;;UCf/C,qBAAA;EACf,KAAA;EACA,UAAA;EACA,YAAA;EACA,MAAA;EACA,MAAA;EACA,eAAA;AAAA;;;;;;;;;;;;;cAeW,kBAAA;EAAA,mBACQ,MAAA,EAAM,MAAA;EAAA,mBACN,QAAA,EAAQ,gBAAA;EAAA,mBACR,GAAA,EADQ,gBAAA,CACL,MAAA;EAAA,mBACH,IAAA,EAAI,aAAA,CAAA,UAAA,WAAA,OAAA;kDADD,SAAA,CAAA,OAAA;;;;;;;;;;;;;qBAEH,GAAA,EAAG,WAAA;;;;;;;qBAQH,SAAA,EAAS,GAAA;EDtCA;;;;;EAAA,mBC6CT,OAAA,EAAO,GAAA;YAGd,eAAA;eACG,MAAA,aAAmB,OAAA,CAAQ,WAAA;EAAA;;;;;EAQnC,cAAA,CACL,KAAA,UACA,MAAA,EAAQ,eAAA,EACR,QAAA,GAAW,MAAA,aAAmB,OAAA,CAAQ,WAAA;;;;;EAS3B,gBAAA,CACX,KAAA,UACA,KAAA;IACE,MAAA;IACA,MAAA;IACA,QAAA;IACA,QAAA;EAAA,IAED,OAAA;IACD,YAAA;IACA,UAAA;IACA,aAAA;EAAA;;;;;;;;EA0BW,kBAAA,CACX,KAAA,UACA,YAAA,WACC,OAAA;IACD,YAAA;IACA,UAAA;IACA,aAAA;EAAA;;;;;EAkBW,QAAA,CACX,OAAA,EAAS,qBAAA,GACR,OAAA,CAAQ,iBAAA;;;;EA+BE,cAAA,CACX,QAAA,WACC,OAAA,CAAQ,iBAAA;;;;;EAWJ,oBAAA,CACL,MAAA,EAAQ,iBAAA,EACR,WAAA;ED/JyB;;;;ECwKd,uBAAA,CACX,KAAA,UACA,KAAA;IACE,MAAA;IACA,QAAA;IACA,WAAA;IACA,aAAA;IACA,MAAA;IACA,QAAA;EAAA,IAED,OAAA;EAjMiC;;;;EAwNvB,wBAAA,CACX,KAAA,UACA,IAAA,UACA,KAAA;IAAS,QAAA;IAAkB,WAAA;IAAqB,YAAA;EAAA,IAC/C,OAAA;IAAU,MAAA;IAAgB,MAAA;IAAkB,QAAA;EAAA;AAAA;;;;;;;;;;cCtNpC,YAAA,EAAY,QAAA,CAAA,IAAA,WAAA,OAAA;SASvB,SAAA,CAAA,OAAA;;;;;;;;;cAOW,eAAA;EAAA,mBACQ,GAAA,EADO,gBAAA,CACJ,MAAA;EAAA,mBACH,OAAA,EAAO,QAAA;;;;;qBACP,OAAA,EAAO,kBAAA;;;;YAKhB,OAAA,CAAQ,GAAA,EAAK,GAAA;EAIvB,QAAA,EAAQ,gBAAA,CAAA,cAAA,CAJkB,gBAAA,CAIlB,mBAAA;EAWR,iBAAA,EAAiB,gBAAA,CAAA,cAAA,CAXT,gBAAA,CAWS,mBAAA;EAYjB,QAAA,mBAAQ,cAAA;;uCAZS,SAAA,CAAA,OAAA;;;;;;;;;;;;EA4CjB,SAAA,mBAAS,cAAA;;qBAhCD,SAAA,CAAA,OAAA;;;;;;;;;;;;;;;;;;;;EAgGR,iBAAA,mBAAiB,cAAA;;gBAhER,SAAA,CAAA,OAAA;;;;;;;;;;;;;;;;;EAmHT,KAAA,mBAAK,cAAA;;sCAnDY,SAAA,CAAA,OAAA;;;;;;;;;;;;;;;;AF7JnB;;;;;;;;;;;;;;;;cGuBa,WAAA,EAAW,QAAA,CAAA,OAAA,CAGtB,QAAA,CAHsB,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../../src/api/oauth/entities/oauthClientEntity.ts","../../../src/api/oauth/services/OAuthClientService.ts","../../../src/api/oauth/controllers/OAuthController.ts","../../../src/api/oauth/index.ts"],"mappings":";;;;;;;;;AAWA;;;;cAAa,iBAAA,uBAAiB,eAAA,mBAAA,OAAA;;;;;;;;;;;;;;KAmBlB,iBAAA,GAAoB,MAAM,QAAQ,iBAAA,CAAkB,MAAA;;;UCf/C,qBAAA;EACf,KAAA;EACA,UAAA;EACA,YAAA;EACA,MAAA;EACA,MAAA;EACA,eAAA;AAAA;;;;;;;;;;;;;cAeW,kBAAA;EAAA,mBACQ,MAAA,EAAM,MAAA;EAAA,mBACN,QAAA,EAAQ,gBAAA;EAAA,mBACR,GAAA,0BAAG,MAAA;EAAA,mBACH,IAAA,uBAAI,UAAA,mBAAA,OAAA;;;;;;;;;;;;;;qBACJ,GAAA,EAAG,WAAA;;;;;;;qBAQH,SAAA,EAAS,GAAA;;;;;;qBAOT,OAAA,EAAO,GAAA;YAGd,eAAA;eACG,MAAA,aAAmB,OAAA,CAAQ,WAAA;EAAA;;AD9B5C;;;ECsCS,cAAA,CACL,KAAA,UACA,MAAA,EAAQ,eAAA,EACR,QAAA,GAAW,MAAA,aAAmB,OAAA,CAAQ,WAAA;EDzCV;;;;ECkDjB,gBAAA,CACX,KAAA,UACA,KAAA;IACE,MAAA;IACA,MAAA;IACA,QAAA;IACA,QAAA;EAAA,IAED,OAAA;IACD,YAAA;IACA,UAAA;IACA,aAAA;EAAA;EAzEF;;;;;AAGe;AAejB;EAiFe,kBAAA,CACX,KAAA,UACA,YAAA,WACC,OAAA;IACD,YAAA;IACA,UAAA;IACA,aAAA;EAAA;EA1E0B;;;;EA4Ff,QAAA,CACX,OAAA,EAAS,qBAAA,GACR,OAAA,CAAQ,iBAAA;EAzED;;;EAwGG,cAAA,CACX,QAAA,WACC,OAAA,CAAQ,iBAAA;EAxDR;;;;EAmEI,oBAAA,CACL,MAAA,EAAQ,iBAAA,EACR,WAAA;EAbC;;;;EAsBU,uBAAA,CACX,KAAA,UACA,KAAA;IACE,MAAA;IACA,QAAA;IACA,WAAA;IACA,aAAA;IACA,MAAA;IACA,QAAA;EAAA,IAED,OAAA;EAxKgB;;;;EA+LN,wBAAA,CACX,KAAA,UACA,IAAA,UACA,KAAA;IAAS,QAAA;IAAkB,WAAA;IAAqB,YAAA;EAAA,IAC/C,OAAA;IAAU,MAAA;IAAgB,MAAA;IAAkB,QAAA;EAAA;AAAA;;;;;;;ADhOjD;;;cEUa,YAAA,mBAAY,IAAA,mBAAA,OAAA;;;;;;;;;;cAgBZ,eAAA;EAAA,mBACQ,GAAA,0BAAG,MAAA;EAAA,mBACH,OAAA,EAAO,QAAA;;;;;qBACP,OAAA,EAAO,kBAAA;;;;YAKhB,OAAA,CAAQ,GAAA,EAAK,GAAA;EAIvB,QAAA,0BAAQ,cAAA,yBAAA,mBAAA;EAWR,iBAAA,0BAAiB,cAAA,yBAAA,mBAAA;EAYjB,QAAA,0BAAQ,cAAA;;;;;;;;;;;;;;EAgCR,SAAA,0BAAS,cAAA;;;;;;;;;;;;;;AF1EX;;;;;;;;EE0IE,iBAAA,0BAAiB,cAAA;;;;;;;;;;;;;EDnJjB;;AAAe;AAejB;;;ECuLE,KAAA,0BAAK,cAAA;;;;;;;;;;;;;AFhNP;;;;;;;;;;;;;;;;;;;;;AAAA,cGuBa,WAAA,mBAAW,OAAA,kBAAA,MAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/api/oauth/helpers/consentPage.ts","../../../src/api/oauth/helpers/oauthMetadata.ts","../../../src/api/oauth/schemas/authorizeDecisionBodySchema.ts","../../../src/api/oauth/schemas/authorizeQuerySchema.ts","../../../src/api/oauth/schemas/registerClientBodySchema.ts","../../../src/api/oauth/schemas/tokenRequestBodySchema.ts","../../../src/api/oauth/entities/oauthClientEntity.ts","../../../src/api/oauth/services/OAuthClientService.ts","../../../src/api/oauth/controllers/OAuthController.ts","../../../src/api/oauth/index.ts"],"sourcesContent":["/**\n * Renders the OAuth consent screen as a self-contained HTML document.\n * Pure server-rendered HTML — no client framework, no @alepha/ui dependency.\n * All authorization parameters are emitted as hidden inputs so the POST to\n * /oauth/authorize is stateless.\n */\nexport interface ConsentPageOptions {\n clientName: string;\n userName: string;\n scopes: string[];\n /**\n * Hidden field name -> value; round-trips the authorization request.\n */\n hidden: Record<string, string>;\n}\n\nconst escapeHtml = (s: string): string =>\n s.replace(\n /[&<>\"']/g,\n (c) =>\n ({\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n })[c] as string,\n );\n\nexport const renderConsentPage = (options: ConsentPageOptions): string => {\n const hidden = Object.entries(options.hidden)\n .map(\n ([k, v]) =>\n `<input type=\"hidden\" name=\"${escapeHtml(k)}\" value=\"${escapeHtml(v)}\" />`,\n )\n .join(\"\");\n const scopes = options.scopes.length\n ? options.scopes.map((s) => `<li>${escapeHtml(s)}</li>`).join(\"\")\n : \"<li>Basic access</li>\";\n return `<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<title>Authorize ${escapeHtml(options.clientName)}</title>\n<style>\nbody{font-family:system-ui,sans-serif;background:#0b0b0f;color:#e5e5e5;\ndisplay:flex;min-height:100vh;align-items:center;justify-content:center;margin:0}\n.card{background:#16161d;border:1px solid #2a2a35;border-radius:12px;\npadding:32px;max-width:380px;width:100%}\nh1{font-size:18px;margin:0 0 4px}p{color:#9a9aa5;font-size:14px;margin:4px 0 16px}\nul{font-size:14px;padding-left:18px}\n.row{display:flex;gap:8px;margin-top:20px}\nbutton{flex:1;padding:10px;border-radius:8px;border:0;font-size:14px;cursor:pointer}\n.allow{background:#6d5cf0;color:#fff}.deny{background:#2a2a35;color:#e5e5e5}\n</style></head><body>\n<div class=\"card\">\n<h1>${escapeHtml(options.clientName)} wants to connect</h1>\n<p>Signed in as ${escapeHtml(options.userName)}</p>\n<p>It will be able to:</p>\n<ul>${scopes}</ul>\n<form method=\"POST\" action=\"/oauth/authorize\">${hidden}\n<div class=\"row\">\n<button class=\"deny\" type=\"submit\" name=\"decision\" value=\"deny\">Deny</button>\n<button class=\"allow\" type=\"submit\" name=\"decision\" value=\"allow\">Allow</button>\n</div></form></div></body></html>`;\n};\n","/**\n * RFC 8414 — OAuth 2.0 Authorization Server Metadata.\n * `baseUrl` is the absolute origin of the deployment (e.g. https://app.com).\n */\nexport const buildAuthorizationServerMetadata = (baseUrl: string) => ({\n issuer: baseUrl,\n authorization_endpoint: `${baseUrl}/oauth/authorize`,\n token_endpoint: `${baseUrl}/oauth/token`,\n registration_endpoint: `${baseUrl}/oauth/register`,\n response_types_supported: [\"code\"],\n grant_types_supported: [\"authorization_code\", \"refresh_token\"],\n code_challenge_methods_supported: [\"S256\"],\n token_endpoint_auth_methods_supported: [\"none\"],\n scopes_supported: [\"mcp\"],\n});\n\n/**\n * RFC 9728 — OAuth 2.0 Protected Resource Metadata.\n * `resource` is the absolute URL of the MCP endpoint being protected.\n */\nexport const buildProtectedResourceMetadata = (\n baseUrl: string,\n resource: string,\n) => ({\n resource,\n authorization_servers: [baseUrl],\n scopes_supported: [\"mcp\"],\n bearer_methods_supported: [\"header\"],\n});\n","import { t } from \"alepha\";\n\n/**\n * Body posted by the consent screen. All authorization-request parameters\n * are round-tripped through hidden form fields so the POST handler can\n * re-validate them without server-side session state.\n */\nexport const authorizeDecisionBodySchema = t.object({\n decision: t.text(),\n response_type: t.text(),\n client_id: t.text(),\n redirect_uri: t.text({ maxLength: 2048 }),\n code_challenge: t.text(),\n code_challenge_method: t.text(),\n scope: t.optional(t.text({ maxLength: 1024 })),\n state: t.optional(t.text({ maxLength: 512 })),\n resource: t.optional(t.text({ maxLength: 2048 })),\n});\n","import { t } from \"alepha\";\n\n/**\n * OAuth 2.1 authorization request query parameters (GET /oauth/authorize).\n */\nexport const authorizeQuerySchema = t.object({\n response_type: t.text(),\n client_id: t.text(),\n redirect_uri: t.text({ maxLength: 2048 }),\n code_challenge: t.text(),\n code_challenge_method: t.text(),\n scope: t.optional(t.text({ maxLength: 1024 })),\n state: t.optional(t.text({ maxLength: 512 })),\n resource: t.optional(t.text({ maxLength: 2048 })),\n});\n","import { t } from \"alepha\";\n\n/**\n * RFC 7591 Dynamic Client Registration request body.\n * Only the fields Alepha consumes are typed; unknown fields are ignored.\n */\nexport const registerClientBodySchema = t.object(\n {\n client_name: t.optional(t.text({ maxLength: 200 })),\n redirect_uris: t.array(t.text({ maxLength: 2048 }), { minItems: 1 }),\n scope: t.optional(t.text({ maxLength: 1024 })),\n grant_types: t.optional(t.array(t.text())),\n token_endpoint_auth_method: t.optional(t.text()),\n },\n { additionalProperties: true },\n);\n","import { t } from \"alepha\";\n\n/**\n * Body of a POST /oauth/token request. OAuth 2.1 mandates\n * application/x-www-form-urlencoded encoding (section 3.2.2). All fields are\n * optional at the schema level; the handler enforces the grant-specific\n * requirements and returns the appropriate OAuth error responses.\n */\nexport const tokenRequestBodySchema = t.object({\n grant_type: t.optional(t.text()),\n code: t.optional(t.text({ maxLength: 4096 })),\n client_id: t.optional(t.text()),\n redirect_uri: t.optional(t.text({ maxLength: 2048 })),\n code_verifier: t.optional(t.text({ maxLength: 256 })),\n refresh_token: t.optional(t.text({ maxLength: 4096 })),\n});\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\n/**\n * A registered OAuth 2.1 client application.\n *\n * Rows are created by Dynamic Client Registration (RFC 7591) when an MCP\n * client (e.g. Claude) first connects. `source` records who created the\n * client; for DCR it is always `\"dcr\"` and `createdByUserId` is null until\n * a user completes an authorization.\n */\nexport const oauthClientEntity = $entity({\n name: \"oauth_clients\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n clientId: t.text({ maxLength: 64 }),\n clientName: t.text({ maxLength: 200 }),\n redirectUris: db.default(t.array(t.text({ maxLength: 2048 })), []),\n scopes: db.default(t.array(t.text({ maxLength: 64 })), []),\n realm: t.text({ maxLength: 100 }),\n source: db.default(t.text({ maxLength: 16 }), \"dcr\"),\n createdByUserId: t.optional(t.uuid()),\n lastUsedAt: t.optional(t.datetime()),\n revokedAt: t.optional(t.datetime()),\n }),\n indexes: [{ columns: [\"clientId\"], unique: true }],\n});\n\nexport type OAuthClientEntity = Static<typeof oauthClientEntity.schema>;\n","import { createHash, randomUUID } from \"node:crypto\";\nimport { $inject, Alepha, AlephaError } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport {\n type IssuerPrimitive,\n JwtProvider,\n type UserAccount,\n} from \"alepha/security\";\nimport {\n type OAuthClientEntity,\n oauthClientEntity,\n} from \"../entities/oauthClientEntity.ts\";\n\nexport interface RegisterClientOptions {\n realm: string;\n clientName: string;\n redirectUris: string[];\n scopes: string[];\n source?: \"dcr\" | \"user\" | \"admin\";\n createdByUserId?: string;\n}\n\n/**\n * Core OAuth 2.1 service backing the authorization server.\n *\n * Responsibilities:\n * - Client registration (RFC 7591 Dynamic Client Registration) and lookup,\n * with exact-match redirect_uri validation.\n * - Stateless PKCE authorization codes: minting short-lived signed JWTs that\n * carry the grant, and verifying/consuming them (replay, expiry, client and\n * redirect_uri checks, S256 PKCE).\n * - Realm issuer registry: realms register an issuer + user loader so the\n * token endpoint can mint access tokens without depending on realm wiring.\n */\nexport class OAuthClientService {\n protected readonly alepha = $inject(Alepha);\n protected readonly dateTime = $inject(DateTimeProvider);\n protected readonly log = $logger();\n protected readonly repo = $repository(oauthClientEntity);\n protected readonly jwt = $inject(JwtProvider);\n\n /**\n * Codes already redeemed in this process. Single-use enforcement only\n * needs to cover the ~60s code lifetime, so a bounded in-memory set is\n * sufficient even on serverless — an expired code fails JWT verification\n * regardless.\n */\n protected readonly usedCodes = new Set<string>();\n\n /**\n * Registry of realm issuers used to mint access tokens. Populated by\n * `$realm` (via `registerIssuer`) so the OAuth module does not depend on the\n * realm wiring directly.\n */\n protected readonly issuers = new Map<\n string,\n {\n issuer: IssuerPrimitive;\n loadUser: (userId: string) => Promise<UserAccount>;\n }\n >();\n\n /**\n * Register a realm issuer and a user loader. Called by `$realm` so the\n * OAuth token endpoint can mint access tokens for that realm.\n */\n public registerIssuer(\n realm: string,\n issuer: IssuerPrimitive,\n loadUser: (userId: string) => Promise<UserAccount>,\n ): void {\n this.issuers.set(realm, { issuer, loadUser });\n }\n\n /**\n * Mint an access token for a consumed authorization-code grant, using the\n * issuer registered for `realm`. Throws if the realm has no issuer.\n */\n public async issueAccessToken(\n realm: string,\n grant: {\n userId: string;\n scopes: string[];\n resource?: string;\n clientId?: string;\n },\n ): Promise<{\n access_token: string;\n expires_in?: number;\n refresh_token?: string;\n }> {\n const entry = this.issuers.get(realm);\n if (!entry) {\n throw new AlephaError(`No issuer registered for realm '${realm}'`);\n }\n const user = await entry.loadUser(grant.userId);\n // Tag the session the issuer creates with the OAuth client, so it can\n // later be surfaced as a \"connected app\" and revoked individually.\n const tokens = await entry.issuer.createToken(user, undefined, {\n clientId: grant.clientId,\n });\n return {\n access_token: tokens.access_token,\n expires_in: tokens.expires_in,\n refresh_token: tokens.refresh_token,\n };\n }\n\n /**\n * Exchange a refresh token for a fresh access token (OAuth 2.1\n * `refresh_token` grant), using the issuer registered for `realm`. Lets an\n * MCP client stay connected for the refresh token's full lifetime without\n * re-running the authorization flow. Throws if the realm has no issuer or\n * the refresh token is invalid/expired.\n */\n public async refreshAccessToken(\n realm: string,\n refreshToken: string,\n ): Promise<{\n access_token: string;\n expires_in?: number;\n refresh_token?: string;\n }> {\n const entry = this.issuers.get(realm);\n if (!entry) {\n throw new AlephaError(`No issuer registered for realm '${realm}'`);\n }\n const { tokens } = await entry.issuer.refreshToken(refreshToken);\n return {\n access_token: tokens.access_token,\n expires_in: tokens.expires_in,\n refresh_token: tokens.refresh_token,\n };\n }\n\n /**\n * Register a new OAuth client. Used by the RFC 7591 DCR endpoint and,\n * later, by user/admin UIs (via the `source` field).\n */\n public async register(\n options: RegisterClientOptions,\n ): Promise<OAuthClientEntity> {\n if (options.redirectUris.length === 0) {\n throw new AlephaError(\"At least one redirect_uri is required\");\n }\n for (const uri of options.redirectUris) {\n if (!uri.startsWith(\"https://\") && !uri.startsWith(\"http://localhost\")) {\n throw new AlephaError(`Invalid redirect_uri: ${uri}`);\n }\n }\n\n const clientId = `mcp_${randomUUID().replace(/-/g, \"\")}`;\n const client = await this.repo.create({\n clientId,\n clientName: options.clientName || \"MCP Client\",\n redirectUris: options.redirectUris,\n scopes: options.scopes,\n realm: options.realm,\n source: options.source ?? \"dcr\",\n createdByUserId: options.createdByUserId,\n });\n\n this.log.info(\"OAuth client registered\", {\n clientId,\n source: client.source,\n });\n return client;\n }\n\n /**\n * Look up a client by its public `clientId`. Returns null if unknown.\n */\n public async findByClientId(\n clientId: string,\n ): Promise<OAuthClientEntity | null> {\n return (\n (await this.repo.findOne({ where: { clientId: { eq: clientId } } })) ??\n null\n );\n }\n\n /**\n * Exact-match redirect_uri check. OAuth 2.1 forbids substring/prefix\n * matching — the value must equal a registered URI byte-for-byte.\n */\n public isRedirectUriAllowed(\n client: OAuthClientEntity,\n redirectUri: string,\n ): boolean {\n return client.redirectUris.includes(redirectUri);\n }\n\n /**\n * Mint a stateless authorization code: a short-lived signed JWT\n * (`typ: \"oauth_code\"`) carrying the grant. No server-side code storage.\n */\n public async createAuthorizationCode(\n realm: string,\n grant: {\n userId: string;\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n scopes: string[];\n resource?: string;\n },\n ): Promise<string> {\n const iat = this.dateTime.now().unix();\n return this.jwt.create(\n {\n sub: grant.userId,\n client_id: grant.clientId,\n redirect_uri: grant.redirectUri,\n code_challenge: grant.codeChallenge,\n scopes: grant.scopes,\n resource: grant.resource,\n iat,\n exp: iat + 60,\n jti: randomUUID(),\n },\n realm,\n { header: { typ: \"oauth_code\" } },\n );\n }\n\n /**\n * Verify and atomically consume an authorization code. Throws on expiry,\n * replay, client/redirect mismatch, or PKCE failure.\n */\n public async consumeAuthorizationCode(\n realm: string,\n code: string,\n check: { clientId: string; redirectUri: string; codeVerifier: string },\n ): Promise<{ userId: string; scopes: string[]; resource?: string }> {\n const { result } = await this.jwt.parse(code, realm, {\n typ: \"oauth_code\",\n });\n const payload = result.payload as Record<string, unknown>;\n\n const jti = payload.jti as string;\n if (this.usedCodes.has(jti)) {\n throw new AlephaError(\"Authorization code already used\");\n }\n if (payload.client_id !== check.clientId) {\n throw new AlephaError(\"client_id mismatch\");\n }\n if (payload.redirect_uri !== check.redirectUri) {\n throw new AlephaError(\"redirect_uri mismatch\");\n }\n\n const computed = createHash(\"sha256\")\n .update(check.codeVerifier)\n .digest(\"base64url\");\n if (computed !== payload.code_challenge) {\n throw new AlephaError(\"PKCE verification failed\");\n }\n\n this.usedCodes.add(jti);\n return {\n userId: payload.sub as string,\n scopes: (payload.scopes as string[]) ?? [],\n resource: payload.resource as string | undefined,\n };\n }\n}\n","import { $atom, $inject, $state, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { $route } from \"alepha/server\";\nimport { renderConsentPage } from \"../helpers/consentPage.ts\";\nimport {\n buildAuthorizationServerMetadata,\n buildProtectedResourceMetadata,\n} from \"../helpers/oauthMetadata.ts\";\nimport { authorizeDecisionBodySchema } from \"../schemas/authorizeDecisionBodySchema.ts\";\nimport { authorizeQuerySchema } from \"../schemas/authorizeQuerySchema.ts\";\nimport { registerClientBodySchema } from \"../schemas/registerClientBodySchema.ts\";\nimport { tokenRequestBodySchema } from \"../schemas/tokenRequestBodySchema.ts\";\nimport { OAuthClientService } from \"../services/OAuthClientService.ts\";\n\n/**\n * Configuration for the OAuth authorization server.\n * `realm` is the issuer realm whose JWTs are minted as access tokens;\n * `resource` is the path of the protected MCP endpoint;\n * `loginPath` is the app-level login page unauthenticated users are\n * redirected to from the authorize endpoint.\n */\nexport const oauthOptions = $atom({\n name: \"alepha.api.oauth.options\",\n description: \"Configuration for the OAuth authorization server.\",\n schema: t.object({\n realm: t.text({ default: \"users\" }),\n resource: t.text({ default: \"/mcp\" }),\n loginPath: t.text({ default: \"/login\" }),\n }),\n default: { realm: \"users\", resource: \"/mcp\", loginPath: \"/login\" },\n});\n\n/**\n * OAuth 2.1 authorization server endpoints: discovery metadata and\n * RFC 7591 dynamic client registration. Authorize/token routes are added\n * separately.\n */\nexport class OAuthController {\n protected readonly log = $logger();\n protected readonly options = $state(oauthOptions);\n protected readonly clients = $inject(OAuthClientService);\n\n /**\n * Absolute origin of the current request, e.g. https://app.com.\n */\n protected baseUrl(url: URL): string {\n return `${url.protocol}//${url.host}`;\n }\n\n metadata = $route({\n method: \"GET\",\n path: \"/.well-known/oauth-authorization-server\",\n handler: ({ url, reply }) => {\n reply.headers[\"content-type\"] = \"application/json\";\n reply.body = JSON.stringify(\n buildAuthorizationServerMetadata(this.baseUrl(url)),\n );\n },\n });\n\n protectedResource = $route({\n method: \"GET\",\n path: \"/.well-known/oauth-protected-resource\",\n handler: ({ url, reply }) => {\n const base = this.baseUrl(url);\n reply.headers[\"content-type\"] = \"application/json\";\n reply.body = JSON.stringify(\n buildProtectedResourceMetadata(base, `${base}${this.options.resource}`),\n );\n },\n });\n\n register = $route({\n method: \"POST\",\n path: \"/oauth/register\",\n schema: { body: registerClientBodySchema },\n handler: async ({ body, reply }) => {\n const client = await this.clients.register({\n realm: this.options.realm,\n clientName: body.client_name ?? \"MCP Client\",\n redirectUris: body.redirect_uris,\n scopes: body.scope ? body.scope.split(\" \") : [\"mcp\"],\n source: \"dcr\",\n });\n reply.status = 201;\n reply.headers[\"content-type\"] = \"application/json\";\n reply.body = JSON.stringify({\n client_id: client.clientId,\n client_id_issued_at: Math.floor(\n new Date(client.createdAt).getTime() / 1000,\n ),\n client_name: client.clientName,\n redirect_uris: client.redirectUris,\n grant_types: [\"authorization_code\"],\n token_endpoint_auth_method: \"none\",\n });\n },\n });\n\n /**\n * GET /oauth/authorize — OAuth 2.1 authorization request. If the user\n * has no session, redirect to the realm login page with a return URL.\n * If authenticated, render the consent screen.\n */\n authorize = $route({\n method: \"GET\",\n path: \"/oauth/authorize\",\n schema: { query: authorizeQuerySchema },\n use: [],\n handler: async ({ query, user, url, reply }) => {\n if (query.response_type !== \"code\") {\n reply.status = 400;\n reply.body = \"unsupported response_type\";\n return;\n }\n if (query.code_challenge_method !== \"S256\") {\n reply.status = 400;\n reply.body = \"code_challenge_method must be S256\";\n return;\n }\n const client = await this.clients.findByClientId(query.client_id);\n if (!client || client.revokedAt) {\n reply.status = 400;\n reply.body = \"unknown client_id\";\n return;\n }\n if (!this.clients.isRedirectUriAllowed(client, query.redirect_uri)) {\n reply.status = 400;\n reply.body = \"redirect_uri not registered\";\n return;\n }\n if (!user) {\n const returnTo = encodeURIComponent(url.pathname + url.search);\n reply.redirect(\n `${this.options.loginPath}?redirect_uri=${returnTo}`,\n 302,\n );\n return;\n }\n reply.headers[\"content-type\"] = \"text/html; charset=utf-8\";\n reply.body = renderConsentPage({\n clientName: client.clientName,\n userName: user.name ?? user.email ?? \"your account\",\n scopes: query.scope ? query.scope.split(\" \") : client.scopes,\n hidden: {\n response_type: query.response_type,\n client_id: query.client_id,\n redirect_uri: query.redirect_uri,\n code_challenge: query.code_challenge,\n code_challenge_method: query.code_challenge_method,\n scope: query.scope ?? \"\",\n state: query.state ?? \"\",\n resource: query.resource ?? \"\",\n },\n });\n },\n });\n\n /**\n * POST /oauth/authorize — consent decision. On \"allow\", mint an\n * authorization code and redirect back to the client's redirect_uri.\n *\n * CSRF: this route carries no CSRF token and relies solely on the session\n * cookie to identify the user. This is a deliberate MVP/MCP tradeoff — a\n * forged consent submit can still only issue an authorization code to an\n * already-registered client, and that code is bound by PKCE, so the\n * attacker cannot redeem it without the matching code_verifier.\n */\n authorizeDecision = $route({\n method: \"POST\",\n path: \"/oauth/authorize\",\n schema: { body: authorizeDecisionBodySchema },\n use: [],\n handler: async ({ body, user, reply }) => {\n if (!user) {\n reply.status = 401;\n reply.body = \"authentication required\";\n return;\n }\n const client = await this.clients.findByClientId(body.client_id);\n if (\n !client ||\n client.revokedAt ||\n !this.clients.isRedirectUriAllowed(client, body.redirect_uri)\n ) {\n reply.status = 400;\n reply.body = \"invalid client\";\n return;\n }\n const redirect = new URL(body.redirect_uri);\n if (body.decision !== \"allow\") {\n redirect.searchParams.set(\"error\", \"access_denied\");\n if (body.state) redirect.searchParams.set(\"state\", body.state);\n reply.redirect(redirect.toString(), 302);\n return;\n }\n const code = await this.clients.createAuthorizationCode(\n this.options.realm,\n {\n userId: user.id,\n clientId: body.client_id,\n redirectUri: body.redirect_uri,\n codeChallenge: body.code_challenge,\n scopes: body.scope ? body.scope.split(\" \") : client.scopes,\n resource: body.resource || undefined,\n },\n );\n redirect.searchParams.set(\"code\", code);\n if (body.state) redirect.searchParams.set(\"state\", body.state);\n reply.redirect(redirect.toString(), 302);\n },\n });\n\n /**\n * POST /oauth/token — supports the `authorization_code` grant (verifies\n * PKCE, mints an access token via the realm issuer) and the\n * `refresh_token` grant (exchanges a refresh token for a fresh access\n * token, so a client stays connected without re-running the flow).\n */\n token = $route({\n method: \"POST\",\n path: \"/oauth/token\",\n schema: { body: tokenRequestBodySchema },\n use: [],\n handler: async ({ body, reply }) => {\n reply.headers[\"content-type\"] = \"application/json\";\n try {\n if (body.grant_type === \"authorization_code\") {\n const grant = await this.clients.consumeAuthorizationCode(\n this.options.realm,\n body.code ?? \"\",\n {\n clientId: body.client_id ?? \"\",\n redirectUri: body.redirect_uri ?? \"\",\n codeVerifier: body.code_verifier ?? \"\",\n },\n );\n const tokens = await this.clients.issueAccessToken(\n this.options.realm,\n { ...grant, clientId: body.client_id ?? \"\" },\n );\n reply.body = JSON.stringify({\n access_token: tokens.access_token,\n token_type: \"Bearer\",\n expires_in: tokens.expires_in,\n refresh_token: tokens.refresh_token,\n scope: grant.scopes.join(\" \"),\n });\n return;\n }\n\n if (body.grant_type === \"refresh_token\") {\n const tokens = await this.clients.refreshAccessToken(\n this.options.realm,\n body.refresh_token ?? \"\",\n );\n reply.body = JSON.stringify({\n access_token: tokens.access_token,\n token_type: \"Bearer\",\n expires_in: tokens.expires_in,\n refresh_token: tokens.refresh_token,\n });\n return;\n }\n\n reply.status = 400;\n reply.body = JSON.stringify({ error: \"unsupported_grant_type\" });\n } catch (e) {\n this.log.warn(\"OAuth token exchange failed\", e);\n reply.status = 400;\n reply.body = JSON.stringify({ error: \"invalid_grant\" });\n }\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { OAuthController } from \"./controllers/OAuthController.ts\";\nimport { OAuthClientService } from \"./services/OAuthClientService.ts\";\n\nexport {\n OAuthController,\n oauthOptions,\n} from \"./controllers/OAuthController.ts\";\nexport type { OAuthClientEntity } from \"./entities/oauthClientEntity.ts\";\nexport { oauthClientEntity } from \"./entities/oauthClientEntity.ts\";\nexport type { RegisterClientOptions } from \"./services/OAuthClientService.ts\";\nexport { OAuthClientService } from \"./services/OAuthClientService.ts\";\n\n/**\n * OAuth 2.1 authorization server module for MCP.\n *\n * **Features:**\n * - OAuth 2.1 authorization code flow with PKCE (RFC 7636)\n * - Dynamic Client Registration (RFC 7591)\n * - Authorization server metadata discovery (RFC 8414)\n * - Stateless authorization codes (short-lived signed JWTs)\n * - Single-use code enforcement\n *\n * **Integration:**\n * Register the module and configure the realm + protected resource path:\n *\n * ```ts\n * const app = Alepha.create()\n * .with(AlephaOAuth)\n * .set(oauthOptions, { realm: \"users\", resource: \"/mcp\" });\n * ```\n *\n * @module alepha.api.oauth\n */\nexport const AlephaOAuth = $module({\n name: \"alepha.api.oauth\",\n services: [OAuthClientService, OAuthController],\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/api/oauth/helpers/consentPage.ts","../../../src/api/oauth/helpers/oauthMetadata.ts","../../../src/api/oauth/schemas/authorizeDecisionBodySchema.ts","../../../src/api/oauth/schemas/authorizeQuerySchema.ts","../../../src/api/oauth/schemas/registerClientBodySchema.ts","../../../src/api/oauth/schemas/tokenRequestBodySchema.ts","../../../src/api/oauth/entities/oauthClientEntity.ts","../../../src/api/oauth/services/OAuthClientService.ts","../../../src/api/oauth/controllers/OAuthController.ts","../../../src/api/oauth/index.ts"],"sourcesContent":["/**\n * Renders the OAuth consent screen as a self-contained HTML document.\n * Pure server-rendered HTML — no client framework, no @alepha/ui dependency.\n * All authorization parameters are emitted as hidden inputs so the POST to\n * /oauth/authorize is stateless.\n */\nexport interface ConsentPageOptions {\n clientName: string;\n userName: string;\n scopes: string[];\n /**\n * Hidden field name -> value; round-trips the authorization request.\n */\n hidden: Record<string, string>;\n}\n\nconst escapeHtml = (s: string): string =>\n s.replace(\n /[&<>\"']/g,\n (c) =>\n ({\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n '\"': \"&quot;\",\n \"'\": \"&#39;\",\n })[c] as string,\n );\n\nexport const renderConsentPage = (options: ConsentPageOptions): string => {\n const hidden = Object.entries(options.hidden)\n .map(\n ([k, v]) =>\n `<input type=\"hidden\" name=\"${escapeHtml(k)}\" value=\"${escapeHtml(v)}\" />`,\n )\n .join(\"\");\n const scopes = options.scopes.length\n ? options.scopes.map((s) => `<li>${escapeHtml(s)}</li>`).join(\"\")\n : \"<li>Basic access</li>\";\n return `<!doctype html>\n<html lang=\"en\"><head><meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<title>Authorize ${escapeHtml(options.clientName)}</title>\n<style>\nbody{font-family:system-ui,sans-serif;background:#0b0b0f;color:#e5e5e5;\ndisplay:flex;min-height:100vh;align-items:center;justify-content:center;margin:0}\n.card{background:#16161d;border:1px solid #2a2a35;border-radius:12px;\npadding:32px;max-width:380px;width:100%}\nh1{font-size:18px;margin:0 0 4px}p{color:#9a9aa5;font-size:14px;margin:4px 0 16px}\nul{font-size:14px;padding-left:18px}\n.row{display:flex;gap:8px;margin-top:20px}\nbutton{flex:1;padding:10px;border-radius:8px;border:0;font-size:14px;cursor:pointer}\n.allow{background:#6d5cf0;color:#fff}.deny{background:#2a2a35;color:#e5e5e5}\n</style></head><body>\n<div class=\"card\">\n<h1>${escapeHtml(options.clientName)} wants to connect</h1>\n<p>Signed in as ${escapeHtml(options.userName)}</p>\n<p>It will be able to:</p>\n<ul>${scopes}</ul>\n<form method=\"POST\" action=\"/oauth/authorize\">${hidden}\n<div class=\"row\">\n<button class=\"deny\" type=\"submit\" name=\"decision\" value=\"deny\">Deny</button>\n<button class=\"allow\" type=\"submit\" name=\"decision\" value=\"allow\">Allow</button>\n</div></form></div></body></html>`;\n};\n","/**\n * RFC 8414 — OAuth 2.0 Authorization Server Metadata.\n * `baseUrl` is the absolute origin of the deployment (e.g. https://app.com).\n */\nexport const buildAuthorizationServerMetadata = (baseUrl: string) => ({\n issuer: baseUrl,\n authorization_endpoint: `${baseUrl}/oauth/authorize`,\n token_endpoint: `${baseUrl}/oauth/token`,\n registration_endpoint: `${baseUrl}/oauth/register`,\n response_types_supported: [\"code\"],\n grant_types_supported: [\"authorization_code\", \"refresh_token\"],\n code_challenge_methods_supported: [\"S256\"],\n token_endpoint_auth_methods_supported: [\"none\"],\n scopes_supported: [\"mcp\"],\n});\n\n/**\n * RFC 9728 — OAuth 2.0 Protected Resource Metadata.\n * `resource` is the absolute URL of the MCP endpoint being protected.\n */\nexport const buildProtectedResourceMetadata = (\n baseUrl: string,\n resource: string,\n) => ({\n resource,\n authorization_servers: [baseUrl],\n scopes_supported: [\"mcp\"],\n bearer_methods_supported: [\"header\"],\n});\n","import { t } from \"alepha\";\n\n/**\n * Body posted by the consent screen. All authorization-request parameters\n * are round-tripped through hidden form fields so the POST handler can\n * re-validate them without server-side session state.\n */\nexport const authorizeDecisionBodySchema = t.object({\n decision: t.text(),\n response_type: t.text(),\n client_id: t.text(),\n redirect_uri: t.text({ maxLength: 2048 }),\n code_challenge: t.text(),\n code_challenge_method: t.text(),\n scope: t.optional(t.text({ maxLength: 1024 })),\n state: t.optional(t.text({ maxLength: 512 })),\n resource: t.optional(t.text({ maxLength: 2048 })),\n});\n","import { t } from \"alepha\";\n\n/**\n * OAuth 2.1 authorization request query parameters (GET /oauth/authorize).\n */\nexport const authorizeQuerySchema = t.object({\n response_type: t.text(),\n client_id: t.text(),\n redirect_uri: t.text({ maxLength: 2048 }),\n code_challenge: t.text(),\n code_challenge_method: t.text(),\n scope: t.optional(t.text({ maxLength: 1024 })),\n state: t.optional(t.text({ maxLength: 512 })),\n resource: t.optional(t.text({ maxLength: 2048 })),\n});\n","import { t } from \"alepha\";\n\n/**\n * RFC 7591 Dynamic Client Registration request body.\n * Only the fields Alepha consumes are typed; unknown fields are ignored.\n */\nexport const registerClientBodySchema = t.object(\n {\n client_name: t.optional(t.text({ maxLength: 200 })),\n redirect_uris: t.array(t.text({ maxLength: 2048 }), { minItems: 1 }),\n scope: t.optional(t.text({ maxLength: 1024 })),\n grant_types: t.optional(t.array(t.text())),\n token_endpoint_auth_method: t.optional(t.text()),\n },\n { additionalProperties: true },\n);\n","import { t } from \"alepha\";\n\n/**\n * Body of a POST /oauth/token request. OAuth 2.1 mandates\n * application/x-www-form-urlencoded encoding (section 3.2.2). All fields are\n * optional at the schema level; the handler enforces the grant-specific\n * requirements and returns the appropriate OAuth error responses.\n */\nexport const tokenRequestBodySchema = t.object({\n grant_type: t.optional(t.text()),\n code: t.optional(t.text({ maxLength: 4096 })),\n client_id: t.optional(t.text()),\n redirect_uri: t.optional(t.text({ maxLength: 2048 })),\n code_verifier: t.optional(t.text({ maxLength: 256 })),\n refresh_token: t.optional(t.text({ maxLength: 4096 })),\n});\n","import { type Static, t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\n/**\n * A registered OAuth 2.1 client application.\n *\n * Rows are created by Dynamic Client Registration (RFC 7591) when an MCP\n * client (e.g. Claude) first connects. `source` records who created the\n * client; for DCR it is always `\"dcr\"` and `createdByUserId` is null until\n * a user completes an authorization.\n */\nexport const oauthClientEntity = $entity({\n name: \"oauth_clients\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n clientId: t.text({ maxLength: 64 }),\n clientName: t.text({ maxLength: 200 }),\n redirectUris: db.default(t.array(t.text({ maxLength: 2048 })), []),\n scopes: db.default(t.array(t.text({ maxLength: 64 })), []),\n realm: t.text({ maxLength: 100 }),\n source: db.default(t.text({ maxLength: 16 }), \"dcr\"),\n createdByUserId: t.optional(t.uuid()),\n lastUsedAt: t.optional(t.datetime()),\n revokedAt: t.optional(t.datetime()),\n }),\n indexes: [{ columns: [\"clientId\"], unique: true }],\n});\n\nexport type OAuthClientEntity = Static<typeof oauthClientEntity.schema>;\n","import { createHash, randomUUID } from \"node:crypto\";\nimport { $inject, Alepha, AlephaError } from \"alepha\";\nimport { DateTimeProvider } from \"alepha/datetime\";\nimport { $logger } from \"alepha/logger\";\nimport { $repository } from \"alepha/orm\";\nimport {\n type IssuerPrimitive,\n JwtProvider,\n type UserAccount,\n} from \"alepha/security\";\nimport {\n type OAuthClientEntity,\n oauthClientEntity,\n} from \"../entities/oauthClientEntity.ts\";\n\nexport interface RegisterClientOptions {\n realm: string;\n clientName: string;\n redirectUris: string[];\n scopes: string[];\n source?: \"dcr\" | \"user\" | \"admin\";\n createdByUserId?: string;\n}\n\n/**\n * Core OAuth 2.1 service backing the authorization server.\n *\n * Responsibilities:\n * - Client registration (RFC 7591 Dynamic Client Registration) and lookup,\n * with exact-match redirect_uri validation.\n * - Stateless PKCE authorization codes: minting short-lived signed JWTs that\n * carry the grant, and verifying/consuming them (replay, expiry, client and\n * redirect_uri checks, S256 PKCE).\n * - Realm issuer registry: realms register an issuer + user loader so the\n * token endpoint can mint access tokens without depending on realm wiring.\n */\nexport class OAuthClientService {\n protected readonly alepha = $inject(Alepha);\n protected readonly dateTime = $inject(DateTimeProvider);\n protected readonly log = $logger();\n protected readonly repo = $repository(oauthClientEntity);\n protected readonly jwt = $inject(JwtProvider);\n\n /**\n * Codes already redeemed in this process. Single-use enforcement only\n * needs to cover the ~60s code lifetime, so a bounded in-memory set is\n * sufficient even on serverless — an expired code fails JWT verification\n * regardless.\n */\n protected readonly usedCodes = new Set<string>();\n\n /**\n * Registry of realm issuers used to mint access tokens. Populated by\n * `$realm` (via `registerIssuer`) so the OAuth module does not depend on the\n * realm wiring directly.\n */\n protected readonly issuers = new Map<\n string,\n {\n issuer: IssuerPrimitive;\n loadUser: (userId: string) => Promise<UserAccount>;\n }\n >();\n\n /**\n * Register a realm issuer and a user loader. Called by `$realm` so the\n * OAuth token endpoint can mint access tokens for that realm.\n */\n public registerIssuer(\n realm: string,\n issuer: IssuerPrimitive,\n loadUser: (userId: string) => Promise<UserAccount>,\n ): void {\n this.issuers.set(realm, { issuer, loadUser });\n }\n\n /**\n * Mint an access token for a consumed authorization-code grant, using the\n * issuer registered for `realm`. Throws if the realm has no issuer.\n */\n public async issueAccessToken(\n realm: string,\n grant: {\n userId: string;\n scopes: string[];\n resource?: string;\n clientId?: string;\n },\n ): Promise<{\n access_token: string;\n expires_in?: number;\n refresh_token?: string;\n }> {\n const entry = this.issuers.get(realm);\n if (!entry) {\n throw new AlephaError(`No issuer registered for realm '${realm}'`);\n }\n const user = await entry.loadUser(grant.userId);\n // Tag the session the issuer creates with the OAuth client, so it can\n // later be surfaced as a \"connected app\" and revoked individually.\n const tokens = await entry.issuer.createToken(user, undefined, {\n clientId: grant.clientId,\n });\n return {\n access_token: tokens.access_token,\n expires_in: tokens.expires_in,\n refresh_token: tokens.refresh_token,\n };\n }\n\n /**\n * Exchange a refresh token for a fresh access token (OAuth 2.1\n * `refresh_token` grant), using the issuer registered for `realm`. Lets an\n * MCP client stay connected for the refresh token's full lifetime without\n * re-running the authorization flow. Throws if the realm has no issuer or\n * the refresh token is invalid/expired.\n */\n public async refreshAccessToken(\n realm: string,\n refreshToken: string,\n ): Promise<{\n access_token: string;\n expires_in?: number;\n refresh_token?: string;\n }> {\n const entry = this.issuers.get(realm);\n if (!entry) {\n throw new AlephaError(`No issuer registered for realm '${realm}'`);\n }\n const { tokens } = await entry.issuer.refreshToken(refreshToken);\n return {\n access_token: tokens.access_token,\n expires_in: tokens.expires_in,\n refresh_token: tokens.refresh_token,\n };\n }\n\n /**\n * Register a new OAuth client. Used by the RFC 7591 DCR endpoint and,\n * later, by user/admin UIs (via the `source` field).\n */\n public async register(\n options: RegisterClientOptions,\n ): Promise<OAuthClientEntity> {\n if (options.redirectUris.length === 0) {\n throw new AlephaError(\"At least one redirect_uri is required\");\n }\n for (const uri of options.redirectUris) {\n if (!uri.startsWith(\"https://\") && !uri.startsWith(\"http://localhost\")) {\n throw new AlephaError(`Invalid redirect_uri: ${uri}`);\n }\n }\n\n const clientId = `mcp_${randomUUID().replace(/-/g, \"\")}`;\n const client = await this.repo.create({\n clientId,\n clientName: options.clientName || \"MCP Client\",\n redirectUris: options.redirectUris,\n scopes: options.scopes,\n realm: options.realm,\n source: options.source ?? \"dcr\",\n createdByUserId: options.createdByUserId,\n });\n\n this.log.info(\"OAuth client registered\", {\n clientId,\n source: client.source,\n });\n return client;\n }\n\n /**\n * Look up a client by its public `clientId`. Returns null if unknown.\n */\n public async findByClientId(\n clientId: string,\n ): Promise<OAuthClientEntity | null> {\n return (\n (await this.repo.findOne({ where: { clientId: { eq: clientId } } })) ??\n null\n );\n }\n\n /**\n * Exact-match redirect_uri check. OAuth 2.1 forbids substring/prefix\n * matching — the value must equal a registered URI byte-for-byte.\n */\n public isRedirectUriAllowed(\n client: OAuthClientEntity,\n redirectUri: string,\n ): boolean {\n return client.redirectUris.includes(redirectUri);\n }\n\n /**\n * Mint a stateless authorization code: a short-lived signed JWT\n * (`typ: \"oauth_code\"`) carrying the grant. No server-side code storage.\n */\n public async createAuthorizationCode(\n realm: string,\n grant: {\n userId: string;\n clientId: string;\n redirectUri: string;\n codeChallenge: string;\n scopes: string[];\n resource?: string;\n },\n ): Promise<string> {\n const iat = this.dateTime.now().unix();\n return this.jwt.create(\n {\n sub: grant.userId,\n client_id: grant.clientId,\n redirect_uri: grant.redirectUri,\n code_challenge: grant.codeChallenge,\n scopes: grant.scopes,\n resource: grant.resource,\n iat,\n exp: iat + 60,\n jti: randomUUID(),\n },\n realm,\n { header: { typ: \"oauth_code\" } },\n );\n }\n\n /**\n * Verify and atomically consume an authorization code. Throws on expiry,\n * replay, client/redirect mismatch, or PKCE failure.\n */\n public async consumeAuthorizationCode(\n realm: string,\n code: string,\n check: { clientId: string; redirectUri: string; codeVerifier: string },\n ): Promise<{ userId: string; scopes: string[]; resource?: string }> {\n const { result } = await this.jwt.parse(code, realm, {\n typ: \"oauth_code\",\n });\n const payload = result.payload as Record<string, unknown>;\n\n const jti = payload.jti as string;\n if (this.usedCodes.has(jti)) {\n throw new AlephaError(\"Authorization code already used\");\n }\n if (payload.client_id !== check.clientId) {\n throw new AlephaError(\"client_id mismatch\");\n }\n if (payload.redirect_uri !== check.redirectUri) {\n throw new AlephaError(\"redirect_uri mismatch\");\n }\n\n const computed = createHash(\"sha256\")\n .update(check.codeVerifier)\n .digest(\"base64url\");\n if (computed !== payload.code_challenge) {\n throw new AlephaError(\"PKCE verification failed\");\n }\n\n this.usedCodes.add(jti);\n return {\n userId: payload.sub as string,\n scopes: (payload.scopes as string[]) ?? [],\n resource: payload.resource as string | undefined,\n };\n }\n}\n","import { $atom, $inject, $state, t } from \"alepha\";\nimport { $logger } from \"alepha/logger\";\nimport { $route } from \"alepha/server\";\nimport { renderConsentPage } from \"../helpers/consentPage.ts\";\nimport {\n buildAuthorizationServerMetadata,\n buildProtectedResourceMetadata,\n} from \"../helpers/oauthMetadata.ts\";\nimport { authorizeDecisionBodySchema } from \"../schemas/authorizeDecisionBodySchema.ts\";\nimport { authorizeQuerySchema } from \"../schemas/authorizeQuerySchema.ts\";\nimport { registerClientBodySchema } from \"../schemas/registerClientBodySchema.ts\";\nimport { tokenRequestBodySchema } from \"../schemas/tokenRequestBodySchema.ts\";\nimport { OAuthClientService } from \"../services/OAuthClientService.ts\";\n\n/**\n * Configuration for the OAuth authorization server.\n * `realm` is the issuer realm whose JWTs are minted as access tokens;\n * `resource` is the path of the protected MCP endpoint;\n * `loginPath` is the app-level login page unauthenticated users are\n * redirected to from the authorize endpoint.\n */\nexport const oauthOptions = $atom({\n name: \"alepha.api.oauth.options\",\n description: \"Configuration for the OAuth authorization server.\",\n schema: t.object({\n realm: t.text({ default: \"users\" }),\n resource: t.text({ default: \"/mcp\" }),\n loginPath: t.text({ default: \"/login\" }),\n }),\n default: { realm: \"users\", resource: \"/mcp\", loginPath: \"/login\" },\n});\n\n/**\n * OAuth 2.1 authorization server endpoints: discovery metadata and\n * RFC 7591 dynamic client registration. Authorize/token routes are added\n * separately.\n */\nexport class OAuthController {\n protected readonly log = $logger();\n protected readonly options = $state(oauthOptions);\n protected readonly clients = $inject(OAuthClientService);\n\n /**\n * Absolute origin of the current request, e.g. https://app.com.\n */\n protected baseUrl(url: URL): string {\n return `${url.protocol}//${url.host}`;\n }\n\n metadata = $route({\n method: \"GET\",\n path: \"/.well-known/oauth-authorization-server\",\n handler: ({ url, reply }) => {\n reply.headers[\"content-type\"] = \"application/json\";\n reply.body = JSON.stringify(\n buildAuthorizationServerMetadata(this.baseUrl(url)),\n );\n },\n });\n\n protectedResource = $route({\n method: \"GET\",\n path: \"/.well-known/oauth-protected-resource\",\n handler: ({ url, reply }) => {\n const base = this.baseUrl(url);\n reply.headers[\"content-type\"] = \"application/json\";\n reply.body = JSON.stringify(\n buildProtectedResourceMetadata(base, `${base}${this.options.resource}`),\n );\n },\n });\n\n register = $route({\n method: \"POST\",\n path: \"/oauth/register\",\n schema: { body: registerClientBodySchema },\n handler: async ({ body, reply }) => {\n const client = await this.clients.register({\n realm: this.options.realm,\n clientName: body.client_name ?? \"MCP Client\",\n redirectUris: body.redirect_uris,\n scopes: body.scope ? body.scope.split(\" \") : [\"mcp\"],\n source: \"dcr\",\n });\n reply.status = 201;\n reply.headers[\"content-type\"] = \"application/json\";\n reply.body = JSON.stringify({\n client_id: client.clientId,\n client_id_issued_at: Math.floor(\n new Date(client.createdAt).getTime() / 1000,\n ),\n client_name: client.clientName,\n redirect_uris: client.redirectUris,\n grant_types: [\"authorization_code\"],\n token_endpoint_auth_method: \"none\",\n });\n },\n });\n\n /**\n * GET /oauth/authorize — OAuth 2.1 authorization request. If the user\n * has no session, redirect to the realm login page with a return URL.\n * If authenticated, render the consent screen.\n */\n authorize = $route({\n method: \"GET\",\n path: \"/oauth/authorize\",\n schema: { query: authorizeQuerySchema },\n use: [],\n handler: async ({ query, user, url, reply }) => {\n if (query.response_type !== \"code\") {\n reply.status = 400;\n reply.body = \"unsupported response_type\";\n return;\n }\n if (query.code_challenge_method !== \"S256\") {\n reply.status = 400;\n reply.body = \"code_challenge_method must be S256\";\n return;\n }\n const client = await this.clients.findByClientId(query.client_id);\n if (!client || client.revokedAt) {\n reply.status = 400;\n reply.body = \"unknown client_id\";\n return;\n }\n if (!this.clients.isRedirectUriAllowed(client, query.redirect_uri)) {\n reply.status = 400;\n reply.body = \"redirect_uri not registered\";\n return;\n }\n if (!user) {\n const returnTo = encodeURIComponent(url.pathname + url.search);\n reply.redirect(\n `${this.options.loginPath}?redirect_uri=${returnTo}`,\n 302,\n );\n return;\n }\n reply.headers[\"content-type\"] = \"text/html; charset=utf-8\";\n reply.body = renderConsentPage({\n clientName: client.clientName,\n userName: user.name ?? user.email ?? \"your account\",\n scopes: query.scope ? query.scope.split(\" \") : client.scopes,\n hidden: {\n response_type: query.response_type,\n client_id: query.client_id,\n redirect_uri: query.redirect_uri,\n code_challenge: query.code_challenge,\n code_challenge_method: query.code_challenge_method,\n scope: query.scope ?? \"\",\n state: query.state ?? \"\",\n resource: query.resource ?? \"\",\n },\n });\n },\n });\n\n /**\n * POST /oauth/authorize — consent decision. On \"allow\", mint an\n * authorization code and redirect back to the client's redirect_uri.\n *\n * CSRF: this route carries no CSRF token and relies solely on the session\n * cookie to identify the user. This is a deliberate MVP/MCP tradeoff — a\n * forged consent submit can still only issue an authorization code to an\n * already-registered client, and that code is bound by PKCE, so the\n * attacker cannot redeem it without the matching code_verifier.\n */\n authorizeDecision = $route({\n method: \"POST\",\n path: \"/oauth/authorize\",\n schema: { body: authorizeDecisionBodySchema },\n use: [],\n handler: async ({ body, user, reply }) => {\n if (!user) {\n reply.status = 401;\n reply.body = \"authentication required\";\n return;\n }\n const client = await this.clients.findByClientId(body.client_id);\n if (\n !client ||\n client.revokedAt ||\n !this.clients.isRedirectUriAllowed(client, body.redirect_uri)\n ) {\n reply.status = 400;\n reply.body = \"invalid client\";\n return;\n }\n const redirect = new URL(body.redirect_uri);\n if (body.decision !== \"allow\") {\n redirect.searchParams.set(\"error\", \"access_denied\");\n if (body.state) redirect.searchParams.set(\"state\", body.state);\n reply.redirect(redirect.toString(), 302);\n return;\n }\n const code = await this.clients.createAuthorizationCode(\n this.options.realm,\n {\n userId: user.id,\n clientId: body.client_id,\n redirectUri: body.redirect_uri,\n codeChallenge: body.code_challenge,\n scopes: body.scope ? body.scope.split(\" \") : client.scopes,\n resource: body.resource || undefined,\n },\n );\n redirect.searchParams.set(\"code\", code);\n if (body.state) redirect.searchParams.set(\"state\", body.state);\n reply.redirect(redirect.toString(), 302);\n },\n });\n\n /**\n * POST /oauth/token — supports the `authorization_code` grant (verifies\n * PKCE, mints an access token via the realm issuer) and the\n * `refresh_token` grant (exchanges a refresh token for a fresh access\n * token, so a client stays connected without re-running the flow).\n */\n token = $route({\n method: \"POST\",\n path: \"/oauth/token\",\n schema: { body: tokenRequestBodySchema },\n use: [],\n handler: async ({ body, reply }) => {\n reply.headers[\"content-type\"] = \"application/json\";\n try {\n if (body.grant_type === \"authorization_code\") {\n const grant = await this.clients.consumeAuthorizationCode(\n this.options.realm,\n body.code ?? \"\",\n {\n clientId: body.client_id ?? \"\",\n redirectUri: body.redirect_uri ?? \"\",\n codeVerifier: body.code_verifier ?? \"\",\n },\n );\n const tokens = await this.clients.issueAccessToken(\n this.options.realm,\n { ...grant, clientId: body.client_id ?? \"\" },\n );\n reply.body = JSON.stringify({\n access_token: tokens.access_token,\n token_type: \"Bearer\",\n expires_in: tokens.expires_in,\n refresh_token: tokens.refresh_token,\n scope: grant.scopes.join(\" \"),\n });\n return;\n }\n\n if (body.grant_type === \"refresh_token\") {\n const tokens = await this.clients.refreshAccessToken(\n this.options.realm,\n body.refresh_token ?? \"\",\n );\n reply.body = JSON.stringify({\n access_token: tokens.access_token,\n token_type: \"Bearer\",\n expires_in: tokens.expires_in,\n refresh_token: tokens.refresh_token,\n });\n return;\n }\n\n reply.status = 400;\n reply.body = JSON.stringify({ error: \"unsupported_grant_type\" });\n } catch (e) {\n this.log.warn(\"OAuth token exchange failed\", e);\n reply.status = 400;\n reply.body = JSON.stringify({ error: \"invalid_grant\" });\n }\n },\n });\n}\n","import { $module } from \"alepha\";\nimport { OAuthController } from \"./controllers/OAuthController.ts\";\nimport { OAuthClientService } from \"./services/OAuthClientService.ts\";\n\nexport {\n OAuthController,\n oauthOptions,\n} from \"./controllers/OAuthController.ts\";\nexport type { OAuthClientEntity } from \"./entities/oauthClientEntity.ts\";\nexport { oauthClientEntity } from \"./entities/oauthClientEntity.ts\";\nexport type { RegisterClientOptions } from \"./services/OAuthClientService.ts\";\nexport { OAuthClientService } from \"./services/OAuthClientService.ts\";\n\n/**\n * OAuth 2.1 authorization server module for MCP.\n *\n * **Features:**\n * - OAuth 2.1 authorization code flow with PKCE (RFC 7636)\n * - Dynamic Client Registration (RFC 7591)\n * - Authorization server metadata discovery (RFC 8414)\n * - Stateless authorization codes (short-lived signed JWTs)\n * - Single-use code enforcement\n *\n * **Integration:**\n * Register the module and configure the realm + protected resource path:\n *\n * ```ts\n * const app = Alepha.create()\n * .with(AlephaOAuth)\n * .set(oauthOptions, { realm: \"users\", resource: \"/mcp\" });\n * ```\n *\n * @module alepha.api.oauth\n */\nexport const AlephaOAuth = $module({\n name: \"alepha.api.oauth\",\n services: [OAuthClientService, OAuthController],\n});\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.js","names":[],"sources":["../../../src/api/organizations/entities/organizations.ts","../../../src/api/organizations/schemas/createOrganizationSchema.ts","../../../src/api/organizations/schemas/organizationQuerySchema.ts","../../../src/api/organizations/schemas/organizationResourceSchema.ts","../../../src/api/organizations/schemas/updateOrganizationSchema.ts"],"sourcesContent":["import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const organizations = $entity({\n name: \"organizations\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n\n name: t.text(),\n slug: t.text({ minLength: 2, maxLength: 100 }),\n enabled: db.default(t.boolean(), true),\n }),\n indexes: [{ columns: [\"slug\"], unique: true }],\n});\n\nexport type OrganizationEntity = Static<typeof organizations.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const createOrganizationSchema = t.object({\n name: t.text(),\n slug: t.text({ minLength: 2, maxLength: 100 }),\n enabled: t.optional(t.boolean()),\n});\n\nexport type CreateOrganization = Static<typeof createOrganizationSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const organizationQuerySchema = t.extend(pageQuerySchema, {\n name: t.optional(t.text({ description: \"Filter by name (partial match)\" })),\n enabled: t.optional(t.boolean({ description: \"Filter by enabled status\" })),\n});\n\nexport type OrganizationQuery = Static<typeof organizationQuerySchema>;\n","import type { Static } from \"alepha\";\nimport { organizations } from \"../entities/organizations.ts\";\n\nexport const organizationResourceSchema = organizations.schema;\n\nexport type OrganizationResource = Static<typeof organizationResourceSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { createOrganizationSchema } from \"./createOrganizationSchema.ts\";\n\nexport const updateOrganizationSchema = t.partial(createOrganizationSchema);\n\nexport type UpdateOrganization = Static<typeof updateOrganizationSchema>;\n"],"mappings":";;;AAIA,MAAa,gBAAgB,QAAQ;CACnC,MAAM;CACN,QAAQ,EAAE,OAAO;EACf,IAAI,GAAG,WAAW,EAAE,MAAM,CAAC;EAC3B,SAAS,GAAG,SAAS;EACrB,WAAW,GAAG,WAAW;EACzB,WAAW,GAAG,WAAW;EAEzB,MAAM,EAAE,MAAM;EACd,MAAM,EAAE,KAAK;GAAE,WAAW;GAAG,WAAW;GAAK,CAAC;EAC9C,SAAS,GAAG,QAAQ,EAAE,SAAS,EAAE,KAAK;EACvC,CAAC;CACF,SAAS,CAAC;EAAE,SAAS,CAAC,OAAO;EAAE,QAAQ;EAAM,CAAC;CAC/C,CAAC;;;ACdF,MAAa,2BAA2B,EAAE,OAAO;CAC/C,MAAM,EAAE,MAAM;CACd,MAAM,EAAE,KAAK;EAAE,WAAW;EAAG,WAAW;EAAK,CAAC;CAC9C,SAAS,EAAE,SAAS,EAAE,SAAS,CAAC;CACjC,CAAC;;;ACHF,MAAa,0BAA0B,EAAE,OAAO,iBAAiB;CAC/D,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,kCAAkC,CAAC,CAAC;CAC3E,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,4BAA4B,CAAC,CAAC;CAC5E,CAAC;;;ACJF,MAAa,6BAA6B,cAAc;;;ACCxD,MAAa,2BAA2B,EAAE,QAAQ,yBAAyB"}
1
+ {"version":3,"file":"index.browser.js","names":[],"sources":["../../../src/api/organizations/entities/organizations.ts","../../../src/api/organizations/schemas/createOrganizationSchema.ts","../../../src/api/organizations/schemas/organizationQuerySchema.ts","../../../src/api/organizations/schemas/organizationResourceSchema.ts","../../../src/api/organizations/schemas/updateOrganizationSchema.ts"],"sourcesContent":["import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { $entity, db } from \"alepha/orm\";\n\nexport const organizations = $entity({\n name: \"organizations\",\n schema: t.object({\n id: db.primaryKey(t.uuid()),\n version: db.version(),\n createdAt: db.createdAt(),\n updatedAt: db.updatedAt(),\n\n name: t.text(),\n slug: t.text({ minLength: 2, maxLength: 100 }),\n enabled: db.default(t.boolean(), true),\n }),\n indexes: [{ columns: [\"slug\"], unique: true }],\n});\n\nexport type OrganizationEntity = Static<typeof organizations.schema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\n\nexport const createOrganizationSchema = t.object({\n name: t.text(),\n slug: t.text({ minLength: 2, maxLength: 100 }),\n enabled: t.optional(t.boolean()),\n});\n\nexport type CreateOrganization = Static<typeof createOrganizationSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { pageQuerySchema } from \"alepha/orm\";\n\nexport const organizationQuerySchema = t.extend(pageQuerySchema, {\n name: t.optional(t.text({ description: \"Filter by name (partial match)\" })),\n enabled: t.optional(t.boolean({ description: \"Filter by enabled status\" })),\n});\n\nexport type OrganizationQuery = Static<typeof organizationQuerySchema>;\n","import type { Static } from \"alepha\";\nimport { organizations } from \"../entities/organizations.ts\";\n\nexport const organizationResourceSchema = organizations.schema;\n\nexport type OrganizationResource = Static<typeof organizationResourceSchema>;\n","import type { Static } from \"alepha\";\nimport { t } from \"alepha\";\nimport { createOrganizationSchema } from \"./createOrganizationSchema.ts\";\n\nexport const updateOrganizationSchema = t.partial(createOrganizationSchema);\n\nexport type UpdateOrganization = Static<typeof updateOrganizationSchema>;\n"],"mappings":";;;AAIA,MAAa,gBAAgB,QAAQ;CACnC,MAAM;CACN,QAAQ,EAAE,OAAO;EACf,IAAI,GAAG,WAAW,EAAE,KAAK,CAAC;EAC1B,SAAS,GAAG,QAAQ;EACpB,WAAW,GAAG,UAAU;EACxB,WAAW,GAAG,UAAU;EAExB,MAAM,EAAE,KAAK;EACb,MAAM,EAAE,KAAK;GAAE,WAAW;GAAG,WAAW;EAAI,CAAC;EAC7C,SAAS,GAAG,QAAQ,EAAE,QAAQ,GAAG,IAAI;CACvC,CAAC;CACD,SAAS,CAAC;EAAE,SAAS,CAAC,MAAM;EAAG,QAAQ;CAAK,CAAC;AAC/C,CAAC;;;ACdD,MAAa,2BAA2B,EAAE,OAAO;CAC/C,MAAM,EAAE,KAAK;CACb,MAAM,EAAE,KAAK;EAAE,WAAW;EAAG,WAAW;CAAI,CAAC;CAC7C,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC;AACjC,CAAC;;;ACHD,MAAa,0BAA0B,EAAE,OAAO,iBAAiB;CAC/D,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,iCAAiC,CAAC,CAAC;CAC1E,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,2BAA2B,CAAC,CAAC;AAC5E,CAAC;;;ACJD,MAAa,6BAA6B,cAAc;;;ACCxD,MAAa,2BAA2B,EAAE,QAAQ,wBAAwB"}