alepha 0.13.0 → 0.13.2

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 (461) hide show
  1. package/README.md +1 -1
  2. package/dist/api-files/index.d.ts +28 -91
  3. package/dist/api-files/index.js +10 -755
  4. package/dist/api-files/index.js.map +1 -1
  5. package/dist/api-jobs/index.d.ts +67 -67
  6. package/dist/api-jobs/index.js +13 -13
  7. package/dist/api-jobs/index.js.map +1 -1
  8. package/dist/api-notifications/index.d.ts +129 -146
  9. package/dist/api-notifications/index.js +17 -39
  10. package/dist/api-notifications/index.js.map +1 -1
  11. package/dist/api-parameters/index.d.ts +21 -22
  12. package/dist/api-parameters/index.js +22 -22
  13. package/dist/api-parameters/index.js.map +1 -1
  14. package/dist/api-users/index.d.ts +224 -2001
  15. package/dist/api-users/index.js +914 -4787
  16. package/dist/api-users/index.js.map +1 -1
  17. package/dist/api-verifications/index.d.ts +96 -96
  18. package/dist/batch/index.d.ts +13 -13
  19. package/dist/batch/index.js +8 -8
  20. package/dist/batch/index.js.map +1 -1
  21. package/dist/bucket/index.d.ts +14 -14
  22. package/dist/bucket/index.js +12 -12
  23. package/dist/bucket/index.js.map +1 -1
  24. package/dist/cache/index.d.ts +11 -11
  25. package/dist/cache/index.js +9 -9
  26. package/dist/cache/index.js.map +1 -1
  27. package/dist/cli/{dist-Sz2EXvQX.cjs → dist-Dl9Vl7Ur.js} +17 -13
  28. package/dist/cli/{dist-BBPjuQ56.js.map → dist-Dl9Vl7Ur.js.map} +1 -1
  29. package/dist/cli/index.d.ts +31 -37
  30. package/dist/cli/index.js +152 -83
  31. package/dist/cli/index.js.map +1 -1
  32. package/dist/command/index.d.ts +19 -19
  33. package/dist/command/index.js +25 -25
  34. package/dist/command/index.js.map +1 -1
  35. package/dist/core/index.browser.js +218 -218
  36. package/dist/core/index.browser.js.map +1 -1
  37. package/dist/core/index.d.ts +232 -232
  38. package/dist/core/index.js +218 -218
  39. package/dist/core/index.js.map +1 -1
  40. package/dist/core/{index.cjs → index.native.js} +304 -455
  41. package/dist/core/index.native.js.map +1 -0
  42. package/dist/datetime/index.d.ts +9 -9
  43. package/dist/datetime/index.js +7 -7
  44. package/dist/datetime/index.js.map +1 -1
  45. package/dist/email/index.d.ts +16 -16
  46. package/dist/email/index.js +80 -82
  47. package/dist/email/index.js.map +1 -1
  48. package/dist/file/index.js +1 -1
  49. package/dist/file/index.js.map +1 -1
  50. package/dist/lock/index.d.ts +9 -9
  51. package/dist/lock/index.js +8 -8
  52. package/dist/lock/index.js.map +1 -1
  53. package/dist/lock-redis/index.js +3 -66
  54. package/dist/lock-redis/index.js.map +1 -1
  55. package/dist/logger/index.d.ts +5 -5
  56. package/dist/logger/index.js +8 -8
  57. package/dist/logger/index.js.map +1 -1
  58. package/dist/orm/index.browser.js +114 -114
  59. package/dist/orm/index.browser.js.map +1 -1
  60. package/dist/orm/index.d.ts +219 -219
  61. package/dist/orm/index.js +46 -46
  62. package/dist/orm/index.js.map +1 -1
  63. package/dist/queue/index.d.ts +25 -25
  64. package/dist/queue/index.js +20 -20
  65. package/dist/queue/index.js.map +1 -1
  66. package/dist/queue-redis/index.d.ts +2 -2
  67. package/dist/redis/index.d.ts +10 -10
  68. package/dist/retry/index.d.ts +20 -20
  69. package/dist/retry/index.js +9 -9
  70. package/dist/retry/index.js.map +1 -1
  71. package/dist/scheduler/index.d.ts +12 -12
  72. package/dist/scheduler/index.js +9 -9
  73. package/dist/scheduler/index.js.map +1 -1
  74. package/dist/security/index.d.ts +53 -53
  75. package/dist/security/index.js +32 -32
  76. package/dist/security/index.js.map +1 -1
  77. package/dist/server/index.browser.js +1 -1
  78. package/dist/server/index.browser.js.map +1 -1
  79. package/dist/server/index.d.ts +101 -101
  80. package/dist/server/index.js +17 -17
  81. package/dist/server/index.js.map +1 -1
  82. package/dist/server-auth/index.browser.js +4 -982
  83. package/dist/server-auth/index.browser.js.map +1 -1
  84. package/dist/server-auth/index.d.ts +204 -785
  85. package/dist/server-auth/index.js +47 -1239
  86. package/dist/server-auth/index.js.map +1 -1
  87. package/dist/server-cache/index.d.ts +10 -10
  88. package/dist/server-cache/index.js +2 -2
  89. package/dist/server-cache/index.js.map +1 -1
  90. package/dist/server-compress/index.d.ts +4 -4
  91. package/dist/server-compress/index.js +1 -1
  92. package/dist/server-compress/index.js.map +1 -1
  93. package/dist/server-cookies/index.browser.js +8 -8
  94. package/dist/server-cookies/index.browser.js.map +1 -1
  95. package/dist/server-cookies/index.d.ts +17 -17
  96. package/dist/server-cookies/index.js +10 -10
  97. package/dist/server-cookies/index.js.map +1 -1
  98. package/dist/server-cors/index.d.ts +17 -17
  99. package/dist/server-cors/index.js +9 -9
  100. package/dist/server-cors/index.js.map +1 -1
  101. package/dist/server-health/index.d.ts +2 -2
  102. package/dist/server-helmet/index.d.ts +1 -1
  103. package/dist/server-links/index.browser.js +12 -12
  104. package/dist/server-links/index.browser.js.map +1 -1
  105. package/dist/server-links/index.d.ts +59 -251
  106. package/dist/server-links/index.js +23 -502
  107. package/dist/server-links/index.js.map +1 -1
  108. package/dist/server-metrics/index.d.ts +4 -4
  109. package/dist/server-metrics/index.js +170 -174
  110. package/dist/server-metrics/index.js.map +1 -1
  111. package/dist/server-multipart/index.d.ts +2 -2
  112. package/dist/server-proxy/index.d.ts +12 -12
  113. package/dist/server-proxy/index.js +10 -10
  114. package/dist/server-proxy/index.js.map +1 -1
  115. package/dist/server-rate-limit/index.d.ts +22 -22
  116. package/dist/server-rate-limit/index.js +12 -12
  117. package/dist/server-rate-limit/index.js.map +1 -1
  118. package/dist/server-security/index.d.ts +24 -24
  119. package/dist/server-security/index.js +15 -15
  120. package/dist/server-security/index.js.map +1 -1
  121. package/dist/server-static/index.d.ts +14 -14
  122. package/dist/server-static/index.js +8 -8
  123. package/dist/server-static/index.js.map +1 -1
  124. package/dist/server-swagger/index.d.ts +25 -184
  125. package/dist/server-swagger/index.js +21 -724
  126. package/dist/server-swagger/index.js.map +1 -1
  127. package/dist/sms/index.d.ts +14 -14
  128. package/dist/sms/index.js +9 -9
  129. package/dist/sms/index.js.map +1 -1
  130. package/dist/thread/index.d.ts +11 -11
  131. package/dist/thread/index.js +17 -17
  132. package/dist/thread/index.js.map +1 -1
  133. package/dist/topic/index.d.ts +26 -26
  134. package/dist/topic/index.js +16 -16
  135. package/dist/topic/index.js.map +1 -1
  136. package/dist/topic-redis/index.d.ts +1 -1
  137. package/dist/vite/index.d.ts +3 -3
  138. package/dist/vite/index.js +12 -13
  139. package/dist/vite/index.js.map +1 -1
  140. package/dist/websocket/index.browser.js +11 -11
  141. package/dist/websocket/index.browser.js.map +1 -1
  142. package/dist/websocket/index.d.ts +51 -51
  143. package/dist/websocket/index.js +13 -13
  144. package/dist/websocket/index.js.map +1 -1
  145. package/package.json +62 -52
  146. package/src/api-files/services/FileService.ts +5 -7
  147. package/src/api-jobs/index.ts +1 -1
  148. package/src/api-jobs/{descriptors → primitives}/$job.ts +8 -8
  149. package/src/api-jobs/providers/JobProvider.ts +9 -9
  150. package/src/api-jobs/services/JobService.ts +5 -5
  151. package/src/api-notifications/index.ts +5 -15
  152. package/src/api-notifications/{descriptors → primitives}/$notification.ts +10 -10
  153. package/src/api-notifications/services/NotificationSenderService.ts +3 -3
  154. package/src/api-parameters/index.ts +1 -1
  155. package/src/api-parameters/{descriptors → primitives}/$config.ts +7 -12
  156. package/src/api-users/index.ts +1 -1
  157. package/src/api-users/{descriptors → primitives}/$userRealm.ts +8 -8
  158. package/src/api-users/providers/UserRealmProvider.ts +1 -1
  159. package/src/batch/index.ts +3 -3
  160. package/src/batch/{descriptors → primitives}/$batch.ts +13 -16
  161. package/src/bucket/index.ts +8 -8
  162. package/src/bucket/{descriptors → primitives}/$bucket.ts +8 -8
  163. package/src/bucket/providers/LocalFileStorageProvider.ts +3 -3
  164. package/src/cache/index.ts +4 -4
  165. package/src/cache/{descriptors → primitives}/$cache.ts +15 -15
  166. package/src/cli/apps/AlephaPackageBuilderCli.ts +30 -3
  167. package/src/cli/assets/appRouterTs.ts +9 -0
  168. package/src/cli/assets/indexHtml.ts +2 -1
  169. package/src/cli/assets/mainBrowserTs.ts +10 -0
  170. package/src/cli/commands/CoreCommands.ts +6 -5
  171. package/src/cli/commands/DrizzleCommands.ts +69 -61
  172. package/src/cli/commands/VerifyCommands.ts +2 -2
  173. package/src/cli/commands/ViteCommands.ts +6 -1
  174. package/src/cli/services/ProjectUtils.ts +78 -41
  175. package/src/command/index.ts +5 -5
  176. package/src/command/{descriptors → primitives}/$command.ts +9 -12
  177. package/src/command/providers/CliProvider.ts +10 -10
  178. package/src/core/Alepha.ts +30 -33
  179. package/src/core/constants/KIND.ts +1 -1
  180. package/src/core/constants/OPTIONS.ts +1 -1
  181. package/src/core/helpers/{descriptor.ts → primitive.ts} +18 -18
  182. package/src/core/helpers/ref.ts +1 -1
  183. package/src/core/index.shared.ts +8 -8
  184. package/src/core/{descriptors → primitives}/$context.ts +5 -5
  185. package/src/core/{descriptors → primitives}/$hook.ts +4 -4
  186. package/src/core/{descriptors → primitives}/$inject.ts +2 -2
  187. package/src/core/{descriptors → primitives}/$module.ts +9 -9
  188. package/src/core/{descriptors → primitives}/$use.ts +2 -2
  189. package/src/core/providers/CodecManager.ts +1 -1
  190. package/src/core/providers/JsonSchemaCodec.ts +1 -1
  191. package/src/core/providers/StateManager.ts +2 -2
  192. package/src/datetime/index.ts +3 -3
  193. package/src/datetime/{descriptors → primitives}/$interval.ts +6 -6
  194. package/src/email/index.ts +4 -4
  195. package/src/email/{descriptors → primitives}/$email.ts +8 -8
  196. package/src/file/index.ts +1 -1
  197. package/src/lock/index.ts +3 -3
  198. package/src/lock/{descriptors → primitives}/$lock.ts +10 -10
  199. package/src/logger/index.ts +8 -8
  200. package/src/logger/{descriptors → primitives}/$logger.ts +2 -2
  201. package/src/logger/services/Logger.ts +1 -1
  202. package/src/orm/constants/PG_SYMBOLS.ts +2 -2
  203. package/src/orm/index.browser.ts +2 -2
  204. package/src/orm/index.ts +8 -8
  205. package/src/orm/{descriptors → primitives}/$entity.ts +11 -11
  206. package/src/orm/{descriptors → primitives}/$repository.ts +2 -2
  207. package/src/orm/{descriptors → primitives}/$sequence.ts +8 -8
  208. package/src/orm/{descriptors → primitives}/$transaction.ts +4 -4
  209. package/src/orm/providers/DrizzleKitProvider.ts +1 -1
  210. package/src/orm/providers/PostgresTypeProvider.ts +3 -3
  211. package/src/orm/providers/RepositoryProvider.ts +4 -4
  212. package/src/orm/providers/drivers/DatabaseProvider.ts +7 -7
  213. package/src/orm/services/ModelBuilder.ts +9 -9
  214. package/src/orm/services/PgRelationManager.ts +2 -2
  215. package/src/orm/services/PostgresModelBuilder.ts +5 -5
  216. package/src/orm/services/Repository.ts +7 -7
  217. package/src/orm/services/SqliteModelBuilder.ts +5 -5
  218. package/src/queue/index.ts +7 -7
  219. package/src/queue/{descriptors → primitives}/$consumer.ts +15 -15
  220. package/src/queue/{descriptors → primitives}/$queue.ts +12 -12
  221. package/src/queue/providers/WorkerProvider.ts +7 -7
  222. package/src/retry/index.ts +3 -3
  223. package/src/retry/{descriptors → primitives}/$retry.ts +19 -17
  224. package/src/scheduler/index.ts +3 -3
  225. package/src/scheduler/{descriptors → primitives}/$scheduler.ts +9 -9
  226. package/src/scheduler/providers/CronProvider.ts +1 -1
  227. package/src/security/index.ts +9 -9
  228. package/src/security/{descriptors → primitives}/$permission.ts +7 -7
  229. package/src/security/{descriptors → primitives}/$realm.ts +6 -12
  230. package/src/security/{descriptors → primitives}/$role.ts +12 -12
  231. package/src/security/{descriptors → primitives}/$serviceAccount.ts +8 -8
  232. package/src/server/index.browser.ts +1 -1
  233. package/src/server/index.ts +14 -14
  234. package/src/server/{descriptors → primitives}/$action.ts +13 -13
  235. package/src/server/{descriptors → primitives}/$route.ts +9 -9
  236. package/src/server/providers/NodeHttpServerProvider.ts +2 -2
  237. package/src/server/services/HttpClient.ts +1 -1
  238. package/src/server-auth/index.browser.ts +1 -1
  239. package/src/server-auth/index.ts +6 -6
  240. package/src/server-auth/{descriptors → primitives}/$auth.ts +10 -10
  241. package/src/server-auth/{descriptors → primitives}/$authCredentials.ts +4 -4
  242. package/src/server-auth/{descriptors → primitives}/$authGithub.ts +4 -4
  243. package/src/server-auth/{descriptors → primitives}/$authGoogle.ts +4 -4
  244. package/src/server-auth/providers/ServerAuthProvider.ts +4 -4
  245. package/src/server-cache/providers/ServerCacheProvider.ts +7 -7
  246. package/src/server-compress/providers/ServerCompressProvider.ts +3 -3
  247. package/src/server-cookies/index.browser.ts +2 -2
  248. package/src/server-cookies/index.ts +5 -5
  249. package/src/server-cookies/{descriptors → primitives}/$cookie.browser.ts +12 -12
  250. package/src/server-cookies/{descriptors → primitives}/$cookie.ts +13 -13
  251. package/src/server-cookies/providers/ServerCookiesProvider.ts +4 -4
  252. package/src/server-cookies/services/CookieParser.ts +1 -1
  253. package/src/server-cors/index.ts +3 -3
  254. package/src/server-cors/{descriptors → primitives}/$cors.ts +11 -13
  255. package/src/server-cors/providers/ServerCorsProvider.ts +5 -5
  256. package/src/server-links/index.browser.ts +5 -5
  257. package/src/server-links/index.ts +9 -9
  258. package/src/server-links/{descriptors → primitives}/$remote.ts +11 -11
  259. package/src/server-links/providers/LinkProvider.ts +7 -7
  260. package/src/server-links/providers/{RemoteDescriptorProvider.ts → RemotePrimitiveProvider.ts} +6 -6
  261. package/src/server-links/providers/ServerLinksProvider.ts +3 -3
  262. package/src/server-proxy/index.ts +3 -3
  263. package/src/server-proxy/{descriptors → primitives}/$proxy.ts +8 -8
  264. package/src/server-proxy/providers/ServerProxyProvider.ts +4 -4
  265. package/src/server-rate-limit/index.ts +6 -6
  266. package/src/server-rate-limit/{descriptors → primitives}/$rateLimit.ts +13 -13
  267. package/src/server-rate-limit/providers/ServerRateLimitProvider.ts +5 -5
  268. package/src/server-security/index.ts +3 -3
  269. package/src/server-security/{descriptors → primitives}/$basicAuth.ts +13 -13
  270. package/src/server-security/providers/ServerBasicAuthProvider.ts +5 -5
  271. package/src/server-security/providers/ServerSecurityProvider.ts +4 -4
  272. package/src/server-static/index.ts +3 -3
  273. package/src/server-static/{descriptors → primitives}/$serve.ts +8 -10
  274. package/src/server-static/providers/ServerStaticProvider.ts +6 -6
  275. package/src/server-swagger/index.ts +5 -5
  276. package/src/server-swagger/{descriptors → primitives}/$swagger.ts +9 -9
  277. package/src/server-swagger/providers/ServerSwaggerProvider.ts +11 -10
  278. package/src/sms/index.ts +4 -4
  279. package/src/sms/{descriptors → primitives}/$sms.ts +8 -8
  280. package/src/thread/index.ts +3 -3
  281. package/src/thread/{descriptors → primitives}/$thread.ts +13 -13
  282. package/src/thread/providers/ThreadProvider.ts +7 -9
  283. package/src/topic/index.ts +5 -5
  284. package/src/topic/{descriptors → primitives}/$subscriber.ts +14 -14
  285. package/src/topic/{descriptors → primitives}/$topic.ts +10 -10
  286. package/src/topic/providers/TopicProvider.ts +4 -4
  287. package/src/vite/helpers/boot.ts +3 -3
  288. package/src/vite/tasks/copyAssets.ts +1 -1
  289. package/src/vite/tasks/generateSitemap.ts +3 -3
  290. package/src/vite/tasks/prerenderPages.ts +2 -2
  291. package/src/vite/tasks/runAlepha.ts +2 -2
  292. package/src/websocket/index.browser.ts +3 -3
  293. package/src/websocket/index.shared.ts +2 -2
  294. package/src/websocket/index.ts +4 -4
  295. package/src/websocket/interfaces/WebSocketInterfaces.ts +3 -3
  296. package/src/websocket/{descriptors → primitives}/$channel.ts +10 -10
  297. package/src/websocket/{descriptors → primitives}/$websocket.ts +8 -8
  298. package/src/websocket/providers/NodeWebSocketServerProvider.ts +7 -7
  299. package/src/websocket/providers/WebSocketServerProvider.ts +3 -3
  300. package/src/websocket/services/WebSocketClient.ts +5 -5
  301. package/dist/api-files/index.cjs +0 -1293
  302. package/dist/api-files/index.cjs.map +0 -1
  303. package/dist/api-files/index.d.cts +0 -829
  304. package/dist/api-jobs/index.cjs +0 -274
  305. package/dist/api-jobs/index.cjs.map +0 -1
  306. package/dist/api-jobs/index.d.cts +0 -654
  307. package/dist/api-notifications/index.cjs +0 -380
  308. package/dist/api-notifications/index.cjs.map +0 -1
  309. package/dist/api-notifications/index.d.cts +0 -289
  310. package/dist/api-parameters/index.cjs +0 -66
  311. package/dist/api-parameters/index.cjs.map +0 -1
  312. package/dist/api-parameters/index.d.cts +0 -84
  313. package/dist/api-users/index.cjs +0 -6009
  314. package/dist/api-users/index.cjs.map +0 -1
  315. package/dist/api-users/index.d.cts +0 -4740
  316. package/dist/api-verifications/index.cjs +0 -407
  317. package/dist/api-verifications/index.cjs.map +0 -1
  318. package/dist/api-verifications/index.d.cts +0 -207
  319. package/dist/batch/index.cjs +0 -408
  320. package/dist/batch/index.cjs.map +0 -1
  321. package/dist/batch/index.d.cts +0 -330
  322. package/dist/bin/index.cjs +0 -17
  323. package/dist/bin/index.cjs.map +0 -1
  324. package/dist/bin/index.d.cts +0 -1
  325. package/dist/bucket/index.cjs +0 -303
  326. package/dist/bucket/index.cjs.map +0 -1
  327. package/dist/bucket/index.d.cts +0 -355
  328. package/dist/cache/index.cjs +0 -241
  329. package/dist/cache/index.cjs.map +0 -1
  330. package/dist/cache/index.d.cts +0 -202
  331. package/dist/cache-redis/index.cjs +0 -84
  332. package/dist/cache-redis/index.cjs.map +0 -1
  333. package/dist/cache-redis/index.d.cts +0 -40
  334. package/dist/cli/chunk-DSlc6foC.cjs +0 -43
  335. package/dist/cli/dist-BBPjuQ56.js +0 -2778
  336. package/dist/cli/dist-Sz2EXvQX.cjs.map +0 -1
  337. package/dist/cli/index.cjs +0 -1241
  338. package/dist/cli/index.cjs.map +0 -1
  339. package/dist/cli/index.d.cts +0 -422
  340. package/dist/command/index.cjs +0 -693
  341. package/dist/command/index.cjs.map +0 -1
  342. package/dist/command/index.d.cts +0 -340
  343. package/dist/core/index.cjs.map +0 -1
  344. package/dist/core/index.d.cts +0 -1927
  345. package/dist/datetime/index.cjs +0 -318
  346. package/dist/datetime/index.cjs.map +0 -1
  347. package/dist/datetime/index.d.cts +0 -145
  348. package/dist/email/index.cjs +0 -10874
  349. package/dist/email/index.cjs.map +0 -1
  350. package/dist/email/index.d.cts +0 -186
  351. package/dist/fake/index.cjs +0 -34641
  352. package/dist/fake/index.cjs.map +0 -1
  353. package/dist/fake/index.d.cts +0 -74
  354. package/dist/file/index.cjs +0 -1212
  355. package/dist/file/index.cjs.map +0 -1
  356. package/dist/file/index.d.cts +0 -698
  357. package/dist/lock/index.cjs +0 -226
  358. package/dist/lock/index.cjs.map +0 -1
  359. package/dist/lock/index.d.cts +0 -361
  360. package/dist/lock-redis/index.cjs +0 -113
  361. package/dist/lock-redis/index.cjs.map +0 -1
  362. package/dist/lock-redis/index.d.cts +0 -24
  363. package/dist/logger/index.cjs +0 -521
  364. package/dist/logger/index.cjs.map +0 -1
  365. package/dist/logger/index.d.cts +0 -281
  366. package/dist/orm/index.cjs +0 -2986
  367. package/dist/orm/index.cjs.map +0 -1
  368. package/dist/orm/index.d.cts +0 -2213
  369. package/dist/queue/index.cjs +0 -1044
  370. package/dist/queue/index.cjs.map +0 -1
  371. package/dist/queue/index.d.cts +0 -1265
  372. package/dist/queue-redis/index.cjs +0 -873
  373. package/dist/queue-redis/index.cjs.map +0 -1
  374. package/dist/queue-redis/index.d.cts +0 -82
  375. package/dist/redis/index.cjs +0 -153
  376. package/dist/redis/index.cjs.map +0 -1
  377. package/dist/redis/index.d.cts +0 -82
  378. package/dist/retry/index.cjs +0 -146
  379. package/dist/retry/index.cjs.map +0 -1
  380. package/dist/retry/index.d.cts +0 -172
  381. package/dist/router/index.cjs +0 -111
  382. package/dist/router/index.cjs.map +0 -1
  383. package/dist/router/index.d.cts +0 -46
  384. package/dist/scheduler/index.cjs +0 -576
  385. package/dist/scheduler/index.cjs.map +0 -1
  386. package/dist/scheduler/index.d.cts +0 -145
  387. package/dist/security/index.cjs +0 -2402
  388. package/dist/security/index.cjs.map +0 -1
  389. package/dist/security/index.d.cts +0 -598
  390. package/dist/server/index.cjs +0 -1680
  391. package/dist/server/index.cjs.map +0 -1
  392. package/dist/server/index.d.cts +0 -810
  393. package/dist/server-auth/index.cjs +0 -3146
  394. package/dist/server-auth/index.cjs.map +0 -1
  395. package/dist/server-auth/index.d.cts +0 -1164
  396. package/dist/server-cache/index.cjs +0 -252
  397. package/dist/server-cache/index.cjs.map +0 -1
  398. package/dist/server-cache/index.d.cts +0 -164
  399. package/dist/server-compress/index.cjs +0 -141
  400. package/dist/server-compress/index.cjs.map +0 -1
  401. package/dist/server-compress/index.d.cts +0 -38
  402. package/dist/server-cookies/index.cjs +0 -234
  403. package/dist/server-cookies/index.cjs.map +0 -1
  404. package/dist/server-cookies/index.d.cts +0 -144
  405. package/dist/server-cors/index.cjs +0 -201
  406. package/dist/server-cors/index.cjs.map +0 -1
  407. package/dist/server-cors/index.d.cts +0 -140
  408. package/dist/server-health/index.cjs +0 -62
  409. package/dist/server-health/index.cjs.map +0 -1
  410. package/dist/server-health/index.d.cts +0 -58
  411. package/dist/server-helmet/index.cjs +0 -131
  412. package/dist/server-helmet/index.cjs.map +0 -1
  413. package/dist/server-helmet/index.d.cts +0 -97
  414. package/dist/server-links/index.cjs +0 -992
  415. package/dist/server-links/index.cjs.map +0 -1
  416. package/dist/server-links/index.d.cts +0 -513
  417. package/dist/server-metrics/index.cjs +0 -4535
  418. package/dist/server-metrics/index.cjs.map +0 -1
  419. package/dist/server-metrics/index.d.cts +0 -35
  420. package/dist/server-multipart/index.cjs +0 -237
  421. package/dist/server-multipart/index.cjs.map +0 -1
  422. package/dist/server-multipart/index.d.cts +0 -50
  423. package/dist/server-proxy/index.cjs +0 -186
  424. package/dist/server-proxy/index.cjs.map +0 -1
  425. package/dist/server-proxy/index.d.cts +0 -234
  426. package/dist/server-rate-limit/index.cjs +0 -241
  427. package/dist/server-rate-limit/index.cjs.map +0 -1
  428. package/dist/server-rate-limit/index.d.cts +0 -183
  429. package/dist/server-security/index.cjs +0 -316
  430. package/dist/server-security/index.cjs.map +0 -1
  431. package/dist/server-security/index.d.cts +0 -173
  432. package/dist/server-static/index.cjs +0 -170
  433. package/dist/server-static/index.cjs.map +0 -1
  434. package/dist/server-static/index.d.cts +0 -121
  435. package/dist/server-swagger/index.cjs +0 -1021
  436. package/dist/server-swagger/index.cjs.map +0 -1
  437. package/dist/server-swagger/index.d.cts +0 -382
  438. package/dist/sms/index.cjs +0 -221
  439. package/dist/sms/index.cjs.map +0 -1
  440. package/dist/sms/index.d.cts +0 -130
  441. package/dist/thread/index.cjs +0 -350
  442. package/dist/thread/index.cjs.map +0 -1
  443. package/dist/thread/index.d.cts +0 -260
  444. package/dist/topic/index.cjs +0 -282
  445. package/dist/topic/index.cjs.map +0 -1
  446. package/dist/topic/index.d.cts +0 -523
  447. package/dist/topic-redis/index.cjs +0 -71
  448. package/dist/topic-redis/index.cjs.map +0 -1
  449. package/dist/topic-redis/index.d.cts +0 -42
  450. package/dist/vite/index.cjs +0 -1077
  451. package/dist/vite/index.cjs.map +0 -1
  452. package/dist/vite/index.d.cts +0 -542
  453. package/dist/websocket/index.cjs +0 -1117
  454. package/dist/websocket/index.cjs.map +0 -1
  455. package/dist/websocket/index.d.cts +0 -861
  456. package/src/api-notifications/providers/MemorySmsProvider.ts +0 -20
  457. package/src/api-notifications/providers/SmsProvider.ts +0 -8
  458. /package/src/core/{descriptors → primitives}/$atom.ts +0 -0
  459. /package/src/core/{descriptors → primitives}/$env.ts +0 -0
  460. /package/src/server-auth/{descriptors → primitives}/$authApple.ts +0 -0
  461. /package/src/server-links/{descriptors → primitives}/$client.ts +0 -0
