cursedbelt-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (606) hide show
  1. package/LICENSE +15 -0
  2. package/dist/server/activity/index.d.ts +36 -0
  3. package/dist/server/activity/index.js +36 -0
  4. package/dist/server/activity/migrations.d.ts +21 -0
  5. package/dist/server/activity/migrations.js +55 -0
  6. package/dist/server/activity/shareSource.d.ts +11 -0
  7. package/dist/server/activity/shareSource.js +45 -0
  8. package/dist/server/activity/store.d.ts +123 -0
  9. package/dist/server/activity/store.js +178 -0
  10. package/dist/server/app.d.ts +56 -0
  11. package/dist/server/app.js +45 -0
  12. package/dist/server/audit/index.d.ts +2 -0
  13. package/dist/server/audit/index.js +2 -0
  14. package/dist/server/audit/migrations.d.ts +2 -0
  15. package/dist/server/audit/migrations.js +79 -0
  16. package/dist/server/audit/sqliteSink.d.ts +36 -0
  17. package/dist/server/audit/sqliteSink.js +95 -0
  18. package/dist/server/audit/sqliteSink.test.d.ts +1 -0
  19. package/dist/server/audit/sqliteSink.test.js +180 -0
  20. package/dist/server/auth/cookie.d.ts +22 -0
  21. package/dist/server/auth/cookie.js +35 -0
  22. package/dist/server/auth/csrf.d.ts +42 -0
  23. package/dist/server/auth/csrf.js +36 -0
  24. package/dist/server/auth/fileTokenSigner.d.ts +10 -0
  25. package/dist/server/auth/fileTokenSigner.js +22 -0
  26. package/dist/server/auth/getOnly.d.ts +20 -0
  27. package/dist/server/auth/getOnly.js +27 -0
  28. package/dist/server/auth/index.d.ts +12 -0
  29. package/dist/server/auth/index.js +15 -0
  30. package/dist/server/auth/jwt.d.ts +18 -0
  31. package/dist/server/auth/jwt.js +56 -0
  32. package/dist/server/auth/loginThrottle.d.ts +118 -0
  33. package/dist/server/auth/loginThrottle.js +204 -0
  34. package/dist/server/auth/middleware.d.ts +14 -0
  35. package/dist/server/auth/middleware.js +47 -0
  36. package/dist/server/auth/password.d.ts +14 -0
  37. package/dist/server/auth/password.js +65 -0
  38. package/dist/server/auth/sessionStore.d.ts +19 -0
  39. package/dist/server/auth/sessionStore.js +221 -0
  40. package/dist/server/auth/totp.d.ts +34 -0
  41. package/dist/server/auth/totp.js +81 -0
  42. package/dist/server/auth/types.d.ts +133 -0
  43. package/dist/server/auth/types.js +1 -0
  44. package/dist/server/auth/vault.d.ts +27 -0
  45. package/dist/server/auth/vault.js +57 -0
  46. package/dist/server/context.d.ts +35 -0
  47. package/dist/server/context.js +1 -0
  48. package/dist/server/ctgr.d.ts +32 -0
  49. package/dist/server/ctgr.js +69 -0
  50. package/dist/server/db/kysely.d.ts +14 -0
  51. package/dist/server/db/kysely.js +16 -0
  52. package/dist/server/db/plumbingTypes.d.ts +116 -0
  53. package/dist/server/db/plumbingTypes.js +12 -0
  54. package/dist/server/dropzone/duplicates.d.ts +82 -0
  55. package/dist/server/dropzone/duplicates.js +90 -0
  56. package/dist/server/dropzone/index.d.ts +13 -0
  57. package/dist/server/dropzone/index.js +13 -0
  58. package/dist/server/dropzone/scan.d.ts +166 -0
  59. package/dist/server/dropzone/scan.js +272 -0
  60. package/dist/server/errors.d.ts +7 -0
  61. package/dist/server/errors.js +7 -0
  62. package/dist/server/guard/argon2Binding.d.ts +97 -0
  63. package/dist/server/guard/argon2Binding.js +138 -0
  64. package/dist/server/guard/index.d.ts +4 -0
  65. package/dist/server/guard/index.js +6 -0
  66. package/dist/server/guard/kdfWorker.d.ts +101 -0
  67. package/dist/server/guard/kdfWorker.js +301 -0
  68. package/dist/server/guard/kdfWorkerEntry.d.ts +1 -0
  69. package/dist/server/guard/kdfWorkerEntry.js +15 -0
  70. package/dist/server/guard/passwordVerifier.d.ts +65 -0
  71. package/dist/server/guard/passwordVerifier.js +122 -0
  72. package/dist/server/guard/revocationStore.d.ts +132 -0
  73. package/dist/server/guard/revocationStore.js +211 -0
  74. package/dist/server/guard/sessionSigner.d.ts +132 -0
  75. package/dist/server/guard/sessionSigner.js +303 -0
  76. package/dist/server/guard/verifier.d.ts +21 -0
  77. package/dist/server/guard/verifier.js +1 -0
  78. package/dist/server/incidents/channels.d.ts +161 -0
  79. package/dist/server/incidents/channels.js +357 -0
  80. package/dist/server/incidents/checks.d.ts +61 -0
  81. package/dist/server/incidents/checks.js +55 -0
  82. package/dist/server/incidents/index.d.ts +51 -0
  83. package/dist/server/incidents/index.js +51 -0
  84. package/dist/server/incidents/migrations.d.ts +20 -0
  85. package/dist/server/incidents/migrations.js +58 -0
  86. package/dist/server/incidents/notifier.d.ts +79 -0
  87. package/dist/server/incidents/notifier.js +141 -0
  88. package/dist/server/incidents/reporter.d.ts +60 -0
  89. package/dist/server/incidents/reporter.js +49 -0
  90. package/dist/server/incidents/routes.d.ts +91 -0
  91. package/dist/server/incidents/routes.js +202 -0
  92. package/dist/server/incidents/store.d.ts +75 -0
  93. package/dist/server/incidents/store.js +176 -0
  94. package/dist/server/incidents/types.d.ts +107 -0
  95. package/dist/server/incidents/types.js +32 -0
  96. package/dist/server/index.d.ts +25 -0
  97. package/dist/server/index.js +53 -0
  98. package/dist/server/jobs/fireQueue.d.ts +12 -0
  99. package/dist/server/jobs/fireQueue.js +37 -0
  100. package/dist/server/jobs/index.d.ts +6 -0
  101. package/dist/server/jobs/index.js +10 -0
  102. package/dist/server/jobs/logger.d.ts +10 -0
  103. package/dist/server/jobs/logger.js +14 -0
  104. package/dist/server/jobs/plainjobAdapter.d.ts +19 -0
  105. package/dist/server/jobs/plainjobAdapter.js +48 -0
  106. package/dist/server/jobs/polling.d.ts +17 -0
  107. package/dist/server/jobs/polling.js +79 -0
  108. package/dist/server/jobs/runner.d.ts +24 -0
  109. package/dist/server/jobs/runner.js +186 -0
  110. package/dist/server/jobs/timeout.d.ts +28 -0
  111. package/dist/server/jobs/timeout.js +47 -0
  112. package/dist/server/jobs/types.d.ts +167 -0
  113. package/dist/server/jobs/types.js +12 -0
  114. package/dist/server/master-lock/guard.d.ts +53 -0
  115. package/dist/server/master-lock/guard.js +205 -0
  116. package/dist/server/master-lock/index.d.ts +21 -0
  117. package/dist/server/master-lock/index.js +21 -0
  118. package/dist/server/master-lock/lockPage.d.ts +34 -0
  119. package/dist/server/master-lock/lockPage.js +281 -0
  120. package/dist/server/master-lock/masterLock.d.ts +294 -0
  121. package/dist/server/master-lock/masterLock.js +523 -0
  122. package/dist/server/master-lock/principals.d.ts +76 -0
  123. package/dist/server/master-lock/principals.js +157 -0
  124. package/dist/server/master-lock/seed.d.ts +47 -0
  125. package/dist/server/master-lock/seed.js +93 -0
  126. package/dist/server/master-lock/store.d.ts +19 -0
  127. package/dist/server/master-lock/store.js +20 -0
  128. package/dist/server/media-bun/image.d.ts +7 -0
  129. package/dist/server/media-bun/image.js +96 -0
  130. package/dist/server/media-bun/index.d.ts +4 -0
  131. package/dist/server/media-bun/index.js +10 -0
  132. package/dist/server/media-bun/streaming.d.ts +86 -0
  133. package/dist/server/media-bun/streaming.js +237 -0
  134. package/dist/server/media-bun/video.d.ts +35 -0
  135. package/dist/server/media-bun/video.js +181 -0
  136. package/dist/server/metrics/metricsBuffer.d.ts +39 -0
  137. package/dist/server/metrics/metricsBuffer.js +36 -0
  138. package/dist/server/metrics/normalizeRoute.d.ts +5 -0
  139. package/dist/server/metrics/normalizeRoute.js +58 -0
  140. package/dist/server/middleware/correlationId.d.ts +9 -0
  141. package/dist/server/middleware/correlationId.js +20 -0
  142. package/dist/server/middleware/corsAllowlist.d.ts +38 -0
  143. package/dist/server/middleware/corsAllowlist.js +37 -0
  144. package/dist/server/middleware/errorEnvelope.d.ts +17 -0
  145. package/dist/server/middleware/errorEnvelope.js +32 -0
  146. package/dist/server/middleware/index.d.ts +9 -0
  147. package/dist/server/middleware/index.js +10 -0
  148. package/dist/server/middleware/localOrigin.d.ts +71 -0
  149. package/dist/server/middleware/localOrigin.js +119 -0
  150. package/dist/server/middleware/rateLimit.d.ts +56 -0
  151. package/dist/server/middleware/rateLimit.js +86 -0
  152. package/dist/server/middleware/requestLogger.d.ts +29 -0
  153. package/dist/server/middleware/requestLogger.js +47 -0
  154. package/dist/server/middleware/securityHeaders.d.ts +29 -0
  155. package/dist/server/middleware/securityHeaders.js +44 -0
  156. package/dist/server/middleware/zValidator.d.ts +29 -0
  157. package/dist/server/middleware/zValidator.js +57 -0
  158. package/dist/server/migration/runner.d.ts +42 -0
  159. package/dist/server/migration/runner.js +70 -0
  160. package/dist/server/migration/types.d.ts +16 -0
  161. package/dist/server/migration/types.js +1 -0
  162. package/dist/server/net/outboundGuard.d.ts +30 -0
  163. package/dist/server/net/outboundGuard.js +199 -0
  164. package/dist/server/notifications/hub.d.ts +67 -0
  165. package/dist/server/notifications/hub.js +187 -0
  166. package/dist/server/notifications/index.d.ts +47 -0
  167. package/dist/server/notifications/index.js +46 -0
  168. package/dist/server/notifications/migrations.d.ts +13 -0
  169. package/dist/server/notifications/migrations.js +20 -0
  170. package/dist/server/notifications/routes.d.ts +58 -0
  171. package/dist/server/notifications/routes.js +113 -0
  172. package/dist/server/notifications/service.d.ts +58 -0
  173. package/dist/server/notifications/service.js +41 -0
  174. package/dist/server/paths/appDataHome.d.ts +19 -0
  175. package/dist/server/paths/appDataHome.js +68 -0
  176. package/dist/server/private-media/crypto.d.ts +37 -0
  177. package/dist/server/private-media/crypto.js +111 -0
  178. package/dist/server/private-media/index-store.d.ts +31 -0
  179. package/dist/server/private-media/index-store.js +36 -0
  180. package/dist/server/private-media/mountPrivateRoutes.d.ts +26 -0
  181. package/dist/server/private-media/mountPrivateRoutes.js +219 -0
  182. package/dist/server/private-media/pipeline.d.ts +37 -0
  183. package/dist/server/private-media/pipeline.js +97 -0
  184. package/dist/server/private-media/unlock.d.ts +52 -0
  185. package/dist/server/private-media/unlock.js +55 -0
  186. package/dist/server/resume/index.d.ts +2 -0
  187. package/dist/server/resume/index.js +2 -0
  188. package/dist/server/resume/routes.d.ts +41 -0
  189. package/dist/server/resume/routes.js +50 -0
  190. package/dist/server/resume/store.d.ts +37 -0
  191. package/dist/server/resume/store.js +38 -0
  192. package/dist/server/retention/archive.d.ts +78 -0
  193. package/dist/server/retention/archive.js +284 -0
  194. package/dist/server/retention/fleet.d.ts +50 -0
  195. package/dist/server/retention/fleet.js +66 -0
  196. package/dist/server/retention/index.d.ts +38 -0
  197. package/dist/server/retention/index.js +38 -0
  198. package/dist/server/retention/policy.d.ts +263 -0
  199. package/dist/server/retention/policy.js +264 -0
  200. package/dist/server/retention/store.d.ts +101 -0
  201. package/dist/server/retention/store.js +251 -0
  202. package/dist/server/retention/sweeper.d.ts +69 -0
  203. package/dist/server/retention/sweeper.js +289 -0
  204. package/dist/server/retention/target.d.ts +143 -0
  205. package/dist/server/retention/target.js +438 -0
  206. package/dist/server/search/index.d.ts +2 -0
  207. package/dist/server/search/index.js +6 -0
  208. package/dist/server/search/pgSearchProvider.d.ts +91 -0
  209. package/dist/server/search/pgSearchProvider.js +330 -0
  210. package/dist/server/search/pgSearchProvider.pg.test.d.ts +1 -0
  211. package/dist/server/search/pgSearchProvider.pg.test.js +338 -0
  212. package/dist/server/search/pgSearchSchema.d.ts +81 -0
  213. package/dist/server/search/pgSearchSchema.js +89 -0
  214. package/dist/server/sharing/index.d.ts +18 -0
  215. package/dist/server/sharing/index.js +16 -0
  216. package/dist/server/sharing/migrations.d.ts +6 -0
  217. package/dist/server/sharing/migrations.js +79 -0
  218. package/dist/server/sharing/router.d.ts +111 -0
  219. package/dist/server/sharing/router.js +93 -0
  220. package/dist/server/sharing/sql.d.ts +67 -0
  221. package/dist/server/sharing/sql.js +88 -0
  222. package/dist/server/sharing/store.d.ts +173 -0
  223. package/dist/server/sharing/store.js +484 -0
  224. package/dist/server/sqlite/backup.d.ts +48 -0
  225. package/dist/server/sqlite/backup.js +97 -0
  226. package/dist/server/sqlite/browse.d.ts +42 -0
  227. package/dist/server/sqlite/browse.js +84 -0
  228. package/dist/server/sqlite/export.d.ts +32 -0
  229. package/dist/server/sqlite/export.js +42 -0
  230. package/dist/server/sqlite/fts5.d.ts +106 -0
  231. package/dist/server/sqlite/fts5.js +154 -0
  232. package/dist/server/sqlite/health.d.ts +44 -0
  233. package/dist/server/sqlite/health.js +52 -0
  234. package/dist/server/sqlite/import.d.ts +36 -0
  235. package/dist/server/sqlite/import.js +108 -0
  236. package/dist/server/sqlite/index.d.ts +9 -0
  237. package/dist/server/sqlite/index.js +15 -0
  238. package/dist/server/sqlite/readonlySql.d.ts +15 -0
  239. package/dist/server/sqlite/readonlySql.js +65 -0
  240. package/dist/server/sqlite/stats.d.ts +22 -0
  241. package/dist/server/sqlite/stats.js +32 -0
  242. package/dist/server/sqlite/viewer.d.ts +17 -0
  243. package/dist/server/sqlite/viewer.js +56 -0
  244. package/dist/server/storage/binaryServerProcessing.d.ts +23 -0
  245. package/dist/server/storage/binaryServerProcessing.js +81 -0
  246. package/dist/server/storage/binaryServerStore.d.ts +3 -0
  247. package/dist/server/storage/binaryServerStore.js +95 -0
  248. package/dist/server/storage/diskStore.d.ts +2 -0
  249. package/dist/server/storage/diskStore.js +88 -0
  250. package/dist/server/storage/files/adapter.d.ts +33 -0
  251. package/dist/server/storage/files/adapter.js +203 -0
  252. package/dist/server/storage/files/catalogue.d.ts +72 -0
  253. package/dist/server/storage/files/catalogue.js +1033 -0
  254. package/dist/server/storage/files/chunkedUpload.d.ts +56 -0
  255. package/dist/server/storage/files/chunkedUpload.js +70 -0
  256. package/dist/server/storage/files/fileRoutes.d.ts +51 -0
  257. package/dist/server/storage/files/fileRoutes.js +405 -0
  258. package/dist/server/storage/files/index.d.ts +9 -0
  259. package/dist/server/storage/files/index.js +11 -0
  260. package/dist/server/storage/files/migrations.d.ts +2 -0
  261. package/dist/server/storage/files/migrations.js +163 -0
  262. package/dist/server/storage/files/mimeByExt.d.ts +1 -0
  263. package/dist/server/storage/files/mimeByExt.js +13 -0
  264. package/dist/server/storage/files/stack.d.ts +136 -0
  265. package/dist/server/storage/files/stack.js +132 -0
  266. package/dist/server/storage/files/storageOps.d.ts +54 -0
  267. package/dist/server/storage/files/storageOps.js +110 -0
  268. package/dist/server/storage/files/types.d.ts +261 -0
  269. package/dist/server/storage/files/types.js +22 -0
  270. package/dist/server/storage/imageThumbnail.d.ts +3 -0
  271. package/dist/server/storage/imageThumbnail.js +50 -0
  272. package/dist/server/storage/index.d.ts +15 -0
  273. package/dist/server/storage/index.js +27 -0
  274. package/dist/server/storage/mountMediaRoutes.d.ts +28 -0
  275. package/dist/server/storage/mountMediaRoutes.js +141 -0
  276. package/dist/server/storage/objectStoreMediaStore.d.ts +4 -0
  277. package/dist/server/storage/objectStoreMediaStore.js +57 -0
  278. package/dist/server/storage/s3ObjectStore.d.ts +63 -0
  279. package/dist/server/storage/s3ObjectStore.js +187 -0
  280. package/dist/server/storage/sources.d.ts +47 -0
  281. package/dist/server/storage/sources.js +148 -0
  282. package/dist/server/storage/streamRange.d.ts +19 -0
  283. package/dist/server/storage/streamRange.js +69 -0
  284. package/dist/server/storage/streamToken.d.ts +16 -0
  285. package/dist/server/storage/streamToken.js +54 -0
  286. package/dist/server/storage/transcodeJobs.d.ts +65 -0
  287. package/dist/server/storage/transcodeJobs.js +104 -0
  288. package/dist/server/storage/types.d.ts +193 -0
  289. package/dist/server/storage/types.js +41 -0
  290. package/dist/server/storage/videoDerivativeKeys.d.ts +127 -0
  291. package/dist/server/storage/videoDerivativeKeys.js +149 -0
  292. package/dist/server/storage/videoProcessor.d.ts +30 -0
  293. package/dist/server/storage/videoProcessor.js +148 -0
  294. package/dist/server/sync/alarm.d.ts +34 -0
  295. package/dist/server/sync/alarm.js +91 -0
  296. package/dist/server/sync/commands.d.ts +87 -0
  297. package/dist/server/sync/commands.js +241 -0
  298. package/dist/server/sync/engine.d.ts +62 -0
  299. package/dist/server/sync/engine.js +184 -0
  300. package/dist/server/sync/http.d.ts +106 -0
  301. package/dist/server/sync/http.js +243 -0
  302. package/dist/server/sync/index.d.ts +41 -0
  303. package/dist/server/sync/index.js +41 -0
  304. package/dist/server/sync/opLog.d.ts +61 -0
  305. package/dist/server/sync/opLog.js +96 -0
  306. package/dist/server/sync/planner.d.ts +31 -0
  307. package/dist/server/sync/planner.js +30 -0
  308. package/dist/server/sync/status.d.ts +63 -0
  309. package/dist/server/sync/status.js +49 -0
  310. package/dist/server/sync/timer.d.ts +52 -0
  311. package/dist/server/sync/timer.js +170 -0
  312. package/dist/server/sync/tokens.d.ts +25 -0
  313. package/dist/server/sync/tokens.js +51 -0
  314. package/dist/server/sync/types.d.ts +101 -0
  315. package/dist/server/sync/types.js +29 -0
  316. package/dist/server/ws/eventBus.d.ts +14 -0
  317. package/dist/server/ws/eventBus.js +42 -0
  318. package/dist/server/ws/hub.d.ts +20 -0
  319. package/dist/server/ws/hub.js +154 -0
  320. package/dist/server/ws/index.d.ts +5 -0
  321. package/dist/server/ws/index.js +7 -0
  322. package/dist/server/ws/presence.d.ts +11 -0
  323. package/dist/server/ws/presence.js +20 -0
  324. package/dist/server/ws/room.d.ts +17 -0
  325. package/dist/server/ws/room.js +23 -0
  326. package/dist/server/ws/types.d.ts +122 -0
  327. package/dist/server/ws/types.js +1 -0
  328. package/package.json +234 -0
  329. package/src/declaredDepsAreImported.spec.ts +87 -0
  330. package/src/leafSubpathsImportNothing.spec.ts +358 -0
  331. package/src/lockfileIsTracked.spec.ts +50 -0
  332. package/src/namedSubpathsResolve.spec.ts +122 -0
  333. package/src/noPathDeps.spec.ts +24 -0
  334. package/src/publishShape.spec.ts +89 -0
  335. package/src/server/activity/index.ts +59 -0
  336. package/src/server/activity/migrations.ts +67 -0
  337. package/src/server/activity/shareSource.ts +57 -0
  338. package/src/server/activity/store.spec.ts +255 -0
  339. package/src/server/activity/store.ts +368 -0
  340. package/src/server/app.spec.ts +243 -0
  341. package/src/server/app.ts +89 -0
  342. package/src/server/audit/index.ts +2 -0
  343. package/src/server/audit/migrations.ts +88 -0
  344. package/src/server/audit/sqliteSink.test.ts +200 -0
  345. package/src/server/audit/sqliteSink.ts +162 -0
  346. package/src/server/auth/cookie.spec.ts +46 -0
  347. package/src/server/auth/cookie.ts +42 -0
  348. package/src/server/auth/csrf.spec.ts +81 -0
  349. package/src/server/auth/csrf.ts +83 -0
  350. package/src/server/auth/fileTokenSigner.ts +40 -0
  351. package/src/server/auth/getOnly.spec.ts +53 -0
  352. package/src/server/auth/getOnly.ts +32 -0
  353. package/src/server/auth/index.ts +51 -0
  354. package/src/server/auth/jwt.spec.ts +106 -0
  355. package/src/server/auth/jwt.ts +75 -0
  356. package/src/server/auth/loginThrottle.spec.ts +213 -0
  357. package/src/server/auth/loginThrottle.ts +261 -0
  358. package/src/server/auth/middleware.spec.ts +148 -0
  359. package/src/server/auth/middleware.ts +56 -0
  360. package/src/server/auth/password.spec.ts +48 -0
  361. package/src/server/auth/password.ts +71 -0
  362. package/src/server/auth/sessionStore.spec.ts +189 -0
  363. package/src/server/auth/sessionStore.ts +261 -0
  364. package/src/server/auth/totp.spec.ts +73 -0
  365. package/src/server/auth/totp.ts +92 -0
  366. package/src/server/auth/types.ts +146 -0
  367. package/src/server/auth/vault.spec.ts +51 -0
  368. package/src/server/auth/vault.ts +71 -0
  369. package/src/server/context.ts +37 -0
  370. package/src/server/ctgr.spec.ts +138 -0
  371. package/src/server/ctgr.ts +108 -0
  372. package/src/server/db/kysely.spec.ts +62 -0
  373. package/src/server/db/kysely.ts +21 -0
  374. package/src/server/db/plumbingTypes.ts +126 -0
  375. package/src/server/dropzone/duplicates.ts +121 -0
  376. package/src/server/dropzone/index.ts +30 -0
  377. package/src/server/dropzone/scan.spec.ts +255 -0
  378. package/src/server/dropzone/scan.ts +342 -0
  379. package/src/server/errors.spec.ts +76 -0
  380. package/src/server/errors.ts +22 -0
  381. package/src/server/guard/argon2Binding.spec.ts +117 -0
  382. package/src/server/guard/argon2Binding.ts +195 -0
  383. package/src/server/guard/argon2Kat.spec.ts +250 -0
  384. package/src/server/guard/guard.spec.ts +390 -0
  385. package/src/server/guard/index.ts +6 -0
  386. package/src/server/guard/kdfWorker.spec.ts +283 -0
  387. package/src/server/guard/kdfWorker.ts +367 -0
  388. package/src/server/guard/kdfWorkerEntry.ts +16 -0
  389. package/src/server/guard/passwordVerifier.spec.ts +71 -0
  390. package/src/server/guard/passwordVerifier.ts +175 -0
  391. package/src/server/guard/revocationStore.spec.ts +135 -0
  392. package/src/server/guard/revocationStore.ts +291 -0
  393. package/src/server/guard/sessionSigner.ts +425 -0
  394. package/src/server/guard/verifier.ts +27 -0
  395. package/src/server/incidents/channels.ts +487 -0
  396. package/src/server/incidents/checks.ts +120 -0
  397. package/src/server/incidents/incidents.spec.ts +463 -0
  398. package/src/server/incidents/index.ts +96 -0
  399. package/src/server/incidents/migrations.ts +66 -0
  400. package/src/server/incidents/notifier.ts +215 -0
  401. package/src/server/incidents/reporter.ts +110 -0
  402. package/src/server/incidents/routes.ts +314 -0
  403. package/src/server/incidents/store.ts +329 -0
  404. package/src/server/incidents/types.ts +116 -0
  405. package/src/server/index.ts +248 -0
  406. package/src/server/jobs/fireQueue.spec.ts +85 -0
  407. package/src/server/jobs/fireQueue.ts +37 -0
  408. package/src/server/jobs/index.ts +26 -0
  409. package/src/server/jobs/logger.ts +16 -0
  410. package/src/server/jobs/plainjobAdapter.spec.ts +34 -0
  411. package/src/server/jobs/plainjobAdapter.ts +54 -0
  412. package/src/server/jobs/polling.spec.ts +219 -0
  413. package/src/server/jobs/polling.ts +81 -0
  414. package/src/server/jobs/runner.spec.ts +213 -0
  415. package/src/server/jobs/runner.ts +213 -0
  416. package/src/server/jobs/timeout.spec.ts +35 -0
  417. package/src/server/jobs/timeout.ts +53 -0
  418. package/src/server/jobs/types.ts +188 -0
  419. package/src/server/master-lock/guard.spec.ts +347 -0
  420. package/src/server/master-lock/guard.ts +290 -0
  421. package/src/server/master-lock/index.ts +44 -0
  422. package/src/server/master-lock/lockPage.spec.ts +88 -0
  423. package/src/server/master-lock/lockPage.ts +313 -0
  424. package/src/server/master-lock/masterLock.spec.ts +338 -0
  425. package/src/server/master-lock/masterLock.ts +627 -0
  426. package/src/server/master-lock/principals.spec.ts +378 -0
  427. package/src/server/master-lock/principals.ts +175 -0
  428. package/src/server/master-lock/seed.spec.ts +69 -0
  429. package/src/server/master-lock/seed.ts +112 -0
  430. package/src/server/master-lock/store.ts +35 -0
  431. package/src/server/media-bun/image.spec.ts +115 -0
  432. package/src/server/media-bun/image.ts +134 -0
  433. package/src/server/media-bun/index.ts +52 -0
  434. package/src/server/media-bun/streaming.spec.ts +134 -0
  435. package/src/server/media-bun/streaming.ts +347 -0
  436. package/src/server/media-bun/video.spec.ts +115 -0
  437. package/src/server/media-bun/video.ts +232 -0
  438. package/src/server/metrics/metricsBuffer.ts +92 -0
  439. package/src/server/metrics/normalizeRoute.spec.ts +51 -0
  440. package/src/server/metrics/normalizeRoute.ts +63 -0
  441. package/src/server/middleware/correlationId.ts +24 -0
  442. package/src/server/middleware/corsAllowlist.spec.ts +118 -0
  443. package/src/server/middleware/corsAllowlist.ts +69 -0
  444. package/src/server/middleware/errorEnvelope.ts +38 -0
  445. package/src/server/middleware/index.ts +19 -0
  446. package/src/server/middleware/localOrigin.spec.ts +168 -0
  447. package/src/server/middleware/localOrigin.ts +139 -0
  448. package/src/server/middleware/rateLimit.spec.ts +72 -0
  449. package/src/server/middleware/rateLimit.ts +125 -0
  450. package/src/server/middleware/requestLogger.ts +95 -0
  451. package/src/server/middleware/securityHeaders.ts +78 -0
  452. package/src/server/middleware/zValidator.spec.ts +251 -0
  453. package/src/server/middleware/zValidator.ts +114 -0
  454. package/src/server/migration/runner.spec.ts +88 -0
  455. package/src/server/migration/runner.ts +87 -0
  456. package/src/server/migration/types.ts +17 -0
  457. package/src/server/net/outboundGuard.spec.ts +90 -0
  458. package/src/server/net/outboundGuard.ts +205 -0
  459. package/src/server/notifications/hub.ts +245 -0
  460. package/src/server/notifications/index.ts +77 -0
  461. package/src/server/notifications/migrations.ts +39 -0
  462. package/src/server/notifications/routes.ts +177 -0
  463. package/src/server/notifications/service.spec.ts +305 -0
  464. package/src/server/notifications/service.ts +112 -0
  465. package/src/server/paths/appDataHome.spec.ts +87 -0
  466. package/src/server/paths/appDataHome.ts +84 -0
  467. package/src/server/private-media/blobRoute.spec.ts +165 -0
  468. package/src/server/private-media/crypto.spec.ts +81 -0
  469. package/src/server/private-media/crypto.ts +155 -0
  470. package/src/server/private-media/gatePolicy.spec.ts +98 -0
  471. package/src/server/private-media/index-store.ts +83 -0
  472. package/src/server/private-media/mountPrivateRoutes.ts +265 -0
  473. package/src/server/private-media/pipeline.spec.ts +111 -0
  474. package/src/server/private-media/pipeline.ts +170 -0
  475. package/src/server/private-media/routes.spec.ts +265 -0
  476. package/src/server/private-media/unlock.ts +112 -0
  477. package/src/server/resume/index.ts +8 -0
  478. package/src/server/resume/resume.spec.ts +118 -0
  479. package/src/server/resume/routes.ts +92 -0
  480. package/src/server/resume/store.ts +82 -0
  481. package/src/server/retention/archive.spec.ts +181 -0
  482. package/src/server/retention/archive.ts +353 -0
  483. package/src/server/retention/fleet.ts +104 -0
  484. package/src/server/retention/index.ts +101 -0
  485. package/src/server/retention/policy.spec.ts +132 -0
  486. package/src/server/retention/policy.ts +392 -0
  487. package/src/server/retention/store.spec.ts +236 -0
  488. package/src/server/retention/store.ts +338 -0
  489. package/src/server/retention/sweeper.spec.ts +321 -0
  490. package/src/server/retention/sweeper.ts +367 -0
  491. package/src/server/retention/target.spec.ts +592 -0
  492. package/src/server/retention/target.ts +613 -0
  493. package/src/server/search/index.ts +12 -0
  494. package/src/server/search/pgSearchProvider.pg.test.ts +392 -0
  495. package/src/server/search/pgSearchProvider.ts +421 -0
  496. package/src/server/search/pgSearchSchema.ts +147 -0
  497. package/src/server/sharing/index.ts +65 -0
  498. package/src/server/sharing/migrations.ts +128 -0
  499. package/src/server/sharing/router.spec.ts +167 -0
  500. package/src/server/sharing/router.ts +194 -0
  501. package/src/server/sharing/sql.ts +109 -0
  502. package/src/server/sharing/store.spec.ts +408 -0
  503. package/src/server/sharing/store.ts +737 -0
  504. package/src/server/sqlite/backup.spec.ts +114 -0
  505. package/src/server/sqlite/backup.ts +154 -0
  506. package/src/server/sqlite/browse.spec.ts +70 -0
  507. package/src/server/sqlite/browse.ts +120 -0
  508. package/src/server/sqlite/export.spec.ts +43 -0
  509. package/src/server/sqlite/export.ts +74 -0
  510. package/src/server/sqlite/fts5.spec.ts +175 -0
  511. package/src/server/sqlite/fts5.ts +255 -0
  512. package/src/server/sqlite/health.spec.ts +79 -0
  513. package/src/server/sqlite/health.ts +105 -0
  514. package/src/server/sqlite/import.spec.ts +75 -0
  515. package/src/server/sqlite/import.ts +141 -0
  516. package/src/server/sqlite/index.ts +64 -0
  517. package/src/server/sqlite/readonlySql.spec.ts +58 -0
  518. package/src/server/sqlite/readonlySql.ts +67 -0
  519. package/src/server/sqlite/stats.spec.ts +21 -0
  520. package/src/server/sqlite/stats.ts +61 -0
  521. package/src/server/sqlite/viewer.spec.ts +127 -0
  522. package/src/server/sqlite/viewer.ts +72 -0
  523. package/src/server/storage/binaryServerProcessing.integration.spec.ts +222 -0
  524. package/src/server/storage/binaryServerProcessing.spec.ts +150 -0
  525. package/src/server/storage/binaryServerProcessing.ts +127 -0
  526. package/src/server/storage/binaryServerStore.ts +102 -0
  527. package/src/server/storage/chunked.integration.spec.ts +185 -0
  528. package/src/server/storage/diskStore.ts +83 -0
  529. package/src/server/storage/files/adapter.photo.display.spec.ts +169 -0
  530. package/src/server/storage/files/adapter.photo.spec.ts +128 -0
  531. package/src/server/storage/files/adapter.spec.ts +121 -0
  532. package/src/server/storage/files/adapter.ts +255 -0
  533. package/src/server/storage/files/catalogue.media-host.spec.ts +184 -0
  534. package/src/server/storage/files/catalogue.orderings.spec.ts +157 -0
  535. package/src/server/storage/files/catalogue.private.spec.ts +106 -0
  536. package/src/server/storage/files/catalogue.recover.spec.ts +131 -0
  537. package/src/server/storage/files/catalogue.sort.spec.ts +154 -0
  538. package/src/server/storage/files/catalogue.spec.ts +133 -0
  539. package/src/server/storage/files/catalogue.trash-import.spec.ts +125 -0
  540. package/src/server/storage/files/catalogue.ts +1326 -0
  541. package/src/server/storage/files/chunkedUpload.spec.ts +111 -0
  542. package/src/server/storage/files/chunkedUpload.ts +146 -0
  543. package/src/server/storage/files/collections.spec.ts +294 -0
  544. package/src/server/storage/files/fileRoutes.replace.spec.ts +88 -0
  545. package/src/server/storage/files/fileRoutes.spec.ts +327 -0
  546. package/src/server/storage/files/fileRoutes.ts +462 -0
  547. package/src/server/storage/files/index.ts +66 -0
  548. package/src/server/storage/files/migrations.ts +187 -0
  549. package/src/server/storage/files/mimeByExt.ts +14 -0
  550. package/src/server/storage/files/stack.spec.ts +258 -0
  551. package/src/server/storage/files/stack.ts +284 -0
  552. package/src/server/storage/files/storageOps.spec.ts +126 -0
  553. package/src/server/storage/files/storageOps.ts +213 -0
  554. package/src/server/storage/files/types.ts +285 -0
  555. package/src/server/storage/imageThumbnail.spec.ts +81 -0
  556. package/src/server/storage/imageThumbnail.ts +60 -0
  557. package/src/server/storage/index.ts +122 -0
  558. package/src/server/storage/mountMediaRoutes.ts +231 -0
  559. package/src/server/storage/objectStore.spec.ts +272 -0
  560. package/src/server/storage/objectStoreMediaStore.ts +63 -0
  561. package/src/server/storage/s3ObjectStore.ts +239 -0
  562. package/src/server/storage/sources.ts +245 -0
  563. package/src/server/storage/storage.spec.ts +219 -0
  564. package/src/server/storage/streamRange.ts +92 -0
  565. package/src/server/storage/streamToken.ts +76 -0
  566. package/src/server/storage/transcode.integration.spec.ts +134 -0
  567. package/src/server/storage/transcodeJobs.spec.ts +152 -0
  568. package/src/server/storage/transcodeJobs.ts +171 -0
  569. package/src/server/storage/types.ts +230 -0
  570. package/src/server/storage/videoDerivativeKeys.spec.ts +112 -0
  571. package/src/server/storage/videoDerivativeKeys.ts +172 -0
  572. package/src/server/storage/videoProcessor.posterOnly.spec.ts +106 -0
  573. package/src/server/storage/videoProcessor.ts +229 -0
  574. package/src/server/sync/alarm.spec.ts +149 -0
  575. package/src/server/sync/alarm.ts +137 -0
  576. package/src/server/sync/commands.spec.ts +145 -0
  577. package/src/server/sync/commands.ts +361 -0
  578. package/src/server/sync/engine.spec.ts +496 -0
  579. package/src/server/sync/engine.ts +255 -0
  580. package/src/server/sync/http.ts +316 -0
  581. package/src/server/sync/httpRemote.spec.ts +158 -0
  582. package/src/server/sync/index.ts +90 -0
  583. package/src/server/sync/opLog.spec.ts +110 -0
  584. package/src/server/sync/opLog.ts +189 -0
  585. package/src/server/sync/planner.spec.ts +53 -0
  586. package/src/server/sync/planner.ts +62 -0
  587. package/src/server/sync/status.spec.ts +92 -0
  588. package/src/server/sync/status.ts +108 -0
  589. package/src/server/sync/timer.spec.ts +150 -0
  590. package/src/server/sync/timer.ts +207 -0
  591. package/src/server/sync/tokens.spec.ts +38 -0
  592. package/src/server/sync/tokens.ts +94 -0
  593. package/src/server/sync/types.ts +108 -0
  594. package/src/server/ws/eventBus.spec.ts +67 -0
  595. package/src/server/ws/eventBus.ts +46 -0
  596. package/src/server/ws/hub.integration.spec.ts +93 -0
  597. package/src/server/ws/hub.spec.ts +282 -0
  598. package/src/server/ws/hub.ts +168 -0
  599. package/src/server/ws/index.ts +20 -0
  600. package/src/server/ws/presence.spec.ts +27 -0
  601. package/src/server/ws/presence.ts +26 -0
  602. package/src/server/ws/room.spec.ts +36 -0
  603. package/src/server/ws/room.ts +29 -0
  604. package/src/server/ws/types.ts +134 -0
  605. package/src/skippedTestsAreAnswered.spec.ts +215 -0
  606. package/src/testTmpRoot.spec.ts +102 -0
