@plitzi/sdk-server 0.37.2 → 0.37.4

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 (359) hide show
  1. package/CHANGELOG.md +1170 -0
  2. package/README.md +100 -7
  3. package/dist/actions.d.ts +6 -2
  4. package/dist/actions.js +5 -2
  5. package/dist/adapters/cloudAdapters.js +4 -3
  6. package/dist/adapters/jsonAdapters.js +28 -6
  7. package/dist/adapters/jsonAdapters.test.d.ts +1 -0
  8. package/dist/auth.d.ts +7 -4
  9. package/dist/auth.js +8 -6
  10. package/dist/core/auth/api.d.ts +29 -1
  11. package/dist/core/auth/api.js +47 -13
  12. package/dist/core/auth/createAuth.d.ts +4 -0
  13. package/dist/core/auth/createAuth.js +3 -3
  14. package/dist/core/auth/credentials.d.ts +3 -0
  15. package/dist/core/auth/credentials.js +1 -1
  16. package/dist/core/auth/csrf.d.ts +25 -1
  17. package/dist/core/auth/csrf.js +29 -4
  18. package/dist/core/auth/renewal.d.ts +26 -0
  19. package/dist/core/auth/renewal.js +24 -0
  20. package/dist/core/auth/routes.js +8 -6
  21. package/dist/core/auth/session.d.ts +45 -0
  22. package/dist/core/auth/session.js +47 -1
  23. package/dist/core/auth/sessionActivity.d.ts +11 -0
  24. package/dist/core/auth/sessionActivity.js +14 -0
  25. package/dist/core/auth/throttle.d.ts +15 -0
  26. package/dist/core/auth/throttle.js +29 -1
  27. package/dist/core/auth/totp.d.ts +9 -1
  28. package/dist/core/auth/totp.js +11 -6
  29. package/dist/core/createServer.actions.test.d.ts +1 -0
  30. package/dist/core/createServer.d.ts +14 -1
  31. package/dist/core/createServer.functions.test.d.ts +1 -0
  32. package/dist/core/createServer.js +22 -12
  33. package/dist/core/fontStore.js +1 -1
  34. package/dist/core/handlers/authRouteHandlers.d.ts +1 -1
  35. package/dist/core/handlers/authRouteHandlers.js +57 -21
  36. package/dist/core/handlers/csrfMiddleware.d.ts +23 -0
  37. package/dist/core/handlers/csrfMiddleware.js +31 -7
  38. package/dist/core/handlers/csrfMiddleware.test.d.ts +1 -0
  39. package/dist/core/handlers/oauthRouteHandlers.js +5 -3
  40. package/dist/core/handlers/socialRouteHandlers.d.ts +1 -1
  41. package/dist/core/handlers/socialRouteHandlers.js +2 -1
  42. package/dist/core/http/dispatcher.d.ts +9 -1
  43. package/dist/core/http/dispatcher.js +38 -11
  44. package/dist/core/http/navigation.d.ts +5 -2
  45. package/dist/core/http/navigation.js +7 -4
  46. package/dist/core/http/socketResponse.d.ts +18 -0
  47. package/dist/core/http/socketResponse.js +59 -0
  48. package/dist/core/http/sse.d.ts +26 -0
  49. package/dist/core/http/sse.js +78 -0
  50. package/dist/core/http/stages/authRoutes.js +3 -1
  51. package/dist/core/http/stages/exchangeRoute.test.d.ts +1 -0
  52. package/dist/core/http/stages/middlewares.d.ts +1 -1
  53. package/dist/core/http/stages/middlewares.js +16 -10
  54. package/dist/core/http/stages/middlewares.test.d.ts +1 -0
  55. package/dist/core/http/stages/oauth.js +2 -2
  56. package/dist/core/http/stages/sessionRenewal.d.ts +16 -0
  57. package/dist/core/http/stages/sessionRenewal.js +81 -0
  58. package/dist/core/http/stages/sessionRenewal.test.d.ts +1 -0
  59. package/dist/core/http/stages/signInRoute.d.ts +2 -0
  60. package/dist/core/http/stages/signInRoute.js +139 -0
  61. package/dist/core/http/stages/signInRoute.test.d.ts +1 -0
  62. package/dist/core/http/types.d.ts +9 -0
  63. package/dist/core/http/webExchange.d.ts +18 -0
  64. package/dist/core/http/webExchange.js +53 -0
  65. package/dist/core/oauth/authorize.js +37 -13
  66. package/dist/core/oauth/consentPage.js +11 -0
  67. package/dist/core/oauth/issueContext.d.ts +14 -0
  68. package/dist/core/oauth/issueContext.js +32 -0
  69. package/dist/core/oauth/records.d.ts +7 -0
  70. package/dist/core/oauth/register.d.ts +2 -0
  71. package/dist/core/oauth/register.js +32 -11
  72. package/dist/core/oauth/register.test.d.ts +1 -0
  73. package/dist/core/oauth/token.js +21 -6
  74. package/dist/core/oauth/token.test.d.ts +1 -0
  75. package/dist/core/requestParser.d.ts +4 -0
  76. package/dist/core/requestParser.js +14 -7
  77. package/dist/core/sdkAssets.js +1 -1
  78. package/dist/core/server/baseServer.d.ts +10 -0
  79. package/dist/core/server/baseServer.js +42 -11
  80. package/dist/core/server/closeOnSignals.js +4 -3
  81. package/dist/core/server/drain.d.ts +18 -0
  82. package/dist/core/server/drain.js +62 -0
  83. package/dist/core/server/drain.test.d.ts +1 -0
  84. package/dist/core/server/fleet/channel.d.ts +103 -0
  85. package/dist/core/server/fleet/channel.js +191 -0
  86. package/dist/core/server/fleet/channel.test.d.ts +1 -0
  87. package/dist/core/server/fleet/fixtures/fleetServer.d.ts +1 -0
  88. package/dist/core/server/fleet/fleet.integration.test.d.ts +1 -0
  89. package/dist/core/server/fleet/link.d.ts +23 -0
  90. package/dist/core/server/fleet/link.js +70 -0
  91. package/dist/core/server/fleet/relay.d.ts +11 -0
  92. package/dist/core/server/fleet/relay.js +104 -0
  93. package/dist/core/server/fleet/relay.test.d.ts +1 -0
  94. package/dist/core/server/fleet/role.d.ts +46 -0
  95. package/dist/core/server/fleet/role.js +59 -0
  96. package/dist/core/server/fleet/role.test.d.ts +1 -0
  97. package/dist/core/server/fleet/stores.d.ts +7 -0
  98. package/dist/core/server/fleet/stores.js +19 -0
  99. package/dist/core/server/fleet/supervisor.d.ts +26 -0
  100. package/dist/core/server/fleet/supervisor.js +108 -0
  101. package/dist/core/server/fleet/supervisor.test.d.ts +1 -0
  102. package/dist/core/server/pageServer.js +34 -5
  103. package/dist/core/services/action.js +10 -2
  104. package/dist/core/services/functionRoutes.d.ts +9 -0
  105. package/dist/core/services/functionRoutes.js +47 -0
  106. package/dist/core/services/realtime.d.ts +8 -0
  107. package/dist/core/services/realtime.js +57 -0
  108. package/dist/core/services/registry.js +6 -2
  109. package/dist/core/staticFiles.js +12 -4
  110. package/dist/core/staticFiles.test.d.ts +1 -0
  111. package/dist/core/transports.d.ts +13 -3
  112. package/dist/core/transports.js +14 -5
  113. package/dist/functions-api.d.ts +281 -0
  114. package/dist/functions.d.ts +19 -0
  115. package/dist/functions.js +5 -0
  116. package/dist/functionsRunner.d.ts +15 -0
  117. package/dist/functionsRunner.js +6 -0
  118. package/dist/handlers.d.ts +2 -2
  119. package/dist/handlers.js +2 -2
  120. package/dist/helpers/atomicFile.d.ts +10 -0
  121. package/dist/helpers/atomicFile.js +33 -0
  122. package/dist/helpers/atomicFile.test.d.ts +1 -0
  123. package/dist/helpers/buildResponseHelpers.js +35 -11
  124. package/dist/helpers/buildServerInfo.js +4 -2
  125. package/dist/helpers/buildServerInfo.test.d.ts +1 -0
  126. package/dist/helpers/cache/CompressedFileCache.d.ts +18 -0
  127. package/dist/helpers/cache/CompressedFileCache.js +44 -0
  128. package/dist/helpers/cache/CompressedFileCache.test.d.ts +1 -0
  129. package/dist/helpers/cache/cacheManager.d.ts +1 -1
  130. package/dist/helpers/cache/cacheManager.js +2 -1
  131. package/dist/helpers/cache/cacheManager.test.d.ts +1 -0
  132. package/dist/helpers/cache/index.d.ts +1 -1
  133. package/dist/helpers/cache/keys.d.ts +15 -1
  134. package/dist/helpers/cache/keys.js +47 -5
  135. package/dist/helpers/cache/serverCaches.d.ts +7 -1
  136. package/dist/helpers/compress.d.ts +3 -1
  137. package/dist/helpers/compress.js +11 -3
  138. package/dist/helpers/hydrationPayload.d.ts +11 -0
  139. package/dist/helpers/hydrationPayload.js +51 -0
  140. package/dist/helpers/hydrationPayload.test.d.ts +1 -0
  141. package/dist/helpers/metrics.d.ts +1 -1
  142. package/dist/helpers/metrics.js +3 -2
  143. package/dist/helpers/outboundGuard.d.ts +14 -6
  144. package/dist/helpers/outboundGuard.js +132 -8
  145. package/dist/helpers/paintedCookie.d.ts +8 -0
  146. package/dist/helpers/paintedCookie.js +13 -0
  147. package/dist/helpers/reportReactBuild.d.ts +9 -0
  148. package/dist/helpers/reportReactBuild.js +18 -0
  149. package/dist/helpers/reportReactBuild.test.d.ts +1 -0
  150. package/dist/helpers/serverLog.d.ts +25 -1
  151. package/dist/helpers/serverLog.js +68 -3
  152. package/dist/helpers/serverLog.test.d.ts +1 -0
  153. package/dist/helpers/wsFrame.d.ts +3 -0
  154. package/dist/helpers/wsFrame.js +5 -0
  155. package/dist/index.d.ts +6 -1
  156. package/dist/index.js +5 -2
  157. package/dist/kernel.d.ts +2 -1
  158. package/dist/kernel.js +3 -2
  159. package/dist/modules/actions/connectorWrite.js +2 -1
  160. package/dist/modules/actions/index.d.ts +29 -10
  161. package/dist/modules/actions/index.js +43 -6
  162. package/dist/modules/actions/jobs/failureStreak.d.ts +17 -0
  163. package/dist/modules/actions/jobs/failureStreak.js +47 -0
  164. package/dist/modules/actions/jobs/failureStreak.test.d.ts +1 -0
  165. package/dist/modules/actions/jobs/index.d.ts +2 -1
  166. package/dist/modules/actions/jobs/index.js +5 -3
  167. package/dist/modules/actions/jobs/memoryQueue.d.ts +3 -0
  168. package/dist/modules/actions/jobs/memoryQueue.js +17 -1
  169. package/dist/modules/actions/jobs/scheduler.d.ts +2 -0
  170. package/dist/modules/actions/jobs/scheduler.js +12 -7
  171. package/dist/modules/actions/jobs/worker.d.ts +3 -1
  172. package/dist/modules/actions/jobs/worker.js +9 -4
  173. package/dist/modules/actions/moduleFor.js +5 -2
  174. package/dist/modules/actions/runtime/errors.d.ts +12 -0
  175. package/dist/modules/actions/runtime/errors.js +16 -1
  176. package/dist/modules/actions/runtime/kvChange.d.ts +27 -0
  177. package/dist/modules/actions/runtime/kvChange.js +23 -0
  178. package/dist/modules/actions/runtime/kvList.d.ts +51 -0
  179. package/dist/modules/actions/runtime/kvList.js +52 -0
  180. package/dist/modules/actions/runtime/kvStore.d.ts +8 -0
  181. package/dist/modules/actions/runtime/kvStore.js +46 -20
  182. package/dist/modules/actions/runtime/memoryKv.d.ts +3 -0
  183. package/dist/modules/actions/runtime/memoryKv.js +19 -1
  184. package/dist/modules/actions/runtime/namespaceKv.d.ts +4 -6
  185. package/dist/modules/actions/runtime/namespaceKv.js +15 -3
  186. package/dist/modules/actions/runtime/namespaceKv.test.d.ts +1 -0
  187. package/dist/modules/actions/runtime/precheck.js +4 -8
  188. package/dist/modules/actions/runtime/rateLimit.d.ts +19 -0
  189. package/dist/modules/actions/runtime/rateLimit.js +19 -0
  190. package/dist/modules/actions/runtime/redisKv.d.ts +27 -0
  191. package/dist/modules/actions/runtime/redisKv.integration.test.d.ts +1 -0
  192. package/dist/modules/actions/runtime/redisKv.js +46 -0
  193. package/dist/modules/actions/runtime/report.js +2 -1
  194. package/dist/modules/actions/runtime/runAction.d.ts +10 -2
  195. package/dist/modules/actions/runtime/runAction.js +52 -116
  196. package/dist/modules/actions/runtime/runLogger.d.ts +5 -0
  197. package/dist/modules/actions/runtime/runLogger.js +8 -2
  198. package/dist/modules/actions/runtime/signing.d.ts +20 -0
  199. package/dist/modules/actions/runtime/signing.js +25 -0
  200. package/dist/modules/actions/runtime/signing.test.d.ts +1 -0
  201. package/dist/modules/actions/runtime/taskContext.d.ts +33 -0
  202. package/dist/modules/actions/runtime/taskContext.js +124 -0
  203. package/dist/modules/actions/tasks/builtins.js +5 -1
  204. package/dist/modules/actions/tasks/flow.js +81 -10
  205. package/dist/modules/actions/tasks/helpers.js +2 -2
  206. package/dist/modules/actions/tasks/http.js +2 -3
  207. package/dist/modules/actions/tasks/kv.js +39 -0
  208. package/dist/modules/actions/tasks/list.d.ts +2 -0
  209. package/dist/modules/actions/tasks/list.js +145 -0
  210. package/dist/modules/actions/tasks/realtime.d.ts +2 -0
  211. package/dist/modules/actions/tasks/realtime.js +104 -0
  212. package/dist/modules/actions/tasks/registry.d.ts +10 -1
  213. package/dist/modules/actions/tasks/registry.js +17 -6
  214. package/dist/modules/actions/transport/callHandler.js +5 -2
  215. package/dist/modules/actions/transport/catalogHandler.d.ts +5 -2
  216. package/dist/modules/actions/transport/catalogHandler.js +3 -2
  217. package/dist/modules/actions/transport/stream.d.ts +4 -5
  218. package/dist/modules/actions/transport/stream.js +12 -76
  219. package/dist/modules/actions/transport/webhookHandler.js +4 -3
  220. package/dist/modules/actions/types.d.ts +86 -3
  221. package/dist/modules/connectors/engine.js +3 -5
  222. package/dist/modules/functions/build.d.ts +22 -0
  223. package/dist/modules/functions/build.js +122 -0
  224. package/dist/modules/functions/build.test.d.ts +1 -0
  225. package/dist/modules/functions/capabilities.d.ts +22 -0
  226. package/dist/modules/functions/capabilities.js +228 -0
  227. package/dist/modules/functions/config.d.ts +34 -0
  228. package/dist/modules/functions/config.js +24 -0
  229. package/dist/modules/functions/context.d.ts +17 -0
  230. package/dist/modules/functions/context.js +88 -0
  231. package/dist/modules/functions/contract.d.ts +121 -0
  232. package/dist/modules/functions/contract.js +9 -0
  233. package/dist/modules/functions/driver.d.ts +36 -0
  234. package/dist/modules/functions/driver.js +150 -0
  235. package/dist/modules/functions/load.d.ts +15 -0
  236. package/dist/modules/functions/load.js +45 -0
  237. package/dist/modules/functions/load.test.d.ts +1 -0
  238. package/dist/modules/functions/local.d.ts +29 -0
  239. package/dist/modules/functions/local.js +61 -0
  240. package/dist/modules/functions/local.test.d.ts +1 -0
  241. package/dist/modules/functions/manifest.d.ts +24 -0
  242. package/dist/modules/functions/manifest.js +163 -0
  243. package/dist/modules/functions/manifest.test.d.ts +1 -0
  244. package/dist/modules/functions/native.d.ts +8 -0
  245. package/dist/modules/functions/native.js +21 -0
  246. package/dist/modules/functions/native.test.d.ts +1 -0
  247. package/dist/modules/functions/protocol.d.ts +216 -0
  248. package/dist/modules/functions/protocol.js +18 -0
  249. package/dist/modules/functions/routes.d.ts +36 -0
  250. package/dist/modules/functions/routes.js +181 -0
  251. package/dist/modules/functions/routes.test.d.ts +1 -0
  252. package/dist/modules/functions/runner/messages.d.ts +5 -0
  253. package/dist/modules/functions/runner/messages.js +188 -0
  254. package/dist/modules/functions/runner/remote.d.ts +19 -0
  255. package/dist/modules/functions/runner/remote.js +116 -0
  256. package/dist/modules/functions/runner/remote.test.d.ts +1 -0
  257. package/dist/modules/functions/runner/service.d.ts +32 -0
  258. package/dist/modules/functions/runner/service.js +209 -0
  259. package/dist/modules/functions/sandbox/guest.d.ts +45 -0
  260. package/dist/modules/functions/sandbox/guest.js +660 -0
  261. package/dist/modules/functions/sandbox/isolate.d.ts +37 -0
  262. package/dist/modules/functions/sandbox/isolate.js +347 -0
  263. package/dist/modules/functions/sandbox/isolate.test.d.ts +1 -0
  264. package/dist/modules/functions/sandbox/prelude.d.ts +2 -0
  265. package/dist/modules/functions/sandbox/prelude.js +49 -0
  266. package/dist/modules/functions/space.d.ts +40 -0
  267. package/dist/modules/functions/space.js +162 -0
  268. package/dist/modules/functions/testing/tasksOf.d.ts +4 -0
  269. package/dist/modules/functions/tryEntry.d.ts +9 -0
  270. package/dist/modules/functions/tryEntry.js +49 -0
  271. package/dist/modules/mongo/kv.js +31 -0
  272. package/dist/modules/mysql/accounts.js +19 -9
  273. package/dist/modules/mysql/index.d.ts +2 -1
  274. package/dist/modules/mysql/jobs/index.d.ts +1 -1
  275. package/dist/modules/mysql/jobs/kv.js +30 -0
  276. package/dist/modules/mysql/jobs/schema.d.ts +9 -0
  277. package/dist/modules/mysql/jobs/schema.js +21 -4
  278. package/dist/modules/mysql/schema.js +25 -0
  279. package/dist/modules/mysql/sessions.d.ts +1 -11
  280. package/dist/modules/mysql/sessions.js +6 -1
  281. package/dist/modules/realtime/declarations.d.ts +11 -0
  282. package/dist/modules/realtime/declarations.js +26 -0
  283. package/dist/modules/realtime/grants.d.ts +23 -0
  284. package/dist/modules/realtime/grants.js +61 -0
  285. package/dist/modules/realtime/handlers.d.ts +74 -0
  286. package/dist/modules/realtime/handlers.js +224 -0
  287. package/dist/modules/realtime/hub.d.ts +62 -0
  288. package/dist/modules/realtime/hub.js +141 -0
  289. package/dist/modules/realtime/index.d.ts +51 -0
  290. package/dist/modules/realtime/index.js +62 -0
  291. package/dist/modules/realtime/memoryPubSub.d.ts +10 -0
  292. package/dist/modules/realtime/memoryPubSub.js +43 -0
  293. package/dist/modules/realtime/realtime.test.d.ts +1 -0
  294. package/dist/modules/realtime/redisPubSub.d.ts +24 -0
  295. package/dist/modules/realtime/redisPubSub.js +36 -0
  296. package/dist/modules/realtime/redisPubSub.test.d.ts +1 -0
  297. package/dist/modules/realtime/socket.d.ts +25 -0
  298. package/dist/modules/realtime/socket.js +122 -0
  299. package/dist/modules/realtime/socket.test.d.ts +1 -0
  300. package/dist/modules/rsc/handler.d.ts +16 -0
  301. package/dist/modules/rsc/handler.js +17 -10
  302. package/dist/modules/rsc/handler.test.d.ts +1 -0
  303. package/dist/modules/rsc/resolveRscData.js +2 -1
  304. package/dist/modules/runtime/bundle.d.ts +24 -0
  305. package/dist/modules/runtime/bundle.js +102 -0
  306. package/dist/modules/runtime/contract.d.ts +37 -0
  307. package/dist/modules/runtime/contract.js +21 -0
  308. package/dist/modules/runtime/entry.test.d.ts +1 -0
  309. package/dist/modules/runtime/host.d.ts +38 -0
  310. package/dist/modules/runtime/host.js +68 -0
  311. package/dist/modules/runtime/inside.d.ts +15 -0
  312. package/dist/modules/runtime/inside.js +47 -0
  313. package/dist/modules/runtime/inside.test.d.ts +1 -0
  314. package/dist/modules/runtime/runner.d.ts +11 -0
  315. package/dist/modules/runtime/runner.js +38 -0
  316. package/dist/modules/runtime/runtime.test.d.ts +1 -0
  317. package/dist/modules/runtime/stages.d.ts +50 -0
  318. package/dist/modules/runtime/stages.js +117 -0
  319. package/dist/modules/ssr/Component.d.ts +3 -1
  320. package/dist/modules/ssr/Component.js +2 -1
  321. package/dist/modules/ssr/handler.js +10 -2
  322. package/dist/modules/ssr/loadPluginComponents.js +2 -1
  323. package/dist/modules/ssr/prepareRender.js +39 -10
  324. package/dist/modules/ssr/preview.d.ts +3 -0
  325. package/dist/modules/ssr/preview.js +17 -7
  326. package/dist/modules/ssr/redirect.test.d.ts +1 -0
  327. package/dist/modules/ssr/registerExternalPlugins.d.ts +6 -1
  328. package/dist/modules/ssr/registerExternalPlugins.js +19 -16
  329. package/dist/modules/ssr/registerExternalPlugins.test.d.ts +1 -0
  330. package/dist/modules/ssr/reportMissingPlugins.d.ts +9 -0
  331. package/dist/modules/ssr/reportMissingPlugins.js +24 -0
  332. package/dist/modules/ssr/reportMissingPlugins.test.d.ts +1 -0
  333. package/dist/modules/ssr/streamBody.d.ts +2 -2
  334. package/dist/modules/ssr/streamBody.js +4 -1
  335. package/dist/modules/ssr/template.js +1 -1
  336. package/dist/modules/ssr/views/template.ejs +8 -5
  337. package/dist/mysql.d.ts +1 -1
  338. package/dist/mysql.js +2 -2
  339. package/dist/oauth.js +2 -2
  340. package/dist/plugins/compile.js +9 -4
  341. package/dist/plugins/copy.js +3 -3
  342. package/dist/plugins/manager.d.ts +35 -3
  343. package/dist/plugins/manager.js +109 -38
  344. package/dist/runtime.d.ts +15 -0
  345. package/dist/runtime.js +7 -0
  346. package/package.json +39 -8
  347. package/skills/plitzi-authoring/SKILL.md +98 -14
  348. package/skills/plitzi-authoring/reference/accessibility.md +111 -0
  349. package/skills/plitzi-authoring/reference/authoring-errors.md +70 -0
  350. package/skills/plitzi-authoring/reference/data-and-visibility.md +84 -18
  351. package/skills/plitzi-authoring/reference/elements-and-styles.md +61 -7
  352. package/skills/plitzi-authoring/reference/flows.md +108 -17
  353. package/skills/plitzi-authoring/reference/lists.md +74 -0
  354. package/skills/plitzi-authoring/reference/performance.md +67 -0
  355. package/skills/plitzi-authoring/reference/plugins.md +167 -0
  356. package/skills/plitzi-authoring/reference/review-checklist.md +19 -2
  357. package/skills/plitzi-authoring/reference/templates.md +67 -27
  358. package/skills/plitzi-authoring/reference/testing.md +72 -5
  359. package/skills/plitzi-authoring/reference/validation.md +73 -0