@@ -1,3146 +0,0 @@
1
- let alepha = require("alepha");
2
- let alepha_server = require("alepha/server");
3
- let node_crypto = require("node:crypto");
4
- let node_zlib = require("node:zlib");
5
- let alepha_datetime = require("alepha/datetime");
6
- let alepha_logger = require("alepha/logger");
7
- let alepha_security = require("alepha/security");
8
- let alepha_retry = require("alepha/retry");
9
- let node_stream_web = require("node:stream/web");
10
-
11
- //#region src/server-cookies/services/CookieParser.ts
12
- var CookieParser = class {
13
- parseRequestCookies(header) {
14
- const cookies = {};
15
- const parts = header.split(";");
16
- for (const part of parts) {
17
- const [key, value] = part.split("=");
18
- if (!key || !value) continue;
19
- cookies[key.trim()] = value.trim();
20
- }
21
- return cookies;
22
- }
23
- serializeResponseCookies(cookies, isHttps) {
24
- const headers$1 = [];
25
- for (const [name, cookie] of Object.entries(cookies)) {
26
- if (cookie == null) {
27
- headers$1.push(`${name}=; Path=/; Max-Age=0`);
28
- continue;
29
- }
30
- if (!cookie.value) continue;
31
- headers$1.push(this.cookieToString(name, cookie, isHttps));
32
- }
33
- return headers$1;
34
- }
35
- cookieToString(name, cookie, isHttps) {
36
- const parts = [];
37
- parts.push(`${name}=${cookie.value}`);
38
- if (cookie.path) parts.push(`Path=${cookie.path}`);
39
- if (cookie.maxAge) parts.push(`Max-Age=${cookie.maxAge}`);
40
- if (cookie.secure !== false && isHttps) parts.push("Secure");
41
- if (cookie.httpOnly) parts.push("HttpOnly");
42
- if (cookie.sameSite) parts.push(`SameSite=${cookie.sameSite}`);
43
- if (cookie.domain) parts.push(`Domain=${cookie.domain}`);
44
- return parts.join("; ");
45
- }
46
- };
47
-
48
- //#endregion
49
- //#region src/server-cookies/providers/ServerCookiesProvider.ts
50
- const envSchema$2 = alepha.t.object({ APP_SECRET: alepha.t.text({ default: alepha_security.DEFAULT_APP_SECRET }) });
51
- var ServerCookiesProvider = class {
52
- alepha = (0, alepha.$inject)(alepha.Alepha);
53
- log = (0, alepha_logger.$logger)();
54
- cookieParser = (0, alepha.$inject)(CookieParser);
55
- dateTimeProvider = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
56
- env = (0, alepha.$env)(envSchema$2);
57
- ALGORITHM = "aes-256-gcm";
58
- IV_LENGTH = 16;
59
- AUTH_TAG_LENGTH = 16;
60
- SIGNATURE_LENGTH = 32;
61
- onRequest = (0, alepha.$hook)({
62
- on: "server:onRequest",
63
- handler: async ({ request }) => {
64
- request.cookies = {
65
- req: this.cookieParser.parseRequestCookies(request.headers.cookie ?? ""),
66
- res: {}
67
- };
68
- }
69
- });
70
- onAction = (0, alepha.$hook)({
71
- on: "action:onRequest",
72
- handler: async ({ request }) => {
73
- request.cookies = {
74
- req: this.cookieParser.parseRequestCookies(request.headers.cookie ?? ""),
75
- res: {}
76
- };
77
- }
78
- });
79
- onSend = (0, alepha.$hook)({
80
- on: "server:onSend",
81
- handler: async ({ request }) => {
82
- if (request.cookies && Object.keys(request.cookies.res).length > 0) {
83
- const setCookieHeaders = this.cookieParser.serializeResponseCookies(request.cookies.res, request.url.protocol === "https:");
84
- if (setCookieHeaders.length > 0) request.reply.headers["set-cookie"] = setCookieHeaders;
85
- }
86
- }
87
- });
88
- getCookiesFromContext(cookies) {
89
- const contextCookies = this.alepha.context.get("request")?.cookies;
90
- if (cookies) return cookies;
91
- if (contextCookies) return contextCookies;
92
- throw new Error("Cookie context is not available. This method must be called within a server request cycle.");
93
- }
94
- getCookie(name, options, contextCookies) {
95
- const cookies = this.getCookiesFromContext(contextCookies);
96
- let rawValue = cookies.req[name];
97
- if (!rawValue) return void 0;
98
- try {
99
- rawValue = decodeURIComponent(rawValue);
100
- if (options.sign) {
101
- const signature = rawValue.substring(0, this.SIGNATURE_LENGTH * 2);
102
- const value = rawValue.substring(this.SIGNATURE_LENGTH * 2);
103
- const expectedSignature = this.sign(value);
104
- if (!(0, node_crypto.timingSafeEqual)(Buffer.from(signature, "hex"), Buffer.from(expectedSignature, "hex"))) {
105
- this.log.warn(`Invalid signature for cookie "${name}".`);
106
- return;
107
- }
108
- rawValue = value;
109
- }
110
- if (options.encrypt) rawValue = this.decrypt(rawValue);
111
- if (options.compress) rawValue = (0, node_zlib.inflateRawSync)(Buffer.from(rawValue, "base64")).toString("utf8");
112
- return this.alepha.codec.decode(options.schema, JSON.parse(rawValue));
113
- } catch (error) {
114
- this.log.warn(`Failed to parse cookie "${name}"`, error);
115
- this.deleteCookie(name, cookies);
116
- return;
117
- }
118
- }
119
- setCookie(name, options, data, contextCookies) {
120
- const cookies = this.getCookiesFromContext(contextCookies);
121
- let value = JSON.stringify(this.alepha.codec.decode(options.schema, data));
122
- if (options.compress) value = (0, node_zlib.deflateRawSync)(value).toString("base64");
123
- if (options.encrypt) value = this.encrypt(value);
124
- if (options.sign) value = this.sign(value) + value;
125
- const cookie = {
126
- value: encodeURIComponent(value),
127
- path: options.path ?? "/",
128
- sameSite: options.sameSite ?? "lax",
129
- secure: options.secure ?? this.alepha.isProduction(),
130
- httpOnly: options.httpOnly,
131
- domain: options.domain
132
- };
133
- if (options.ttl) cookie.maxAge = this.dateTimeProvider.duration(options.ttl).as("seconds");
134
- cookies.res[name] = cookie;
135
- }
136
- deleteCookie(name, contextCookies) {
137
- const cookies = this.getCookiesFromContext(contextCookies);
138
- cookies.res[name] = null;
139
- }
140
- encrypt(text) {
141
- const iv = (0, node_crypto.randomBytes)(this.IV_LENGTH);
142
- const cipher = (0, node_crypto.createCipheriv)(this.ALGORITHM, Buffer.from(this.secretKey()), iv);
143
- const encrypted = Buffer.concat([cipher.update(text, "utf8"), cipher.final()]);
144
- const authTag = cipher.getAuthTag();
145
- return Buffer.concat([
146
- iv,
147
- authTag,
148
- encrypted
149
- ]).toString("base64");
150
- }
151
- decrypt(encryptedText) {
152
- const data = Buffer.from(encryptedText, "base64");
153
- const iv = data.subarray(0, this.IV_LENGTH);
154
- const authTag = data.subarray(this.IV_LENGTH, this.IV_LENGTH + this.AUTH_TAG_LENGTH);
155
- const encrypted = data.subarray(this.IV_LENGTH + this.AUTH_TAG_LENGTH);
156
- const decipher = (0, node_crypto.createDecipheriv)(this.ALGORITHM, Buffer.from(this.secretKey()), iv);
157
- decipher.setAuthTag(authTag);
158
- return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
159
- }
160
- secretKey() {
161
- let secret = this.env.APP_SECRET;
162
- if (secret.length < 32) secret = secret.padEnd(32, "0");
163
- else if (secret.length > 32) secret = secret.substring(0, 32);
164
- return secret;
165
- }
166
- sign(data) {
167
- return (0, node_crypto.createHmac)("sha256", this.secretKey()).update(data).digest("hex");
168
- }
169
- };
170
-
171
- //#endregion
172
- //#region src/server-cookies/descriptors/$cookie.ts
173
- /**
174
- * Declares a type-safe, configurable HTTP cookie.
175
- * This descriptor provides methods to get, set, and delete the cookie
176
- * within the server request/response cycle.
177
- */
178
- const $cookie = (options) => {
179
- return (0, alepha.createDescriptor)(CookieDescriptor, options);
180
- };
181
- var CookieDescriptor = class extends alepha.Descriptor {
182
- serverCookiesProvider = (0, alepha.$inject)(ServerCookiesProvider);
183
- get schema() {
184
- return this.options.schema;
185
- }
186
- get name() {
187
- return this.options.name ?? `${this.config.propertyKey}`;
188
- }
189
- /**
190
- * Sets the cookie with the given value in the current request's response.
191
- */
192
- set(value, options) {
193
- this.serverCookiesProvider.setCookie(this.name, {
194
- ...this.options,
195
- ttl: options?.ttl ?? this.options.ttl
196
- }, value, options?.cookies);
197
- }
198
- /**
199
- * Gets the cookie value from the current request. Returns undefined if not found or invalid.
200
- */
201
- get(options) {
202
- return this.serverCookiesProvider.getCookie(this.name, this.options, options?.cookies);
203
- }
204
- /**
205
- * Deletes the cookie in the current request's response.
206
- */
207
- del(options) {
208
- this.serverCookiesProvider.deleteCookie(this.name, options?.cookies);
209
- }
210
- };
211
- $cookie[alepha.KIND] = CookieDescriptor;
212
-
213
- //#endregion
214
- //#region src/server-cookies/index.ts
215
- /**
216
- * Provides HTTP cookie management capabilities for server requests and responses with type-safe cookie descriptors.
217
- *
218
- * The server-cookies module enables declarative cookie handling using the `$cookie` descriptor on class properties.
219
- * It offers automatic cookie parsing, secure cookie configuration, and seamless integration with server routes
220
- * for managing user sessions, preferences, and authentication tokens.
221
- *
222
- * @see {@link $cookie}
223
- * @module alepha.server.cookies
224
- */
225
- const AlephaServerCookies = (0, alepha.$module)({
226
- name: "alepha.server.cookies",
227
- descriptors: [$cookie],
228
- services: [alepha_server.AlephaServer, ServerCookiesProvider]
229
- });
230
-
231
- //#endregion
232
- //#region ../../node_modules/oauth4webapi/build/index.js
233
- let USER_AGENT$1;
234
- if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) USER_AGENT$1 = `oauth4webapi/v3.8.2`;
235
- function looseInstanceOf(input, expected) {
236
- if (input == null) return false;
237
- try {
238
- return input instanceof expected || Object.getPrototypeOf(input)[Symbol.toStringTag] === expected.prototype[Symbol.toStringTag];
239
- } catch {
240
- return false;
241
- }
242
- }
243
- const ERR_INVALID_ARG_VALUE$1 = "ERR_INVALID_ARG_VALUE";
244
- const ERR_INVALID_ARG_TYPE$1 = "ERR_INVALID_ARG_TYPE";
245
- function CodedTypeError$1(message, code, cause) {
246
- const err = new TypeError(message, { cause });
247
- Object.assign(err, { code });
248
- return err;
249
- }
250
- const allowInsecureRequests$1 = Symbol();
251
- const clockSkew$1 = Symbol();
252
- const clockTolerance$1 = Symbol();
253
- const customFetch$1 = Symbol();
254
- const modifyAssertion$1 = Symbol();
255
- const jweDecrypt = Symbol();
256
- const encoder = new TextEncoder();
257
- const decoder$1 = new TextDecoder();
258
- function buf(input) {
259
- if (typeof input === "string") return encoder.encode(input);
260
- return decoder$1.decode(input);
261
- }
262
- let encodeBase64Url;
263
- if (Uint8Array.prototype.toBase64) encodeBase64Url = (input) => {
264
- if (input instanceof ArrayBuffer) input = new Uint8Array(input);
265
- return input.toBase64({
266
- alphabet: "base64url",
267
- omitPadding: true
268
- });
269
- };
270
- else {
271
- const CHUNK_SIZE = 32768;
272
- encodeBase64Url = (input) => {
273
- if (input instanceof ArrayBuffer) input = new Uint8Array(input);
274
- const arr = [];
275
- for (let i = 0; i < input.byteLength; i += CHUNK_SIZE) arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
276
- return btoa(arr.join("")).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
277
- };
278
- }
279
- let decodeBase64Url;
280
- if (Uint8Array.fromBase64) decodeBase64Url = (input) => {
281
- try {
282
- return Uint8Array.fromBase64(input, { alphabet: "base64url" });
283
- } catch (cause) {
284
- throw CodedTypeError$1("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE$1, cause);
285
- }
286
- };
287
- else decodeBase64Url = (input) => {
288
- try {
289
- const binary = atob(input.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, ""));
290
- const bytes = new Uint8Array(binary.length);
291
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
292
- return bytes;
293
- } catch (cause) {
294
- throw CodedTypeError$1("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE$1, cause);
295
- }
296
- };
297
- function b64u(input) {
298
- if (typeof input === "string") return decodeBase64Url(input);
299
- return encodeBase64Url(input);
300
- }
301
- var UnsupportedOperationError = class extends Error {
302
- code;
303
- constructor(message, options) {
304
- super(message, options);
305
- this.name = this.constructor.name;
306
- this.code = UNSUPPORTED_OPERATION;
307
- Error.captureStackTrace?.(this, this.constructor);
308
- }
309
- };
310
- var OperationProcessingError = class extends Error {
311
- code;
312
- constructor(message, options) {
313
- super(message, options);
314
- this.name = this.constructor.name;
315
- if (options?.code) this.code = options?.code;
316
- Error.captureStackTrace?.(this, this.constructor);
317
- }
318
- };
319
- function OPE(message, code, cause) {
320
- return new OperationProcessingError(message, {
321
- code,
322
- cause
323
- });
324
- }
325
- function isJsonObject(input) {
326
- if (input === null || typeof input !== "object" || Array.isArray(input)) return false;
327
- return true;
328
- }
329
- function prepareHeaders(input) {
330
- if (looseInstanceOf(input, Headers)) input = Object.fromEntries(input.entries());
331
- const headers$1 = new Headers(input ?? {});
332
- if (USER_AGENT$1 && !headers$1.has("user-agent")) headers$1.set("user-agent", USER_AGENT$1);
333
- if (headers$1.has("authorization")) throw CodedTypeError$1("\"options.headers\" must not include the \"authorization\" header name", ERR_INVALID_ARG_VALUE$1);
334
- return headers$1;
335
- }
336
- function signal$1(url, value) {
337
- if (value !== void 0) {
338
- if (typeof value === "function") value = value(url.href);
339
- if (!(value instanceof AbortSignal)) throw CodedTypeError$1("\"options.signal\" must return or be an instance of AbortSignal", ERR_INVALID_ARG_TYPE$1);
340
- return value;
341
- }
342
- }
343
- function replaceDoubleSlash(pathname) {
344
- if (pathname.includes("//")) return pathname.replace("//", "/");
345
- return pathname;
346
- }
347
- function prependWellKnown(url, wellKnown, allowTerminatingSlash = false) {
348
- if (url.pathname === "/") url.pathname = wellKnown;
349
- else url.pathname = replaceDoubleSlash(`${wellKnown}/${allowTerminatingSlash ? url.pathname : url.pathname.replace(/(\/)$/, "")}`);
350
- return url;
351
- }
352
- function appendWellKnown(url, wellKnown) {
353
- url.pathname = replaceDoubleSlash(`${url.pathname}/${wellKnown}`);
354
- return url;
355
- }
356
- async function performDiscovery$1(input, urlName, transform, options) {
357
- if (!(input instanceof URL)) throw CodedTypeError$1(`"${urlName}" must be an instance of URL`, ERR_INVALID_ARG_TYPE$1);
358
- checkProtocol(input, options?.[allowInsecureRequests$1] !== true);
359
- const url = transform(new URL(input.href));
360
- const headers$1 = prepareHeaders(options?.headers);
361
- headers$1.set("accept", "application/json");
362
- return (options?.[customFetch$1] || fetch)(url.href, {
363
- body: void 0,
364
- headers: Object.fromEntries(headers$1.entries()),
365
- method: "GET",
366
- redirect: "manual",
367
- signal: signal$1(url, options?.signal)
368
- });
369
- }
370
- async function discoveryRequest(issuerIdentifier, options) {
371
- return performDiscovery$1(issuerIdentifier, "issuerIdentifier", (url) => {
372
- switch (options?.algorithm) {
373
- case void 0:
374
- case "oidc":
375
- appendWellKnown(url, ".well-known/openid-configuration");
376
- break;
377
- case "oauth2":
378
- prependWellKnown(url, ".well-known/oauth-authorization-server");
379
- break;
380
- default: throw CodedTypeError$1("\"options.algorithm\" must be \"oidc\" (default), or \"oauth2\"", ERR_INVALID_ARG_VALUE$1);
381
- }
382
- return url;
383
- }, options);
384
- }
385
- function assertNumber(input, allow0, it, code, cause) {
386
- try {
387
- if (typeof input !== "number" || !Number.isFinite(input)) throw CodedTypeError$1(`${it} must be a number`, ERR_INVALID_ARG_TYPE$1, cause);
388
- if (input > 0) return;
389
- if (allow0) {
390
- if (input !== 0) throw CodedTypeError$1(`${it} must be a non-negative number`, ERR_INVALID_ARG_VALUE$1, cause);
391
- return;
392
- }
393
- throw CodedTypeError$1(`${it} must be a positive number`, ERR_INVALID_ARG_VALUE$1, cause);
394
- } catch (err) {
395
- if (code) throw OPE(err.message, code, cause);
396
- throw err;
397
- }
398
- }
399
- function assertString$1(input, it, code, cause) {
400
- try {
401
- if (typeof input !== "string") throw CodedTypeError$1(`${it} must be a string`, ERR_INVALID_ARG_TYPE$1, cause);
402
- if (input.length === 0) throw CodedTypeError$1(`${it} must not be empty`, ERR_INVALID_ARG_VALUE$1, cause);
403
- } catch (err) {
404
- if (code) throw OPE(err.message, code, cause);
405
- throw err;
406
- }
407
- }
408
- async function processDiscoveryResponse(expectedIssuerIdentifier, response) {
409
- const expected = expectedIssuerIdentifier;
410
- if (!(expected instanceof URL) && expected !== _nodiscoverycheck) throw CodedTypeError$1("\"expectedIssuerIdentifier\" must be an instance of URL", ERR_INVALID_ARG_TYPE$1);
411
- if (!looseInstanceOf(response, Response)) throw CodedTypeError$1("\"response\" must be an instance of Response", ERR_INVALID_ARG_TYPE$1);
412
- if (response.status !== 200) throw OPE("\"response\" is not a conform Authorization Server Metadata response (unexpected HTTP status code)", RESPONSE_IS_NOT_CONFORM, response);
413
- assertReadableResponse(response);
414
- const json = await getResponseJsonBody(response);
415
- assertString$1(json.issuer, "\"response\" body \"issuer\" property", INVALID_RESPONSE, { body: json });
416
- if (expected !== _nodiscoverycheck && new URL(json.issuer).href !== expected.href) throw OPE("\"response\" body \"issuer\" property does not match the expected value", JSON_ATTRIBUTE_COMPARISON, {
417
- expected: expected.href,
418
- body: json,
419
- attribute: "issuer"
420
- });
421
- return json;
422
- }
423
- function assertApplicationJson(response) {
424
- assertContentType(response, "application/json");
425
- }
426
- function notJson(response, ...types) {
427
- let msg = "\"response\" content-type must be ";
428
- if (types.length > 2) {
429
- const last = types.pop();
430
- msg += `${types.join(", ")}, or ${last}`;
431
- } else if (types.length === 2) msg += `${types[0]} or ${types[1]}`;
432
- else msg += types[0];
433
- return OPE(msg, RESPONSE_IS_NOT_JSON, response);
434
- }
435
- function assertContentType(response, contentType) {
436
- if (getContentType(response) !== contentType) throw notJson(response, contentType);
437
- }
438
- function randomBytes() {
439
- return b64u(crypto.getRandomValues(new Uint8Array(32)));
440
- }
441
- function generateRandomCodeVerifier() {
442
- return randomBytes();
443
- }
444
- function generateRandomState() {
445
- return randomBytes();
446
- }
447
- async function calculatePKCECodeChallenge$1(codeVerifier) {
448
- assertString$1(codeVerifier, "codeVerifier");
449
- return b64u(await crypto.subtle.digest("SHA-256", buf(codeVerifier)));
450
- }
451
- function getClockSkew(client) {
452
- const skew = client?.[clockSkew$1];
453
- return typeof skew === "number" && Number.isFinite(skew) ? skew : 0;
454
- }
455
- function getClockTolerance(client) {
456
- const tolerance = client?.[clockTolerance$1];
457
- return typeof tolerance === "number" && Number.isFinite(tolerance) && Math.sign(tolerance) !== -1 ? tolerance : 30;
458
- }
459
- function epochTime() {
460
- return Math.floor(Date.now() / 1e3);
461
- }
462
- function assertAs(as) {
463
- if (typeof as !== "object" || as === null) throw CodedTypeError$1("\"as\" must be an object", ERR_INVALID_ARG_TYPE$1);
464
- assertString$1(as.issuer, "\"as.issuer\"");
465
- }
466
- function assertClient(client) {
467
- if (typeof client !== "object" || client === null) throw CodedTypeError$1("\"client\" must be an object", ERR_INVALID_ARG_TYPE$1);
468
- assertString$1(client.client_id, "\"client.client_id\"");
469
- }
470
- function ClientSecretPost$1(clientSecret) {
471
- assertString$1(clientSecret, "\"clientSecret\"");
472
- return (_as, client, body, _headers) => {
473
- body.set("client_id", client.client_id);
474
- body.set("client_secret", clientSecret);
475
- };
476
- }
477
- function None$1() {
478
- return (_as, client, body, _headers) => {
479
- body.set("client_id", client.client_id);
480
- };
481
- }
482
- const URLParse = URL.parse ? (url, base) => URL.parse(url, base) : (url, base) => {
483
- try {
484
- return new URL(url, base);
485
- } catch {
486
- return null;
487
- }
488
- };
489
- function checkProtocol(url, enforceHttps) {
490
- if (enforceHttps && url.protocol !== "https:") throw OPE("only requests to HTTPS are allowed", HTTP_REQUEST_FORBIDDEN, url);
491
- if (url.protocol !== "https:" && url.protocol !== "http:") throw OPE("only HTTP and HTTPS requests are allowed", REQUEST_PROTOCOL_FORBIDDEN, url);
492
- }
493
- function validateEndpoint(value, endpoint, useMtlsAlias, enforceHttps) {
494
- let url;
495
- if (typeof value !== "string" || !(url = URLParse(value))) throw OPE(`authorization server metadata does not contain a valid ${useMtlsAlias ? `"as.mtls_endpoint_aliases.${endpoint}"` : `"as.${endpoint}"`}`, value === void 0 ? MISSING_SERVER_METADATA : INVALID_SERVER_METADATA, { attribute: useMtlsAlias ? `mtls_endpoint_aliases.${endpoint}` : endpoint });
496
- checkProtocol(url, enforceHttps);
497
- return url;
498
- }
499
- function resolveEndpoint(as, endpoint, useMtlsAlias, enforceHttps) {
500
- if (useMtlsAlias && as.mtls_endpoint_aliases && endpoint in as.mtls_endpoint_aliases) return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, useMtlsAlias, enforceHttps);
501
- return validateEndpoint(as[endpoint], endpoint, useMtlsAlias, enforceHttps);
502
- }
503
- function isDPoPNonceError(err) {
504
- if (err instanceof WWWAuthenticateChallengeError) {
505
- const { 0: challenge, length } = err.cause;
506
- return length === 1 && challenge.scheme === "dpop" && challenge.parameters.error === "use_dpop_nonce";
507
- }
508
- if (err instanceof ResponseBodyError) return err.error === "use_dpop_nonce";
509
- return false;
510
- }
511
- var ResponseBodyError = class extends Error {
512
- cause;
513
- code;
514
- error;
515
- status;
516
- error_description;
517
- response;
518
- constructor(message, options) {
519
- super(message, options);
520
- this.name = this.constructor.name;
521
- this.code = RESPONSE_BODY_ERROR;
522
- this.cause = options.cause;
523
- this.error = options.cause.error;
524
- this.status = options.response.status;
525
- this.error_description = options.cause.error_description;
526
- Object.defineProperty(this, "response", {
527
- enumerable: false,
528
- value: options.response
529
- });
530
- Error.captureStackTrace?.(this, this.constructor);
531
- }
532
- };
533
- var AuthorizationResponseError = class extends Error {
534
- cause;
535
- code;
536
- error;
537
- error_description;
538
- constructor(message, options) {
539
- super(message, options);
540
- this.name = this.constructor.name;
541
- this.code = AUTHORIZATION_RESPONSE_ERROR;
542
- this.cause = options.cause;
543
- this.error = options.cause.get("error");
544
- this.error_description = options.cause.get("error_description") ?? void 0;
545
- Error.captureStackTrace?.(this, this.constructor);
546
- }
547
- };
548
- var WWWAuthenticateChallengeError = class extends Error {
549
- cause;
550
- code;
551
- response;
552
- status;
553
- constructor(message, options) {
554
- super(message, options);
555
- this.name = this.constructor.name;
556
- this.code = WWW_AUTHENTICATE_CHALLENGE;
557
- this.cause = options.cause;
558
- this.status = options.response.status;
559
- this.response = options.response;
560
- Object.defineProperty(this, "response", { enumerable: false });
561
- Error.captureStackTrace?.(this, this.constructor);
562
- }
563
- };
564
- const tokenMatch = "[a-zA-Z0-9!#$%&\\'\\*\\+\\-\\.\\^_`\\|~]+";
565
- const token68Match = "[a-zA-Z0-9\\-\\._\\~\\+\\/]+[=]{0,2}";
566
- const quotedParamMatcher = "(" + tokenMatch + ")\\s*=\\s*\"((?:[^\"\\\\]|\\\\.)*)\"";
567
- const paramMatcher = "(" + tokenMatch + ")\\s*=\\s*([a-zA-Z0-9!#$%&\\'\\*\\+\\-\\.\\^_`\\|~]+)";
568
- const schemeRE = /* @__PURE__ */ new RegExp("^[,\\s]*(" + tokenMatch + ")\\s(.*)");
569
- const quotedParamRE = /* @__PURE__ */ new RegExp("^[,\\s]*" + quotedParamMatcher + "[,\\s]*(.*)");
570
- const unquotedParamRE = /* @__PURE__ */ new RegExp("^[,\\s]*" + paramMatcher + "[,\\s]*(.*)");
571
- const token68ParamRE = /* @__PURE__ */ new RegExp("^(" + token68Match + ")(?:$|[,\\s])(.*)");
572
- function parseWwwAuthenticateChallenges(response) {
573
- if (!looseInstanceOf(response, Response)) throw CodedTypeError$1("\"response\" must be an instance of Response", ERR_INVALID_ARG_TYPE$1);
574
- const header = response.headers.get("www-authenticate");
575
- if (header === null) return;
576
- const challenges = [];
577
- let rest = header;
578
- while (rest) {
579
- let match = rest.match(schemeRE);
580
- const scheme = match?.["1"].toLowerCase();
581
- rest = match?.["2"];
582
- if (!scheme) return;
583
- const parameters = {};
584
- let token68;
585
- while (rest) {
586
- let key;
587
- let value;
588
- if (match = rest.match(quotedParamRE)) {
589
- [, key, value, rest] = match;
590
- if (value.includes("\\")) try {
591
- value = JSON.parse(`"${value}"`);
592
- } catch {}
593
- parameters[key.toLowerCase()] = value;
594
- continue;
595
- }
596
- if (match = rest.match(unquotedParamRE)) {
597
- [, key, value, rest] = match;
598
- parameters[key.toLowerCase()] = value;
599
- continue;
600
- }
601
- if (match = rest.match(token68ParamRE)) {
602
- if (Object.keys(parameters).length) break;
603
- [, token68, rest] = match;
604
- break;
605
- }
606
- return;
607
- }
608
- const challenge = {
609
- scheme,
610
- parameters
611
- };
612
- if (token68) challenge.token68 = token68;
613
- challenges.push(challenge);
614
- }
615
- if (!challenges.length) return;
616
- return challenges;
617
- }
618
- async function parseOAuthResponseErrorBody(response) {
619
- if (response.status > 399 && response.status < 500) {
620
- assertReadableResponse(response);
621
- assertApplicationJson(response);
622
- try {
623
- const json = await response.clone().json();
624
- if (isJsonObject(json) && typeof json.error === "string" && json.error.length) return json;
625
- } catch {}
626
- }
627
- }
628
- async function checkOAuthBodyError(response, expected, label) {
629
- if (response.status !== expected) {
630
- checkAuthenticationChallenges(response);
631
- let err;
632
- if (err = await parseOAuthResponseErrorBody(response)) {
633
- await response.body?.cancel();
634
- throw new ResponseBodyError("server responded with an error in the response body", {
635
- cause: err,
636
- response
637
- });
638
- }
639
- throw OPE(`"response" is not a conform ${label} response (unexpected HTTP status code)`, RESPONSE_IS_NOT_CONFORM, response);
640
- }
641
- }
642
- function assertDPoP(option) {
643
- if (!branded.has(option)) throw CodedTypeError$1("\"options.DPoP\" is not a valid DPoPHandle", ERR_INVALID_ARG_VALUE$1);
644
- }
645
- const skipSubjectCheck$1 = Symbol();
646
- function getContentType(input) {
647
- return input.headers.get("content-type")?.split(";")[0];
648
- }
649
- async function authenticatedRequest(as, client, clientAuthentication, url, body, headers$1, options) {
650
- await clientAuthentication(as, client, body, headers$1);
651
- headers$1.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
652
- return (options?.[customFetch$1] || fetch)(url.href, {
653
- body,
654
- headers: Object.fromEntries(headers$1.entries()),
655
- method: "POST",
656
- redirect: "manual",
657
- signal: signal$1(url, options?.signal)
658
- });
659
- }
660
- async function tokenEndpointRequest(as, client, clientAuthentication, grantType, parameters, options) {
661
- const url = resolveEndpoint(as, "token_endpoint", client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests$1] !== true);
662
- parameters.set("grant_type", grantType);
663
- const headers$1 = prepareHeaders(options?.headers);
664
- headers$1.set("accept", "application/json");
665
- if (options?.DPoP !== void 0) {
666
- assertDPoP(options.DPoP);
667
- await options.DPoP.addProof(url, headers$1, "POST");
668
- }
669
- const response = await authenticatedRequest(as, client, clientAuthentication, url, parameters, headers$1, options);
670
- options?.DPoP?.cacheNonce(response, url);
671
- return response;
672
- }
673
- async function refreshTokenGrantRequest(as, client, clientAuthentication, refreshToken, options) {
674
- assertAs(as);
675
- assertClient(client);
676
- assertString$1(refreshToken, "\"refreshToken\"");
677
- const parameters = new URLSearchParams(options?.additionalParameters);
678
- parameters.set("refresh_token", refreshToken);
679
- return tokenEndpointRequest(as, client, clientAuthentication, "refresh_token", parameters, options);
680
- }
681
- const idTokenClaims = /* @__PURE__ */ new WeakMap();
682
- const jwtRefs = /* @__PURE__ */ new WeakMap();
683
- function getValidatedIdTokenClaims(ref) {
684
- if (!ref.id_token) return;
685
- const claims = idTokenClaims.get(ref);
686
- if (!claims) throw CodedTypeError$1("\"ref\" was already garbage collected or did not resolve from the proper sources", ERR_INVALID_ARG_VALUE$1);
687
- return claims;
688
- }
689
- async function processGenericAccessTokenResponse(as, client, response, additionalRequiredIdTokenClaims, decryptFn, recognizedTokenTypes) {
690
- assertAs(as);
691
- assertClient(client);
692
- if (!looseInstanceOf(response, Response)) throw CodedTypeError$1("\"response\" must be an instance of Response", ERR_INVALID_ARG_TYPE$1);
693
- await checkOAuthBodyError(response, 200, "Token Endpoint");
694
- assertReadableResponse(response);
695
- const json = await getResponseJsonBody(response);
696
- assertString$1(json.access_token, "\"response\" body \"access_token\" property", INVALID_RESPONSE, { body: json });
697
- assertString$1(json.token_type, "\"response\" body \"token_type\" property", INVALID_RESPONSE, { body: json });
698
- json.token_type = json.token_type.toLowerCase();
699
- if (json.expires_in !== void 0) {
700
- let expiresIn = typeof json.expires_in !== "number" ? parseFloat(json.expires_in) : json.expires_in;
701
- assertNumber(expiresIn, true, "\"response\" body \"expires_in\" property", INVALID_RESPONSE, { body: json });
702
- json.expires_in = expiresIn;
703
- }
704
- if (json.refresh_token !== void 0) assertString$1(json.refresh_token, "\"response\" body \"refresh_token\" property", INVALID_RESPONSE, { body: json });
705
- if (json.scope !== void 0 && typeof json.scope !== "string") throw OPE("\"response\" body \"scope\" property must be a string", INVALID_RESPONSE, { body: json });
706
- if (json.id_token !== void 0) {
707
- assertString$1(json.id_token, "\"response\" body \"id_token\" property", INVALID_RESPONSE, { body: json });
708
- const requiredClaims = [
709
- "aud",
710
- "exp",
711
- "iat",
712
- "iss",
713
- "sub"
714
- ];
715
- if (client.require_auth_time === true) requiredClaims.push("auth_time");
716
- if (client.default_max_age !== void 0) {
717
- assertNumber(client.default_max_age, true, "\"client.default_max_age\"");
718
- requiredClaims.push("auth_time");
719
- }
720
- if (additionalRequiredIdTokenClaims?.length) requiredClaims.push(...additionalRequiredIdTokenClaims);
721
- const { claims, jwt } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(void 0, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported, "RS256"), getClockSkew(client), getClockTolerance(client), decryptFn).then(validatePresence.bind(void 0, requiredClaims)).then(validateIssuer.bind(void 0, as)).then(validateAudience.bind(void 0, client.client_id));
722
- if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
723
- if (claims.azp === void 0) throw OPE("ID Token \"aud\" (audience) claim includes additional untrusted audiences", JWT_CLAIM_COMPARISON, {
724
- claims,
725
- claim: "aud"
726
- });
727
- if (claims.azp !== client.client_id) throw OPE("unexpected ID Token \"azp\" (authorized party) claim value", JWT_CLAIM_COMPARISON, {
728
- expected: client.client_id,
729
- claims,
730
- claim: "azp"
731
- });
732
- }
733
- if (claims.auth_time !== void 0) assertNumber(claims.auth_time, true, "ID Token \"auth_time\" (authentication time)", INVALID_RESPONSE, { claims });
734
- jwtRefs.set(response, jwt);
735
- idTokenClaims.set(json, claims);
736
- }
737
- if (recognizedTokenTypes?.[json.token_type] !== void 0) recognizedTokenTypes[json.token_type](response, json);
738
- else if (json.token_type !== "dpop" && json.token_type !== "bearer") throw new UnsupportedOperationError("unsupported `token_type` value", { cause: { body: json } });
739
- return json;
740
- }
741
- function checkAuthenticationChallenges(response) {
742
- let challenges;
743
- if (challenges = parseWwwAuthenticateChallenges(response)) throw new WWWAuthenticateChallengeError("server responded with a challenge in the WWW-Authenticate HTTP Header", {
744
- cause: challenges,
745
- response
746
- });
747
- }
748
- async function processRefreshTokenResponse(as, client, response, options) {
749
- return processGenericAccessTokenResponse(as, client, response, void 0, options?.[jweDecrypt], options?.recognizedTokenTypes);
750
- }
751
- function validateAudience(expected, result) {
752
- if (Array.isArray(result.claims.aud)) {
753
- if (!result.claims.aud.includes(expected)) throw OPE("unexpected JWT \"aud\" (audience) claim value", JWT_CLAIM_COMPARISON, {
754
- expected,
755
- claims: result.claims,
756
- claim: "aud"
757
- });
758
- } else if (result.claims.aud !== expected) throw OPE("unexpected JWT \"aud\" (audience) claim value", JWT_CLAIM_COMPARISON, {
759
- expected,
760
- claims: result.claims,
761
- claim: "aud"
762
- });
763
- return result;
764
- }
765
- function validateIssuer(as, result) {
766
- const expected = as[_expectedIssuer]?.(result) ?? as.issuer;
767
- if (result.claims.iss !== expected) throw OPE("unexpected JWT \"iss\" (issuer) claim value", JWT_CLAIM_COMPARISON, {
768
- expected,
769
- claims: result.claims,
770
- claim: "iss"
771
- });
772
- return result;
773
- }
774
- const branded = /* @__PURE__ */ new WeakSet();
775
- function brand(searchParams) {
776
- branded.add(searchParams);
777
- return searchParams;
778
- }
779
- const nopkce = Symbol();
780
- async function authorizationCodeGrantRequest(as, client, clientAuthentication, callbackParameters, redirectUri, codeVerifier, options) {
781
- assertAs(as);
782
- assertClient(client);
783
- if (!branded.has(callbackParameters)) throw CodedTypeError$1("\"callbackParameters\" must be an instance of URLSearchParams obtained from \"validateAuthResponse()\", or \"validateJwtAuthResponse()", ERR_INVALID_ARG_VALUE$1);
784
- assertString$1(redirectUri, "\"redirectUri\"");
785
- const code = getURLSearchParameter(callbackParameters, "code");
786
- if (!code) throw OPE("no authorization code in \"callbackParameters\"", INVALID_RESPONSE);
787
- const parameters = new URLSearchParams(options?.additionalParameters);
788
- parameters.set("redirect_uri", redirectUri);
789
- parameters.set("code", code);
790
- if (codeVerifier !== nopkce) {
791
- assertString$1(codeVerifier, "\"codeVerifier\"");
792
- parameters.set("code_verifier", codeVerifier);
793
- }
794
- return tokenEndpointRequest(as, client, clientAuthentication, "authorization_code", parameters, options);
795
- }
796
- const jwtClaimNames = {
797
- aud: "audience",
798
- c_hash: "code hash",
799
- client_id: "client id",
800
- exp: "expiration time",
801
- iat: "issued at",
802
- iss: "issuer",
803
- jti: "jwt id",
804
- nonce: "nonce",
805
- s_hash: "state hash",
806
- sub: "subject",
807
- ath: "access token hash",
808
- htm: "http method",
809
- htu: "http uri",
810
- cnf: "confirmation",
811
- auth_time: "authentication time"
812
- };
813
- function validatePresence(required, result) {
814
- for (const claim of required) if (result.claims[claim] === void 0) throw OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`, INVALID_RESPONSE, { claims: result.claims });
815
- return result;
816
- }
817
- const expectNoNonce = Symbol();
818
- const skipAuthTimeCheck = Symbol();
819
- async function processAuthorizationCodeResponse(as, client, response, options) {
820
- if (typeof options?.expectedNonce === "string" || typeof options?.maxAge === "number" || options?.requireIdToken) return processAuthorizationCodeOpenIDResponse(as, client, response, options.expectedNonce, options.maxAge, options[jweDecrypt], options.recognizedTokenTypes);
821
- return processAuthorizationCodeOAuth2Response(as, client, response, options?.[jweDecrypt], options?.recognizedTokenTypes);
822
- }
823
- async function processAuthorizationCodeOpenIDResponse(as, client, response, expectedNonce, maxAge, decryptFn, recognizedTokenTypes) {
824
- const additionalRequiredClaims = [];
825
- switch (expectedNonce) {
826
- case void 0:
827
- expectedNonce = expectNoNonce;
828
- break;
829
- case expectNoNonce: break;
830
- default:
831
- assertString$1(expectedNonce, "\"expectedNonce\" argument");
832
- additionalRequiredClaims.push("nonce");
833
- }
834
- maxAge ??= client.default_max_age;
835
- switch (maxAge) {
836
- case void 0:
837
- maxAge = skipAuthTimeCheck;
838
- break;
839
- case skipAuthTimeCheck: break;
840
- default:
841
- assertNumber(maxAge, true, "\"maxAge\" argument");
842
- additionalRequiredClaims.push("auth_time");
843
- }
844
- const result = await processGenericAccessTokenResponse(as, client, response, additionalRequiredClaims, decryptFn, recognizedTokenTypes);
845
- assertString$1(result.id_token, "\"response\" body \"id_token\" property", INVALID_RESPONSE, { body: result });
846
- const claims = getValidatedIdTokenClaims(result);
847
- if (maxAge !== skipAuthTimeCheck) {
848
- const now = epochTime() + getClockSkew(client);
849
- const tolerance = getClockTolerance(client);
850
- if (claims.auth_time + maxAge < now - tolerance) throw OPE("too much time has elapsed since the last End-User authentication", JWT_TIMESTAMP_CHECK, {
851
- claims,
852
- now,
853
- tolerance,
854
- claim: "auth_time"
855
- });
856
- }
857
- if (expectedNonce === expectNoNonce) {
858
- if (claims.nonce !== void 0) throw OPE("unexpected ID Token \"nonce\" claim value", JWT_CLAIM_COMPARISON, {
859
- expected: void 0,
860
- claims,
861
- claim: "nonce"
862
- });
863
- } else if (claims.nonce !== expectedNonce) throw OPE("unexpected ID Token \"nonce\" claim value", JWT_CLAIM_COMPARISON, {
864
- expected: expectedNonce,
865
- claims,
866
- claim: "nonce"
867
- });
868
- return result;
869
- }
870
- async function processAuthorizationCodeOAuth2Response(as, client, response, decryptFn, recognizedTokenTypes) {
871
- const result = await processGenericAccessTokenResponse(as, client, response, void 0, decryptFn, recognizedTokenTypes);
872
- const claims = getValidatedIdTokenClaims(result);
873
- if (claims) {
874
- if (client.default_max_age !== void 0) {
875
- assertNumber(client.default_max_age, true, "\"client.default_max_age\"");
876
- const now = epochTime() + getClockSkew(client);
877
- const tolerance = getClockTolerance(client);
878
- if (claims.auth_time + client.default_max_age < now - tolerance) throw OPE("too much time has elapsed since the last End-User authentication", JWT_TIMESTAMP_CHECK, {
879
- claims,
880
- now,
881
- tolerance,
882
- claim: "auth_time"
883
- });
884
- }
885
- if (claims.nonce !== void 0) throw OPE("unexpected ID Token \"nonce\" claim value", JWT_CLAIM_COMPARISON, {
886
- expected: void 0,
887
- claims,
888
- claim: "nonce"
889
- });
890
- }
891
- return result;
892
- }
893
- const WWW_AUTHENTICATE_CHALLENGE = "OAUTH_WWW_AUTHENTICATE_CHALLENGE";
894
- const RESPONSE_BODY_ERROR = "OAUTH_RESPONSE_BODY_ERROR";
895
- const UNSUPPORTED_OPERATION = "OAUTH_UNSUPPORTED_OPERATION";
896
- const AUTHORIZATION_RESPONSE_ERROR = "OAUTH_AUTHORIZATION_RESPONSE_ERROR";
897
- const PARSE_ERROR = "OAUTH_PARSE_ERROR";
898
- const INVALID_RESPONSE = "OAUTH_INVALID_RESPONSE";
899
- const RESPONSE_IS_NOT_JSON = "OAUTH_RESPONSE_IS_NOT_JSON";
900
- const RESPONSE_IS_NOT_CONFORM = "OAUTH_RESPONSE_IS_NOT_CONFORM";
901
- const HTTP_REQUEST_FORBIDDEN = "OAUTH_HTTP_REQUEST_FORBIDDEN";
902
- const REQUEST_PROTOCOL_FORBIDDEN = "OAUTH_REQUEST_PROTOCOL_FORBIDDEN";
903
- const JWT_TIMESTAMP_CHECK = "OAUTH_JWT_TIMESTAMP_CHECK_FAILED";
904
- const JWT_CLAIM_COMPARISON = "OAUTH_JWT_CLAIM_COMPARISON_FAILED";
905
- const JSON_ATTRIBUTE_COMPARISON = "OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED";
906
- const MISSING_SERVER_METADATA = "OAUTH_MISSING_SERVER_METADATA";
907
- const INVALID_SERVER_METADATA = "OAUTH_INVALID_SERVER_METADATA";
908
- function assertReadableResponse(response) {
909
- if (response.bodyUsed) throw CodedTypeError$1("\"response\" body has been used already", ERR_INVALID_ARG_VALUE$1);
910
- }
911
- async function validateJwt(jws, checkAlg, clockSkew$2, clockTolerance$2, decryptJwt) {
912
- let { 0: protectedHeader, 1: payload, length } = jws.split(".");
913
- if (length === 5) if (decryptJwt !== void 0) {
914
- jws = await decryptJwt(jws);
915
- ({0: protectedHeader, 1: payload, length} = jws.split("."));
916
- } else throw new UnsupportedOperationError("JWE decryption is not configured", { cause: jws });
917
- if (length !== 3) throw OPE("Invalid JWT", INVALID_RESPONSE, jws);
918
- let header;
919
- try {
920
- header = JSON.parse(buf(b64u(protectedHeader)));
921
- } catch (cause) {
922
- throw OPE("failed to parse JWT Header body as base64url encoded JSON", PARSE_ERROR, cause);
923
- }
924
- if (!isJsonObject(header)) throw OPE("JWT Header must be a top level object", INVALID_RESPONSE, jws);
925
- checkAlg(header);
926
- if (header.crit !== void 0) throw new UnsupportedOperationError("no JWT \"crit\" header parameter extensions are supported", { cause: { header } });
927
- let claims;
928
- try {
929
- claims = JSON.parse(buf(b64u(payload)));
930
- } catch (cause) {
931
- throw OPE("failed to parse JWT Payload body as base64url encoded JSON", PARSE_ERROR, cause);
932
- }
933
- if (!isJsonObject(claims)) throw OPE("JWT Payload must be a top level object", INVALID_RESPONSE, jws);
934
- const now = epochTime() + clockSkew$2;
935
- if (claims.exp !== void 0) {
936
- if (typeof claims.exp !== "number") throw OPE("unexpected JWT \"exp\" (expiration time) claim type", INVALID_RESPONSE, { claims });
937
- if (claims.exp <= now - clockTolerance$2) throw OPE("unexpected JWT \"exp\" (expiration time) claim value, expiration is past current timestamp", JWT_TIMESTAMP_CHECK, {
938
- claims,
939
- now,
940
- tolerance: clockTolerance$2,
941
- claim: "exp"
942
- });
943
- }
944
- if (claims.iat !== void 0) {
945
- if (typeof claims.iat !== "number") throw OPE("unexpected JWT \"iat\" (issued at) claim type", INVALID_RESPONSE, { claims });
946
- }
947
- if (claims.iss !== void 0) {
948
- if (typeof claims.iss !== "string") throw OPE("unexpected JWT \"iss\" (issuer) claim type", INVALID_RESPONSE, { claims });
949
- }
950
- if (claims.nbf !== void 0) {
951
- if (typeof claims.nbf !== "number") throw OPE("unexpected JWT \"nbf\" (not before) claim type", INVALID_RESPONSE, { claims });
952
- if (claims.nbf > now + clockTolerance$2) throw OPE("unexpected JWT \"nbf\" (not before) claim value", JWT_TIMESTAMP_CHECK, {
953
- claims,
954
- now,
955
- tolerance: clockTolerance$2,
956
- claim: "nbf"
957
- });
958
- }
959
- if (claims.aud !== void 0) {
960
- if (typeof claims.aud !== "string" && !Array.isArray(claims.aud)) throw OPE("unexpected JWT \"aud\" (audience) claim type", INVALID_RESPONSE, { claims });
961
- }
962
- return {
963
- header,
964
- claims,
965
- jwt: jws
966
- };
967
- }
968
- async function consumeStream(request) {
969
- if (request.bodyUsed) throw CodedTypeError$1("form_post Request instances must contain a readable body", ERR_INVALID_ARG_VALUE$1, { cause: request });
970
- return request.text();
971
- }
972
- async function formPostResponse(request) {
973
- if (request.method !== "POST") throw CodedTypeError$1("form_post responses are expected to use the POST method", ERR_INVALID_ARG_VALUE$1, { cause: request });
974
- if (getContentType(request) !== "application/x-www-form-urlencoded") throw CodedTypeError$1("form_post responses are expected to use the application/x-www-form-urlencoded content-type", ERR_INVALID_ARG_VALUE$1, { cause: request });
975
- return consumeStream(request);
976
- }
977
- function checkSigningAlgorithm(client, issuer, fallback, header) {
978
- if (client !== void 0) {
979
- if (typeof client === "string" ? header.alg !== client : !client.includes(header.alg)) throw OPE("unexpected JWT \"alg\" header parameter", INVALID_RESPONSE, {
980
- header,
981
- expected: client,
982
- reason: "client configuration"
983
- });
984
- return;
985
- }
986
- if (Array.isArray(issuer)) {
987
- if (!issuer.includes(header.alg)) throw OPE("unexpected JWT \"alg\" header parameter", INVALID_RESPONSE, {
988
- header,
989
- expected: issuer,
990
- reason: "authorization server metadata"
991
- });
992
- return;
993
- }
994
- if (fallback !== void 0) {
995
- if (typeof fallback === "string" ? header.alg !== fallback : typeof fallback === "function" ? !fallback(header.alg) : !fallback.includes(header.alg)) throw OPE("unexpected JWT \"alg\" header parameter", INVALID_RESPONSE, {
996
- header,
997
- expected: fallback,
998
- reason: "default value"
999
- });
1000
- return;
1001
- }
1002
- throw OPE("missing client or server configuration to verify used JWT \"alg\" header parameter", void 0, {
1003
- client,
1004
- issuer,
1005
- fallback
1006
- });
1007
- }
1008
- function getURLSearchParameter(parameters, name) {
1009
- const { 0: value, length } = parameters.getAll(name);
1010
- if (length > 1) throw OPE(`"${name}" parameter must be provided only once`, INVALID_RESPONSE);
1011
- return value;
1012
- }
1013
- const skipStateCheck$1 = Symbol();
1014
- const expectNoState = Symbol();
1015
- function validateAuthResponse(as, client, parameters, expectedState) {
1016
- assertAs(as);
1017
- assertClient(client);
1018
- if (parameters instanceof URL) parameters = parameters.searchParams;
1019
- if (!(parameters instanceof URLSearchParams)) throw CodedTypeError$1("\"parameters\" must be an instance of URLSearchParams, or URL", ERR_INVALID_ARG_TYPE$1);
1020
- if (getURLSearchParameter(parameters, "response")) throw OPE("\"parameters\" contains a JARM response, use validateJwtAuthResponse() instead of validateAuthResponse()", INVALID_RESPONSE, { parameters });
1021
- const iss = getURLSearchParameter(parameters, "iss");
1022
- const state = getURLSearchParameter(parameters, "state");
1023
- if (!iss && as.authorization_response_iss_parameter_supported) throw OPE("response parameter \"iss\" (issuer) missing", INVALID_RESPONSE, { parameters });
1024
- if (iss && iss !== as.issuer) throw OPE("unexpected \"iss\" (issuer) response parameter value", INVALID_RESPONSE, {
1025
- expected: as.issuer,
1026
- parameters
1027
- });
1028
- switch (expectedState) {
1029
- case void 0:
1030
- case expectNoState:
1031
- if (state !== void 0) throw OPE("unexpected \"state\" response parameter encountered", INVALID_RESPONSE, {
1032
- expected: void 0,
1033
- parameters
1034
- });
1035
- break;
1036
- case skipStateCheck$1: break;
1037
- default:
1038
- assertString$1(expectedState, "\"expectedState\" argument");
1039
- if (state !== expectedState) throw OPE(state === void 0 ? "response parameter \"state\" missing" : "unexpected \"state\" response parameter value", INVALID_RESPONSE, {
1040
- expected: expectedState,
1041
- parameters
1042
- });
1043
- }
1044
- if (getURLSearchParameter(parameters, "error")) throw new AuthorizationResponseError("authorization response from the server is an error", { cause: parameters });
1045
- const id_token = getURLSearchParameter(parameters, "id_token");
1046
- const token = getURLSearchParameter(parameters, "token");
1047
- if (id_token !== void 0 || token !== void 0) throw new UnsupportedOperationError("implicit and hybrid flows are not supported");
1048
- return brand(new URLSearchParams(parameters));
1049
- }
1050
- async function getResponseJsonBody(response, check = assertApplicationJson) {
1051
- let json;
1052
- try {
1053
- json = await response.json();
1054
- } catch (cause) {
1055
- check(response);
1056
- throw OPE("failed to parse \"response\" body as JSON", PARSE_ERROR, cause);
1057
- }
1058
- if (!isJsonObject(json)) throw OPE("\"response\" body must be a top level object", INVALID_RESPONSE, { body: json });
1059
- return json;
1060
- }
1061
- const _nodiscoverycheck = Symbol();
1062
- const _expectedIssuer = Symbol();
1063
-
1064
- //#endregion
1065
- //#region ../../node_modules/openid-client/build/index.js
1066
- let headers;
1067
- let USER_AGENT;
1068
- if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) {
1069
- USER_AGENT = `openid-client/v6.8.1`;
1070
- headers = { "user-agent": USER_AGENT };
1071
- }
1072
- const int = (config) => {
1073
- return props.get(config);
1074
- };
1075
- let props;
1076
- let tbi;
1077
- function ClientSecretPost(clientSecret) {
1078
- if (clientSecret !== void 0) return ClientSecretPost$1(clientSecret);
1079
- tbi ||= /* @__PURE__ */ new WeakMap();
1080
- return (as, client, body, headers$1) => {
1081
- let auth;
1082
- if (!(auth = tbi.get(client))) {
1083
- assertString(client.client_secret, "\"metadata.client_secret\"");
1084
- auth = ClientSecretPost$1(client.client_secret);
1085
- tbi.set(client, auth);
1086
- }
1087
- return auth(as, client, body, headers$1);
1088
- };
1089
- }
1090
- function assertString(input, it) {
1091
- if (typeof input !== "string") throw CodedTypeError(`${it} must be a string`, ERR_INVALID_ARG_TYPE);
1092
- if (input.length === 0) throw CodedTypeError(`${it} must not be empty`, ERR_INVALID_ARG_VALUE);
1093
- }
1094
- function None() {
1095
- return None$1();
1096
- }
1097
- const skipStateCheck = skipStateCheck$1;
1098
- const skipSubjectCheck = skipSubjectCheck$1;
1099
- const customFetch = customFetch$1;
1100
- const modifyAssertion = modifyAssertion$1;
1101
- const clockSkew = clockSkew$1;
1102
- const clockTolerance = clockTolerance$1;
1103
- const ERR_INVALID_ARG_VALUE = "ERR_INVALID_ARG_VALUE";
1104
- const ERR_INVALID_ARG_TYPE = "ERR_INVALID_ARG_TYPE";
1105
- function CodedTypeError(message, code, cause) {
1106
- const err = new TypeError(message, { cause });
1107
- Object.assign(err, { code });
1108
- return err;
1109
- }
1110
- function calculatePKCECodeChallenge(codeVerifier) {
1111
- return calculatePKCECodeChallenge$1(codeVerifier);
1112
- }
1113
- function randomPKCECodeVerifier() {
1114
- return generateRandomCodeVerifier();
1115
- }
1116
- function randomState() {
1117
- return generateRandomState();
1118
- }
1119
- var ClientError = class extends Error {
1120
- code;
1121
- constructor(message, options) {
1122
- super(message, options);
1123
- this.name = this.constructor.name;
1124
- this.code = options?.code;
1125
- Error.captureStackTrace?.(this, this.constructor);
1126
- }
1127
- };
1128
- const decoder = new TextDecoder();
1129
- function e(msg, cause, code) {
1130
- return new ClientError(msg, {
1131
- cause,
1132
- code
1133
- });
1134
- }
1135
- function errorHandler(err) {
1136
- if (err instanceof TypeError || err instanceof ClientError || err instanceof ResponseBodyError || err instanceof AuthorizationResponseError || err instanceof WWWAuthenticateChallengeError) throw err;
1137
- if (err instanceof OperationProcessingError) switch (err.code) {
1138
- case HTTP_REQUEST_FORBIDDEN: throw e("only requests to HTTPS are allowed", err, err.code);
1139
- case REQUEST_PROTOCOL_FORBIDDEN: throw e("only requests to HTTP or HTTPS are allowed", err, err.code);
1140
- case RESPONSE_IS_NOT_CONFORM: throw e("unexpected HTTP response status code", err.cause, err.code);
1141
- case RESPONSE_IS_NOT_JSON: throw e("unexpected response content-type", err.cause, err.code);
1142
- case PARSE_ERROR: throw e("parsing error occured", err, err.code);
1143
- case INVALID_RESPONSE: throw e("invalid response encountered", err, err.code);
1144
- case JWT_CLAIM_COMPARISON: throw e("unexpected JWT claim value encountered", err, err.code);
1145
- case JSON_ATTRIBUTE_COMPARISON: throw e("unexpected JSON attribute value encountered", err, err.code);
1146
- case JWT_TIMESTAMP_CHECK: throw e("JWT timestamp claim value failed validation", err, err.code);
1147
- default: throw e(err.message, err, err.code);
1148
- }
1149
- if (err instanceof UnsupportedOperationError) throw e("unsupported operation", err, err.code);
1150
- if (err instanceof DOMException) switch (err.name) {
1151
- case "OperationError": throw e("runtime operation error", err, UNSUPPORTED_OPERATION);
1152
- case "NotSupportedError": throw e("runtime unsupported operation", err, UNSUPPORTED_OPERATION);
1153
- case "TimeoutError": throw e("operation timed out", err, "OAUTH_TIMEOUT");
1154
- case "AbortError": throw e("operation aborted", err, "OAUTH_ABORT");
1155
- }
1156
- throw new ClientError("something went wrong", { cause: err });
1157
- }
1158
- function handleEntraId(server, as, options) {
1159
- if (server.origin === "https://login.microsoftonline.com" && (!options?.algorithm || options.algorithm === "oidc")) {
1160
- as[kEntraId] = true;
1161
- return true;
1162
- }
1163
- return false;
1164
- }
1165
- function handleB2Clogin(server, options) {
1166
- if (server.hostname.endsWith(".b2clogin.com") && (!options?.algorithm || options.algorithm === "oidc")) return true;
1167
- return false;
1168
- }
1169
- async function discovery(server, clientId, metadata, clientAuthentication, options) {
1170
- const instance = new Configuration(await performDiscovery(server, options), clientId, metadata, clientAuthentication);
1171
- let internals = int(instance);
1172
- if (options?.[customFetch]) internals.fetch = options[customFetch];
1173
- if (options?.timeout) internals.timeout = options.timeout;
1174
- if (options?.execute) for (const extension of options.execute) extension(instance);
1175
- return instance;
1176
- }
1177
- async function performDiscovery(server, options) {
1178
- if (!(server instanceof URL)) throw CodedTypeError("\"server\" must be an instance of URL", ERR_INVALID_ARG_TYPE);
1179
- const resolve = !server.href.includes("/.well-known/");
1180
- const timeout = options?.timeout ?? 30;
1181
- const signal$2 = AbortSignal.timeout(timeout * 1e3);
1182
- const as = await (resolve ? discoveryRequest(server, {
1183
- algorithm: options?.algorithm,
1184
- [customFetch$1]: options?.[customFetch],
1185
- [allowInsecureRequests$1]: options?.execute?.includes(allowInsecureRequests),
1186
- signal: signal$2,
1187
- headers: new Headers(headers)
1188
- }) : (options?.[customFetch] || fetch)((() => {
1189
- checkProtocol(server, options?.execute?.includes(allowInsecureRequests) ? false : true);
1190
- return server.href;
1191
- })(), {
1192
- headers: Object.fromEntries(new Headers({
1193
- accept: "application/json",
1194
- ...headers
1195
- }).entries()),
1196
- body: void 0,
1197
- method: "GET",
1198
- redirect: "manual",
1199
- signal: signal$2
1200
- })).then((response) => processDiscoveryResponse(_nodiscoverycheck, response)).catch(errorHandler);
1201
- if (resolve && new URL(as.issuer).href !== server.href) handleEntraId(server, as, options) || handleB2Clogin(server, options) || (() => {
1202
- throw new ClientError("discovered metadata issuer does not match the expected issuer", {
1203
- code: JSON_ATTRIBUTE_COMPARISON,
1204
- cause: {
1205
- expected: server.href,
1206
- body: as,
1207
- attribute: "issuer"
1208
- }
1209
- });
1210
- })();
1211
- return as;
1212
- }
1213
- function getServerHelpers(metadata) {
1214
- return { supportsPKCE: {
1215
- __proto__: null,
1216
- value(method = "S256") {
1217
- return metadata.code_challenge_methods_supported?.includes(method) === true;
1218
- }
1219
- } };
1220
- }
1221
- function addServerHelpers(metadata) {
1222
- Object.defineProperties(metadata, getServerHelpers(metadata));
1223
- }
1224
- const kEntraId = Symbol();
1225
- var Configuration = class {
1226
- constructor(server, clientId, metadata, clientAuthentication) {
1227
- if (typeof clientId !== "string" || !clientId.length) throw CodedTypeError("\"clientId\" must be a non-empty string", ERR_INVALID_ARG_TYPE);
1228
- if (typeof metadata === "string") metadata = { client_secret: metadata };
1229
- if (metadata?.client_id !== void 0 && clientId !== metadata.client_id) throw CodedTypeError("\"clientId\" and \"metadata.client_id\" must be the same", ERR_INVALID_ARG_VALUE);
1230
- const client = {
1231
- ...structuredClone(metadata),
1232
- client_id: clientId
1233
- };
1234
- client[clockSkew$1] = metadata?.[clockSkew$1] ?? 0;
1235
- client[clockTolerance$1] = metadata?.[clockTolerance$1] ?? 30;
1236
- let auth;
1237
- if (clientAuthentication) auth = clientAuthentication;
1238
- else if (typeof client.client_secret === "string" && client.client_secret.length) auth = ClientSecretPost(client.client_secret);
1239
- else auth = None();
1240
- let c = Object.freeze(client);
1241
- const clone = structuredClone(server);
1242
- if (kEntraId in server) clone[_expectedIssuer] = ({ claims: { tid } }) => server.issuer.replace("{tenantid}", tid);
1243
- let as = Object.freeze(clone);
1244
- props ||= /* @__PURE__ */ new WeakMap();
1245
- props.set(this, {
1246
- __proto__: null,
1247
- as,
1248
- c,
1249
- auth,
1250
- tlsOnly: true,
1251
- jwksCache: {}
1252
- });
1253
- }
1254
- serverMetadata() {
1255
- const metadata = structuredClone(int(this).as);
1256
- addServerHelpers(metadata);
1257
- return metadata;
1258
- }
1259
- clientMetadata() {
1260
- return structuredClone(int(this).c);
1261
- }
1262
- get timeout() {
1263
- return int(this).timeout;
1264
- }
1265
- set timeout(value) {
1266
- int(this).timeout = value;
1267
- }
1268
- get [customFetch]() {
1269
- return int(this).fetch;
1270
- }
1271
- set [customFetch](value) {
1272
- int(this).fetch = value;
1273
- }
1274
- };
1275
- Object.freeze(Configuration.prototype);
1276
- function getHelpers(response) {
1277
- let exp = void 0;
1278
- if (response.expires_in !== void 0) {
1279
- const now = /* @__PURE__ */ new Date();
1280
- now.setSeconds(now.getSeconds() + response.expires_in);
1281
- exp = now.getTime();
1282
- }
1283
- return {
1284
- expiresIn: {
1285
- __proto__: null,
1286
- value() {
1287
- if (exp) {
1288
- const now = Date.now();
1289
- if (exp > now) return Math.floor((exp - now) / 1e3);
1290
- return 0;
1291
- }
1292
- }
1293
- },
1294
- claims: {
1295
- __proto__: null,
1296
- value() {
1297
- try {
1298
- return getValidatedIdTokenClaims(this);
1299
- } catch {
1300
- return;
1301
- }
1302
- }
1303
- }
1304
- };
1305
- }
1306
- function addHelpers(response) {
1307
- Object.defineProperties(response, getHelpers(response));
1308
- }
1309
- function allowInsecureRequests(config) {
1310
- int(config).tlsOnly = false;
1311
- }
1312
- function stripParams(url) {
1313
- url = new URL(url);
1314
- url.search = "";
1315
- url.hash = "";
1316
- return url.href;
1317
- }
1318
- function webInstanceOf(input, toStringTag) {
1319
- try {
1320
- return Object.getPrototypeOf(input)[Symbol.toStringTag] === toStringTag;
1321
- } catch {
1322
- return false;
1323
- }
1324
- }
1325
- async function authorizationCodeGrant(config, currentUrl, checks, tokenEndpointParameters, options) {
1326
- checkConfig(config);
1327
- if (options?.flag !== retry && !(currentUrl instanceof URL) && !webInstanceOf(currentUrl, "Request")) throw CodedTypeError("\"currentUrl\" must be an instance of URL, or Request", ERR_INVALID_ARG_TYPE);
1328
- let authResponse;
1329
- let redirectUri;
1330
- const { as, c, auth, fetch: fetch$1, tlsOnly, jarm, hybrid, nonRepudiation, timeout, decrypt, implicit } = int(config);
1331
- if (options?.flag === retry) {
1332
- authResponse = options.authResponse;
1333
- redirectUri = options.redirectUri;
1334
- } else {
1335
- if (!(currentUrl instanceof URL)) {
1336
- const request = currentUrl;
1337
- currentUrl = new URL(currentUrl.url);
1338
- switch (request.method) {
1339
- case "GET": break;
1340
- case "POST":
1341
- const params = new URLSearchParams(await formPostResponse(request));
1342
- if (hybrid) currentUrl.hash = params.toString();
1343
- else for (const [k, v] of params.entries()) currentUrl.searchParams.append(k, v);
1344
- break;
1345
- default: throw CodedTypeError("unexpected Request HTTP method", ERR_INVALID_ARG_VALUE);
1346
- }
1347
- }
1348
- redirectUri = stripParams(currentUrl);
1349
- switch (true) {
1350
- case !!jarm:
1351
- authResponse = await jarm(currentUrl, checks?.expectedState);
1352
- break;
1353
- case !!hybrid:
1354
- authResponse = await hybrid(currentUrl, checks?.expectedNonce, checks?.expectedState, checks?.maxAge);
1355
- break;
1356
- case !!implicit: throw new TypeError("authorizationCodeGrant() cannot be used by response_type=id_token clients");
1357
- default: try {
1358
- authResponse = validateAuthResponse(as, c, currentUrl.searchParams, checks?.expectedState);
1359
- } catch (err) {
1360
- errorHandler(err);
1361
- }
1362
- }
1363
- }
1364
- const response = await authorizationCodeGrantRequest(as, c, auth, authResponse, redirectUri, checks?.pkceCodeVerifier || nopkce, {
1365
- additionalParameters: tokenEndpointParameters,
1366
- [customFetch$1]: fetch$1,
1367
- [allowInsecureRequests$1]: !tlsOnly,
1368
- DPoP: options?.DPoP,
1369
- headers: new Headers(headers),
1370
- signal: signal(timeout)
1371
- }).catch(errorHandler);
1372
- if (typeof checks?.expectedNonce === "string" || typeof checks?.maxAge === "number") checks.idTokenExpected = true;
1373
- const p = processAuthorizationCodeResponse(as, c, response, {
1374
- expectedNonce: checks?.expectedNonce,
1375
- maxAge: checks?.maxAge,
1376
- requireIdToken: checks?.idTokenExpected,
1377
- [jweDecrypt]: decrypt
1378
- });
1379
- let result;
1380
- try {
1381
- result = await p;
1382
- } catch (err) {
1383
- if (retryable(err, options)) return authorizationCodeGrant(config, void 0, checks, tokenEndpointParameters, {
1384
- ...options,
1385
- flag: retry,
1386
- authResponse,
1387
- redirectUri
1388
- });
1389
- errorHandler(err);
1390
- }
1391
- result.id_token && await nonRepudiation?.(response);
1392
- addHelpers(result);
1393
- return result;
1394
- }
1395
- async function refreshTokenGrant(config, refreshToken, parameters, options) {
1396
- checkConfig(config);
1397
- parameters = new URLSearchParams(parameters);
1398
- const { as, c, auth, fetch: fetch$1, tlsOnly, nonRepudiation, timeout, decrypt } = int(config);
1399
- const response = await refreshTokenGrantRequest(as, c, auth, refreshToken, {
1400
- [customFetch$1]: fetch$1,
1401
- [allowInsecureRequests$1]: !tlsOnly,
1402
- additionalParameters: parameters,
1403
- DPoP: options?.DPoP,
1404
- headers: new Headers(headers),
1405
- signal: signal(timeout)
1406
- }).catch(errorHandler);
1407
- const p = processRefreshTokenResponse(as, c, response, { [jweDecrypt]: decrypt });
1408
- let result;
1409
- try {
1410
- result = await p;
1411
- } catch (err) {
1412
- if (retryable(err, options)) return refreshTokenGrant(config, refreshToken, parameters, {
1413
- ...options,
1414
- flag: retry
1415
- });
1416
- errorHandler(err);
1417
- }
1418
- result.id_token && await nonRepudiation?.(response);
1419
- addHelpers(result);
1420
- return result;
1421
- }
1422
- function buildAuthorizationUrl(config, parameters) {
1423
- checkConfig(config);
1424
- const { as, c, tlsOnly, hybrid, jarm, implicit } = int(config);
1425
- const authorizationEndpoint = resolveEndpoint(as, "authorization_endpoint", false, tlsOnly);
1426
- parameters = new URLSearchParams(parameters);
1427
- if (!parameters.has("client_id")) parameters.set("client_id", c.client_id);
1428
- if (!parameters.has("request_uri") && !parameters.has("request")) {
1429
- if (!parameters.has("response_type")) parameters.set("response_type", hybrid ? "code id_token" : implicit ? "id_token" : "code");
1430
- if (implicit && !parameters.has("nonce")) throw CodedTypeError("response_type=id_token clients must provide a nonce parameter in their authorization request parameters", ERR_INVALID_ARG_VALUE);
1431
- if (jarm) parameters.set("response_mode", "jwt");
1432
- }
1433
- for (const [k, v] of parameters.entries()) authorizationEndpoint.searchParams.append(k, v);
1434
- return authorizationEndpoint;
1435
- }
1436
- function buildEndSessionUrl(config, parameters) {
1437
- checkConfig(config);
1438
- const { as, c, tlsOnly } = int(config);
1439
- const endSessionEndpoint = resolveEndpoint(as, "end_session_endpoint", false, tlsOnly);
1440
- parameters = new URLSearchParams(parameters);
1441
- if (!parameters.has("client_id")) parameters.set("client_id", c.client_id);
1442
- for (const [k, v] of parameters.entries()) endSessionEndpoint.searchParams.append(k, v);
1443
- return endSessionEndpoint;
1444
- }
1445
- function checkConfig(input) {
1446
- if (!(input instanceof Configuration)) throw CodedTypeError("\"config\" must be an instance of Configuration", ERR_INVALID_ARG_TYPE);
1447
- if (Object.getPrototypeOf(input) !== Configuration.prototype) throw CodedTypeError("subclassing Configuration is not allowed", ERR_INVALID_ARG_VALUE);
1448
- }
1449
- function signal(timeout) {
1450
- return timeout ? AbortSignal.timeout(timeout * 1e3) : void 0;
1451
- }
1452
- function retryable(err, options) {
1453
- if (options?.DPoP && options.flag !== retry) return isDPoPNonceError(err);
1454
- return false;
1455
- }
1456
- const retry = Symbol();
1457
-
1458
- //#endregion
1459
- //#region src/server-auth/descriptors/$auth.ts
1460
- /**
1461
- * Creates an authentication provider descriptor for handling user login flows.
1462
- *
1463
- * Supports multiple authentication strategies: credentials (username/password), OAuth2,
1464
- * and OIDC (OpenID Connect). Handles token management, user profile retrieval, and
1465
- * integration with both external identity providers (Auth0, Keycloak) and internal realms.
1466
- *
1467
- * **Authentication Types**: Credentials, OAuth2 (Google, GitHub), OIDC, External providers
1468
- *
1469
- * @example
1470
- * ```ts
1471
- * class AuthProviders {
1472
- * // Internal credentials-based auth
1473
- * credentials = $auth({
1474
- * realm: this.userRealm,
1475
- * credentials: {
1476
- * account: async ({ username, password }) => {
1477
- * return await this.validateUser(username, password);
1478
- * }
1479
- * }
1480
- * });
1481
- *
1482
- * // External OIDC provider
1483
- * keycloak = $auth({
1484
- * oidc: {
1485
- * issuer: "https://auth.example.com",
1486
- * clientId: "my-app",
1487
- * clientSecret: "secret",
1488
- * redirectUri: "/auth/callback"
1489
- * }
1490
- * });
1491
- * }
1492
- * ```
1493
- */
1494
- const $auth = (options) => {
1495
- return (0, alepha.createDescriptor)(AuthDescriptor, options);
1496
- };
1497
- var AuthDescriptor = class extends alepha.Descriptor {
1498
- securityProvider = (0, alepha.$inject)(alepha_security.SecurityProvider);
1499
- dateTimeProvider = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
1500
- oauth;
1501
- get name() {
1502
- return this.options.name ?? this.config.propertyKey;
1503
- }
1504
- get jwks_uri() {
1505
- const jwks = this.oauth?.serverMetadata().jwks_uri;
1506
- if (!jwks) throw new alepha.AlephaError("No JWKS URI available for the auth provider");
1507
- return jwks;
1508
- }
1509
- get scope() {
1510
- if ("oauth" in this.options) return this.options.oauth.scope;
1511
- if ("oidc" in this.options) return this.options.oidc.scope || "openid profile email";
1512
- throw new alepha.AlephaError("No OAuth2 or OIDC configuration available for the auth provider");
1513
- }
1514
- get redirect_uri() {
1515
- if ("oauth" in this.options) return this.options.oauth.redirectUri;
1516
- if ("oidc" in this.options) return this.options.oidc.redirectUri;
1517
- throw new alepha.AlephaError("No OAuth2 or OIDC configuration available for the auth provider");
1518
- }
1519
- /**
1520
- * Refreshes the access token using the refresh token.
1521
- * Can be used on oauth2, oidc or credentials auth providers.
1522
- */
1523
- async refresh(refreshToken, accessToken) {
1524
- if ("realm" in this.options) return this.options.realm.refreshToken(refreshToken, accessToken).then((it) => it.tokens).catch((error) => {
1525
- throw new alepha_security.SecurityError("Failed to refresh access token using the refresh token (realm)", { cause: error });
1526
- });
1527
- else if (this.oauth) try {
1528
- return {
1529
- ...await refreshTokenGrant(this.oauth, refreshToken),
1530
- issued_at: this.dateTimeProvider.now().unix()
1531
- };
1532
- } catch (error) {
1533
- throw new alepha_security.SecurityError("Failed to refresh access token using the refresh token (oauth2)", { cause: error });
1534
- }
1535
- throw new alepha.AlephaError("No realm or OAuth2 configuration available for refreshing the access token");
1536
- }
1537
- /**
1538
- * Extracts user information from the access token.
1539
- * This is used to create a user account from the access token.
1540
- */
1541
- async user(tokens) {
1542
- try {
1543
- if ("oauth" in this.options) {
1544
- const profile = await this.options.oauth.userinfo(tokens);
1545
- if (this.options.oauth.account) return this.options.oauth.account({
1546
- ...tokens,
1547
- user: profile
1548
- });
1549
- return this.securityProvider.createUserFromPayload(profile);
1550
- }
1551
- if ("oidc" in this.options) {
1552
- const payload = this.getUserFromIdToken(tokens.id_token || "");
1553
- if (this.options.oidc.account) return this.options.oidc.account({
1554
- ...tokens,
1555
- user: payload
1556
- });
1557
- return this.securityProvider.createUserFromPayload(payload);
1558
- }
1559
- } catch (error) {
1560
- throw new alepha_security.SecurityError("Failed to extract user from identity provider tokens", { cause: error });
1561
- }
1562
- throw new alepha.AlephaError("This authentication does not support user extraction from tokens");
1563
- }
1564
- getUserFromIdToken(idToken) {
1565
- try {
1566
- return JSON.parse(Buffer.from(idToken.split(".")[1], "base64").toString("utf8"));
1567
- } catch (error) {
1568
- throw new alepha.AlephaError("Failed to parse ID Token payload", { cause: error });
1569
- }
1570
- }
1571
- async prepare() {
1572
- const addons = [];
1573
- addons.push(allowInsecureRequests);
1574
- if ("oidc" in this.options) {
1575
- const { oidc } = this.options;
1576
- this.oauth = await discovery(new URL(oidc.issuer), oidc.clientId, { client_secret: oidc.clientSecret }, void 0, { execute: addons });
1577
- }
1578
- if ("oauth" in this.options) {
1579
- const { oauth } = this.options;
1580
- this.oauth = new Configuration({
1581
- authorization_endpoint: oauth.authorization,
1582
- token_endpoint: oauth.token,
1583
- issuer: oauth.authorization,
1584
- jwks_uri: void 0,
1585
- end_session_endpoint: void 0
1586
- }, oauth.clientId, { client_secret: oauth.clientSecret });
1587
- }
1588
- }
1589
- };
1590
- $auth[alepha.KIND] = AuthDescriptor;
1591
-
1592
- //#endregion
1593
- //#region src/server-security/providers/ServerBasicAuthProvider.ts
1594
- var ServerBasicAuthProvider = class {
1595
- alepha = (0, alepha.$inject)(alepha.Alepha);
1596
- log = (0, alepha_logger.$logger)();
1597
- routerProvider = (0, alepha.$inject)(alepha_server.ServerRouterProvider);
1598
- realm = "Secure Area";
1599
- /**
1600
- * Registered basic auth descriptors with their configurations
1601
- */
1602
- registeredAuths = [];
1603
- /**
1604
- * Register a basic auth configuration (called by descriptors)
1605
- */
1606
- registerAuth(config) {
1607
- this.registeredAuths.push(config);
1608
- }
1609
- onStart = (0, alepha.$hook)({
1610
- on: "start",
1611
- handler: async () => {
1612
- for (const auth of this.registeredAuths) if (auth.paths) for (const pattern of auth.paths) {
1613
- const matchedRoutes = this.routerProvider.getRoutes(pattern);
1614
- for (const route of matchedRoutes) route.secure = { basic: {
1615
- username: auth.username,
1616
- password: auth.password
1617
- } };
1618
- }
1619
- if (this.registeredAuths.length > 0) this.log.info(`Initialized with ${this.registeredAuths.length} registered basic-auth configurations.`);
1620
- }
1621
- });
1622
- /**
1623
- * Hook into server:onRequest to check basic auth
1624
- */
1625
- onRequest = (0, alepha.$hook)({
1626
- on: "server:onRequest",
1627
- handler: async ({ route, request }) => {
1628
- const routeAuth = route.secure;
1629
- if (typeof routeAuth === "object" && "basic" in routeAuth && routeAuth.basic) this.checkAuth(request, routeAuth.basic);
1630
- }
1631
- });
1632
- /**
1633
- * Hook into action:onRequest to check basic auth for actions
1634
- */
1635
- onActionRequest = (0, alepha.$hook)({
1636
- on: "action:onRequest",
1637
- handler: async ({ action, request }) => {
1638
- const routeAuth = action.route.secure;
1639
- if (isBasicAuth(routeAuth)) this.checkAuth(request, routeAuth.basic);
1640
- }
1641
- });
1642
- /**
1643
- * Check basic authentication
1644
- */
1645
- checkAuth(request, options) {
1646
- const authHeader = request.headers?.authorization;
1647
- if (!authHeader || !authHeader.startsWith("Basic ")) {
1648
- this.sendAuthRequired(request);
1649
- throw new alepha_server.HttpError({
1650
- status: 401,
1651
- message: "Authentication required"
1652
- });
1653
- }
1654
- const base64Credentials = authHeader.slice(6);
1655
- const credentials = Buffer.from(base64Credentials, "base64").toString("utf-8");
1656
- const colonIndex = credentials.indexOf(":");
1657
- const username = colonIndex !== -1 ? credentials.slice(0, colonIndex) : credentials;
1658
- const password = colonIndex !== -1 ? credentials.slice(colonIndex + 1) : "";
1659
- if (!this.timingSafeCredentialCheck(username, password, options.username, options.password)) {
1660
- this.sendAuthRequired(request);
1661
- this.log.warn(`Failed basic auth attempt for user`, { username });
1662
- throw new alepha_server.HttpError({
1663
- status: 401,
1664
- message: "Invalid credentials"
1665
- });
1666
- }
1667
- }
1668
- /**
1669
- * Performs a timing-safe comparison of credentials to prevent timing attacks.
1670
- * Always compares both username and password to avoid leaking which one is wrong.
1671
- */
1672
- timingSafeCredentialCheck(inputUsername, inputPassword, expectedUsername, expectedPassword) {
1673
- const inputUserBuf = Buffer.from(inputUsername, "utf-8");
1674
- const expectedUserBuf = Buffer.from(expectedUsername, "utf-8");
1675
- const inputPassBuf = Buffer.from(inputPassword, "utf-8");
1676
- const expectedPassBuf = Buffer.from(expectedPassword, "utf-8");
1677
- return (this.safeCompare(inputUserBuf, expectedUserBuf) & this.safeCompare(inputPassBuf, expectedPassBuf)) === 1;
1678
- }
1679
- /**
1680
- * Compares two buffers in constant time, handling different lengths safely.
1681
- * Returns 1 if equal, 0 if not equal.
1682
- */
1683
- safeCompare(input, expected) {
1684
- if (input.length !== expected.length) {
1685
- (0, node_crypto.timingSafeEqual)(input, input);
1686
- return 0;
1687
- }
1688
- return (0, node_crypto.timingSafeEqual)(input, expected) ? 1 : 0;
1689
- }
1690
- /**
1691
- * Send WWW-Authenticate header
1692
- */
1693
- sendAuthRequired(request) {
1694
- request.reply.setHeader("WWW-Authenticate", `Basic realm="${this.realm}"`);
1695
- }
1696
- };
1697
- const isBasicAuth = (value) => {
1698
- return typeof value === "object" && !!value && "basic" in value && !!value.basic;
1699
- };
1700
-
1701
- //#endregion
1702
- //#region src/server-security/descriptors/$basicAuth.ts
1703
- /**
1704
- * Declares HTTP Basic Authentication for server routes.
1705
- * This descriptor provides methods to protect routes with username/password authentication.
1706
- */
1707
- const $basicAuth = (options) => {
1708
- return (0, alepha.createDescriptor)(BasicAuthDescriptor, options);
1709
- };
1710
- var BasicAuthDescriptor = class extends alepha.Descriptor {
1711
- serverBasicAuthProvider = (0, alepha.$inject)(ServerBasicAuthProvider);
1712
- get name() {
1713
- return this.options.name ?? `${this.config.propertyKey}`;
1714
- }
1715
- onInit() {
1716
- this.serverBasicAuthProvider.registerAuth(this.options);
1717
- }
1718
- /**
1719
- * Checks basic auth for the given request using this descriptor's configuration.
1720
- */
1721
- check(request, options) {
1722
- const mergedOptions = {
1723
- ...this.options,
1724
- ...options
1725
- };
1726
- this.serverBasicAuthProvider.checkAuth(request, mergedOptions);
1727
- }
1728
- };
1729
- $basicAuth[alepha.KIND] = BasicAuthDescriptor;
1730
-
1731
- //#endregion
1732
- //#region src/server-security/providers/ServerSecurityProvider.ts
1733
- var ServerSecurityProvider = class {
1734
- log = (0, alepha_logger.$logger)();
1735
- securityProvider = (0, alepha.$inject)(alepha_security.SecurityProvider);
1736
- jwtProvider = (0, alepha.$inject)(alepha_security.JwtProvider);
1737
- alepha = (0, alepha.$inject)(alepha.Alepha);
1738
- onConfigure = (0, alepha.$hook)({
1739
- on: "configure",
1740
- handler: async () => {
1741
- for (const action of this.alepha.descriptors(alepha_server.$action)) {
1742
- if (action.options.disabled || action.options.secure === false || this.securityProvider.getRealms().length === 0) continue;
1743
- if (typeof action.options.secure !== "object") this.securityProvider.createPermission({
1744
- name: action.name,
1745
- group: action.group,
1746
- method: action.route.method,
1747
- path: action.route.path
1748
- });
1749
- }
1750
- }
1751
- });
1752
- onActionRequest = (0, alepha.$hook)({
1753
- on: "action:onRequest",
1754
- handler: async ({ action, request, options }) => {
1755
- if (action.options.secure === false && !options.user) {
1756
- this.log.trace("Skipping security check for route");
1757
- return;
1758
- }
1759
- if (isBasicAuth(action.route.secure)) return;
1760
- const permission = this.securityProvider.getPermissions().find((it) => it.path === action.route.path && it.method === action.route.method);
1761
- try {
1762
- request.user = this.createUserFromLocalFunctionContext(options, permission);
1763
- const route = action.route;
1764
- if (typeof route.secure === "object") this.check(request.user, route.secure);
1765
- this.alepha.state.set("alepha.server.request.user", this.alepha.codec.decode(alepha_security.userAccountInfoSchema, request.user));
1766
- } catch (error) {
1767
- if (action.options.secure || permission) throw error;
1768
- this.log.trace("Skipping security check for action");
1769
- }
1770
- }
1771
- });
1772
- onRequest = (0, alepha.$hook)({
1773
- on: "server:onRequest",
1774
- priority: "last",
1775
- handler: async ({ request, route }) => {
1776
- if (route.secure === false) {
1777
- this.log.trace("Skipping security check for route - explicitly disabled");
1778
- return;
1779
- }
1780
- if (isBasicAuth(route.secure)) return;
1781
- const permission = this.securityProvider.getPermissions().find((it) => it.path === route.path && it.method === route.method);
1782
- if (!request.headers.authorization && !route.secure && !permission) {
1783
- this.log.trace("Skipping security check for route - no authorization header and not secure");
1784
- return;
1785
- }
1786
- try {
1787
- request.user = await this.securityProvider.createUserFromToken(request.headers.authorization, { permission });
1788
- if (typeof route.secure === "object") this.check(request.user, route.secure);
1789
- this.alepha.state.set("alepha.server.request.user", this.alepha.codec.decode(alepha_security.userAccountInfoSchema, request.user));
1790
- this.log.trace("User set from request token", {
1791
- user: request.user,
1792
- permission
1793
- });
1794
- } catch (error) {
1795
- if (route.secure || permission) throw error;
1796
- this.log.trace("Skipping security check for route - error occurred", error);
1797
- }
1798
- }
1799
- });
1800
- check(user, secure) {
1801
- if (secure.realm) {
1802
- if (user.realm !== secure.realm) throw new alepha_server.ForbiddenError(`User must belong to realm '${secure.realm}' to access this route`);
1803
- }
1804
- }
1805
- /**
1806
- * Get the user account token for a local action call.
1807
- * There are three possible sources for the user:
1808
- * - `options.user`: the user passed in the options
1809
- * - `"system"`: the system user from the state (you MUST set state `server.security.system.user`)
1810
- * - `"context"`: the user from the request context (you MUST be in an HTTP request context)
1811
- *
1812
- * Priority order: `options.user` > `"system"` > `"context"`.
1813
- *
1814
- * In testing environment, if no user is provided, a test user is created based on the SecurityProvider's roles.
1815
- */
1816
- createUserFromLocalFunctionContext(options, permission) {
1817
- const fromOptions = typeof options.user === "object" ? options.user : void 0;
1818
- const type = typeof options.user === "string" ? options.user : void 0;
1819
- let user;
1820
- const fromContext = this.alepha.context.get("request")?.user;
1821
- const fromSystem = this.alepha.state.get("alepha.server.security.system.user");
1822
- if (type === "system") user = fromSystem;
1823
- else if (type === "context") user = fromContext;
1824
- else user = fromOptions ?? fromContext ?? fromSystem;
1825
- if (!user) {
1826
- if (this.alepha.isTest() && !("user" in options)) return this.createTestUser();
1827
- throw new alepha_server.UnauthorizedError("User is required for calling this action");
1828
- }
1829
- const roles = user.roles ?? (this.alepha.isTest() ? this.securityProvider.getRoles().map((role) => role.name) : []);
1830
- let ownership;
1831
- if (permission) {
1832
- const result = this.securityProvider.checkPermission(permission, ...roles);
1833
- if (!result.isAuthorized) throw new alepha_server.ForbiddenError(`Permission '${this.securityProvider.permissionToString(permission)}' is required for this route`);
1834
- ownership = result.ownership;
1835
- }
1836
- return {
1837
- ...user,
1838
- ownership
1839
- };
1840
- }
1841
- createTestUser() {
1842
- return {
1843
- id: (0, node_crypto.randomUUID)(),
1844
- name: "Test",
1845
- roles: this.securityProvider.getRoles().map((role) => role.name)
1846
- };
1847
- }
1848
- onClientRequest = (0, alepha.$hook)({
1849
- on: "client:onRequest",
1850
- handler: async ({ request, options }) => {
1851
- if (!this.alepha.isTest()) return;
1852
- if ("user" in options && options.user === void 0) return;
1853
- request.headers = new Headers(request.headers);
1854
- if (!request.headers.has("authorization")) {
1855
- const test = this.createTestUser();
1856
- const user = typeof options?.user === "object" ? options.user : void 0;
1857
- const sub = user?.id ?? test.id;
1858
- const roles = user?.roles ?? test.roles;
1859
- const token = await this.jwtProvider.create({
1860
- sub,
1861
- roles
1862
- }, user?.realm ?? this.securityProvider.getRealms()[0]?.name);
1863
- request.headers.set("authorization", `Bearer ${token}`);
1864
- }
1865
- }
1866
- });
1867
- };
1868
-
1869
- //#endregion
1870
- //#region src/server-security/index.ts
1871
- /**
1872
- * Plugin for Alepha Server that provides security features. Based on the Alepha Security module.
1873
- *
1874
- * By default, all $action will be guarded by a permission check.
1875
- *
1876
- * @see {@link ServerSecurityProvider}
1877
- * @module alepha.server.security
1878
- */
1879
- const AlephaServerSecurity = (0, alepha.$module)({
1880
- name: "alepha.server.security",
1881
- descriptors: [
1882
- alepha_security.$realm,
1883
- alepha_security.$role,
1884
- alepha_security.$permission,
1885
- $basicAuth
1886
- ],
1887
- services: [
1888
- alepha_server.AlephaServer,
1889
- alepha_security.AlephaSecurity,
1890
- ServerSecurityProvider,
1891
- ServerBasicAuthProvider
1892
- ]
1893
- });
1894
-
1895
- //#endregion
1896
- //#region src/server-links/schemas/apiLinksResponseSchema.ts
1897
- const apiLinkSchema = alepha.t.object({
1898
- name: alepha.t.text({ description: "Name of the API link, used for identification." }),
1899
- group: alepha.t.optional(alepha.t.text({ description: "Group to which the API link belongs, used for categorization." })),
1900
- path: alepha.t.text({ description: "Pathname used to access the API link." }),
1901
- method: alepha.t.optional(alepha.t.text({ description: "HTTP method used for the API link, e.g., GET, POST, etc. If not specified, defaults to GET." })),
1902
- requestBodyType: alepha.t.optional(alepha.t.text({ description: "Type of the request body for the API link. Default is application/json for POST/PUT/PATCH, null for others." })),
1903
- service: alepha.t.optional(alepha.t.text({ description: "Service name associated with the API link, used for service discovery." }))
1904
- });
1905
- const apiLinksResponseSchema = alepha.t.object({
1906
- prefix: alepha.t.optional(alepha.t.text()),
1907
- links: alepha.t.array(apiLinkSchema)
1908
- });
1909
-
1910
- //#endregion
1911
- //#region src/server-links/providers/LinkProvider.ts
1912
- /**
1913
- * Browser, SSR friendly, service to handle links.
1914
- */
1915
- var LinkProvider = class LinkProvider {
1916
- static path = {
1917
- apiLinks: "/api/_links",
1918
- apiSchema: "/api/_links/:name/schema"
1919
- };
1920
- log = (0, alepha_logger.$logger)();
1921
- alepha = (0, alepha.$inject)(alepha.Alepha);
1922
- httpClient = (0, alepha.$inject)(alepha_server.HttpClient);
1923
- serverLinks = [];
1924
- /**
1925
- * Get applicative links registered on the server.
1926
- * This does not include lazy-loaded remote links.
1927
- */
1928
- getServerLinks() {
1929
- if (this.alepha.isBrowser()) {
1930
- this.log.warn("Getting server links in the browser is not supported. Use `fetchLinks` to get links from the server.");
1931
- return [];
1932
- }
1933
- return this.serverLinks;
1934
- }
1935
- /**
1936
- * Register a new link for the application.
1937
- */
1938
- registerLink(link) {
1939
- if (this.alepha.isBrowser()) {
1940
- this.log.warn("Registering links in the browser is not supported. Use `fetchLinks` to get links from the server.");
1941
- return;
1942
- }
1943
- if (!link.handler && !link.host) throw new alepha.AlephaError("Can't create link - 'handler' or 'host' is required");
1944
- if (this.serverLinks.some((l) => l.name === link.name)) this.serverLinks = this.serverLinks.filter((l) => l.name !== link.name);
1945
- this.serverLinks.push(link);
1946
- }
1947
- get links() {
1948
- const apiLinks = this.alepha.state.get("alepha.server.request.apiLinks")?.links;
1949
- if (apiLinks) {
1950
- if (this.alepha.isBrowser()) return apiLinks;
1951
- const links = [];
1952
- for (const link of apiLinks) {
1953
- const originalLink = this.serverLinks.find((l) => l.name === link.name);
1954
- if (originalLink) links.push(originalLink);
1955
- }
1956
- return links;
1957
- }
1958
- return this.serverLinks ?? [];
1959
- }
1960
- /**
1961
- * Force browser to refresh links from the server.
1962
- */
1963
- async fetchLinks() {
1964
- const { data } = await this.httpClient.fetch(`${LinkProvider.path.apiLinks}`, {
1965
- method: "GET",
1966
- schema: { response: apiLinksResponseSchema }
1967
- });
1968
- this.alepha.state.set("alepha.server.request.apiLinks", data);
1969
- return data.links;
1970
- }
1971
- /**
1972
- * Create a virtual client that can be used to call actions.
1973
- *
1974
- * Use js Proxy under the hood.
1975
- */
1976
- client(scope = {}) {
1977
- return new Proxy({}, { get: (_, prop) => {
1978
- if (typeof prop !== "string") return;
1979
- return this.createVirtualAction(prop, scope);
1980
- } });
1981
- }
1982
- /**
1983
- * Check if a link with the given name exists.
1984
- * @param name
1985
- */
1986
- can(name) {
1987
- return this.links.some((link) => link.name === name);
1988
- }
1989
- /**
1990
- * Resolve a link by its name and call it.
1991
- * - If link is local, it will call the local handler.
1992
- * - If link is remote, it will make a fetch request to the remote server.
1993
- */
1994
- async follow(name, config = {}, options = {}) {
1995
- this.log.trace("Following link", {
1996
- name,
1997
- config,
1998
- options
1999
- });
2000
- const link = await this.getLinkByName(name, options);
2001
- if (link.handler && !options.request) {
2002
- this.log.trace("Local link found", { name });
2003
- return link.handler({
2004
- method: link.method,
2005
- url: new URL(`http://localhost${link.path}`),
2006
- query: config.query ?? {},
2007
- body: config.body ?? {},
2008
- params: config.params ?? {},
2009
- headers: config.headers ?? {},
2010
- metadata: {},
2011
- reply: new alepha_server.ServerReply()
2012
- }, options);
2013
- }
2014
- this.log.trace("Remote link found", {
2015
- name,
2016
- host: link.host,
2017
- service: link.service
2018
- });
2019
- return this.followRemote(link, config, options).then((response) => response.data);
2020
- }
2021
- createVirtualAction(name, scope = {}) {
2022
- const $ = async (config = {}, options = {}) => {
2023
- return this.follow(name, config, {
2024
- ...scope,
2025
- ...options
2026
- });
2027
- };
2028
- Object.defineProperty($, "name", {
2029
- value: name,
2030
- writable: false
2031
- });
2032
- $.run = async (config = {}, options = {}) => {
2033
- return this.follow(name, config, {
2034
- ...scope,
2035
- ...options
2036
- });
2037
- };
2038
- $.fetch = async (config = {}, options = {}) => {
2039
- const link = await this.getLinkByName(name, scope);
2040
- return this.followRemote(link, config, options);
2041
- };
2042
- $.can = () => {
2043
- return this.can(name);
2044
- };
2045
- return $;
2046
- }
2047
- async followRemote(link, config = {}, options = {}) {
2048
- options.request ??= {};
2049
- options.request.headers = new Headers(options.request.headers);
2050
- const als = this.alepha.context.get("request");
2051
- if (als?.headers.authorization) options.request.headers.set("authorization", als.headers.authorization);
2052
- const context = this.alepha.context.get("context");
2053
- if (typeof context === "string") options.request.headers.set("x-request-id", context);
2054
- const action = {
2055
- ...link,
2056
- schema: {
2057
- body: alepha.t.any(),
2058
- response: alepha.t.any()
2059
- }
2060
- };
2061
- if (!link.host && link.service) action.path = `/${link.service}${action.path}`;
2062
- action.path = `${action.prefix ?? "/api"}${action.path}`;
2063
- action.prefix = void 0;
2064
- return this.httpClient.fetchAction({
2065
- host: link.host,
2066
- config,
2067
- options,
2068
- action
2069
- });
2070
- }
2071
- async getLinkByName(name, options = {}) {
2072
- if (this.alepha.isBrowser() && !this.alepha.state.get("alepha.server.request.apiLinks")) await this.fetchLinks();
2073
- const link = this.links.find((a) => a.name === name && (!options.group || a.group === options.group) && (!options.service || options.service === a.service));
2074
- if (!link) {
2075
- const error = new alepha_server.UnauthorizedError(`Action ${name} not found.`);
2076
- await this.alepha.events.emit("client:onError", {
2077
- route: link,
2078
- error
2079
- });
2080
- throw error;
2081
- }
2082
- if (options.hostname) return {
2083
- ...link,
2084
- host: options.hostname
2085
- };
2086
- return link;
2087
- }
2088
- };
2089
-
2090
- //#endregion
2091
- //#region src/server-links/descriptors/$client.ts
2092
- /**
2093
- * Create a new client.
2094
- */
2095
- const $client = (scope) => {
2096
- return (0, alepha.$inject)(LinkProvider).client(scope);
2097
- };
2098
- $client[alepha.KIND] = "$client";
2099
-
2100
- //#endregion
2101
- //#region src/server-links/descriptors/$remote.ts
2102
- /**
2103
- * $remote is a descriptor that allows you to define remote service access.
2104
- *
2105
- * Use it only when you have 2 or more services that need to communicate with each other.
2106
- *
2107
- * All remote services can be exposed as actions, ... or not.
2108
- *
2109
- * You can add a service account if you want to use a security layer.
2110
- */
2111
- const $remote = (options) => {
2112
- return (0, alepha.createDescriptor)(RemoteDescriptor, options);
2113
- };
2114
- var RemoteDescriptor = class extends alepha.Descriptor {
2115
- get name() {
2116
- return this.options.name ?? this.config.propertyKey;
2117
- }
2118
- };
2119
- $remote[alepha.KIND] = RemoteDescriptor;
2120
-
2121
- //#endregion
2122
- //#region src/server-proxy/descriptors/$proxy.ts
2123
- /**
2124
- * Creates a proxy descriptor to forward requests to another server.
2125
- *
2126
- * This descriptor enables you to create reverse proxy functionality, allowing your Alepha server
2127
- * to forward requests to other services while maintaining a unified API surface. It's particularly
2128
- * useful for microservice architectures, API gateways, or when you need to aggregate multiple
2129
- * services behind a single endpoint.
2130
- *
2131
- * **Key Features**
2132
- *
2133
- * - **Path-based routing**: Match specific paths or patterns to proxy
2134
- * - **Dynamic targets**: Support both static and dynamic target resolution
2135
- * - **Request/Response hooks**: Modify requests before forwarding and responses after receiving
2136
- * - **URL rewriting**: Transform URLs before forwarding to the target
2137
- * - **Conditional proxying**: Enable/disable proxies based on environment or conditions
2138
- *
2139
- * @example
2140
- * **Basic proxy setup:**
2141
- * ```ts
2142
- * import { $proxy } from "alepha/server/proxy";
2143
- *
2144
- * class ApiGateway {
2145
- * // Forward all /api/* requests to external service
2146
- * api = $proxy({
2147
- * path: "/api/*",
2148
- * target: "https://api.example.com"
2149
- * });
2150
- * }
2151
- * ```
2152
- *
2153
- * @example
2154
- * **Dynamic target with environment-based routing:**
2155
- * ```ts
2156
- * class ApiGateway {
2157
- * // Route to different environments based on configuration
2158
- * api = $proxy({
2159
- * path: "/api/*",
2160
- * target: () => process.env.NODE_ENV === "production"
2161
- * ? "https://api.prod.example.com"
2162
- * : "https://api.dev.example.com"
2163
- * });
2164
- * }
2165
- * ```
2166
- *
2167
- * @example
2168
- * **Advanced proxy with request/response modification:**
2169
- * ```ts
2170
- * class SecureProxy {
2171
- * secure = $proxy({
2172
- * path: "/secure/*",
2173
- * target: "https://secure-api.example.com",
2174
- * beforeRequest: async (request, proxyRequest) => {
2175
- * // Add authentication headers
2176
- * proxyRequest.headers = {
2177
- * ...proxyRequest.headers,
2178
- * 'Authorization': `Bearer ${await getServiceToken()}`,
2179
- * 'X-Forwarded-For': request.headers['x-forwarded-for'] || request.ip
2180
- * };
2181
- * },
2182
- * afterResponse: async (request, proxyResponse) => {
2183
- * // Log response for monitoring
2184
- * console.log(`Proxied ${request.url} -> ${proxyResponse.status}`);
2185
- * },
2186
- * rewrite: (url) => {
2187
- * // Remove /secure prefix when forwarding
2188
- * url.pathname = url.pathname.replace('/secure', '');
2189
- * }
2190
- * });
2191
- * }
2192
- * ```
2193
- *
2194
- * @example
2195
- * **Conditional proxy based on feature flags:**
2196
- * ```ts
2197
- * class FeatureProxy {
2198
- * newApi = $proxy({
2199
- * path: "/v2/*",
2200
- * target: "https://new-api.example.com",
2201
- * disabled: !process.env.ENABLE_V2_API // Disable if feature flag is off
2202
- * });
2203
- * }
2204
- * ```
2205
- */
2206
- const $proxy = (options) => {
2207
- return (0, alepha.createDescriptor)(ProxyDescriptor, options);
2208
- };
2209
- var ProxyDescriptor = class extends alepha.Descriptor {};
2210
- $proxy[alepha.KIND] = ProxyDescriptor;
2211
-
2212
- //#endregion
2213
- //#region src/server-proxy/providers/ServerProxyProvider.ts
2214
- var ServerProxyProvider = class {
2215
- log = (0, alepha_logger.$logger)();
2216
- routerProvider = (0, alepha.$inject)(alepha_server.ServerRouterProvider);
2217
- alepha = (0, alepha.$inject)(alepha.Alepha);
2218
- configure = (0, alepha.$hook)({
2219
- on: "configure",
2220
- handler: () => {
2221
- for (const proxy of this.alepha.descriptors($proxy)) this.createProxy(proxy.options);
2222
- }
2223
- });
2224
- createProxy(options) {
2225
- if (options.disabled) return;
2226
- const path = options.path;
2227
- const target = typeof options.target === "function" ? options.target() : options.target;
2228
- if (!path.endsWith("/*")) throw new alepha.AlephaError("Proxy path should end with '/*'");
2229
- const handler = this.createProxyHandler(target, options);
2230
- for (const method of alepha_server.routeMethods) this.routerProvider.createRoute({
2231
- method,
2232
- path,
2233
- handler
2234
- });
2235
- this.log.info("Proxying", {
2236
- path,
2237
- target
2238
- });
2239
- }
2240
- createProxyHandler(target, options) {
2241
- return async (request) => {
2242
- const url = new URL(target + request.url.pathname);
2243
- if (request.url.search) url.search = request.url.search;
2244
- options.rewrite?.(url);
2245
- const requestInit = {
2246
- url: url.toString(),
2247
- method: request.method,
2248
- headers: {
2249
- ...request.headers,
2250
- "accept-encoding": "identity"
2251
- },
2252
- body: this.getRawRequestBody(request)
2253
- };
2254
- if (requestInit.body) requestInit.duplex = "half";
2255
- if (options.beforeRequest) await options.beforeRequest(request, requestInit);
2256
- this.log.debug("Proxying request", {
2257
- url: url.toString(),
2258
- method: request.method,
2259
- headers: request.headers
2260
- });
2261
- const response = await fetch(requestInit.url, requestInit);
2262
- request.reply.status = response.status;
2263
- request.reply.headers = Object.fromEntries(response.headers.entries());
2264
- request.reply.body = response.body;
2265
- this.log.debug("Received response", {
2266
- status: request.reply.status,
2267
- headers: request.reply.headers
2268
- });
2269
- if (options.afterResponse) await options.afterResponse(request, response);
2270
- };
2271
- }
2272
- getRawRequestBody(req) {
2273
- const { method } = req;
2274
- if (method === "GET" || method === "HEAD" || method === "OPTIONS") return;
2275
- if (req.raw?.web?.req) return req.raw.web.req.body;
2276
- if (req.raw?.node?.req) {
2277
- const nodeReq = req.raw.node.req;
2278
- return node_stream_web.ReadableStream.from(nodeReq);
2279
- }
2280
- }
2281
- };
2282
-
2283
- //#endregion
2284
- //#region src/server-proxy/index.ts
2285
- /**
2286
- * Plugin for Alepha that provides a proxy server functionality.
2287
- *
2288
- * @see {@link $proxy}
2289
- * @module alepha.server.proxy
2290
- */
2291
- const AlephaServerProxy = (0, alepha.$module)({
2292
- name: "alepha.server.proxy",
2293
- descriptors: [$proxy],
2294
- services: [alepha_server.AlephaServer, ServerProxyProvider]
2295
- });
2296
-
2297
- //#endregion
2298
- //#region src/server-links/providers/RemoteDescriptorProvider.ts
2299
- const envSchema$1 = alepha.t.object({ SERVER_API_PREFIX: alepha.t.text({
2300
- description: "Prefix for all API routes (e.g. $action).",
2301
- default: "/api"
2302
- }) });
2303
- var RemoteDescriptorProvider = class {
2304
- env = (0, alepha.$env)(envSchema$1);
2305
- alepha = (0, alepha.$inject)(alepha.Alepha);
2306
- proxyProvider = (0, alepha.$inject)(ServerProxyProvider);
2307
- linkProvider = (0, alepha.$inject)(LinkProvider);
2308
- remotes = [];
2309
- log = (0, alepha_logger.$logger)();
2310
- getRemotes() {
2311
- return this.remotes;
2312
- }
2313
- configure = (0, alepha.$hook)({
2314
- on: "configure",
2315
- handler: async () => {
2316
- const remotes = this.alepha.descriptors($remote);
2317
- for (const remote of remotes) await this.registerRemote(remote);
2318
- }
2319
- });
2320
- start = (0, alepha.$hook)({
2321
- on: "start",
2322
- handler: async () => {
2323
- for (const remote of this.remotes) {
2324
- const token = typeof remote.serviceAccount?.token === "function" ? await remote.serviceAccount.token() : void 0;
2325
- if (!remote.internal) continue;
2326
- const { links } = await remote.links({ authorization: token });
2327
- for (const link of links) {
2328
- let path = link.path.replace(remote.prefix, "");
2329
- if (link.service) path = `/${link.service}${path}`;
2330
- this.linkProvider.registerLink({
2331
- ...link,
2332
- prefix: remote.prefix,
2333
- path,
2334
- method: link.method ?? "GET",
2335
- host: remote.url,
2336
- service: remote.name
2337
- });
2338
- }
2339
- this.log.info(`Remote '${remote.name}' OK`, {
2340
- links: remote.links.length,
2341
- prefix: remote.prefix
2342
- });
2343
- }
2344
- }
2345
- });
2346
- async registerRemote(value) {
2347
- const options = value.options;
2348
- const url = typeof options.url === "string" ? options.url : options.url();
2349
- const linkPath = LinkProvider.path.apiLinks;
2350
- const name = value.name;
2351
- const proxy = typeof options.proxy === "object" ? options.proxy : {};
2352
- const remote = {
2353
- url,
2354
- name,
2355
- prefix: "/api",
2356
- serviceAccount: options.serviceAccount,
2357
- proxy: !!options.proxy,
2358
- internal: !proxy.noInternal,
2359
- schema: async (opts) => {
2360
- const { authorization, name: name$1 } = opts;
2361
- return await fetch(`${url}${linkPath}/${name$1}/schema`, { headers: new Headers(authorization ? { authorization } : {}) }).then((it) => it.json());
2362
- },
2363
- links: async (opts) => {
2364
- const { authorization } = opts;
2365
- const remoteApi = await this.fetchLinks.run({
2366
- service: name,
2367
- url: `${url}${linkPath}`,
2368
- authorization
2369
- });
2370
- if (remoteApi.prefix != null) remote.prefix = remoteApi.prefix;
2371
- return remoteApi;
2372
- }
2373
- };
2374
- this.remotes.push(remote);
2375
- if (options.proxy) this.proxyProvider.createProxy({
2376
- path: `${this.env.SERVER_API_PREFIX}/${name}/*`,
2377
- target: url,
2378
- rewrite: (url$1) => {
2379
- url$1.pathname = url$1.pathname.replace(`${this.env.SERVER_API_PREFIX}/${name}`, remote.prefix);
2380
- },
2381
- ...proxy
2382
- });
2383
- }
2384
- fetchLinks = (0, alepha_retry.$retry)({
2385
- max: 10,
2386
- backoff: { initial: 1e3 },
2387
- onError: (_, attempt, { service, url }) => {
2388
- this.log.warn(`Failed to fetch links, retry (${attempt})...`, {
2389
- service,
2390
- url
2391
- });
2392
- },
2393
- handler: async (opts) => {
2394
- const { url, authorization } = opts;
2395
- const response = await fetch(url, { headers: new Headers(authorization ? { authorization } : {}) });
2396
- if (!response.ok) throw new Error(`Failed to fetch links from ${url}`);
2397
- return this.alepha.codec.decode(apiLinksResponseSchema, await response.json());
2398
- }
2399
- });
2400
- };
2401
-
2402
- //#endregion
2403
- //#region src/server-links/providers/ServerLinksProvider.ts
2404
- const envSchema = alepha.t.object({ SERVER_API_PREFIX: alepha.t.text({
2405
- description: "Prefix for all API routes (e.g. $action).",
2406
- default: "/api"
2407
- }) });
2408
- var ServerLinksProvider = class {
2409
- env = (0, alepha.$env)(envSchema);
2410
- alepha = (0, alepha.$inject)(alepha.Alepha);
2411
- linkProvider = (0, alepha.$inject)(LinkProvider);
2412
- remoteProvider = (0, alepha.$inject)(RemoteDescriptorProvider);
2413
- serverTimingProvider = (0, alepha.$inject)(alepha_server.ServerTimingProvider);
2414
- get prefix() {
2415
- return this.env.SERVER_API_PREFIX;
2416
- }
2417
- onRoute = (0, alepha.$hook)({
2418
- on: "configure",
2419
- handler: () => {
2420
- for (const action of this.alepha.descriptors(alepha_server.$action)) this.linkProvider.registerLink({
2421
- name: action.name,
2422
- group: action.group,
2423
- schema: action.options.schema,
2424
- requestBodyType: action.getBodyContentType(),
2425
- secured: action.options.secure ?? true,
2426
- method: action.method === "GET" ? void 0 : action.method,
2427
- prefix: action.prefix,
2428
- path: action.path,
2429
- handler: (config, options = {}) => action.run(config, options)
2430
- });
2431
- }
2432
- });
2433
- /**
2434
- * First API - Get all API links for the user.
2435
- *
2436
- * This is based on the user's permissions.
2437
- */
2438
- links = (0, alepha_server.$route)({
2439
- path: LinkProvider.path.apiLinks,
2440
- schema: { response: apiLinksResponseSchema },
2441
- handler: ({ user, headers: headers$1 }) => {
2442
- return this.getUserApiLinks({
2443
- user,
2444
- authorization: headers$1.authorization
2445
- });
2446
- }
2447
- });
2448
- /**
2449
- * Second API - Get schema for a specific API link.
2450
- *
2451
- * Note: Body/Response schema are not included in `links` API because it's TOO BIG.
2452
- * I mean for 150+ links, you got 50ms of serialization time.
2453
- */
2454
- schema = (0, alepha_server.$route)({
2455
- path: LinkProvider.path.apiSchema,
2456
- schema: {
2457
- params: alepha.t.object({ name: alepha.t.text() }),
2458
- response: alepha.t.json()
2459
- },
2460
- handler: ({ params, user, headers: headers$1 }) => {
2461
- return this.getSchemaByName(params.name, {
2462
- user,
2463
- authorization: headers$1.authorization
2464
- });
2465
- }
2466
- });
2467
- async getSchemaByName(name, options = {}) {
2468
- const authorization = options.authorization;
2469
- const api = await this.getUserApiLinks({
2470
- user: options.user,
2471
- authorization
2472
- });
2473
- for (const link of api.links) if (link.name === name) {
2474
- if (link.service) return this.remoteProvider.getRemotes().find((it) => it.name === link.service)?.schema({
2475
- name,
2476
- authorization
2477
- });
2478
- return this.linkProvider.getServerLinks().find((it) => it.name === name)?.schema ?? {};
2479
- }
2480
- return {};
2481
- }
2482
- /**
2483
- * Retrieves API links for the user based on their permissions.
2484
- * Will check on local links and remote links.
2485
- */
2486
- async getUserApiLinks(options) {
2487
- const { user } = options;
2488
- let permissions;
2489
- let permissionMap;
2490
- const hasSecurity = this.alepha.has(alepha_security.SecurityProvider);
2491
- if (hasSecurity && user) {
2492
- permissions = this.alepha.inject(alepha_security.SecurityProvider).getPermissions(user);
2493
- permissionMap = new Map(permissions.map((it) => [`${it.group}:${it.name}`, it]));
2494
- }
2495
- const userLinks = [];
2496
- for (const permission of permissions ?? []) if (!permission.path && !permission.method && permission.name && permission.group) userLinks.push({
2497
- path: "",
2498
- name: permission.name,
2499
- group: permission.group
2500
- });
2501
- for (const link of this.linkProvider.getServerLinks()) {
2502
- if (link.host) continue;
2503
- if (hasSecurity && link.secured) {
2504
- if (!user) continue;
2505
- if (typeof link.secured === "object" && link.secured.realm) {
2506
- if (user.realm !== link.secured.realm) continue;
2507
- } else if (permissionMap) {
2508
- if (!permissionMap.has(`${link.group}:${link.name}`)) continue;
2509
- }
2510
- }
2511
- userLinks.push({
2512
- name: link.name,
2513
- group: link.group,
2514
- requestBodyType: link.requestBodyType,
2515
- method: link.method,
2516
- path: link.path
2517
- });
2518
- }
2519
- this.serverTimingProvider.beginTiming("fetchRemoteLinks");
2520
- const promises = this.remoteProvider.getRemotes().filter((it) => it.proxy).map(async (remote) => {
2521
- const { links, prefix } = await remote.links(options);
2522
- return links.map((link) => {
2523
- let path = link.path.replace(prefix ?? "/api", "");
2524
- if (link.service) path = `/${link.service}${path}`;
2525
- return {
2526
- ...link,
2527
- path,
2528
- proxy: true,
2529
- service: remote.name
2530
- };
2531
- });
2532
- });
2533
- userLinks.push(...(await Promise.all(promises)).flat());
2534
- this.serverTimingProvider.endTiming("fetchRemoteLinks");
2535
- return {
2536
- prefix: this.env.SERVER_API_PREFIX,
2537
- links: userLinks
2538
- };
2539
- }
2540
- };
2541
-
2542
- //#endregion
2543
- //#region src/server-links/index.ts
2544
- /**
2545
- * Provides server-side link management and remote capabilities for client-server interactions.
2546
- *
2547
- * The server-links module enables declarative link definitions using `$remote` and `$client` descriptors,
2548
- * facilitating seamless API endpoint management and client-server communication. It integrates with server
2549
- * security features to ensure safe and controlled access to resources.
2550
- *
2551
- * @see {@link $remote}
2552
- * @see {@link $client}
2553
- * @module alepha.server.links
2554
- */
2555
- const AlephaServerLinks = (0, alepha.$module)({
2556
- name: "alepha.server.links",
2557
- descriptors: [$remote, $client],
2558
- services: [
2559
- alepha_server.AlephaServer,
2560
- ServerLinksProvider,
2561
- RemoteDescriptorProvider,
2562
- LinkProvider
2563
- ]
2564
- });
2565
-
2566
- //#endregion
2567
- //#region src/server-auth/constants/routes.ts
2568
- const alephaServerAuthRoutes = {
2569
- login: "/oauth/login",
2570
- callback: "/oauth/callback",
2571
- logout: "/oauth/logout",
2572
- token: "/_auth/token",
2573
- refresh: "/_auth/refresh",
2574
- userinfo: "/_auth/userinfo"
2575
- };
2576
-
2577
- //#endregion
2578
- //#region src/server-auth/schemas/tokensSchema.ts
2579
- const tokensSchema = alepha.t.object({
2580
- provider: alepha.t.text(),
2581
- access_token: alepha.t.text({ size: "rich" }),
2582
- issued_at: alepha.t.number(),
2583
- expires_in: alepha.t.optional(alepha.t.number()),
2584
- refresh_token: alepha.t.optional(alepha.t.text({ size: "rich" })),
2585
- refresh_token_expires_in: alepha.t.optional(alepha.t.number()),
2586
- refresh_expires_in: alepha.t.optional(alepha.t.number({ description: "Alias of `refresh_token_expires_in` for compatibility with some providers." })),
2587
- id_token: alepha.t.optional(alepha.t.text({ size: "rich" })),
2588
- scope: alepha.t.optional(alepha.t.text())
2589
- });
2590
-
2591
- //#endregion
2592
- //#region src/server-auth/schemas/tokenResponseSchema.ts
2593
- const tokenResponseSchema = alepha.t.extend(tokensSchema, {
2594
- user: alepha_security.userAccountInfoSchema,
2595
- api: apiLinksResponseSchema
2596
- });
2597
-
2598
- //#endregion
2599
- //#region src/server-auth/schemas/userinfoResponseSchema.ts
2600
- const userinfoResponseSchema = alepha.t.object({
2601
- user: alepha.t.optional(alepha_security.userAccountInfoSchema),
2602
- api: apiLinksResponseSchema
2603
- });
2604
-
2605
- //#endregion
2606
- //#region src/server-auth/providers/ServerAuthProvider.ts
2607
- var ServerAuthProvider = class {
2608
- log = (0, alepha_logger.$logger)();
2609
- alepha = (0, alepha.$inject)(alepha.Alepha);
2610
- serverCookiesProvider = (0, alepha.$inject)(ServerCookiesProvider);
2611
- dateTimeProvider = (0, alepha.$inject)(alepha_datetime.DateTimeProvider);
2612
- serverLinksProvider = (0, alepha.$inject)(ServerLinksProvider);
2613
- authorizationCode = $cookie({
2614
- name: "authorizationCode",
2615
- ttl: [15, "minutes"],
2616
- httpOnly: true,
2617
- schema: alepha.t.object({
2618
- provider: alepha.t.text(),
2619
- codeVerifier: alepha.t.optional(alepha.t.text({ size: "long" })),
2620
- redirectUri: alepha.t.optional(alepha.t.text({ size: "long" })),
2621
- state: alepha.t.optional(alepha.t.text()),
2622
- nonce: alepha.t.optional(alepha.t.text())
2623
- })
2624
- });
2625
- tokens = $cookie({
2626
- name: "tokens",
2627
- ttl: [30, "days"],
2628
- httpOnly: true,
2629
- compress: true,
2630
- encrypt: true,
2631
- schema: tokensSchema
2632
- });
2633
- get identities() {
2634
- return this.alepha.descriptors($auth).filter((auth) => !auth.options.disabled);
2635
- }
2636
- getAuthenticationProviders(filters = {}) {
2637
- const providers = [];
2638
- for (const identity of this.identities) {
2639
- if (filters.realmName) {
2640
- const realm = "realm" in identity.options && identity.options.realm;
2641
- if (!realm || realm.name !== filters.realmName) continue;
2642
- }
2643
- const type = "oidc" in identity.options ? "OIDC" : "oauth" in identity.options ? "OAUTH2" : "credentials" in identity.options ? "CREDENTIALS" : void 0;
2644
- if (!type) continue;
2645
- providers.push({
2646
- name: identity.name,
2647
- type
2648
- });
2649
- }
2650
- return providers;
2651
- }
2652
- configure = (0, alepha.$hook)({
2653
- on: "configure",
2654
- handler: async () => {
2655
- for (const identity of this.identities) await identity.prepare();
2656
- }
2657
- });
2658
- getAccessTokens(tokens) {
2659
- const idp = this.provider(tokens.provider);
2660
- if ("oidc" in idp.options && !("realm" in idp.options) && idp.options.oidc?.useIdToken) return tokens.id_token;
2661
- return tokens.access_token;
2662
- }
2663
- /**
2664
- * Fill request headers with access token from cookies or fallback to provider's fallback function.
2665
- */
2666
- onRequest = (0, alepha.$hook)({
2667
- on: "server:onRequest",
2668
- after: this.serverCookiesProvider,
2669
- handler: async ({ request }) => {
2670
- const cookies = request.cookies;
2671
- if (cookies) {
2672
- const tokens = await this.cookiesToTokens(cookies);
2673
- if (tokens) {
2674
- request.headers.authorization = `Bearer ${this.getAccessTokens(tokens)}`;
2675
- this.log.trace("Access token set in request headers", { provider: tokens.provider });
2676
- }
2677
- }
2678
- if (!request.headers.authorization) {
2679
- for (const provider of this.identities) if (!("realm" in provider.options) && !!provider.options.fallback) {
2680
- const token = await provider.options.fallback();
2681
- if (token) {
2682
- request.headers.authorization = `Bearer ${token}`;
2683
- break;
2684
- }
2685
- }
2686
- }
2687
- }
2688
- });
2689
- /**
2690
- * Convert cookies to tokens.
2691
- * If the tokens are expired, try to refresh them using the refresh token.
2692
- */
2693
- async cookiesToTokens(cookies) {
2694
- const tokens = this.tokens.get({ cookies });
2695
- if (!tokens) {
2696
- this.log.trace("No tokens found in cookies");
2697
- return;
2698
- }
2699
- this.log.trace("Tokens found in cookies", {
2700
- expires_in: tokens.expires_in,
2701
- issued_at: tokens.issued_at
2702
- });
2703
- const refreshedTokens = await this.refreshTokens(tokens);
2704
- if (!refreshedTokens) {
2705
- this.tokens.del({ cookies });
2706
- return;
2707
- }
2708
- if (refreshedTokens.access_token !== tokens.access_token) this.setTokens(refreshedTokens, cookies);
2709
- return refreshedTokens;
2710
- }
2711
- async refreshTokens(tokens) {
2712
- if (tokens.expires_in && tokens.issued_at) {
2713
- if (tokens.issued_at + (tokens.expires_in - 10) < this.dateTimeProvider.now().unix()) {
2714
- this.log.trace("Tokens are expired");
2715
- if (tokens.refresh_token) {
2716
- this.log.trace("Trying to refresh tokens using refresh token");
2717
- try {
2718
- const newTokens = {
2719
- ...await this.provider(tokens).refresh(tokens.refresh_token, tokens.access_token),
2720
- provider: tokens.provider,
2721
- issued_at: this.dateTimeProvider.now().unix()
2722
- };
2723
- this.log.debug("Tokens refreshed successfully");
2724
- return newTokens;
2725
- } catch (e$1) {
2726
- this.log.warn("Failed to refresh token", e$1);
2727
- }
2728
- }
2729
- return;
2730
- }
2731
- }
2732
- if (!tokens.issued_at && tokens.access_token) return;
2733
- return tokens;
2734
- }
2735
- /**
2736
- * Get user information.
2737
- */
2738
- userinfo = (0, alepha_server.$route)({
2739
- path: alephaServerAuthRoutes.userinfo,
2740
- schema: { response: userinfoResponseSchema },
2741
- handler: async ({ user, headers: headers$1, cookies }) => {
2742
- const tokens = this.tokens.get({ cookies });
2743
- if (tokens) {
2744
- const provider = this.provider(tokens);
2745
- if (!("realm" in provider.options)) {
2746
- const user$1 = await provider.user(tokens);
2747
- return {
2748
- api: await this.serverLinksProvider.getUserApiLinks({
2749
- authorization: headers$1.authorization,
2750
- user: user$1
2751
- }),
2752
- user: user$1
2753
- };
2754
- }
2755
- }
2756
- return {
2757
- api: await this.serverLinksProvider.getUserApiLinks({
2758
- authorization: headers$1.authorization,
2759
- user
2760
- }),
2761
- user
2762
- };
2763
- }
2764
- });
2765
- /**
2766
- * Refresh a token for internal providers.
2767
- */
2768
- refresh = (0, alepha_server.$route)({
2769
- path: alephaServerAuthRoutes.refresh,
2770
- method: "POST",
2771
- schema: {
2772
- query: alepha.t.object({ provider: alepha.t.text() }),
2773
- body: alepha.t.object({
2774
- refresh_token: alepha.t.text({ size: "rich" }),
2775
- access_token: alepha.t.optional(alepha.t.text({
2776
- size: "rich",
2777
- description: "Required if provider has stateless refresh token on credentials mode"
2778
- }))
2779
- }),
2780
- response: tokensSchema
2781
- },
2782
- handler: async ({ query, body, cookies }) => {
2783
- const provider = this.provider(query);
2784
- const tokens = {
2785
- provider: query.provider,
2786
- ...await provider.refresh(body.refresh_token, body.access_token)
2787
- };
2788
- this.setTokens(tokens, cookies);
2789
- return tokens;
2790
- }
2791
- });
2792
- /**
2793
- * Login for local password-based authentication.
2794
- */
2795
- token = (0, alepha_server.$route)({
2796
- path: alephaServerAuthRoutes.token,
2797
- method: "POST",
2798
- schema: {
2799
- query: alepha.t.object({ provider: alepha.t.text() }),
2800
- body: alepha.t.object({
2801
- username: alepha.t.text(),
2802
- password: alepha.t.text()
2803
- }),
2804
- response: tokenResponseSchema
2805
- },
2806
- handler: async ({ query, body, cookies }) => {
2807
- const provider = this.provider(query);
2808
- const realm = "realm" in provider.options && provider.options.realm;
2809
- if (!realm) throw new alepha_security.SecurityError(`Auth provider '${query.provider}' does not support password grant`);
2810
- const credentials = "credentials" in provider.options && provider.options.credentials;
2811
- if (!credentials) throw new alepha_security.SecurityError(`Auth provider '${query.provider}' does not support password grant`);
2812
- let user;
2813
- try {
2814
- user = await credentials.account(body);
2815
- } catch (e$1) {
2816
- if (e$1 instanceof alepha_security.InvalidCredentialsError) throw e$1;
2817
- this.log.error("Failed to authenticate user", e$1);
2818
- throw new alepha_security.InvalidCredentialsError();
2819
- }
2820
- if (!user) throw new alepha_security.InvalidCredentialsError();
2821
- const tokens = {
2822
- provider: query.provider,
2823
- ...await realm.createToken(user)
2824
- };
2825
- this.setTokens(tokens, cookies);
2826
- const api = await this.serverLinksProvider.getUserApiLinks({ user });
2827
- return {
2828
- ...tokens,
2829
- user,
2830
- api
2831
- };
2832
- }
2833
- });
2834
- /**
2835
- * Oauth2/OIDC login route.
2836
- */
2837
- login = (0, alepha_server.$route)({
2838
- path: alephaServerAuthRoutes.login,
2839
- schema: { query: alepha.t.object({
2840
- provider: alepha.t.text(),
2841
- redirect_uri: alepha.t.optional(alepha.t.text({ size: "rich" }))
2842
- }) },
2843
- handler: async ({ query, url, reply }) => {
2844
- const provider = this.provider(query);
2845
- const oauth = provider.oauth;
2846
- if (!oauth) throw new alepha_security.SecurityError(`Auth provider '${query.provider}' does not support OAuth2`);
2847
- const scope = provider.scope;
2848
- let redirect_uri = provider.redirect_uri || alephaServerAuthRoutes.callback;
2849
- if (redirect_uri.startsWith("/")) redirect_uri = `${url.protocol}//${url.host}${redirect_uri}`;
2850
- const oidc = "oidc" in provider.options && provider.options.oidc;
2851
- if (!oauth.serverMetadata().supportsPKCE()) {
2852
- const state = randomState();
2853
- const parameters$1 = {
2854
- redirect_uri,
2855
- state
2856
- };
2857
- if (oidc) parameters$1.nonce = randomState();
2858
- if (scope) parameters$1.scope = scope;
2859
- this.authorizationCode.set({
2860
- state,
2861
- nonce: parameters$1.nonce,
2862
- redirectUri: query.redirect_uri ?? "/",
2863
- provider: query.provider
2864
- });
2865
- reply.redirect(buildAuthorizationUrl(oauth, parameters$1).toString());
2866
- return;
2867
- }
2868
- const codeVerifier = randomPKCECodeVerifier();
2869
- const codeChallenge = await calculatePKCECodeChallenge(codeVerifier);
2870
- const parameters = {
2871
- redirect_uri,
2872
- code_challenge: codeChallenge,
2873
- code_challenge_method: "S256"
2874
- };
2875
- if (scope) parameters.scope = scope;
2876
- this.authorizationCode.set({
2877
- codeVerifier,
2878
- redirectUri: query.redirect_uri ?? "/",
2879
- provider: query.provider
2880
- });
2881
- reply.redirect(buildAuthorizationUrl(oauth, parameters).toString());
2882
- }
2883
- });
2884
- /**
2885
- * Callback for OAuth2/OIDC providers.
2886
- * It handles the authorization code flow and retrieves the access token.
2887
- */
2888
- callback = (0, alepha_server.$route)({
2889
- path: alephaServerAuthRoutes.callback,
2890
- handler: async ({ url, reply, cookies }) => {
2891
- const authorizationCode = this.authorizationCode.get({ cookies });
2892
- if (!authorizationCode) throw new alepha_server.BadRequestError("Missing code verifier");
2893
- const provider = this.provider(authorizationCode);
2894
- const oauth = provider.oauth;
2895
- if (!oauth) throw new alepha_security.SecurityError(`Auth provider '${provider.name}' does not support OAuth2`);
2896
- const redirectUri = authorizationCode.redirectUri ?? "/";
2897
- const externalTokens = await authorizationCodeGrant(oauth, url, {
2898
- pkceCodeVerifier: authorizationCode.codeVerifier,
2899
- expectedState: authorizationCode.state,
2900
- expectedNonce: authorizationCode.nonce
2901
- }).then((tokens$1) => ({
2902
- issued_at: this.dateTimeProvider.now().unix(),
2903
- provider: provider.name,
2904
- ...tokens$1
2905
- })).catch((e$1) => {
2906
- this.log.error("Failed to get access token", e$1);
2907
- throw new alepha_security.SecurityError("Failed to get access token", { cause: e$1 });
2908
- });
2909
- this.authorizationCode.del({ cookies });
2910
- const realm = "realm" in provider.options && provider.options.realm;
2911
- if (!realm) {
2912
- this.setTokens(externalTokens, cookies);
2913
- reply.redirect(redirectUri);
2914
- return;
2915
- }
2916
- const user = await provider.user(externalTokens);
2917
- const tokens = await realm.createToken(user);
2918
- this.setTokens({
2919
- ...tokens,
2920
- issued_at: this.dateTimeProvider.now().unix(),
2921
- provider: provider.name
2922
- }, cookies);
2923
- reply.redirect(redirectUri);
2924
- }
2925
- });
2926
- /**
2927
- * Logout route for OAuth2/OIDC providers.
2928
- */
2929
- logout = (0, alepha_server.$route)({
2930
- path: alephaServerAuthRoutes.logout,
2931
- method: "GET",
2932
- schema: { query: alepha.t.object({ post_logout_redirect_uri: alepha.t.optional(alepha.t.text()) }) },
2933
- handler: async ({ query, reply, cookies }) => {
2934
- const redirect = query.post_logout_redirect_uri ?? "/";
2935
- const tokens = this.tokens.get({ cookies });
2936
- if (!tokens) {
2937
- reply.redirect(redirect);
2938
- return;
2939
- }
2940
- const provider = this.provider(tokens.provider);
2941
- this.tokens.del({ cookies });
2942
- if ("realm" in provider.options && tokens.refresh_token) {
2943
- const onDeleteSession = provider.options.realm.options.settings?.onDeleteSession;
2944
- if (onDeleteSession) try {
2945
- await onDeleteSession(tokens.refresh_token);
2946
- } catch (e$1) {
2947
- this.log.error("Failed to delete session", e$1);
2948
- }
2949
- }
2950
- const oauth = provider.oauth;
2951
- if (!oauth) {
2952
- reply.redirect(redirect);
2953
- return;
2954
- }
2955
- const params = new URLSearchParams();
2956
- const idToken = tokens?.id_token;
2957
- params.set("post_logout_redirect_uri", redirect);
2958
- if (idToken) params.set("id_token_hint", idToken);
2959
- const customLogoutUri = "oidc" in provider.options ? provider.options.oidc?.logoutUri : void 0;
2960
- if (customLogoutUri) {
2961
- reply.redirect(`${customLogoutUri}?${params}`);
2962
- return;
2963
- }
2964
- if (!oauth.serverMetadata().end_session_endpoint) {
2965
- reply.redirect(redirect);
2966
- return;
2967
- }
2968
- reply.redirect(buildEndSessionUrl(oauth, params).toString());
2969
- }
2970
- });
2971
- provider(opts) {
2972
- const name = typeof opts === "string" ? opts : opts.provider;
2973
- const identity = this.identities.find((identity$1) => identity$1.name === name);
2974
- if (!identity) throw new alepha_security.SecurityError(`Auth provider '${name}' not found`);
2975
- return identity;
2976
- }
2977
- setTokens(tokens, cookies) {
2978
- const exp = tokens.refresh_token_expires_in || tokens.refresh_expires_in || tokens.expires_in;
2979
- const ttl = exp ? this.dateTimeProvider.duration(exp, "seconds") : void 0;
2980
- this.tokens.set(tokens, {
2981
- cookies,
2982
- ttl
2983
- });
2984
- }
2985
- };
2986
-
2987
- //#endregion
2988
- //#region src/server-auth/descriptors/$authCredentials.ts
2989
- /**
2990
- * Already configured Credentials authentication descriptor.
2991
- *
2992
- * Uses username and password to authenticate users.
2993
- */
2994
- const $authCredentials = (realm, options = {}) => {
2995
- const name = "credentials";
2996
- const account = realm.login ? realm.login(name) : options.account;
2997
- if (!account) throw new alepha.AlephaError("Credentials authentication requires a login function in the realm descriptor.");
2998
- return $auth({
2999
- realm,
3000
- name,
3001
- credentials: { account }
3002
- });
3003
- };
3004
-
3005
- //#endregion
3006
- //#region src/server-auth/descriptors/$authGithub.ts
3007
- /**
3008
- * Already configured GitHub authentication descriptor.
3009
- *
3010
- * Uses OAuth2 to authenticate users via their GitHub accounts.
3011
- * Upon successful authentication, it links the GitHub account to a user session.
3012
- *
3013
- * Environment Variables:
3014
- * - `GITHUB_CLIENT_ID`: The client ID obtained from the GitHub Developer Settings.
3015
- * - `GITHUB_CLIENT_SECRET`: The client secret obtained from the GitHub Developer Settings.
3016
- */
3017
- const $authGithub = (realm, options = {}) => {
3018
- const { alepha: alepha$1 } = (0, alepha.$context)();
3019
- const env = alepha$1.parseEnv(alepha.t.object({
3020
- GITHUB_CLIENT_ID: alepha.t.optional(alepha.t.text()),
3021
- GITHUB_CLIENT_SECRET: alepha.t.optional(alepha.t.text())
3022
- }));
3023
- const disabled = !env.GITHUB_CLIENT_ID || !env.GITHUB_CLIENT_SECRET;
3024
- const name = "github";
3025
- const account = options.account ?? (realm.link ? realm.link(name) : void 0);
3026
- if (!account) throw new alepha.AlephaError("Authentication requires a link function in the realm descriptor.");
3027
- return $auth({
3028
- realm,
3029
- name,
3030
- oauth: {
3031
- clientId: env.GITHUB_CLIENT_ID,
3032
- clientSecret: env.GITHUB_CLIENT_SECRET,
3033
- authorization: "https://github.com/login/oauth/authorize",
3034
- token: "https://github.com/login/oauth/access_token",
3035
- scope: "read:user user:email",
3036
- userinfo: async (tokens) => {
3037
- const BASE_URL = "https://api.github.com";
3038
- const res = await fetch(`${BASE_URL}/user`, { headers: {
3039
- Authorization: `Bearer ${tokens.access_token}`,
3040
- "User-Agent": "Alepha"
3041
- } }).then((res$1) => res$1.json());
3042
- const user = { sub: res.id.toString() };
3043
- if (res.email) user.email = res.email;
3044
- if (res.name) user.name = res.name.trim();
3045
- if (res.avatar_url) user.picture = res.avatar_url;
3046
- if (!user.email) {
3047
- const res$1 = await fetch(`${BASE_URL}/user/emails`, { headers: {
3048
- Authorization: `Bearer ${tokens.access_token}`,
3049
- "User-Agent": "Alepha"
3050
- } });
3051
- if (res$1.ok) {
3052
- const emails = await res$1.json();
3053
- user.email = (emails.find((e$1) => e$1.primary) ?? emails[0]).email;
3054
- }
3055
- }
3056
- return user;
3057
- },
3058
- ...options,
3059
- account
3060
- },
3061
- disabled
3062
- });
3063
- };
3064
-
3065
- //#endregion
3066
- //#region src/server-auth/descriptors/$authGoogle.ts
3067
- /**
3068
- * Already configured Google authentication descriptor.
3069
- *
3070
- * Uses OpenID Connect (OIDC) to authenticate users via their Google accounts.
3071
- * Upon successful authentication, it links the Google account to a user session.
3072
- *
3073
- * Environment Variables:
3074
- * - `GOOGLE_CLIENT_ID`: The client ID obtained from the Google Developer Console.
3075
- * - `GOOGLE_CLIENT_SECRET`: The client secret obtained from the Google Developer Console.
3076
- */
3077
- const $authGoogle = (realm, options = {}) => {
3078
- const { alepha: alepha$1 } = (0, alepha.$context)();
3079
- const env = alepha$1.parseEnv(alepha.t.object({
3080
- GOOGLE_CLIENT_ID: alepha.t.optional(alepha.t.text()),
3081
- GOOGLE_CLIENT_SECRET: alepha.t.optional(alepha.t.text())
3082
- }));
3083
- const disabled = !env.GOOGLE_CLIENT_ID || !env.GOOGLE_CLIENT_SECRET;
3084
- const name = "google";
3085
- const account = options.account ?? (realm.link ? realm.link(name) : void 0);
3086
- if (!account) throw new alepha.AlephaError("Authentication requires a link function in the realm descriptor.");
3087
- return $auth({
3088
- realm,
3089
- name,
3090
- oidc: {
3091
- issuer: "https://accounts.google.com",
3092
- clientId: env.GOOGLE_CLIENT_ID,
3093
- clientSecret: env.GOOGLE_CLIENT_SECRET,
3094
- ...options,
3095
- account
3096
- },
3097
- disabled
3098
- });
3099
- };
3100
-
3101
- //#endregion
3102
- //#region src/server-auth/schemas/authenticationProviderSchema.ts
3103
- const authenticationProviderSchema = alepha.t.object({
3104
- name: alepha.t.text({ description: "Name of the authentication provider." }),
3105
- type: alepha.t.enum([
3106
- "OAUTH2",
3107
- "OIDC",
3108
- "CREDENTIALS"
3109
- ], { description: "Type of the authentication provider." })
3110
- }, { title: "AuthenticationProvider" });
3111
-
3112
- //#endregion
3113
- //#region src/server-auth/index.ts
3114
- /**
3115
- * Allow authentication services for server applications.
3116
- * It provides login and logout functionalities.
3117
- *
3118
- * There are multiple authentication providers available (e.g., Google, GitHub).
3119
- * You can also delegate authentication to your own OIDC/OAuth2, for example using Keycloak or Auth0.
3120
- *
3121
- * It's cookie-based and SSR friendly.
3122
- *
3123
- * @see {@link $auth}
3124
- * @see {@link ServerAuthProvider}
3125
- * @module alepha.server.auth
3126
- */
3127
- const AlephaServerAuth = (0, alepha.$module)({
3128
- name: "alepha.server.auth",
3129
- descriptors: [$auth],
3130
- services: [AlephaServerCookies, ServerAuthProvider]
3131
- });
3132
-
3133
- //#endregion
3134
- exports.$auth = $auth;
3135
- exports.$authCredentials = $authCredentials;
3136
- exports.$authGithub = $authGithub;
3137
- exports.$authGoogle = $authGoogle;
3138
- exports.AlephaServerAuth = AlephaServerAuth;
3139
- exports.AuthDescriptor = AuthDescriptor;
3140
- exports.ServerAuthProvider = ServerAuthProvider;
3141
- exports.alephaServerAuthRoutes = alephaServerAuthRoutes;
3142
- exports.authenticationProviderSchema = authenticationProviderSchema;
3143
- exports.tokenResponseSchema = tokenResponseSchema;
3144
- exports.tokensSchema = tokensSchema;
3145
- exports.userinfoResponseSchema = userinfoResponseSchema;
3146
- //# sourceMappingURL=index.cjs.map