@@ -0,0 +1,211 @@
1
+ /**
2
+ * The one revocation store, in two shapes for two hot paths (DEDUP-7).
3
+ *
4
+ * "Was this session/token withdrawn early?" is a single concern, but it is
5
+ * consulted from two verify paths whose constraints do not meet in the middle,
6
+ * so this file settles ONE contract per constraint and gives the async one a
7
+ * bridge back to the sync one:
8
+ *
9
+ * • {@link RevocationStore} — SYNCHRONOUS (`isRevoked`/`revoke`/`sweep`, epoch
10
+ * **milliseconds**). The single-owner satellites' `createOwnerAuth` consults
11
+ * it inside `isAuthenticated`, which is called on every request and cannot
12
+ * become a promise without turning every route async. Its durable
13
+ * implementation is a local sqlite file — the denylist a redeploy must not
14
+ * forget (`apps/roms`'s deploy smoke caught a logout that did not log out).
15
+ *
16
+ * • {@link AsyncRevocationStore} — ASYNCHRONOUS (`set`/`get`/`prune`, epoch
17
+ * **seconds**). The cross-node seam (03 §4a, audit G6): a Redis hash or a
18
+ * Postgres row that every node in a saas deployment reads, so a `lock()` on
19
+ * node A closes the token on node B too. `createSessionSigner` consumes THIS
20
+ * one and keeps its own read-through cache so its `verify` stays sync.
21
+ *
22
+ * Before DEDUP-7 these lived in two packages, BOTH exporting a
23
+ * `createMemoryRevocationStore` of a different shape — a live footgun the moment
24
+ * a file imported from the wrong one. They are one file now; `satellite-kit`
25
+ * re-exports the sync half so its callers are unchanged.
26
+ *
27
+ * The bridge between them is {@link createAsyncBackedRevocationStore}: a SYNC
28
+ * store whose durable backing is an async cross-node store, using the same
29
+ * read-through-cache trick the signer does. It is what lets a satellite opt into
30
+ * the cross-node seam without its `isAuthenticated` becoming a promise.
31
+ */
32
+ import { mkdirSync } from 'node:fs';
33
+ import { dirname } from 'node:path';
34
+ import { Database } from 'bun:sqlite';
35
+ import { assertCcPragmas } from 'cwip/sqlite';
36
+ /**
37
+ * An in-memory sync {@link RevocationStore}.
38
+ *
39
+ * For tests, and for a single-process caller that does not need durability.
40
+ * NOT for a deployed satellite: an in-memory table would be worse than none —
41
+ * sign out, redeploy, and the session works again, which reads as the feature
42
+ * being broken rather than absent. Give a deployed door
43
+ * {@link createSqliteRevocationStore} (durable) or nothing.
44
+ */
45
+ export function createMemoryRevocationStore() {
46
+ const entries = new Map();
47
+ return {
48
+ isRevoked: (id) => entries.has(id),
49
+ revoke: (id, expiresAtMs) => void entries.set(id, expiresAtMs),
50
+ sweep: (nowMs) => {
51
+ for (const [id, expiresAt] of entries)
52
+ if (expiresAt <= nowMs)
53
+ entries.delete(id);
54
+ },
55
+ size: () => entries.size,
56
+ };
57
+ }
58
+ /**
59
+ * A durable sync {@link RevocationStore} backed by a sqlite file.
60
+ *
61
+ * Because an in-memory table would be worse than none (see above), and the
62
+ * deploy engine persists each release's data directory, so durability costs one
63
+ * file. The table is self-limiting: an entry is meaningless once the session it
64
+ * names would have expired anyway, and `sweep` drops those on logout — rare,
65
+ * cheap, and exactly when the table has just grown, so there is no background
66
+ * timer to own.
67
+ *
68
+ * Its OWN file, not a table in an app's database: keeping a credential surface
69
+ * out of an app's data file bounds the blast radius of a corrupt or deleted
70
+ * file to "everyone signs in again".
71
+ */
72
+ export function createSqliteRevocationStore(dbPath) {
73
+ if (dbPath !== ':memory:' && !dbPath.startsWith('file:')) {
74
+ mkdirSync(dirname(dbPath), { recursive: true });
75
+ }
76
+ const db = new Database(dbPath, { create: true });
77
+ // Route pragmas through cwip's one controller (applies WAL + busy_timeout +
78
+ // foreign_keys + wal_autocheckpoint and re-reads to prove each took), rather
79
+ // than hand-rolling a lone `journal_mode = WAL` the pragma sweep rejects.
80
+ assertCcPragmas(db);
81
+ db.exec(`
82
+ CREATE TABLE IF NOT EXISTS revoked_sessions (
83
+ id TEXT PRIMARY KEY,
84
+ expires_at INTEGER NOT NULL
85
+ )
86
+ `);
87
+ const has = db.query('SELECT id FROM revoked_sessions WHERE id = ?');
88
+ const insert = db.query('INSERT OR REPLACE INTO revoked_sessions (id, expires_at) VALUES (?, ?)');
89
+ const prune = db.query('DELETE FROM revoked_sessions WHERE expires_at <= ?');
90
+ return {
91
+ isRevoked: (id) => has.get(id) !== null,
92
+ revoke: (id, expiresAtMs) => void insert.run(id, expiresAtMs),
93
+ sweep: (nowMs) => void prune.run(nowMs),
94
+ close: () => db.close(),
95
+ };
96
+ }
97
+ /**
98
+ * An in-memory {@link AsyncRevocationStore}.
99
+ *
100
+ * Two uses: a single-process saas test that wants the seam exercised without a
101
+ * real Redis, and — crucially — the **two-signer replay probe**, where one
102
+ * shared instance is handed to signer A and signer B so "revoke on A, verify on
103
+ * B" becomes provable in one process (V7.3). It is NOT a cross-process store:
104
+ * two OS processes do not share this Map, which is the whole reason a real
105
+ * deployment injects Redis/Postgres instead.
106
+ *
107
+ * (Named `…Async…` to stand apart from the sync {@link createMemoryRevocationStore};
108
+ * before DEDUP-7 both shapes shared the bare name across two packages and that
109
+ * collision was the footgun this file exists to remove.)
110
+ */
111
+ export function createMemoryAsyncRevocationStore() {
112
+ const revoked = new Map();
113
+ return {
114
+ async set(jti, expEpochSeconds) {
115
+ const existing = revoked.get(jti);
116
+ // Keep the later exp: two nodes revoking the same jti with slightly
117
+ // different clocks must not shorten the window.
118
+ if (existing === undefined || expEpochSeconds > existing)
119
+ revoked.set(jti, expEpochSeconds);
120
+ },
121
+ async get(jti) {
122
+ return revoked.get(jti) ?? null;
123
+ },
124
+ async prune(nowEpochSeconds) {
125
+ let removed = 0;
126
+ for (const [jti, exp] of revoked) {
127
+ if (exp <= nowEpochSeconds) {
128
+ revoked.delete(jti);
129
+ removed += 1;
130
+ }
131
+ }
132
+ return removed;
133
+ },
134
+ size() {
135
+ return revoked.size;
136
+ },
137
+ };
138
+ }
139
+ /**
140
+ * A SYNC {@link RevocationStore} whose durable backing is an async cross-node
141
+ * {@link AsyncRevocationStore} — the bridge that gives a satellite the cross-node
142
+ * seam without its `isAuthenticated` becoming a promise.
143
+ *
144
+ * It is the same read-through-cache trick {@link createSessionSigner} keeps
145
+ * internally, packaged so a plain sync consumer can use it: `revoke` writes
146
+ * through fire-and-forget and caches locally so the id is refused HERE the
147
+ * instant it returns; `isRevoked` answers from the cache and schedules a refresh
148
+ * when the entry is missing or older than `cacheTtlMs`, never blocking. Ids and
149
+ * expiries cross the ms↔seconds boundary here (the sync contract is ms, the
150
+ * async one seconds).
151
+ *
152
+ * {@link flush} settles the fire-and-forget work — for shutdown/handover (a node
153
+ * draining must not lose an unwritten revocation) and for tests that need the
154
+ * cross-node state to have settled.
155
+ */
156
+ export function createAsyncBackedRevocationStore(backing, options = {}) {
157
+ const now = options.now ?? (() => Date.now());
158
+ const cacheTtlMs = options.cacheTtlMs ?? 5000;
159
+ const onError = options.onError ?? (() => undefined);
160
+ /** id → the last backing verdict (exp in ms, or null) and when it was read. */
161
+ const cache = new Map();
162
+ const pending = new Set();
163
+ /** Reads in flight per id, so a burst of `isRevoked` coalesces into one read. */
164
+ const inflightReads = new Map();
165
+ const track = (work) => {
166
+ pending.add(work);
167
+ void work.finally(() => pending.delete(work));
168
+ };
169
+ const refresh = (id) => {
170
+ if (inflightReads.has(id))
171
+ return;
172
+ const read = backing
173
+ .get(id)
174
+ .then((expSec) => {
175
+ cache.set(id, { expMs: expSec === null ? null : expSec * 1000, readAtMs: now() });
176
+ })
177
+ .catch((error) => onError('get', error))
178
+ .finally(() => inflightReads.delete(id));
179
+ inflightReads.set(id, read);
180
+ track(read);
181
+ };
182
+ return {
183
+ isRevoked(id) {
184
+ const cached = cache.get(id);
185
+ if (cached === undefined || now() - cached.readAtMs >= cacheTtlMs)
186
+ refresh(id);
187
+ // A fresh cached exp still in the future ⇒ revoked. An expired cached exp
188
+ // is treated as not-revoked: the token's own expiry already refuses it, so
189
+ // the denylist has nothing left to add.
190
+ return cached?.expMs != null && cached.expMs > now();
191
+ },
192
+ revoke(id, expiresAtMs) {
193
+ // Cache locally FIRST so this node refuses the id the instant this returns,
194
+ // regardless of whether the write-through has propagated yet.
195
+ cache.set(id, { expMs: expiresAtMs, readAtMs: now() });
196
+ track(backing.set(id, Math.ceil(expiresAtMs / 1000)).catch((error) => onError('set', error)));
197
+ },
198
+ sweep(nowMs) {
199
+ for (const [id, cached] of cache)
200
+ if (cached.expMs !== null && cached.expMs <= nowMs)
201
+ cache.delete(id);
202
+ track(backing.prune(Math.floor(nowMs / 1000)).catch((error) => onError('prune', error)));
203
+ },
204
+ async flush() {
205
+ // Settle in-flight work, then whatever it scheduled (a read can resolve
206
+ // after we snapshot), so the caller sees a fully-quiesced state.
207
+ while (pending.size > 0)
208
+ await Promise.allSettled([...pending]);
209
+ },
210
+ };
211
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * The production session signer (02 §4.2) — Ed25519-signed `CCUserClaims` plus
3
+ * the jti denylist that makes `lock()` mean something.
4
+ *
5
+ * The verify harness ships an HMAC stand-in because a probe suite tests the
6
+ * DECISION table (signature validity, expiry, revocation), not the algorithm.
7
+ * This is the real one, and the differences are the parts a stand-in cannot
8
+ * model:
9
+ *
10
+ * * **Asymmetric.** Only the server that holds the private key can mint a
11
+ * session. A verifier can be handed the public key alone, so a read-only
12
+ * replica or an edge worker can verify without being able to forge.
13
+ * * **Revocation is bounded, not unbounded.** A denylist that only ever grows
14
+ * is a memory leak wearing a security hat. An entry is dropped once its
15
+ * token would have expired anyway, because after `exp` the signature check
16
+ * already refuses it.
17
+ * * **Expiry before revocation.** An expired token is expired whether or not
18
+ * anyone bothered to revoke it — checking in the other order makes the
19
+ * denylist load-bearing for a case it does not need to cover.
20
+ */
21
+ import { type KeyObject } from 'node:crypto';
22
+ import { type CCUserClaims } from 'cursedbelt-core/session/claims';
23
+ import type { GuardVerifier } from './verifier';
24
+ import type { AsyncRevocationStore } from './revocationStore';
25
+ export interface SessionSignerOptions {
26
+ /**
27
+ * Ed25519 private key (PEM/DER/`KeyObject`). Omitted ⇒ an ephemeral pair is
28
+ * generated, which is correct ONLY for a single-process dev server: every
29
+ * restart invalidates every session, and a second process cannot verify.
30
+ */
31
+ privateKey?: KeyObject | string | Buffer;
32
+ /** Public half, when the verifier is separate from the minter. */
33
+ publicKey?: KeyObject | string | Buffer;
34
+ /** Default session lifetime. `guard.features.sessionTtlMinutes` feeds this. */
35
+ ttlSeconds?: number;
36
+ /** Clock injection for deterministic tests. */
37
+ now?: () => number;
38
+ /** Id generator for `jti`. */
39
+ mintJti?: () => string;
40
+ /**
41
+ * The cross-node denylist (03 §4a). Absent ⇒ the in-process `Map` is the whole
42
+ * story, which is correct for the local profile. Present (saas) ⇒ a `revoke`
43
+ * ALSO writes through to the shared store, and `verify` consults it for jtis
44
+ * revoked on OTHER nodes — so `lock()` on node A closes the token everywhere,
45
+ * not just where the request landed.
46
+ */
47
+ revocationStore?: AsyncRevocationStore;
48
+ /**
49
+ * How long a store verdict is trusted before `verify` re-reads it (ms). This is
50
+ * the cross-node revocation LATENCY BOUND: a jti revoked on another node is
51
+ * seen here within this window (plus the store's own read latency), because
52
+ * `verify` stays synchronous — it answers from the cache and refreshes it off
53
+ * the hot path rather than awaiting a network round trip on every route.
54
+ * Default 5000 (03 §4a's ≤5s). Ignored when no `revocationStore` is set.
55
+ */
56
+ revocationCacheTtlMs?: number;
57
+ /**
58
+ * Reports a store read/write that failed. A failed write-through means a
59
+ * revocation did not reach other nodes — the local Map still refuses the token
60
+ * HERE, but the host needs to know propagation failed (and may retry). Default:
61
+ * swallow, because a signer must never throw out of `verify`/`revoke` on the
62
+ * request path.
63
+ */
64
+ onRevocationStoreError?: (op: 'get' | 'set' | 'prune', error: unknown) => void;
65
+ }
66
+ /**
67
+ * One live session this signer minted (02 §4) — the `(userId, tenantId,
68
+ * deviceLabel)` association the spec asks for, keyed by `jti`.
69
+ *
70
+ * The device label is deliberately NOT a claim: `CCUserClaimsSchema` is
71
+ * `.strict()`, and widening the SIGNED payload with a human-readable string a
72
+ * client supplies would put attacker-controlled text inside the token every
73
+ * verifier parses. It is display metadata for "your devices", so it lives here,
74
+ * server-side, beside the jti it describes.
75
+ */
76
+ export interface SessionRecord {
77
+ jti: string;
78
+ userId: string;
79
+ tenantId: string;
80
+ /** What the client called itself (`x-cc-device`), if anything. */
81
+ deviceLabel?: string;
82
+ /** Epoch SECONDS, matching `exp`'s unit. */
83
+ issuedAt: number;
84
+ exp: number;
85
+ }
86
+ /** Non-claim session metadata recorded at mint time (02 §4). */
87
+ export interface SessionMintMeta {
88
+ /** Free-text device name for the sessions panel. Never signed, never trusted. */
89
+ deviceLabel?: string;
90
+ }
91
+ export interface SessionSigner extends GuardVerifier {
92
+ /** Mint a signed session token. `exp`/`jti` are stamped here, never by a caller. */
93
+ mint(claims: Omit<CCUserClaims, 'exp' | 'jti'> & Partial<Pick<CCUserClaims, 'exp' | 'jti'>>, meta?: SessionMintMeta): {
94
+ token: string;
95
+ claims: CCUserClaims;
96
+ };
97
+ /**
98
+ * Revoke every live session this signer minted for `userId`, returning the
99
+ * jtis it closed. The primitive behind a role/permission change taking effect
100
+ * NOW (02 §3 — authorization is decided from the token's claims, so a grant
101
+ * change that leaves the old token alive is a grant change that did not
102
+ * happen) and behind "log out everywhere" (02 §4).
103
+ *
104
+ * Node-local by construction: it can only enumerate what THIS process minted.
105
+ * Each revocation still writes through to the shared `revocationStore`, so the
106
+ * jtis it does know about die fleet-wide; a session minted on another node is
107
+ * closed when that node calls this (or by its own next login).
108
+ */
109
+ revokeUser(userId: string): readonly string[];
110
+ /**
111
+ * The live sessions this signer minted for `userId`, newest first — the
112
+ * "your devices" list (02 §4). Node-local for the same reason `revokeUser` is:
113
+ * a process can only enumerate what it minted. Expired and revoked entries are
114
+ * dropped, so what it returns is what a request would still be accepted on.
115
+ */
116
+ sessionsOf(userId: string): readonly SessionRecord[];
117
+ /** Revoked-but-not-yet-expired jtis. Exposed for the shutdown/handover path. */
118
+ activeRevocations(): readonly string[];
119
+ /** The public half, for a verifier in another process. */
120
+ publicKeyPem(): string;
121
+ /**
122
+ * Settle every pending revocation-store read and write, then prune the store.
123
+ *
124
+ * The store writes on `revoke` and the read-through refreshes on `verify` are
125
+ * fire-and-forget so neither blocks the request path. This awaits them — for
126
+ * the shutdown/handover path (a node draining must not lose an unwritten
127
+ * revocation), for the two-signer replay probe, and for any test that needs the
128
+ * cross-node state to have settled. A no-op when no `revocationStore` is wired.
129
+ */
130
+ flushRevocations(): Promise<void>;
131
+ }
132
+ export declare const createSessionSigner: (options?: SessionSignerOptions) => SessionSigner;
@@ -0,0 +1,303 @@
1
+ /**
2
+ * The production session signer (02 §4.2) — Ed25519-signed `CCUserClaims` plus
3
+ * the jti denylist that makes `lock()` mean something.
4
+ *
5
+ * The verify harness ships an HMAC stand-in because a probe suite tests the
6
+ * DECISION table (signature validity, expiry, revocation), not the algorithm.
7
+ * This is the real one, and the differences are the parts a stand-in cannot
8
+ * model:
9
+ *
10
+ * * **Asymmetric.** Only the server that holds the private key can mint a
11
+ * session. A verifier can be handed the public key alone, so a read-only
12
+ * replica or an edge worker can verify without being able to forge.
13
+ * * **Revocation is bounded, not unbounded.** A denylist that only ever grows
14
+ * is a memory leak wearing a security hat. An entry is dropped once its
15
+ * token would have expired anyway, because after `exp` the signature check
16
+ * already refuses it.
17
+ * * **Expiry before revocation.** An expired token is expired whether or not
18
+ * anyone bothered to revoke it — checking in the other order makes the
19
+ * denylist load-bearing for a case it does not need to cover.
20
+ */
21
+ import { createPrivateKey, createPublicKey, generateKeyPairSync, sign, verify } from 'node:crypto';
22
+ import { CCUserClaimsSchema } from 'cursedbelt-core/session/claims';
23
+ import { AppError } from '../errors';
24
+ const b64url = (input) => Buffer.from(input).toString('base64url');
25
+ /** Ceiling on a client-supplied device label — display text, not a payload. */
26
+ const MAX_DEVICE_LABEL = 64;
27
+ let jtiSequence = 0;
28
+ const defaultJti = () => {
29
+ jtiSequence += 1;
30
+ return `jti_${Date.now().toString(36)}_${jtiSequence.toString(36)}`;
31
+ };
32
+ export const createSessionSigner = (options = {}) => {
33
+ const now = options.now ?? (() => Date.now());
34
+ const mintJti = options.mintJti ?? defaultJti;
35
+ const ttlSeconds = options.ttlSeconds ?? 30 * 60;
36
+ let privateKey = null;
37
+ let publicKey;
38
+ if (options.privateKey !== undefined) {
39
+ privateKey =
40
+ typeof options.privateKey === 'string' || Buffer.isBuffer(options.privateKey)
41
+ ? createPrivateKey(options.privateKey)
42
+ : options.privateKey;
43
+ publicKey =
44
+ options.publicKey === undefined
45
+ ? createPublicKey(privateKey)
46
+ : typeof options.publicKey === 'string' || Buffer.isBuffer(options.publicKey)
47
+ ? createPublicKey(options.publicKey)
48
+ : options.publicKey;
49
+ }
50
+ else if (options.publicKey !== undefined) {
51
+ publicKey =
52
+ typeof options.publicKey === 'string' || Buffer.isBuffer(options.publicKey)
53
+ ? createPublicKey(options.publicKey)
54
+ : options.publicKey;
55
+ }
56
+ else {
57
+ const pair = generateKeyPairSync('ed25519');
58
+ privateKey = pair.privateKey;
59
+ publicKey = pair.publicKey;
60
+ }
61
+ /** jti → the epoch-seconds `exp` after which the entry is pointless. */
62
+ const revoked = new Map();
63
+ /**
64
+ * Live jtis this signer minted, per user — the index behind
65
+ * {@link SessionSigner.revokeUser} (02 §3's role change, 02 §4's "log out
66
+ * everywhere"). Deliberately NOT the cross-node picture: a node can only
67
+ * enumerate what it minted, so under saas the authority for "is this token
68
+ * dead" stays the shared `revocationStore` that `revokeUser` writes through
69
+ * to. Entries are dropped as they expire (`pruneMinted`), so this is bounded
70
+ * by live sessions, not by all sessions ever.
71
+ */
72
+ const mintedByUser = new Map();
73
+ /** jti → its minter, so a revoke can drop the record without scanning users. */
74
+ const userByJti = new Map();
75
+ const rememberMinted = (record) => {
76
+ const forUser = mintedByUser.get(record.userId) ?? new Map();
77
+ forUser.set(record.jti, record);
78
+ mintedByUser.set(record.userId, forUser);
79
+ userByJti.set(record.jti, record.userId);
80
+ };
81
+ /**
82
+ * Drop a jti from the live index. Called on every revoke, so a revoked session
83
+ * stops being a live one for BOTH `sessionsOf` (the panel) and `revokeUser`
84
+ * (whose count is then "sessions actually closed", not "jtis ever minted").
85
+ */
86
+ const forgetMinted = (jti) => {
87
+ const userId = userByJti.get(jti);
88
+ if (userId === undefined)
89
+ return;
90
+ userByJti.delete(jti);
91
+ const forUser = mintedByUser.get(userId);
92
+ forUser?.delete(jti);
93
+ if (forUser && forUser.size === 0)
94
+ mintedByUser.delete(userId);
95
+ };
96
+ const pruneMinted = () => {
97
+ const cutoff = Math.floor(now() / 1000);
98
+ for (const [userId, sessions] of mintedByUser) {
99
+ for (const [jti, record] of sessions)
100
+ if (record.exp <= cutoff) {
101
+ sessions.delete(jti);
102
+ userByJti.delete(jti);
103
+ }
104
+ if (sessions.size === 0)
105
+ mintedByUser.delete(userId);
106
+ }
107
+ };
108
+ // ── the cross-node denylist (03 §4a) ────────────────────────────────────────
109
+ const store = options.revocationStore;
110
+ const cacheTtlMs = options.revocationCacheTtlMs ?? 5000;
111
+ const onStoreError = options.onRevocationStoreError ?? (() => undefined);
112
+ /** jti → the last store verdict and when it was read, so `verify` stays sync. */
113
+ const storeCache = new Map();
114
+ /** In-flight async work, so `flushRevocations` (and shutdown) can await it. */
115
+ const pending = new Set();
116
+ /** Reads in flight per jti, so a burst of verifies coalesces into one store read. */
117
+ const inflightReads = new Map();
118
+ const track = (work) => {
119
+ pending.add(work);
120
+ void work.finally(() => pending.delete(work));
121
+ };
122
+ /** Read the store for `jti` off the hot path and fold the verdict into the cache. */
123
+ const refreshFromStore = (jti) => {
124
+ if (!store || inflightReads.has(jti))
125
+ return;
126
+ const read = store
127
+ .get(jti)
128
+ .then((exp) => {
129
+ storeCache.set(jti, { exp, readAtMs: now() });
130
+ // A confirmed revocation is promoted into the authoritative Map so every
131
+ // later verify is instant and `activeRevocations()` (the handover set)
132
+ // includes jtis this node learned about from its peers.
133
+ if (exp !== null) {
134
+ revoked.set(jti, exp);
135
+ // A peer revoked it, so it is no longer a live session here either.
136
+ forgetMinted(jti);
137
+ }
138
+ })
139
+ .catch((error) => onStoreError('get', error))
140
+ .finally(() => inflightReads.delete(jti));
141
+ inflightReads.set(jti, read);
142
+ track(read);
143
+ };
144
+ /**
145
+ * Is `jti` revoked, per the shared store? Answered synchronously from the cache;
146
+ * a stale or missing entry schedules a refresh but does NOT block. The bound on
147
+ * how stale the answer can be is `cacheTtlMs` — the cross-node latency the seam
148
+ * documents.
149
+ */
150
+ const storeSaysRevoked = (jti) => {
151
+ if (!store)
152
+ return false;
153
+ const cached = storeCache.get(jti);
154
+ if (cached === undefined || now() - cached.readAtMs >= cacheTtlMs)
155
+ refreshFromStore(jti);
156
+ // A fresh cached exp that is still in the future ⇒ revoked. An expired cached
157
+ // exp is treated as not-revoked: the signature check already refuses the
158
+ // token past `exp`, so the denylist has nothing left to add.
159
+ return cached?.exp != null && cached.exp * 1000 > now();
160
+ };
161
+ /**
162
+ * The one revocation write path — a free function rather than a method so
163
+ * `revokeUser` cannot depend on `this` (a destructured `const { revokeUser } =
164
+ * signer` would silently lose it, and losing a revocation is the one bug this
165
+ * seam must not have).
166
+ */
167
+ const revokeJti = (jti) => {
168
+ pruneRevocations();
169
+ // An unknown jti is still recorded: `lock()` may revoke a token this
170
+ // process never minted (another node in the fleet did), and refusing to
171
+ // remember it would let that token keep working here.
172
+ const exp = Math.floor(now() / 1000) + ttlSeconds;
173
+ revoked.set(jti, exp);
174
+ forgetMinted(jti);
175
+ // Write through so every OTHER node sees it too. Fire-and-forget: `revoke`
176
+ // is called on the request path (a `lock`), and the local Map already
177
+ // refuses the token HERE the instant this returns — propagation is what the
178
+ // store adds, and `flushRevocations`/the error hook cover its durability.
179
+ if (store) {
180
+ storeCache.set(jti, { exp, readAtMs: now() });
181
+ track(store.set(jti, exp).catch((error) => onStoreError('set', error)));
182
+ }
183
+ };
184
+ const pruneRevocations = () => {
185
+ const cutoff = Math.floor(now() / 1000);
186
+ for (const [jti, exp] of revoked)
187
+ if (exp <= cutoff)
188
+ revoked.delete(jti);
189
+ for (const [jti, cached] of storeCache)
190
+ if (cached.exp !== null && cached.exp <= cutoff)
191
+ storeCache.delete(jti);
192
+ };
193
+ return {
194
+ mint(input, meta) {
195
+ if (privateKey === null)
196
+ throw new AppError('This session signer holds only a public key and cannot mint tokens.', {
197
+ code: 'GUARD_SIGNER_READONLY',
198
+ status: 500,
199
+ category: 'security',
200
+ isOperational: false,
201
+ });
202
+ const claims = CCUserClaimsSchema.parse({
203
+ ...input,
204
+ exp: input.exp ?? Math.floor(now() / 1000) + ttlSeconds,
205
+ jti: input.jti ?? mintJti(),
206
+ });
207
+ const payload = b64url(JSON.stringify(claims));
208
+ const signature = b64url(sign(null, Buffer.from(payload), privateKey));
209
+ pruneMinted();
210
+ rememberMinted({
211
+ jti: claims.jti,
212
+ userId: claims.userId,
213
+ tenantId: claims.tenantId,
214
+ // A label is truncated rather than rejected: it is display text, and a
215
+ // login must never fail because a browser sent a long device string.
216
+ ...(meta?.deviceLabel ? { deviceLabel: meta.deviceLabel.slice(0, MAX_DEVICE_LABEL) } : {}),
217
+ issuedAt: Math.floor(now() / 1000),
218
+ exp: claims.exp,
219
+ });
220
+ return { token: `${payload}.${signature}`, claims };
221
+ },
222
+ verify(token) {
223
+ if (!token)
224
+ return { ok: false, failure: 'missing' };
225
+ const dot = token.indexOf('.');
226
+ if (dot <= 0 || dot === token.length - 1)
227
+ return { ok: false, failure: 'malformed' };
228
+ const payload = token.slice(0, dot);
229
+ const signature = token.slice(dot + 1);
230
+ let valid = false;
231
+ try {
232
+ valid = verify(null, Buffer.from(payload), publicKey, Buffer.from(signature, 'base64url'));
233
+ }
234
+ catch {
235
+ // A signature that is not even decodable is malformed, not merely wrong.
236
+ return { ok: false, failure: 'malformed' };
237
+ }
238
+ if (!valid)
239
+ return { ok: false, failure: 'invalid-signature' };
240
+ let parsedJson;
241
+ try {
242
+ parsedJson = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
243
+ }
244
+ catch {
245
+ return { ok: false, failure: 'malformed' };
246
+ }
247
+ const parsed = CCUserClaimsSchema.safeParse(parsedJson);
248
+ // `.strict()`: an unknown claim key is a forged or version-skewed token,
249
+ // and silently ignoring it is how a `permissions` typo becomes a grant.
250
+ if (!parsed.success)
251
+ return { ok: false, failure: 'malformed' };
252
+ if (parsed.data.exp * 1000 <= now())
253
+ return { ok: false, failure: 'expired' };
254
+ // Local Map first (this node's own revokes, and peers' revokes it has
255
+ // already learned) — always authoritative and free. Then the shared store,
256
+ // consulted through the sync cache, for a jti revoked on another node.
257
+ if (revoked.has(parsed.data.jti) || storeSaysRevoked(parsed.data.jti))
258
+ return { ok: false, failure: 'revoked' };
259
+ return { ok: true, claims: parsed.data };
260
+ },
261
+ revoke(jti) {
262
+ revokeJti(jti);
263
+ },
264
+ revokeUser(userId) {
265
+ pruneMinted();
266
+ const jtis = [...(mintedByUser.get(userId)?.keys() ?? [])];
267
+ for (const jti of jtis)
268
+ revokeJti(jti);
269
+ mintedByUser.delete(userId);
270
+ return jtis;
271
+ },
272
+ sessionsOf(userId) {
273
+ pruneMinted();
274
+ pruneRevocations();
275
+ return [...(mintedByUser.get(userId)?.values() ?? [])]
276
+ // A revoked-but-unexpired jti is a dead session; listing it would invite
277
+ // "log this device out" on a device that is already out.
278
+ .filter((record) => !revoked.has(record.jti) && !storeSaysRevoked(record.jti))
279
+ .sort((left, right) => right.issuedAt - left.issuedAt);
280
+ },
281
+ isRevoked(jti) {
282
+ pruneRevocations();
283
+ return revoked.has(jti) || storeSaysRevoked(jti);
284
+ },
285
+ activeRevocations() {
286
+ pruneRevocations();
287
+ return [...revoked.keys()];
288
+ },
289
+ async flushRevocations() {
290
+ // Settle in-flight work, then whatever it scheduled (a read can resolve
291
+ // after we snapshot), so the caller sees a fully-quiesced state.
292
+ while (pending.size > 0)
293
+ await Promise.allSettled([...pending]);
294
+ if (store)
295
+ track(store.prune(Math.floor(now() / 1000)).catch((error) => onStoreError('prune', error)));
296
+ while (pending.size > 0)
297
+ await Promise.allSettled([...pending]);
298
+ },
299
+ publicKeyPem() {
300
+ return publicKey.export({ type: 'spki', format: 'pem' }).toString();
301
+ },
302
+ };
303
+ };
@@ -0,0 +1,21 @@
1
+ import type { CCUserClaims } from 'cursedbelt-core/session/claims';
2
+ /** Why a presented session token was refused. Mapped to status by `guardVerify`. */
3
+ export type GuardFailure = 'missing' | 'malformed' | 'invalid-signature' | 'expired' | 'revoked';
4
+ export type GuardVerification = {
5
+ ok: true;
6
+ claims: CCUserClaims;
7
+ } | {
8
+ ok: false;
9
+ failure: GuardFailure;
10
+ };
11
+ /**
12
+ * Per-request token verification (02 §4.2). The wire only signals; this is what
13
+ * decides. `verify` is called on every private or mutating route — a pane's
14
+ * unlock is never cached for another pane's request.
15
+ */
16
+ export interface GuardVerifier {
17
+ verify(token: string | undefined): GuardVerification;
18
+ /** Revoke a jti until its `exp` — what lock does, across every tab. */
19
+ revoke(jti: string): void;
20
+ isRevoked(jti: string): boolean;
21
+ }
@@ -0,0 +1 @@
1
+ export {};