@@ -0,0 +1,81 @@
1
+ import { readRenewalGuard, readSessionHint, readSessionToken, writeRenewalGuard } from "../../auth/session.js";
2
+ import { isDocumentNavigation, renewForNavigation } from "../../auth/renewal.js";
3
+ //#region src/core/http/stages/sessionRenewal.ts
4
+ /** Renewal this close to its deadline is not worth the round trip: it would be refused on arrival. */
5
+ var REFRESH_MARGIN_SECONDS = 30;
6
+ var nowInSeconds = () => Math.floor(Date.now() / 1e3);
7
+ /**
8
+ * This page's own address, absolute, for a renewal served by another host to send the browser back to. The port is
9
+ * read off the authority rather than taken whole, so a forged `Host` cannot put anything but digits after the name.
10
+ */
11
+ var pageUrl = (req) => {
12
+ const authority = req.headers[":authority"] ?? req.headers.host ?? "";
13
+ const port = /:(\d{1,5})$/u.exec(authority)?.[1];
14
+ return `${req.protocol}://${req.hostname}${port ? `:${port}` : ""}${req.path}${req.search}`;
15
+ };
16
+ /**
17
+ * Whether this page request comes from a browser whose session has only half-lapsed.
18
+ *
19
+ * The access cookie dies with its token, while the refresh cookie lives on — confined to the refresh path, so it never
20
+ * reaches a page. Rendered now, the page would be a guest's, and the browser, which renews as soon as it boots, would
21
+ * swap it for the signed-in one: a guest page in the HTML, a different page on screen, and a guest-only page shown
22
+ * and then redirected away from instead of answered with a redirect.
23
+ *
24
+ * The readable hint is what tells the two apart without presenting anything: it outlives the access cookie exactly
25
+ * as long as renewal is still possible. It is also what keeps this from looping — a renewal that succeeds rewrites
26
+ * it with an access expiry in the future, one that is refused clears it, and one that fails any other way leaves the
27
+ * guard cookie behind.
28
+ */
29
+ var halfLapsed = (req, cookie) => {
30
+ if (readSessionToken(req, cookie) !== void 0 || readRenewalGuard(req, cookie)) return false;
31
+ const hint = readSessionHint(req, cookie);
32
+ if (!hint?.refreshExpiresAt) return false;
33
+ const now = nowInSeconds();
34
+ return hint.expiresAt <= now && hint.refreshExpiresAt > now + REFRESH_MARGIN_SECONDS;
35
+ };
36
+ /**
37
+ * Sends a page request from a half-lapsed session to renew first — to `url`, the renewal endpoint — so the server
38
+ * renders the visitor the browser is going to end up with. See `renewForNavigation` for the other half.
39
+ *
40
+ * `url` is a path when this server serves `/auth` itself, and an absolute URL when another host does; the page's
41
+ * own address travels as `redirect` accordingly, and the endpoint only sends the browser back to a host that shares
42
+ * the session's cookies. Only whole-tab `GET` navigations take part: a renewal rotates the session.
43
+ */
44
+ var createSessionRenewalStage = (url) => {
45
+ const sameHost = url.startsWith("/") && !url.startsWith("//");
46
+ const renewalPath = sameHost ? new URL(url, "http://localhost").pathname : void 0;
47
+ return ({ req, res, config }) => {
48
+ if (req.method !== "GET" || req.path === renewalPath || !isDocumentNavigation(req.headers)) return false;
49
+ if (!halfLapsed(req, config.authCookie)) return false;
50
+ const target = new URL(url, pageUrl(req));
51
+ target.searchParams.set("redirect", sameHost ? `${req.path}${req.search}` : pageUrl(req));
52
+ writeRenewalGuard(req, res, config.authCookie);
53
+ res.setStatus(303);
54
+ res.setHeader("Cache-Control", "no-store");
55
+ res.setHeader("Location", sameHost ? `${target.pathname}${target.search}` : target.toString());
56
+ res.end();
57
+ return true;
58
+ };
59
+ };
60
+ /**
61
+ * The renewal endpoint, on a page server that serves `/auth` itself: `GET <basePath>/refresh`. The `POST` flow at the
62
+ * same path is the auth API stage's; this one only ever answers a whole-tab navigation.
63
+ */
64
+ var createRenewalEndpointStage = (auth) => {
65
+ const path = `${auth.basePath}/refresh`;
66
+ return async ({ req, res }) => {
67
+ if (req.method !== "GET" || req.path !== path || !isDocumentNavigation(req.headers)) return false;
68
+ const location = await renewForNavigation(req, res, {
69
+ api: auth.api,
70
+ cookies: auth.cookies,
71
+ csrf: auth.csrf
72
+ });
73
+ res.setStatus(303);
74
+ res.setHeader("Cache-Control", "no-store");
75
+ res.setHeader("Location", location);
76
+ res.end();
77
+ return true;
78
+ };
79
+ };
80
+ //#endregion
81
+ export { createRenewalEndpointStage, createSessionRenewalStage };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { Stage } from '../types';
2
+ export declare const signInStage: Stage;
@@ -0,0 +1,139 @@
1
+ import { requestOrigin } from "../../requestParser.js";
2
+ import { safeRedirectTarget } from "../navigation.js";
3
+ import { appendCookies, writeSessionCookies } from "../../auth/session.js";
4
+ import { readCookies } from "../../auth/credentials.js";
5
+ import { createHash, randomBytes } from "node:crypto";
6
+ //#region src/core/http/stages/signInRoute.ts
7
+ /**
8
+ * Signing a visitor in by redirect (`config.signIn`): out to an authorization server, back with a code, and a session
9
+ * of this host's own made from it through `exchangeCredential`.
10
+ *
11
+ * Everything that decides whether the person may sign in, and into what, is the authorization server's and the
12
+ * exchange adapter's. What is here is the part that is the same for any of them: registering this host as a client,
13
+ * PKCE, the state that ties the callback to the browser that started, and never sending anybody off-site on the way
14
+ * back.
15
+ */
16
+ /**
17
+ * The flow's state, in the browser that started it. `__Host-` is the point: a cookie with that prefix cannot carry a
18
+ * Domain, so a sibling host — another space on the same parent domain — cannot plant one and finish a sign-in it
19
+ * started in this browser, as whoever it signed in as.
20
+ */
21
+ var STATE_COOKIE = "__Host-sign-in";
22
+ var STATE_TTL_SECONDS = 600;
23
+ var base64url = (bytes) => bytes.toString("base64url");
24
+ var readState = (req) => {
25
+ const raw = readCookies(req)[STATE_COOKIE];
26
+ if (!raw) return;
27
+ try {
28
+ const parsed = JSON.parse(Buffer.from(raw, "base64url").toString("utf-8"));
29
+ return typeof parsed.state === "string" && typeof parsed.verifier === "string" && typeof parsed.returnTo === "string" ? {
30
+ state: parsed.state,
31
+ verifier: parsed.verifier,
32
+ returnTo: parsed.returnTo
33
+ } : void 0;
34
+ } catch {
35
+ return;
36
+ }
37
+ };
38
+ var stateCookie = (value, maxAge) => `${STATE_COOKIE}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${maxAge}`;
39
+ /**
40
+ * The client id this host is registered under, per authorization server and redirect address. Registration is
41
+ * idempotent there — the same metadata is the same client — so each process asks once and keeps the answer.
42
+ */
43
+ var clients = /* @__PURE__ */ new Map();
44
+ var clientFor = (signIn, redirectUri, hostname) => {
45
+ const key = `${signIn.registerUrl} ${redirectUri}`;
46
+ const known = clients.get(key);
47
+ if (known) return known;
48
+ const registering = (async () => {
49
+ const response = await fetch(signIn.registerUrl, {
50
+ method: "POST",
51
+ headers: { "Content-Type": "application/json" },
52
+ body: JSON.stringify({
53
+ client_name: hostname,
54
+ redirect_uris: [redirectUri],
55
+ grant_types: ["authorization_code"],
56
+ response_types: ["code"],
57
+ token_endpoint_auth_method: "none"
58
+ })
59
+ });
60
+ const body = await response.json();
61
+ if (!response.ok || typeof body.client_id !== "string") throw new Error(`Registering ${hostname} with the authorization server failed (${response.status})`);
62
+ return body.client_id;
63
+ })();
64
+ clients.set(key, registering);
65
+ registering.catch(() => clients.delete(key));
66
+ return registering;
67
+ };
68
+ var redirect = (res, location) => {
69
+ res.setStatus(303);
70
+ res.setHeader("Location", location);
71
+ res.end();
72
+ return true;
73
+ };
74
+ var signInStage = async (ctx) => {
75
+ const { config, req, res } = ctx;
76
+ const { signIn } = config;
77
+ if (!signIn || req.method !== "GET") return false;
78
+ const path = signIn.path ?? "/auth/sign-in";
79
+ const callbackPath = `${path}/callback`;
80
+ if (req.path !== path && req.path !== callbackPath) return false;
81
+ const { exchangeCredential, getSpaceDeployment } = config.adapters;
82
+ const origin = requestOrigin(req);
83
+ if (!exchangeCredential || !getSpaceDeployment || !origin) {
84
+ res.setStatus(404);
85
+ res.end();
86
+ return true;
87
+ }
88
+ const redirectUri = `${origin}${callbackPath}`;
89
+ if (req.path === path) {
90
+ const deployment = await getSpaceDeployment(req);
91
+ if (typeof deployment.spaceId !== "number") {
92
+ res.setStatus(404);
93
+ res.end();
94
+ return true;
95
+ }
96
+ const clientId = await clientFor(signIn, redirectUri, req.hostname);
97
+ const verifier = base64url(randomBytes(32));
98
+ const state = base64url(randomBytes(16));
99
+ const flow = {
100
+ state,
101
+ verifier,
102
+ returnTo: safeRedirectTarget(req, "return")
103
+ };
104
+ appendCookies(res, [stateCookie(base64url(Buffer.from(JSON.stringify(flow))), STATE_TTL_SECONDS)]);
105
+ const authorize = new URL(signIn.authorizeUrl);
106
+ authorize.search = new URLSearchParams({
107
+ response_type: "code",
108
+ client_id: clientId,
109
+ redirect_uri: redirectUri,
110
+ scope: signIn.scope(deployment.spaceId),
111
+ state,
112
+ code_challenge: base64url(createHash("sha256").update(verifier).digest()),
113
+ code_challenge_method: "S256"
114
+ }).toString();
115
+ return redirect(res, authorize.toString());
116
+ }
117
+ const flow = readState(req);
118
+ appendCookies(res, [stateCookie("", 0)]);
119
+ const code = req.query["code"];
120
+ if (!flow || !code || req.query["state"] !== flow.state) return redirect(res, flow?.returnTo ?? "/");
121
+ const token = await fetch(signIn.tokenUrl, {
122
+ method: "POST",
123
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
124
+ body: new URLSearchParams({
125
+ grant_type: "authorization_code",
126
+ code,
127
+ redirect_uri: redirectUri,
128
+ client_id: await clientFor(signIn, redirectUri, req.hostname),
129
+ code_verifier: flow.verifier
130
+ }).toString()
131
+ });
132
+ const granted = await token.json();
133
+ if (!token.ok || typeof granted.access_token !== "string") return redirect(res, flow.returnTo);
134
+ const result = await exchangeCredential(signIn.provider, granted.access_token, req);
135
+ if (result.ok) writeSessionCookies(req, res, result.session, config.authCookie);
136
+ return redirect(res, flow.returnTo);
137
+ };
138
+ //#endregion
139
+ export { signInStage };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,8 @@
1
+ import { UpgradeRequest } from './socketResponse';
1
2
  import { RawResponse } from '../../helpers/buildResponseHelpers';
