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,425 @@
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, type KeyObject, sign, verify } from 'node:crypto';
22
+ import { type CCUserClaims, CCUserClaimsSchema } from 'cursedbelt-core/session/claims';
23
+ import { AppError } from '../errors';
24
+ import type { GuardVerification, GuardVerifier } from './verifier';
25
+ import type { AsyncRevocationStore } from './revocationStore';
26
+
27
+ export interface SessionSignerOptions {
28
+ /**
29
+ * Ed25519 private key (PEM/DER/`KeyObject`). Omitted ⇒ an ephemeral pair is
30
+ * generated, which is correct ONLY for a single-process dev server: every
31
+ * restart invalidates every session, and a second process cannot verify.
32
+ */
33
+ privateKey?: KeyObject | string | Buffer;
34
+ /** Public half, when the verifier is separate from the minter. */
35
+ publicKey?: KeyObject | string | Buffer;
36
+ /** Default session lifetime. `guard.features.sessionTtlMinutes` feeds this. */
37
+ ttlSeconds?: number;
38
+ /** Clock injection for deterministic tests. */
39
+ now?: () => number;
40
+ /** Id generator for `jti`. */
41
+ mintJti?: () => string;
42
+ /**
43
+ * The cross-node denylist (03 §4a). Absent ⇒ the in-process `Map` is the whole
44
+ * story, which is correct for the local profile. Present (saas) ⇒ a `revoke`
45
+ * ALSO writes through to the shared store, and `verify` consults it for jtis
46
+ * revoked on OTHER nodes — so `lock()` on node A closes the token everywhere,
47
+ * not just where the request landed.
48
+ */
49
+ revocationStore?: AsyncRevocationStore;
50
+ /**
51
+ * How long a store verdict is trusted before `verify` re-reads it (ms). This is
52
+ * the cross-node revocation LATENCY BOUND: a jti revoked on another node is
53
+ * seen here within this window (plus the store's own read latency), because
54
+ * `verify` stays synchronous — it answers from the cache and refreshes it off
55
+ * the hot path rather than awaiting a network round trip on every route.
56
+ * Default 5000 (03 §4a's ≤5s). Ignored when no `revocationStore` is set.
57
+ */
58
+ revocationCacheTtlMs?: number;
59
+ /**
60
+ * Reports a store read/write that failed. A failed write-through means a
61
+ * revocation did not reach other nodes — the local Map still refuses the token
62
+ * HERE, but the host needs to know propagation failed (and may retry). Default:
63
+ * swallow, because a signer must never throw out of `verify`/`revoke` on the
64
+ * request path.
65
+ */
66
+ onRevocationStoreError?: (op: 'get' | 'set' | 'prune', error: unknown) => void;
67
+ }
68
+
69
+ /**
70
+ * One live session this signer minted (02 §4) — the `(userId, tenantId,
71
+ * deviceLabel)` association the spec asks for, keyed by `jti`.
72
+ *
73
+ * The device label is deliberately NOT a claim: `CCUserClaimsSchema` is
74
+ * `.strict()`, and widening the SIGNED payload with a human-readable string a
75
+ * client supplies would put attacker-controlled text inside the token every
76
+ * verifier parses. It is display metadata for "your devices", so it lives here,
77
+ * server-side, beside the jti it describes.
78
+ */
79
+ export interface SessionRecord {
80
+ jti: string;
81
+ userId: string;
82
+ tenantId: string;
83
+ /** What the client called itself (`x-cc-device`), if anything. */
84
+ deviceLabel?: string;
85
+ /** Epoch SECONDS, matching `exp`'s unit. */
86
+ issuedAt: number;
87
+ exp: number;
88
+ }
89
+
90
+ /** Non-claim session metadata recorded at mint time (02 §4). */
91
+ export interface SessionMintMeta {
92
+ /** Free-text device name for the sessions panel. Never signed, never trusted. */
93
+ deviceLabel?: string;
94
+ }
95
+
96
+ export interface SessionSigner extends GuardVerifier {
97
+ /** Mint a signed session token. `exp`/`jti` are stamped here, never by a caller. */
98
+ mint(
99
+ claims: Omit<CCUserClaims, 'exp' | 'jti'> & Partial<Pick<CCUserClaims, 'exp' | 'jti'>>,
100
+ meta?: SessionMintMeta,
101
+ ): {
102
+ token: string;
103
+ claims: CCUserClaims;
104
+ };
105
+ /**
106
+ * Revoke every live session this signer minted for `userId`, returning the
107
+ * jtis it closed. The primitive behind a role/permission change taking effect
108
+ * NOW (02 §3 — authorization is decided from the token's claims, so a grant
109
+ * change that leaves the old token alive is a grant change that did not
110
+ * happen) and behind "log out everywhere" (02 §4).
111
+ *
112
+ * Node-local by construction: it can only enumerate what THIS process minted.
113
+ * Each revocation still writes through to the shared `revocationStore`, so the
114
+ * jtis it does know about die fleet-wide; a session minted on another node is
115
+ * closed when that node calls this (or by its own next login).
116
+ */
117
+ revokeUser(userId: string): readonly string[];
118
+ /**
119
+ * The live sessions this signer minted for `userId`, newest first — the
120
+ * "your devices" list (02 §4). Node-local for the same reason `revokeUser` is:
121
+ * a process can only enumerate what it minted. Expired and revoked entries are
122
+ * dropped, so what it returns is what a request would still be accepted on.
123
+ */
124
+ sessionsOf(userId: string): readonly SessionRecord[];
125
+ /** Revoked-but-not-yet-expired jtis. Exposed for the shutdown/handover path. */
126
+ activeRevocations(): readonly string[];
127
+ /** The public half, for a verifier in another process. */
128
+ publicKeyPem(): string;
129
+ /**
130
+ * Settle every pending revocation-store read and write, then prune the store.
131
+ *
132
+ * The store writes on `revoke` and the read-through refreshes on `verify` are
133
+ * fire-and-forget so neither blocks the request path. This awaits them — for
134
+ * the shutdown/handover path (a node draining must not lose an unwritten
135
+ * revocation), for the two-signer replay probe, and for any test that needs the
136
+ * cross-node state to have settled. A no-op when no `revocationStore` is wired.
137
+ */
138
+ flushRevocations(): Promise<void>;
139
+ }
140
+
141
+ const b64url = (input: string | Buffer | Uint8Array): string => Buffer.from(input).toString('base64url');
142
+
143
+ /** Ceiling on a client-supplied device label — display text, not a payload. */
144
+ const MAX_DEVICE_LABEL = 64;
145
+
146
+ let jtiSequence = 0;
147
+ const defaultJti = (): string => {
148
+ jtiSequence += 1;
149
+ return `jti_${Date.now().toString(36)}_${jtiSequence.toString(36)}`;
150
+ };
151
+
152
+ export const createSessionSigner = (options: SessionSignerOptions = {}): SessionSigner => {
153
+ const now = options.now ?? (() => Date.now());
154
+ const mintJti = options.mintJti ?? defaultJti;
155
+ const ttlSeconds = options.ttlSeconds ?? 30 * 60;
156
+
157
+ let privateKey: KeyObject | null = null;
158
+ let publicKey: KeyObject;
159
+
160
+ if (options.privateKey !== undefined) {
161
+ privateKey =
162
+ typeof options.privateKey === 'string' || Buffer.isBuffer(options.privateKey)
163
+ ? createPrivateKey(options.privateKey)
164
+ : options.privateKey;
165
+ publicKey =
166
+ options.publicKey === undefined
167
+ ? createPublicKey(privateKey)
168
+ : typeof options.publicKey === 'string' || Buffer.isBuffer(options.publicKey)
169
+ ? createPublicKey(options.publicKey)
170
+ : options.publicKey;
171
+ } else if (options.publicKey !== undefined) {
172
+ publicKey =
173
+ typeof options.publicKey === 'string' || Buffer.isBuffer(options.publicKey)
174
+ ? createPublicKey(options.publicKey)
175
+ : options.publicKey;
176
+ } else {
177
+ const pair = generateKeyPairSync('ed25519');
178
+ privateKey = pair.privateKey;
179
+ publicKey = pair.publicKey;
180
+ }
181
+
182
+ /** jti → the epoch-seconds `exp` after which the entry is pointless. */
183
+ const revoked = new Map<string, number>();
184
+
185
+ /**
186
+ * Live jtis this signer minted, per user — the index behind
187
+ * {@link SessionSigner.revokeUser} (02 §3's role change, 02 §4's "log out
188
+ * everywhere"). Deliberately NOT the cross-node picture: a node can only
189
+ * enumerate what it minted, so under saas the authority for "is this token
190
+ * dead" stays the shared `revocationStore` that `revokeUser` writes through
191
+ * to. Entries are dropped as they expire (`pruneMinted`), so this is bounded
192
+ * by live sessions, not by all sessions ever.
193
+ */
194
+ const mintedByUser = new Map<string, Map<string, SessionRecord>>();
195
+ /** jti → its minter, so a revoke can drop the record without scanning users. */
196
+ const userByJti = new Map<string, string>();
197
+
198
+ const rememberMinted = (record: SessionRecord): void => {
199
+ const forUser = mintedByUser.get(record.userId) ?? new Map<string, SessionRecord>();
200
+ forUser.set(record.jti, record);
201
+ mintedByUser.set(record.userId, forUser);
202
+ userByJti.set(record.jti, record.userId);
203
+ };
204
+
205
+ /**
206
+ * Drop a jti from the live index. Called on every revoke, so a revoked session
207
+ * stops being a live one for BOTH `sessionsOf` (the panel) and `revokeUser`
208
+ * (whose count is then "sessions actually closed", not "jtis ever minted").
209
+ */
210
+ const forgetMinted = (jti: string): void => {
211
+ const userId = userByJti.get(jti);
212
+ if (userId === undefined) return;
213
+ userByJti.delete(jti);
214
+ const forUser = mintedByUser.get(userId);
215
+ forUser?.delete(jti);
216
+ if (forUser && forUser.size === 0) mintedByUser.delete(userId);
217
+ };
218
+
219
+ const pruneMinted = (): void => {
220
+ const cutoff = Math.floor(now() / 1000);
221
+ for (const [userId, sessions] of mintedByUser) {
222
+ for (const [jti, record] of sessions)
223
+ if (record.exp <= cutoff) {
224
+ sessions.delete(jti);
225
+ userByJti.delete(jti);
226
+ }
227
+ if (sessions.size === 0) mintedByUser.delete(userId);
228
+ }
229
+ };
230
+
231
+ // ── the cross-node denylist (03 §4a) ────────────────────────────────────────
232
+ const store = options.revocationStore;
233
+ const cacheTtlMs = options.revocationCacheTtlMs ?? 5000;
234
+ const onStoreError = options.onRevocationStoreError ?? (() => undefined);
235
+ /** jti → the last store verdict and when it was read, so `verify` stays sync. */
236
+ const storeCache = new Map<string, { exp: number | null; readAtMs: number }>();
237
+ /** In-flight async work, so `flushRevocations` (and shutdown) can await it. */
238
+ const pending = new Set<Promise<unknown>>();
239
+ /** Reads in flight per jti, so a burst of verifies coalesces into one store read. */
240
+ const inflightReads = new Map<string, Promise<void>>();
241
+
242
+ const track = (work: Promise<unknown>): void => {
243
+ pending.add(work);
244
+ void work.finally(() => pending.delete(work));
245
+ };
246
+
247
+ /** Read the store for `jti` off the hot path and fold the verdict into the cache. */
248
+ const refreshFromStore = (jti: string): void => {
249
+ if (!store || inflightReads.has(jti)) return;
250
+ const read = store
251
+ .get(jti)
252
+ .then((exp) => {
253
+ storeCache.set(jti, { exp, readAtMs: now() });
254
+ // A confirmed revocation is promoted into the authoritative Map so every
255
+ // later verify is instant and `activeRevocations()` (the handover set)
256
+ // includes jtis this node learned about from its peers.
257
+ if (exp !== null) {
258
+ revoked.set(jti, exp);
259
+ // A peer revoked it, so it is no longer a live session here either.
260
+ forgetMinted(jti);
261
+ }
262
+ })
263
+ .catch((error) => onStoreError('get', error))
264
+ .finally(() => inflightReads.delete(jti));
265
+ inflightReads.set(jti, read);
266
+ track(read);
267
+ };
268
+
269
+ /**
270
+ * Is `jti` revoked, per the shared store? Answered synchronously from the cache;
271
+ * a stale or missing entry schedules a refresh but does NOT block. The bound on
272
+ * how stale the answer can be is `cacheTtlMs` — the cross-node latency the seam
273
+ * documents.
274
+ */
275
+ const storeSaysRevoked = (jti: string): boolean => {
276
+ if (!store) return false;
277
+ const cached = storeCache.get(jti);
278
+ if (cached === undefined || now() - cached.readAtMs >= cacheTtlMs) refreshFromStore(jti);
279
+ // A fresh cached exp that is still in the future ⇒ revoked. An expired cached
280
+ // exp is treated as not-revoked: the signature check already refuses the
281
+ // token past `exp`, so the denylist has nothing left to add.
282
+ return cached?.exp != null && cached.exp * 1000 > now();
283
+ };
284
+
285
+ /**
286
+ * The one revocation write path — a free function rather than a method so
287
+ * `revokeUser` cannot depend on `this` (a destructured `const { revokeUser } =
288
+ * signer` would silently lose it, and losing a revocation is the one bug this
289
+ * seam must not have).
290
+ */
291
+ const revokeJti = (jti: string): void => {
292
+ pruneRevocations();
293
+ // An unknown jti is still recorded: `lock()` may revoke a token this
294
+ // process never minted (another node in the fleet did), and refusing to
295
+ // remember it would let that token keep working here.
296
+ const exp = Math.floor(now() / 1000) + ttlSeconds;
297
+ revoked.set(jti, exp);
298
+ forgetMinted(jti);
299
+ // Write through so every OTHER node sees it too. Fire-and-forget: `revoke`
300
+ // is called on the request path (a `lock`), and the local Map already
301
+ // refuses the token HERE the instant this returns — propagation is what the
302
+ // store adds, and `flushRevocations`/the error hook cover its durability.
303
+ if (store) {
304
+ storeCache.set(jti, { exp, readAtMs: now() });
305
+ track(store.set(jti, exp).catch((error) => onStoreError('set', error)));
306
+ }
307
+ };
308
+
309
+ const pruneRevocations = (): void => {
310
+ const cutoff = Math.floor(now() / 1000);
311
+ for (const [jti, exp] of revoked) if (exp <= cutoff) revoked.delete(jti);
312
+ for (const [jti, cached] of storeCache) if (cached.exp !== null && cached.exp <= cutoff) storeCache.delete(jti);
313
+ };
314
+
315
+ return {
316
+ mint(input, meta) {
317
+ if (privateKey === null)
318
+ throw new AppError('This session signer holds only a public key and cannot mint tokens.', {
319
+ code: 'GUARD_SIGNER_READONLY',
320
+ status: 500,
321
+ category: 'security',
322
+ isOperational: false,
323
+ });
324
+
325
+ const claims: CCUserClaims = CCUserClaimsSchema.parse({
326
+ ...input,
327
+ exp: input.exp ?? Math.floor(now() / 1000) + ttlSeconds,
328
+ jti: input.jti ?? mintJti(),
329
+ });
330
+ const payload = b64url(JSON.stringify(claims));
331
+ const signature = b64url(sign(null, Buffer.from(payload), privateKey));
332
+ pruneMinted();
333
+ rememberMinted({
334
+ jti: claims.jti,
335
+ userId: claims.userId,
336
+ tenantId: claims.tenantId,
337
+ // A label is truncated rather than rejected: it is display text, and a
338
+ // login must never fail because a browser sent a long device string.
339
+ ...(meta?.deviceLabel ? { deviceLabel: meta.deviceLabel.slice(0, MAX_DEVICE_LABEL) } : {}),
340
+ issuedAt: Math.floor(now() / 1000),
341
+ exp: claims.exp,
342
+ });
343
+ return { token: `${payload}.${signature}`, claims };
344
+ },
345
+
346
+ verify(token: string | undefined): GuardVerification {
347
+ if (!token) return { ok: false, failure: 'missing' };
348
+ const dot = token.indexOf('.');
349
+ if (dot <= 0 || dot === token.length - 1) return { ok: false, failure: 'malformed' };
350
+ const payload = token.slice(0, dot);
351
+ const signature = token.slice(dot + 1);
352
+
353
+ let valid = false;
354
+ try {
355
+ valid = verify(null, Buffer.from(payload), publicKey, Buffer.from(signature, 'base64url'));
356
+ } catch {
357
+ // A signature that is not even decodable is malformed, not merely wrong.
358
+ return { ok: false, failure: 'malformed' };
359
+ }
360
+ if (!valid) return { ok: false, failure: 'invalid-signature' };
361
+
362
+ let parsedJson: unknown;
363
+ try {
364
+ parsedJson = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
365
+ } catch {
366
+ return { ok: false, failure: 'malformed' };
367
+ }
368
+ const parsed = CCUserClaimsSchema.safeParse(parsedJson);
369
+ // `.strict()`: an unknown claim key is a forged or version-skewed token,
370
+ // and silently ignoring it is how a `permissions` typo becomes a grant.
371
+ if (!parsed.success) return { ok: false, failure: 'malformed' };
372
+
373
+ if (parsed.data.exp * 1000 <= now()) return { ok: false, failure: 'expired' };
374
+ // Local Map first (this node's own revokes, and peers' revokes it has
375
+ // already learned) — always authoritative and free. Then the shared store,
376
+ // consulted through the sync cache, for a jti revoked on another node.
377
+ if (revoked.has(parsed.data.jti) || storeSaysRevoked(parsed.data.jti)) return { ok: false, failure: 'revoked' };
378
+ return { ok: true, claims: parsed.data };
379
+ },
380
+
381
+ revoke(jti: string): void {
382
+ revokeJti(jti);
383
+ },
384
+
385
+ revokeUser(userId: string): readonly string[] {
386
+ pruneMinted();
387
+ const jtis = [...(mintedByUser.get(userId)?.keys() ?? [])];
388
+ for (const jti of jtis) revokeJti(jti);
389
+ mintedByUser.delete(userId);
390
+ return jtis;
391
+ },
392
+
393
+ sessionsOf(userId: string): readonly SessionRecord[] {
394
+ pruneMinted();
395
+ pruneRevocations();
396
+ return [...(mintedByUser.get(userId)?.values() ?? [])]
397
+ // A revoked-but-unexpired jti is a dead session; listing it would invite
398
+ // "log this device out" on a device that is already out.
399
+ .filter((record) => !revoked.has(record.jti) && !storeSaysRevoked(record.jti))
400
+ .sort((left, right) => right.issuedAt - left.issuedAt);
401
+ },
402
+
403
+ isRevoked(jti: string): boolean {
404
+ pruneRevocations();
405
+ return revoked.has(jti) || storeSaysRevoked(jti);
406
+ },
407
+
408
+ activeRevocations(): readonly string[] {
409
+ pruneRevocations();
410
+ return [...revoked.keys()];
411
+ },
412
+
413
+ async flushRevocations(): Promise<void> {
414
+ // Settle in-flight work, then whatever it scheduled (a read can resolve
415
+ // after we snapshot), so the caller sees a fully-quiesced state.
416
+ while (pending.size > 0) await Promise.allSettled([...pending]);
417
+ if (store) track(store.prune(Math.floor(now() / 1000)).catch((error) => onStoreError('prune', error)));
418
+ while (pending.size > 0) await Promise.allSettled([...pending]);
419
+ },
420
+
421
+ publicKeyPem(): string {
422
+ return publicKey.export({ type: 'spki', format: 'pem' }).toString();
423
+ },
424
+ };
425
+ };
@@ -0,0 +1,27 @@
1
+ /*
2
+ * The per-request session-verification contract. Moved here from the cc engine's
3
+ * `mixins/types.ts` at the 2026-09-15 split: `sessionSigner` — the production
4
+ * implementation, which every app-tier login door uses — must not depend on the
5
+ * SaaS engine package for its own return type. `cursedbelt-cc` re-exports these
6
+ * from here, so the engine and the app tier keep speaking the same words.
7
+ */
8
+ import type { CCUserClaims } from 'cursedbelt-core/session/claims';
9
+
10
+ /** Why a presented session token was refused. Mapped to status by `guardVerify`. */
11
+ export type GuardFailure = 'missing' | 'malformed' | 'invalid-signature' | 'expired' | 'revoked';
12
+
13
+ export type GuardVerification =
14
+ | { ok: true; claims: CCUserClaims }
15
+ | { ok: false; failure: GuardFailure };
16
+
17
+ /**
18
+ * Per-request token verification (02 §4.2). The wire only signals; this is what
19
+ * decides. `verify` is called on every private or mutating route — a pane's
20
+ * unlock is never cached for another pane's request.
21
+ */
22
+ export interface GuardVerifier {
23
+ verify(token: string | undefined): GuardVerification;
24
+ /** Revoke a jti until its `exp` — what lock does, across every tab. */
25
+ revoke(jti: string): void;
26
+ isRevoked(jti: string): boolean;
27
+ }