@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
@@ -1,11 +1,11 @@
1
1
  import { createSessionCookies, sessionCookieParams } from "./session.js";
2
+ import { createCsrf } from "./csrf.js";
3
+ import { applySessionOutcome, authPolicyRules, authRoutes } from "./routes.js";
4
+ import { createCarriers, presentedOrigin } from "./credentials.js";
2
5
  import { createAuthorizer } from "./authorize.js";
3
6
  import { createTokens } from "./tokens.js";
4
7
  import { createAuthApi } from "./api.js";
5
- import { createCarriers, presentedOrigin } from "./credentials.js";
6
- import { createCsrf } from "./csrf.js";
7
8
  import { createIdentity } from "./identity.js";
8
- import { applySessionOutcome, authPolicyRules, authRoutes } from "./routes.js";
9
9
  //#region src/core/auth/createAuth.ts
10
10
  /**
11
11
  * The whole auth cycle, wired.
@@ -9,7 +9,10 @@ export interface CredentialCarrier {
9
9
  query?: Record<string, unknown>;
10
10
  /** Populated by cookie-parser on Express; absent elsewhere, where the header is parsed. */
11
11
  cookies?: Record<string, string>;
12
+ /** The client's address as the host resolved it (Express's `req.ip`, honouring its `trust proxy`). */
13
+ ip?: string;
12
14
  }
15
+ export declare const readCookies: (carrier: CredentialCarrier) => Record<string, string>;
13
16
  /**
14
17
  * Where a credential of each kind is allowed to ride.
15
18
  *
@@ -51,4 +51,4 @@ var createCarriers = (sessionCookieName) => ({
51
51
  */
52
52
  var presentedOrigin = (carrier) => headerValue(carrier.headers.origin);
53
53
  //#endregion