2
3
  import { ServerCaches } from '../../helpers/cache';
3
4
  import { ActionsModule } from '../../modules/actions';
5
+ import { RealtimeModule } from '../../modules/realtime';
4
6
  import { PluginManager } from '../../plugins/manager';
5
7
  import { SSRPageServerConfig, SSRRequest, SSRResponseHelpers, SSRServerConfig, SSRTemplateFn } from '@plitzi/sdk-shared';
6
8
  import { IncomingMessage } from 'node:http';
@@ -22,12 +24,19 @@ export interface BaseContext {
22
24
  * spend real work on behalf of a caller should hand this to whatever they call.
23
25
  */
24
26
  signal: AbortSignal;
27
+ /**
28
+ * Present when the request asks to switch protocols (a WebSocket). The stage that takes it performs the switch on
29
+ * the socket and marks it `taken`; any other answer is written to the socket as HTTP and closes it.
30
+ */
31
+ upgrade?: UpgradeRequest;
25
32
  }
26
33
  export interface SSRContext extends BaseContext {
27
34
  config: SSRPageServerConfig;
28
35
  /** Built at boot when the deployment configured `action.lookups`, so its task registry is validated once and
29
36
  * its guards are a single set for the process. Absent means this server runs no actions. */
30
37
  actions?: ActionsModule;
38
+ /** The realtime channels, built at boot unless the config turned them off. Absent means `/_realtime` is not served. */
39
+ realtime?: RealtimeModule;
31
40
  renderFn: SSRTemplateFn;
32
41
  caches: ServerCaches;
33
42
  pluginManager: PluginManager;
@@ -0,0 +1,18 @@
1
+ import { IncomingMessage } from 'node:http';
2
+ /** What a response is written onto: node's own, or the page server's raw one. */
3
+ export type WebResponseSink = {
4
+ writeHead(statusCode: number, headers: Record<string, string>): unknown;
5
+ write(chunk: Buffer): unknown;
6
+ end(): unknown;
7
+ };
8
+ /**
9
+ * An incoming request as a web `Request` at `url` — its headers but those named in `without` (lower case), and its
10
+ * body read whole, within the server's body limit. What a web-standard handler takes: an MCP transport, an endpoint a
11
+ * space runtime declares.
12
+ */
13
+ export declare const webRequestOf: (raw: IncomingMessage, url: string, without?: readonly string[]) => Promise<Request>;
14
+ /**
15
+ * A web `Response` written onto a node one: its status and headers, then its body as it comes — a stream too, chunk by
16
+ * chunk, which is what an event stream needs — until it ends or `signal` says the caller went away.
17
+ */
18
+ export declare const writeWebResponse: (rawRes: WebResponseSink, response: Response, signal?: AbortSignal) => Promise<void>;
@@ -0,0 +1,53 @@
1
+ import { readRawBytes } from "../requestParser.js";
2
+ //#region src/core/http/webExchange.ts
3
+ var BODYLESS = /* @__PURE__ */ new Set(["GET", "HEAD"]);
4
+ /**
5
+ * An incoming request as a web `Request` at `url` — its headers but those named in `without` (lower case), and its
6
+ * body read whole, within the server's body limit. What a web-standard handler takes: an MCP transport, an endpoint a
7
+ * space runtime declares.
8
+ */
9
+ var webRequestOf = async (raw, url, without = []) => {
10
+ const headers = new Headers();
11
+ Object.entries(raw.headers).forEach(([name, value]) => {
12
+ if (name.startsWith(":") || without.includes(name)) return;
13
+ (Array.isArray(value) ? value : value === void 0 ? [] : [value]).forEach((one) => headers.append(name, one));
14
+ });
15
+ const method = raw.method ?? "GET";
16
+ return new Request(url, {
17
+ method,
18
+ headers,
19
+ ...BODYLESS.has(method) ? {} : { body: new Uint8Array(await readRawBytes(raw)) }
20
+ });
21
+ };
22
+ /**
23
+ * A web `Response` written onto a node one: its status and headers, then its body as it comes — a stream too, chunk by
24
+ * chunk, which is what an event stream needs — until it ends or `signal` says the caller went away.
25
+ */
26
+ var writeWebResponse = async (rawRes, response, signal) => {
27
+ const headers = {};
28
+ response.headers.forEach((value, name) => {
29
+ headers[name] = value;
30
+ });
31
+ rawRes.writeHead(response.status, headers);
32
+ if (!response.body) {
33
+ rawRes.end();
34
+ return;
35
+ }
36
+ const reader = response.body.getReader();
37
+ const stop = () => {
38
+ reader.cancel();
39
+ };
40
+ signal?.addEventListener("abort", stop, { once: true });
41
+ try {
42
+ for (;;) {
43
+ const { done, value } = await reader.read();
44
+ if (done) break;
45
+ rawRes.write(Buffer.from(value));
46
+ }
47
+ } catch {} finally {
48
+ signal?.removeEventListener("abort", stop);
49
+ rawRes.end();
50
+ }
51
+ };
52
+ //#endregion
53
+ export { webRequestOf, writeWebResponse };
@@ -1,9 +1,11 @@
1
1
  import { renderConsentPage } from "./consentPage.js";
2
+ import { dropPending, getClient, getPending, putCode, putPending } from "./records.js";
3
+ import { issueContextFor } from "./issueContext.js";
2
4
  import { AUTHORIZE_PATH } from "./metadata.js";
3
5
  import { field, optionalField } from "./params.js";
4
6
  import { randomId } from "./pkce.js";
5
- import { dropPending, getClient, getPending, putCode, putPending } from "./records.js";
6
7
  import { redirectToSignIn, redirectWithCode, redirectWithError, sendErrorPage, sendHtml } from "./respond.js";
8
+ import { isLoopbackRedirectUri } from "./register.js";
7
9
  //#region src/core/oauth/authorize.ts
8
10
  var DEFAULT_CODE_TTL_SECONDS = 60;
9
11
  var DEFAULT_GUEST_LABEL = "Continue without an account";
@@ -27,6 +29,12 @@ var hiddenFieldsFor = (request, pendingId) => {
27
29
  if (pendingId !== void 0) hidden["pending"] = pendingId;
28
30
  return hidden;
29
31
  };
32
+ /** Who is asking and where the answer goes, as the grant screen shows it (see `OAuthConsentView.client`). */
33
+ var consentClient = (request) => ({
34
+ name: request.clientName,
35
+ redirectHost: new URL(request.redirectUri).host,
36
+ loopback: isLoopbackRedirectUri(request.redirectUri)
37
+ });
30
38
  var renderConsent = (res, view) => {
31
39
  sendHtml(res, 200, renderConsentPage(view));
32
40
  };
@@ -36,9 +44,16 @@ var renderConsent = (res, view) => {
36
44
  * Rebuilt from the params rather than read off `req.url` because the two can differ — a proxy may rewrite the
37
45
  * path — and what has to survive the round trip is the QUERY, which is the authorization request itself.
38
46
  */
47
+ /**
48
+ * Where `/authorize` is, for this server: its issuer and the path, joined — the issuer may carry a path of its own when
49
+ * the server is mounted under a prefix, which resolving `/authorize` against it would drop. The same joining the
50
+ * discovery document publishes the endpoint by.
51
+ */
52
+ var authorizeEndpoint = (config, req) => new URL(`${(config.issuer ?? `https://${req.headers.host ?? ""}`).replace(/\/+$/u, "")}${AUTHORIZE_PATH}`);
53
+ /** The same endpoint, as the path the grant screen's form posts to — this server's own origin, whatever it is. */
54
+ var authorizeAction = (config) => config.issuer ? `${new URL(config.issuer).pathname.replace(/\/+$/u, "")}${AUTHORIZE_PATH}` : AUTHORIZE_PATH;
39
55
  var authorizeUrl = (config, req, params) => {
40
- const base = config.issuer ?? `https://${req.headers.host ?? ""}`;
41
- const url = new URL(AUTHORIZE_PATH, base);
56
+ const url = authorizeEndpoint(config, req);
42
57
  for (const [key, value] of Object.entries(params)) if (typeof value === "string") url.searchParams.set(key, value);
43
58
  return url.toString();
44
59
  };
@@ -73,8 +88,7 @@ var signInWithReturn = (config, req, params) => {
73
88
  * both paths already carry, instead of from whichever shape they came in.
74
89
  */
75
90
  var authorizeUrlFor = (config, req, request) => {
76
- const base = config.issuer ?? `https://${req.headers.host ?? ""}`;
77
- const url = new URL(AUTHORIZE_PATH, base);
91
+ const url = authorizeEndpoint(config, req);
78
92
  url.searchParams.set("response_type", "code");
79
93
  url.searchParams.set("client_id", request.clientId);
80
94
  url.searchParams.set("redirect_uri", request.redirectUri);
@@ -98,6 +112,10 @@ var resolveRequest = async (config, res, params) => {
98
112
  sendErrorPage(res, "Invalid redirect", "The application asked to be sent back to an address it did not register.");
99
113
  return;
100
114
  }
115
+ if (config.loopbackRedirectsOnly && !isLoopbackRedirectUri(redirectUri)) {
116
+ sendErrorPage(res, "Invalid redirect", "Only an application running on this computer can sign in here.");
117
+ return;
118
+ }
101
119
  const state = optionalField(params, "state");
102
120
  const responseType = field(params, "response_type");
103
121
  if (responseType && responseType !== "code") {
@@ -112,6 +130,7 @@ var resolveRequest = async (config, res, params) => {
112
130
  }
113
131
  return {
114
132
  clientId,
133
+ clientName: client.clientName,
115
134
  redirectUri,
116
135
  challenge,
117
136
  state,
@@ -120,8 +139,11 @@ var resolveRequest = async (config, res, params) => {
120
139
  };
121
140
  /** Consent granted: mint the bearer now, park it behind a one-shot code and send the browser back. Minting here
122
141
  * rather than at redemption keeps a failure the user can act on — no space, revoked access — on this screen. */
123
- var completeGrant = async (config, res, request, user, target) => {
124
- const issued = await config.adapters.issueToken(user, target);
142
+ var completeGrant = async (config, res, req, request, user, target) => {
143
+ const issued = await config.adapters.issueToken(user, target, await issueContextFor(config, request.clientId, {
144
+ req,
145
+ redirectUri: request.redirectUri
146
+ }));
125
147
  if (!issued) {
126
148
  redirectWithError(res, request.redirectUri, "access_denied", "The account may not grant access to this resource.", request.state);
127
149
  return;
@@ -146,7 +168,7 @@ var completeGrant = async (config, res, request, user, target) => {
146
168
  * proves — on that POST — that identity was established before anything was granted.
147
169
  */
148
170
  var askForTarget = async (config, res, request, user, error) => {
149
- const targets = await config.adapters.grantTargets(user);
171
+ const targets = await config.adapters.grantTargets(user, { scope: request.scope });
150
172
  if (targets.length === 0) {
151
173
  redirectWithError(res, request.redirectUri, "access_denied", "This account has nothing to grant access to.", request.state);
152
174
  return;
@@ -161,12 +183,13 @@ var askForTarget = async (config, res, request, user, error) => {
161
183
  user
162
184
  });
163
185
  renderConsent(res, {
164
- action: AUTHORIZE_PATH,
186
+ action: authorizeAction(config),
165
187
  hidden: hiddenFieldsFor(request, pendingId),
166
188
  targets,
167
189
  user,
168
190
  canSwitchUser: config.adapters.signOut !== void 0,
169
191
  error,
192
+ client: consentClient(request),
170
193
  branding: config.branding ?? {}
171
194
  });
172
195
  };
@@ -192,11 +215,12 @@ var handleAuthorizeStart = async (config, res, params, req) => {
192
215
  }
193
216
  if (config.guest) {
194
217
  renderConsent(res, {
195
- action: AUTHORIZE_PATH,
218
+ action: authorizeAction(config),
196
219
  hidden: hiddenFieldsFor(request),
197
220
  targets: [],
198
221
  guest: guestView(config.guest),
199
222
  signInUrl: signInWithReturn(config, req, params),
223
+ client: consentClient(request),
200
224
  branding: config.branding ?? {}
201
225
  });
202
226
  return;
@@ -228,7 +252,7 @@ var handleAuthorizeSubmit = async (config, res, params, req) => {
228
252
  }
229
253
  const { guest } = config;
230
254
  if (guest && optionalField(params, "guest")) {
231
- await completeGrant(config, res, request, guest.user ?? DEFAULT_GUEST_USER, guest.target);
255
+ await completeGrant(config, res, req, request, guest.user ?? DEFAULT_GUEST_USER, guest.target);
232
256
  return;
233
257
  }
234
258
  const pendingId = optionalField(params, "pending");
@@ -241,14 +265,14 @@ var handleAuthorizeSubmit = async (config, res, params, req) => {
241
265
  redirectWithError(res, request.redirectUri, "access_denied", "The sign-in expired. Try connecting again.", request.state);
242
266
  return;
243
267
  }
244
- const chosen = (await config.adapters.grantTargets(pending.user)).find((target) => target.value === field(params, "target"));
268
+ const chosen = (await config.adapters.grantTargets(pending.user, { scope: request.scope })).find((target) => target.value === field(params, "target"));
245
269
  if (!chosen) {
246
270
  await dropPending(config.adapters.store, pendingId);
247
271
  await askForTarget(config, res, request, pending.user, "Choose what to grant access to.");
248
272
  return;
249
273
  }
250
274
  await dropPending(config.adapters.store, pendingId);
251
- await completeGrant(config, res, request, pending.user, chosen);
275
+ await completeGrant(config, res, req, request, pending.user, chosen);
252
276
  };
253
277
  //#endregion
254
278
  export { handleAuthorizeStart, handleAuthorizeSubmit };
@@ -24,6 +24,8 @@ var STYLES = `
24
24
  img.logo { display: block; height: 32px; margin-bottom: 20px; }
25
25
  h1 { margin: 0 0 6px; font-size: 20px; font-weight: 600; }
26
26
  p.lede { margin: 0 0 24px; color: var(--muted); font-size: 14px; }
27
+ p.client { margin: -12px 0 24px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px;
28
+ font-size: 14px; }
27
29
  input:focus-visible, button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
28
30
  ul.targets { list-style: none; margin: 0 0 20px; padding: 0; display: grid; gap: 8px; }
29
31
  ul.targets label { display: flex; gap: 10px; align-items: flex-start; margin: 0; padding: 12px;
@@ -73,6 +75,14 @@ var anonymousFields = (view) => {
73
75
  return `${view.guest ? guestButton(view.guest) : ""}${signIn}`;
74
76
  };
75
77
  /**
78
+ * Who is asking and where the grant goes. The name is whatever the client registered as, so it is never shown alone:
79
+ * the destination host is what a person can check against the app they just opened.
80
+ */
81
+ var clientNotice = (client) => {
82
+ const destination = client.loopback ? "an app on this computer" : `<strong>${escapeHtml(client.redirectHost)}</strong>`;
83
+ return `<p class="client"><strong>${escapeHtml(client.name)}</strong> is asking for access. If you continue, it is sent to ${destination}.</p>`;
84
+ };
85
+ /**
76
86
  * The grant screen — the ONE page this server renders, and it never asks who anybody is.
77
87
  *
78
88
  * It used to be two steps, the first of which took a username and a password. That is gone: signing in happens on
@@ -99,6 +109,7 @@ var renderConsentPage = (view) => {
99
109
  ${logo}
100
110
  <h1>${escapeHtml(productName)}</h1>
101
111
  <p class="lede">${lede}</p>
112
+ ${clientNotice(view.client)}
102
113
  ${error}
103
114
  <form method="post" action="${escapeHtml(view.action)}">
104
115
  ${hiddenFields(view.hidden)}
@@ -0,0 +1,14 @@
1
+ import { OAuthConfig, OAuthIssueContext, SSRRequest } from '@plitzi/sdk-shared';
2
+ /**
3
+ * What `issueToken` is told about who it issues to, beyond the person: the application, as it registered, and the
4
+ * request the credential is issued on.
5
+ *
6
+ * The application is read from its registration rather than trusted from the request — a client names itself once,
7
+ * when it registers, and every credential its grants mint carries that name. A registration that has aged out of the
8
+ * store (a grant renewed for longer than the client record lives) is still an application, just an unnamed one.
9
+ */
10
+ export declare const issueContextFor: (config: OAuthConfig, clientId: string, extra?: {
11
+ req?: SSRRequest;
12
+ replaces?: string;
13
+ redirectUri?: string;
14
+ }) => Promise<OAuthIssueContext>;
@@ -0,0 +1,32 @@
1
+ import { forwardedIp } from "../requestParser.js";
2
+ import { getClient } from "./records.js";
3
+ //#region src/core/oauth/issueContext.ts
4
+ /**
5
+ * What `issueToken` is told about who it issues to, beyond the person: the application, as it registered, and the
6
+ * request the credential is issued on.
7
+ *
8
+ * The application is read from its registration rather than trusted from the request — a client names itself once,
9
+ * when it registers, and every credential its grants mint carries that name. A registration that has aged out of the
10
+ * store (a grant renewed for longer than the client record lives) is still an application, just an unnamed one.
11
+ */
12
+ var issueContextFor = async (config, clientId, extra = {}) => {
13
+ const client = await getClient(config.adapters.store, clientId);
14
+ const userAgent = extra.req?.headers["user-agent"];
15
+ const ip = extra.req ? extra.req.ip ?? forwardedIp(extra.req.headers) : "";
16
+ const request = {
17
+ ...typeof userAgent === "string" && userAgent ? { userAgent } : {},
18
+ ...ip ? { ip } : {}
19
+ };
20
+ return {
21
+ client: {
22
+ clientId,
23
+ name: client?.clientName ?? "Unnamed application",
24
+ ...client?.softwareId ? { softwareId: client.softwareId } : {}
25
+ },
26
+ ...request.userAgent || request.ip ? { request } : {},
27
+ ...extra.replaces ? { replaces: extra.replaces } : {},
28
+ ...extra.redirectUri ? { redirectUri: extra.redirectUri } : {}
29
+ };
30
+ };
31
+ //#endregion
32
+ export { issueContextFor };
@@ -4,6 +4,8 @@ import { OAuthGrantTarget, OAuthStore, OAuthUser } from '@plitzi/sdk-shared';
4
4
  export type ClientRecord = {
5
5
  clientId: string;
6
6
  clientName: string;
7
+ /** RFC 7591 `software_id`, when the client sent one. */
8
+ softwareId?: string;
7
9
  redirectUris: string[];
8
10
  /** When this registration was first handed out, so re-registering the same client keeps reporting its real age. */
9
11
  issuedAt: number;
@@ -37,6 +39,11 @@ export type RefreshRecord = {
37
39
  scope?: string;
38
40
  user: OAuthUser;
39
41
  target: OAuthGrantTarget;
42
+ /**
43
+ * What `issueToken` last minted for this grant. Handed back as `replaces` on the renewal, so the deployment can
44
+ * rotate that credential instead of adding another, and to `revokeToken` when the grant is revoked.
45
+ */
46
+ credential?: string;
40
47
  };
41
48
  /** A bearer this server handed out. The token a client holds is an opaque handle minted here, and this record is
42
49
  * what it stands for — including `credential`, the thing the deployment's own adapters understand, which never
@@ -1,4 +1,6 @@
1
1
  import { OAuthConfig, SSRResponseHelpers } from '@plitzi/sdk-shared';
2
+ /** Plain http on the loopback interface — the exception RFC 8252 carves out for a native app listening on a port. */
3
+ export declare const isLoopbackRedirectUri: (value: string) => boolean;
2
4
  /** RFC 7591 dynamic client registration. A remote host has no way to be configured into this server ahead of
3
5
  * time, so it registers itself on first connect; the record it gets back is only ever used to pin the redirect
4
6
  * target, since a public client authenticates with PKCE rather than with credentials. */