54
- export { createCarriers, presentedOrigin };
54
+ export { createCarriers, presentedOrigin, readCookies };
@@ -26,7 +26,7 @@ export interface CsrfConfig {
26
26
  * space declared. Applying the check below on top would refuse every legitimate embed.
27
27
  */
28
28
  export type CsrfSubject = 'write' | 'signIn' | 'delegated';
29
- export type CsrfFailure = 'missing' | 'malformed' | 'expired' | 'mismatch';
29
+ export type CsrfFailure = 'missing' | 'malformed' | 'expired' | 'mismatch' | 'foreign';
30
30
  export declare const csrfFailureMessage: Record<CsrfFailure, string>;
31
31
  export type CsrfResult = {
32
32
  ok: true;
@@ -42,6 +42,20 @@ export interface CsrfCarrier {
42
42
  cookies?: Record<string, string>;
43
43
  body?: unknown;
44
44
  }
45
+ /**
46
+ * The carrier, read off whatever request a host hands over — named field by field, never spread.
47
+ *
48
+ * A spread copies OWN properties, and on Node's `IncomingMessage` (which an Express request is) `headers` is a getter on
49
+ * the prototype: `{ ...req }` arrives with no headers at all, and the first header read throws. Every guard built on a
50
+ * spread failed that way on every request.
51
+ */
52
+ export declare const carrierOf: (req: {
53
+ method?: string;
54
+ headers: CsrfCarrier["headers"];
55
+ hostname: string;
56
+ cookies?: unknown;
57
+ body?: unknown;
58
+ }, method?: string | undefined) => CsrfCarrier;
45
59
  export declare const createCsrf: (config: CsrfConfig) => {
46
60
  issue: (sessionToken?: string) => string;
47
61
  /** The name the cookie lands under, for a client that reads it by hand. */
@@ -79,6 +93,16 @@ export declare const createCsrf: (config: CsrfConfig) => {
79
93
  * protected by the session cookie's presence, a sign-in by where the request came from. See {@link CsrfSubject}.
80
94
  */
81
95
  required: (carrier: CsrfCarrier, subject?: CsrfSubject) => boolean;
96
+ /**
97
+ * Whether a browser sent this from a site this deployment does not recognise — its own origins, and
98
+ * `alsoAllowed` (the origins a SPACE declared, for a request that carries that space's credential).
99
+ *
100
+ * The rule `signIn` flows are guarded by, offered on its own for a router whose writes cannot all carry a token:
101
+ * a cookie-carried write that a foreign page caused is refused by where it came from — see
102
+ * `createOriginGuardMiddleware`. A request with neither Fetch Metadata nor `Origin` is not a browser's, and is
103
+ * never foreign: there is no victim's session in a script.
104
+ */
105
+ crossSite: (carrier: CsrfCarrier, alsoAllowed?: readonly string[]) => boolean;
82
106
  /**
83
107
  * The whole check. The echoed token must verify against the session the request carries, and — when the
84
108
  * cookie is also present — match it. The second half costs nothing and blocks a token that leaked somewhere
@@ -40,8 +40,23 @@ var csrfFailureMessage = {
40
40
  missing: "A CSRF token is required",
41
41
  malformed: "CSRF token malformed",
42
42
  expired: "CSRF token expired",
43
- mismatch: "CSRF token does not match this session"
43
+ mismatch: "CSRF token does not match this session",
44
+ foreign: "A request from another site may not act with this session"
44
45
  };
46
+ /**
47
+ * The carrier, read off whatever request a host hands over — named field by field, never spread.
48
+ *
49
+ * A spread copies OWN properties, and on Node's `IncomingMessage` (which an Express request is) `headers` is a getter on
50
+ * the prototype: `{ ...req }` arrives with no headers at all, and the first header read throws. Every guard built on a
51
+ * spread failed that way on every request.
52
+ */
53
+ var carrierOf = (req, method = req.method) => ({
54
+ method,
55
+ headers: req.headers,
56
+ hostname: req.hostname,
57
+ ...req.cookies && typeof req.cookies === "object" ? { cookies: req.cookies } : {},
58
+ body: req.body
59
+ });
45
60
  var equal = (a, b) => {
46
61
  const left = Buffer.from(a);
47
62
  const right = Buffer.from(b);
@@ -94,10 +109,10 @@ var createCsrf = (config) => {
94
109
  * anything: there is no victim's session sitting in it. That is what keeps every API client, mobile app and
95
110
  * script signing in with nothing extra to send.
96
111
  */
97
- const foreign = (carrier) => {
112
+ const foreign = (carrier, alsoAllowed = []) => {
98
113
  const site = header(carrier, "sec-fetch-site");
99
114
  const origin = header(carrier, "origin");
100
- const allowed = origin !== void 0 && permitted(origin);
115
+ const allowed = origin !== void 0 && (permitted(origin) || alsoAllowed.includes(origin));
101
116
  if (site !== void 0) return site === "cross-site" && !allowed;
102
117
  if (!origin) return false;
103
118
  return originHost(origin) !== carrier.hostname && !allowed;
@@ -212,6 +227,16 @@ var createCsrf = (config) => {
212
227
  return readCookieHeader(carrier, sessionCookieParams(carrier.hostname, cookie).name) !== void 0;
213
228
  },
214
229
  /**
230
+ * Whether a browser sent this from a site this deployment does not recognise — its own origins, and
231
+ * `alsoAllowed` (the origins a SPACE declared, for a request that carries that space's credential).
232
+ *
233
+ * The rule `signIn` flows are guarded by, offered on its own for a router whose writes cannot all carry a token:
234
+ * a cookie-carried write that a foreign page caused is refused by where it came from — see
235
+ * `createOriginGuardMiddleware`. A request with neither Fetch Metadata nor `Origin` is not a browser's, and is
236
+ * never foreign: there is no victim's session in a script.
237
+ */
238
+ crossSite: (carrier, alsoAllowed) => foreign(carrier, alsoAllowed),
239
+ /**
215
240
  * The whole check. The echoed token must verify against the session the request carries, and — when the
216
241
  * cookie is also present — match it. The second half costs nothing and blocks a token that leaked somewhere
217
242
  * the cookie did not.
@@ -231,4 +256,4 @@ var createCsrf = (config) => {
231
256
  };
232
257
  };
233
258
  //#endregion
234
- export { createCsrf, csrfFailureMessage };
259
+ export { carrierOf, createCsrf, csrfFailureMessage };
@@ -0,0 +1,26 @@
1
+ import { AuthApi } from './api';
2
+ import { CredentialCarrier } from './credentials';
3
+ import { Csrf } from './csrf';
4
+ import { CookieSink, SessionCookies } from './session';
5
+ /**
6
+ * A navigation of the whole tab — not a frame, not an image, not a script's fetch. The one kind of request another
7
+ * site cannot make on a visitor's behalf without the visitor watching it happen, which is what lets a renewal (it
8
+ * rotates the session) answer a `GET`.
9
+ */
10
+ export declare const isDocumentNavigation: (headers: Record<string, string | string[] | undefined>) => boolean;
11
+ export interface RenewalOptions {
12
+ api: AuthApi;
13
+ cookies: SessionCookies;
14
+ /** Re-issued for the renewed session, as every flow that grants one does. */
15
+ csrf?: Csrf;
16
+ }
17
+ /**
18
+ * `GET <basePath>/refresh?redirect=<page>`: renews the session this browser's refresh cookie holds, and answers where
19
+ * to send the browser next — always back to the page, whether or not the renewal worked.
20
+ *
21
+ * It is where a page server sends a browser whose access cookie has died while its refresh cookie lives on, because
22
+ * this is the one path the refresh cookie is sent to. What it writes is what makes the page render right on the way
23
+ * back: the new session, or — for a refresh token that can never work again — no session and no hint, so the page
24
+ * renders a guest and does not send the browser here a second time.
25
+ */
26
+ export declare const renewForNavigation: (req: CredentialCarrier, res: CookieSink, { api, cookies, csrf }: RenewalOptions) => Promise<string>;
@@ -0,0 +1,24 @@
1
+ import { applySessionOutcome } from "./routes.js";
2
+ //#region src/core/auth/renewal.ts
3
+ /**
4
+ * A navigation of the whole tab — not a frame, not an image, not a script's fetch. The one kind of request another
5
+ * site cannot make on a visitor's behalf without the visitor watching it happen, which is what lets a renewal (it
6
+ * rotates the session) answer a `GET`.
7
+ */
8
+ var isDocumentNavigation = (headers) => headers["sec-fetch-mode"] === "navigate" && headers["sec-fetch-dest"] === "document";
9
+ /**
10
+ * `GET <basePath>/refresh?redirect=<page>`: renews the session this browser's refresh cookie holds, and answers where
11
+ * to send the browser next — always back to the page, whether or not the renewal worked.
12
+ *
13
+ * It is where a page server sends a browser whose access cookie has died while its refresh cookie lives on, because
14
+ * this is the one path the refresh cookie is sent to. What it writes is what makes the page render right on the way
15
+ * back: the new session, or — for a refresh token that can never work again — no session and no hint, so the page
16
+ * renders a guest and does not send the browser here a second time.
17
+ */
18
+ var renewForNavigation = async (req, res, { api, cookies, csrf }) => {
19
+ const outcome = await api.refresh(cookies.resolveRefreshToken(req), req);
20
+ applySessionOutcome(req, res, outcome, cookies, csrf);
21
+ return cookies.returnTarget(req, req.query?.redirect);
22
+ };
23
+ //#endregion
24
+ export { isDocumentNavigation, renewForNavigation };
@@ -1,9 +1,14 @@
1
- import { csrfFailureMessage } from "./csrf.js";
1
+ import { carrierOf, csrfFailureMessage } from "./csrf.js";
2
2
  //#region src/core/auth/routes.ts
3
3
  var body = (req) => req.body ?? {};
4
- /** A body field as a string, whatever arrived. Handlers validate what they need; this only stops `[object Object]`. */
4
+ /**
5
+ * A body field as text. A NUMBER counts — JSON carries an all-digit code or password as one, and a client that sent
6
+ * `123456` has sent a code: reading it as absent answered a right second-factor code with "Invalid code". Anything else
7
+ * (an object, a boolean) is not a value anybody typed, and is absent.
8
+ */
5
9
  var field = (req, name) => {
6
10
  const value = body(req)[name];
11
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
7
12
  return typeof value === "string" ? value : "";
8
13
  };
9
14
  /** A body field as a whole number. `NaN` for anything else, which every handler refuses as a missing id. */
@@ -286,10 +291,7 @@ var authRoutes = ({ api, cookies, csrf }) => FLOWS.map(({ run, csrf: policy, ...
286
291
  * unbound token satisfies a signed-out caller, which is how signing in is protected when a deployment asks
287
292
  * for that.
288
293
  */
289
- const carrier = {
290
- ...req,
291
- method: route.method
292
- };
294
+ const carrier = carrierOf(req, route.method);
293
295
  if (csrf && csrfNeeded(csrf, policy, carrier)) {
294
296
  const result = csrf.verify(carrier, cookies.resolveSessionToken(req));
295
297
  if (!result.ok) return {
@@ -44,6 +44,14 @@ export declare const sessionCookieParams: (hostname: string, config?: SSRAuthCoo
44
44
  * same question with `credentials: 'include'`.
45
45
  */
46
46
  export declare const sessionHintValue: (expiresAt: number, refreshExpiresAt?: number) => string;
47
+ export type SessionHint = {
48
+ /** Unix seconds the access credential dies at. */
49
+ expiresAt: number;
50
+ /** Unix seconds renewal stops being possible at, when the session has a refresh half. */
51
+ refreshExpiresAt?: number;
52
+ };
53
+ /** {@link sessionHintValue} read back — the same reading `@plitzi/sdk-auth` gives it in the browser. */
54
+ export declare const parseSessionHint: (value: string | undefined) => SessionHint | undefined;
47
55
  /**
48
56
  * Adds to `Set-Cookie` rather than replacing it. A handler may already have written one — clearing an OAuth flow
49
57
  * before granting the session it produced is exactly that — and replacing the header would silently drop it.
@@ -93,6 +101,40 @@ export declare const readRefreshToken: (req: SSRRequest | {
93
101
  };
94
102
  hostname: string;
95
103
  }, config?: SSRAuthCookie) => string | undefined;
104
+ /** What the readable companion cookie says about this browser's session, without presenting any credential. */
105
+ export declare const readSessionHint: (req: SSRRequest | {
106
+ headers: {
107
+ cookie?: string;
108
+ };
109
+ hostname: string;
110
+ }, config?: SSRAuthCookie) => SessionHint | undefined;
111
+ /**
112
+ * How long a page server holds off sending a browser to renew again. Long enough to cover the round trip there and
113
+ * back, short enough that a renewal which failed for a passing reason is retried on a later visit.
114
+ */
115
+ export declare const RENEWAL_GUARD_SECONDS = 30;
116
+ /**
117
+ * Marks a browser as having just been sent to renew its session, so the page it comes back to is rendered rather
118
+ * than bounced again. Every renewal that can fail without ending the session — the refresh cookie not reaching the
119
+ * endpoint, the store being down — would otherwise send the visitor round in a loop.
120
+ */
121
+ export declare const writeRenewalGuard: (req: {
122
+ hostname: string;
123
+ }, res: CookieSink, config?: SSRAuthCookie) => void;
124
+ /**
125
+ * Where a renewal sends the browser back to: a path on this host, or a page on any host this session's cookies are
126
+ * scoped to — the only hosts that could have sent a browser here to renew, since the hint that triggers it rides on
127
+ * the same cookie domain. Anything else is an open redirect, and goes to `/` instead.
128
+ */
129
+ export declare const sessionReturnTarget: (req: {
130
+ hostname: string;
131
+ }, target: unknown, config?: SSRAuthCookie) => string;
132
+ export declare const readRenewalGuard: (req: SSRRequest | {
133
+ headers: {
134
+ cookie?: string;
135
+ };
136
+ hostname: string;
137
+ }, config?: SSRAuthCookie) => boolean;
96
138
  /** Anything a credential can be read off: this server's own request, an Express one, a bare `node:http` one. */
97
139
  export type CookieCarrier = SSRRequest | {
98
140
  headers: {
@@ -124,6 +166,9 @@ export declare const createSessionCookies: (config?: SSRAuthCookie) => {
124
166
  hostname: string;
125
167
  }, res: CookieSink) => void;
126
168
  readFlow: (req: CookieCarrier) => string | undefined;
169
+ returnTarget: (req: {
170
+ hostname: string;
171
+ }, target: unknown) => string;
127
172
  /**
128
173
  * The session credential from `Authorization: Bearer` or the cookie, so one endpoint serves a bearer client and a
129
174
  * browser. The header wins: a caller that went to the trouble of presenting one means it.
@@ -51,6 +51,18 @@ var sessionCookieParams = (hostname, config = {}) => {
51
51
  * same question with `credentials: 'include'`.
52
52
  */
53
53
  var sessionHintValue = (expiresAt, refreshExpiresAt) => `${Math.floor(expiresAt)}.${refreshExpiresAt === void 0 ? "" : Math.floor(refreshExpiresAt)}`;
54
+ /** {@link sessionHintValue} read back — the same reading `@plitzi/sdk-auth` gives it in the browser. */
55
+ var parseSessionHint = (value) => {
56
+ if (!value) return;
57
+ const [expiresAt, refreshExpiresAt] = value.split(".");
58
+ const expiry = Number(expiresAt);
59
+ if (!Number.isFinite(expiry) || expiry <= 0) return;
60
+ const refreshExpiry = Number(refreshExpiresAt);
61
+ return {
62
+ expiresAt: expiry,
63
+ refreshExpiresAt: Number.isFinite(refreshExpiry) && refreshExpiry > 0 ? refreshExpiry : void 0
64
+ };
65
+ };
54
66
  var serializeCookie = (name, value, maxAgeSeconds, params, { httpOnly = true, path = "/" } = {}) => {
55
67
  const parts = [
56
68
  `${name}=${encodeURIComponent(value)}`,
@@ -127,6 +139,39 @@ var readCookie = (req, name) => {
127
139
  var readSessionToken = (req, config) => readCookie(req, sessionCookieParams(req.hostname, config).name);
128
140
  /** The renewal credential, which rides in its own cookie confined to the refresh path. */
129
141
  var readRefreshToken = (req, config) => readCookie(req, `${sessionCookieParams(req.hostname, config).name}_refresh`);
142
+ /** What the readable companion cookie says about this browser's session, without presenting any credential. */
143
+ var readSessionHint = (req, config) => {
144
+ const params = sessionCookieParams(req.hostname, config);
145
+ return parseSessionHint(readCookie(req, `${params.name}${params.hintSuffix}`));
146
+ };
147
+ /**
148
+ * Marks a browser as having just been sent to renew its session, so the page it comes back to is rendered rather
149
+ * than bounced again. Every renewal that can fail without ending the session — the refresh cookie not reaching the
150
+ * endpoint, the store being down — would otherwise send the visitor round in a loop.
151
+ */
152
+ var writeRenewalGuard = (req, res, config) => {
153
+ const params = sessionCookieParams(req.hostname, config);
154
+ appendCookies(res, [serializeCookie(`${params.name}_renewing`, "1", 30, params)]);
155
+ };
156
+ /**
157
+ * Where a renewal sends the browser back to: a path on this host, or a page on any host this session's cookies are
158
+ * scoped to — the only hosts that could have sent a browser here to renew, since the hint that triggers it rides on
159
+ * the same cookie domain. Anything else is an open redirect, and goes to `/` instead.
160
+ */
161
+ var sessionReturnTarget = (req, target, config) => {
162
+ if (typeof target !== "string" || target === "") return "/";
163
+ if (target.startsWith("/") && !target.startsWith("//") && !target.startsWith("/\\")) return target;
164
+ let url;
165
+ try {
166
+ url = new URL(target);
167
+ } catch {
168
+ return "/";
169
+ }
170
+ if (url.protocol !== "https:" && url.protocol !== "http:") return "/";
171
+ const { domain } = sessionCookieParams(req.hostname, config);
172
+ return url.hostname === req.hostname || domain !== void 0 && (url.hostname === domain.slice(1) || url.hostname.endsWith(domain)) ? url.toString() : "/";
173
+ };
174
+ var readRenewalGuard = (req, config) => readCookie(req, `${sessionCookieParams(req.hostname, config).name}_renewing`) !== void 0;
130
175
  /**
131
176
  * The cookie side of a session with this deployment's naming already bound in.
132
177
  *
@@ -142,6 +187,7 @@ var createSessionCookies = (config) => ({
142
187
  writeFlow: (req, res, value, ttlSeconds) => writeFlowCookie(req, res, value, ttlSeconds, config),
143
188
  clearFlow: (req, res) => clearFlowCookie(req, res, config),
144
189
  readFlow: (req) => readFlowCookie(req, config),
190
+ returnTarget: (req, target) => sessionReturnTarget(req, target, config),
145
191
  /**
146
192
  * The session credential from `Authorization: Bearer` or the cookie, so one endpoint serves a bearer client and a
147
193
  * browser. The header wins: a caller that went to the trouble of presenting one means it.
@@ -162,4 +208,4 @@ var createSessionCookies = (config) => ({
162
208
  }
163
209
  });
164
210
  //#endregion
165
- export { appendCookies, clearFlowCookie, clearSessionCookies, createSessionCookies, isLocalHost, readFlowCookie, readRefreshToken, readSessionToken, registrableDomain, sessionCookieParams, sessionHintValue, writeFlowCookie, writeSessionCookies };
211
+ export { appendCookies, clearFlowCookie, clearSessionCookies, createSessionCookies, isLocalHost, parseSessionHint, readFlowCookie, readRefreshToken, readRenewalGuard, readSessionHint, readSessionToken, registrableDomain, sessionCookieParams, sessionHintValue, sessionReturnTarget, writeFlowCookie, writeRenewalGuard, writeSessionCookies };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * When a session was last used, to the resolution a device list needs.
3
+ *
4
+ * "Active 3 minutes ago" against "active last March" is what tells somebody which of their devices is the one they do
5
+ * not recognise — and the time a session was CREATED says neither: a laptop signed in a month ago and used this morning
6
+ * reads as a month-old session. Recording every request would be a write per request on the hottest read in the
7
+ * system, so a store records it at most once per this many seconds, on the lookup it already makes.
8
+ */
9
+ export declare const SESSION_ACTIVITY_RESOLUTION_SECONDS = 300;
10
+ /** Whether a session last seen at `lastActiveAt` (unix seconds, absent when never) is due a new record at `now`. */
11
+ export declare const activityDue: (lastActiveAt: number | null | undefined, now: number) => boolean;
@@ -0,0 +1,14 @@
1
+ //#region src/core/auth/sessionActivity.ts
2
+ /**
3
+ * When a session was last used, to the resolution a device list needs.
4
+ *
5
+ * "Active 3 minutes ago" against "active last March" is what tells somebody which of their devices is the one they do
6
+ * not recognise — and the time a session was CREATED says neither: a laptop signed in a month ago and used this morning
7
+ * reads as a month-old session. Recording every request would be a write per request on the hottest read in the
8
+ * system, so a store records it at most once per this many seconds, on the lookup it already makes.
9
+ */
10
+ var SESSION_ACTIVITY_RESOLUTION_SECONDS = 300;
11
+ /** Whether a session last seen at `lastActiveAt` (unix seconds, absent when never) is due a new record at `now`. */
12
+ var activityDue = (lastActiveAt, now) => lastActiveAt === null || lastActiveAt === void 0 || now - lastActiveAt >= 300;
13
+ //#endregion
14
+ export { SESSION_ACTIVITY_RESOLUTION_SECONDS, activityDue };
@@ -1,5 +1,20 @@
1
1
  import { ThrottleAttempt } from './api';
2
+ import { StoreMethods } from '../server/fleet/channel';
2
3
  export declare const createMemoryRateLimit: () => ((attempt: ThrottleAttempt) => Promise<{
3
4
  allowed: boolean;
4
5
  retryAfter?: number;
5
6
  }>);
7
+ type RateLimitStore = {
8
+ check: ReturnType<typeof createMemoryRateLimit>;
9
+ };
10
+ /** Every method of the store, for the copy a fleet's workers share (see `fleet/stores.ts`). */
11
+ export declare const RATE_LIMIT_METHODS: StoreMethods<RateLimitStore>;
12
+ /**
13
+ * In a worker of a fleet, the primary's {@link createMemoryRateLimit}, so an attacker spreading attempts over
14
+ * connections meets one counter rather than one per worker. Nothing anywhere else.
15
+ */
16
+ export declare const fleetRateLimit: () => ((attempt: ThrottleAttempt) => Promise<{
17
+ allowed: boolean;
18
+ retryAfter?: number;
19
+ }>) | undefined;
20
+ export {};
@@ -1,4 +1,17 @@
1
+ import { fleetStore } from "../server/fleet/link.js";
1
2
  //#region src/core/auth/throttle.ts
3
+ /**
4
+ * The rate limit a deployment gets without asking for one.
5
+ *
6
+ * This used to be a required-to-be-safe option: no `rateLimit`, no limit, and sign-in was an unmetered password
7
+ * oracle. Nobody sets an option they have not read about, so the effect of "there is no sensible default" was that
8
+ * the common deployment shipped without any — which is not a neutral outcome, it is the bad one.
9
+ *
10
+ * It counts in memory, and that is an honest limitation rather than a hidden one: several replicas each keep
11
+ * their own window, so four replicas tolerate four times the attempts. Four times a small number is still a small
12
+ * number, and it beats infinity. A deployment that wants one counter for all of them supplies `rateLimit` and puts
13
+ * it in Redis. The workers of one server are one replica: they count in the primary (`fleetRateLimit`).
14
+ */
2
15
  /** Attempts allowed per window, per key, per action. */
3
16
  var LIMITS = {
4
17
  /** Credential stuffing is the thing being priced out. Ten wrong passwords in five minutes is already a lot. */
@@ -77,5 +90,20 @@ var createMemoryRateLimit = () => {
77
90
  return Promise.resolve({ allowed: true });
78
91
  };
79
92
  };
93
+ /** Every method of the store, for the copy a fleet's workers share (see `fleet/stores.ts`). */
94
+ var RATE_LIMIT_METHODS = { check: true };
95
+ /**
96
+ * In a worker of a fleet, the primary's {@link createMemoryRateLimit}, so an attacker spreading attempts over
97
+ * connections meets one counter rather than one per worker. Nothing anywhere else.
98
+ */
99
+ var fleetRateLimit = () => {
100
+ const shared = fleetStore("auth.rateLimit", RATE_LIMIT_METHODS);
101
+ if (!shared) return;
102
+ return (attempt) => shared.check({
103
+ action: attempt.action,
104
+ key: attempt.key,
105
+ ...attempt.succeeded ? { succeeded: true } : {}
106
+ });
107
+ };
80
108
  //#endregion
81
- export { createMemoryRateLimit };
109
+ export { RATE_LIMIT_METHODS, createMemoryRateLimit, fleetRateLimit };
@@ -3,15 +3,23 @@ export declare const generateTotpSecret: () => string;
3
3
  /** The code an app would be showing right now. Exported so a test does not have to reimplement the algorithm. */
4
4
  export declare const totpCode: (secret: string, at?: number) => string;
5
5
  /**
6
- * Is this the code?
6
+ * Which time step this code belongs to, or `undefined` when it belongs to none near now.
7
7
  *
8
8
  * `window` steps either side are accepted, one by default — thirty seconds of tolerance for a clock that drifts
9
9
  * and for a person who starts typing at second twenty-nine. Wider is friendlier and weaker; this is the usual
10
10
  * trade and the same one Google Authenticator's own guidance makes.
11
11
  *
12
+ * The step is the answer, not a yes: a code stays valid for its whole window, so a verifier that remembers nothing
13
+ * accepts the same code twice. RFC 6238 §5.2 forbids that — see `MfaRecord.lastUsedStep`.
14
+ *
12
15
  * Compared in constant time: a comparison that returns on the first differing digit leaks how much of a guess was
13
16
  * right, which turns a million-guess space into ten guesses per position.
14
17
  */
18
+ export declare const totpStep: (secret: string, code: string, options?: {
19
+ window?: number;
20
+ at?: number;
21
+ }) => number | undefined;
22
+ /** Is this the code? `totpStep`, for a caller that does not need to know which step it was. */
15
23
  export declare const verifyTotp: (secret: string, code: string, options?: {
16
24
  window?: number;
17
25
  at?: number;
@@ -55,28 +55,33 @@ var codeAt = (secret, counter) => {
55
55
  /** The code an app would be showing right now. Exported so a test does not have to reimplement the algorithm. */
56
56
  var totpCode = (secret, at = Date.now()) => codeAt(secret, Math.floor(at / 1e3 / STEP_SECONDS));
57
57
  /**
58
- * Is this the code?
58
+ * Which time step this code belongs to, or `undefined` when it belongs to none near now.
59
59
  *
60
60
  * `window` steps either side are accepted, one by default — thirty seconds of tolerance for a clock that drifts
61
61
  * and for a person who starts typing at second twenty-nine. Wider is friendlier and weaker; this is the usual
62
62
  * trade and the same one Google Authenticator's own guidance makes.
63
63
  *
64
+ * The step is the answer, not a yes: a code stays valid for its whole window, so a verifier that remembers nothing
65
+ * accepts the same code twice. RFC 6238 §5.2 forbids that — see `MfaRecord.lastUsedStep`.
66
+ *
64
67
  * Compared in constant time: a comparison that returns on the first differing digit leaks how much of a guess was
65
68
  * right, which turns a million-guess space into ten guesses per position.
66
69
  */
67
- var verifyTotp = (secret, code, options = {}) => {
70
+ var totpStep = (secret, code, options = {}) => {
68
71
  const { window = 1, at = Date.now() } = options;
69
72
  const candidate = code.trim();
70
- if (!/^\d{6}$/.test(candidate)) return false;
73
+ if (!/^\d{6}$/.test(candidate)) return;
71
74
  const counter = Math.floor(at / 1e3 / STEP_SECONDS);
72
75
  const supplied = Buffer.from(candidate);
73
- let matched = false;
76
+ let matched;
74
77
  for (let drift = -window; drift <= window; drift++) {
75
78
  const expected = Buffer.from(codeAt(secret, counter + drift));
76
- matched = timingSafeEqual(expected, supplied) || matched;
79
+ if (timingSafeEqual(expected, supplied)) matched = counter + drift;
77
80
  }
78
81
  return matched;
79
82
  };
83
+ /** Is this the code? `totpStep`, for a caller that does not need to know which step it was. */
84
+ var verifyTotp = (secret, code, options = {}) => totpStep(secret, code, options) !== void 0;
80
85
  /**
81
86
  * The URI an authenticator app scans. `issuer` appears twice by convention — in the label and as a parameter —
82
87
  * because different apps read different ones, and an entry that says only an email address is unidentifiable in a
@@ -115,4 +120,4 @@ var generateRecoveryCodes = (count = 10) => Array.from({ length: count }, () =>
115
120
  /** Normalised before hashing so that case and the hyphen a person types or omits do not decide whether it works. */
116
121
  var normalizeRecoveryCode = (code) => code.trim().toUpperCase().replace(/[^A-Z0-9]/g, "");
117
122
  //#endregion
118
- export { generateRecoveryCodes, generateTotpSecret, normalizeRecoveryCode, randomCode, totpCode, totpUri, verifyTotp };
123
+ export { generateRecoveryCodes, generateTotpSecret, normalizeRecoveryCode, randomCode, totpCode, totpStep, totpUri, verifyTotp };
@@ -0,0 +1 @@
1
+ export {};
@@ -19,5 +19,18 @@ export type ServerConfig = Omit<SSRServerConfig, 'adapters'> & {
19
19
  * `adapters` or `authCookie` still wins, so a deployment can override one piece without giving up the rest.
20
20
  */
21
21
  auth?: Auth;
22
+ /**
23
+ * Where a page request whose access cookie has died, while its refresh cookie lives on, is sent to renew before
24
+ * the page is rendered — so the server renders the visitor the browser is about to become, rather than a guest
25
+ * the browser then replaces. The endpoint is `GET <basePath>/refresh`, served by `auth` here or by
26
+ * `mountAuthRoutes` wherever `/auth` lives.
27
+ *
28
+ * On by default with `auth`, at its own `/refresh`. Without it, name the endpoint — an absolute URL when another
29
+ * host serves `/auth` (a host sharing the session's cookie domain, or the hint that triggers this never arrives).
30
+ * `false` turns it off.
31
+ */
32
+ sessionRenewal?: {
33
+ url: string;
34
+ } | false;
22
35
  };
23
- export declare const createServer: ({ auth, ...config }: ServerConfig, extensions?: PipelineExtensions) => SSRServer;
36
+ export declare const createServer: ({ auth, sessionRenewal, ...config }: ServerConfig, extensions?: PipelineExtensions) => SSRServer;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,7 @@
1
1
  import { actionsModuleFor } from "../modules/actions/moduleFor.js";
2
2
  import { connectorRscData } from "../modules/rsc/connectorRscData.js";
3
3
  import { createAuthApiStage } from "./http/stages/authApi.js";
4
+ import { createRenewalEndpointStage, createSessionRenewalStage } from "./http/stages/sessionRenewal.js";
4
5
  import { resolveServices } from "./services/resolve.js";
5
6
  import { createPageServer } from "./server/pageServer.js";
6
7
  //#region src/core/createServer.ts
@@ -24,23 +25,27 @@ import { createPageServer } from "./server/pageServer.js";
24
25
  */
25
26
  var withConnectorRsc = (config) => {
26
27
  if (config.adapters.getRscData || !config.connectors && !config.action?.lookups) return config;
27
- const module = actionsModuleFor(config);
28
+ const resolved = {
29
+ ...config,
30
+ adapters: { ...config.adapters }
31
+ };
32
+ const module = actionsModuleFor(resolved);
28
33
  const actions = module && config.action?.lookups ? {
29
34
  lookups: config.action.lookups,
30
35
  module
31
36
  } : void 0;
32
- return {
33
- ...config,
34
- adapters: {
35
- ...config.adapters,
36
- getRscData: connectorRscData(config.connectors, actions, config.rsc?.elementTimeoutMs)
37
- }
38
- };
37
+ resolved.adapters.getRscData = connectorRscData(config.connectors, actions, config.rsc?.elementTimeoutMs);
38
+ return resolved;
39
39
  };
40
- var createServer = ({ auth, ...config }, extensions) => {
40
+ /** The renewal stage in front of whatever else gates itself, when there is anywhere to renew. */
41
+ var withRenewal = (extensions, url) => ({
42
+ ...extensions,
43
+ preAuth: [...extensions?.preAuth ?? [], ...url ? [createSessionRenewalStage(url)] : []]
44
+ });
45
+ var createServer = ({ auth, sessionRenewal, ...config }, extensions) => {
41
46
  if (!auth) {
42
47
  const resolvedConfig = withConnectorRsc(config);
43
- return createPageServer(resolvedConfig, resolveServices(resolvedConfig), extensions);
48
+ return createPageServer(resolvedConfig, resolveServices(resolvedConfig), withRenewal(extensions, sessionRenewal ? sessionRenewal.url : void 0));
44
49
  }
45
50
  const entries = Object.entries(config.adapters);
46
51
  const supplied = Object.fromEntries(entries.filter(([, value]) => value !== void 0));
@@ -57,9 +62,14 @@ var createServer = ({ auth, ...config }, extensions) => {
57
62
  });
58
63
  const withAuthRoutes = {
59
64
  ...extensions,
60
- preAuth: [...extensions?.preAuth ?? [], createAuthApiStage(auth, auth.basePath)]
65
+ preAuth: [
66
+ ...extensions?.preAuth ?? [],
67
+ createAuthApiStage(auth, auth.basePath),
68
+ createRenewalEndpointStage(auth)
69
+ ]
61
70
  };
62
- return createPageServer(resolved, resolveServices(resolved), withAuthRoutes);
71
+ const renewalUrl = sessionRenewal === false ? void 0 : sessionRenewal?.url ?? `${auth.basePath}/refresh`;
72
+ return createPageServer(resolved, resolveServices(resolved), withRenewal(withAuthRoutes, renewalUrl));
63
73
  };
64
74
  //#endregion
65
75
  export { createServer, resolveServices };
@@ -1,5 +1,5 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
2
+ import fs from "node:fs";
3
3
  //#region src/core/fontStore.ts
4
4
  /** `<spaceId>/<name>` — the space segment is what keeps one space's files out of another's. */
5
5
  var storePathOf = (spaceId, name) => `${spaceId}/${name}`;
@@ -12,7 +12,7 @@ export interface AuthRouteHandlersOptions {
12
12
  * deployment that means to go without says so with `csrf: false` there rather than by forgetting here.
13
13
  */
14
14
  csrf?: Csrf;
15
- /** Reports a flow that threw. Without it the failure goes to `console.error`; the caller still gets a 500. */
15
+ /** Reports a flow that threw. Without it the failure goes to the server log at `error`; the caller still gets a 500. */
16
16
  onError?: (error: unknown, context: {
17
17
  method: string;
18
18
  path: string;