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,390 @@
1
+ /**
2
+ * The GuardEngine server wiring: the Ed25519 session signer and the argon2id
3
+ * binding adapter that arms cwip's KDF seam.
4
+ *
5
+ * The KDF tests use a FAKE binding on purpose. A real known-answer vector needs
6
+ * a real argon2id, and cwip ships none by contract (`@node-rs/argon2` is an
7
+ * optional peer) — so what is testable here is the ADAPTER's contract: that the
8
+ * envelope's parameters and version reach the binding unchanged, and that a
9
+ * binding returning a string is refused rather than accepted as key material.
10
+ * The known-answer test lands with the binding, in the app that installs it.
11
+ */
12
+ import { generateKeyPairSync } from 'node:crypto';
13
+ import { afterEach, describe, expect, it } from 'bun:test';
14
+ import { deriveArgon2idKey, hasArgon2idDeriver, setArgon2idDeriver } from 'cwip/guard';
15
+ import { ARGON2ID_ALGORITHM, argon2idDeriverFrom, registerArgon2idBinding, type RawArgon2idBinding } from './argon2Binding';
16
+ import { createMemoryAsyncRevocationStore } from './revocationStore';
17
+ import { createSessionSigner } from './sessionSigner';
18
+
19
+ afterEach(() => {
20
+ setArgon2idDeriver(null);
21
+ });
22
+
23
+ /** Records what the adapter passed through, and returns deterministic bytes. */
24
+ const recordingBinding = (): RawArgon2idBinding & { calls: Record<string, unknown>[] } => {
25
+ const calls: Record<string, unknown>[] = [];
26
+ return {
27
+ calls,
28
+ hashRawSync(password, options) {
29
+ calls.push({ ...options, passwordLength: password.byteLength });
30
+ return new Uint8Array(options.outputLen).fill(7);
31
+ },
32
+ };
33
+ };
34
+
35
+ describe('argon2idDeriverFrom', () => {
36
+ it('passes the envelope’s parameters and version through unchanged', () => {
37
+ const binding = recordingBinding();
38
+ const key = deriveArgon2idKey(
39
+ {
40
+ password: new Uint8Array([1, 2, 3]),
41
+ salt: new Uint8Array(16),
42
+ params: { m: 1024, t: 2, p: 1 },
43
+ keyLength: 32,
44
+ version: 0x10,
45
+ label: 'test',
46
+ },
47
+ argon2idDeriverFrom(binding),
48
+ );
49
+
50
+ expect(binding.calls[0]).toMatchObject({
51
+ memoryCost: 1024,
52
+ timeCost: 2,
53
+ parallelism: 1,
54
+ outputLen: 32,
55
+ algorithm: ARGON2ID_ALGORITHM,
56
+ // 0x10 and 0x13 produce DIFFERENT bytes; honoring the envelope's value is
57
+ // the whole reason the version is recorded per envelope.
58
+ version: 0x10,
59
+ });
60
+ expect(key.byteLength).toBe(32);
61
+ key.destroy();
62
+ });
63
+
64
+ it('defaults to argon2 v1.3 when the caller pins no version', () => {
65
+ const binding = recordingBinding();
66
+ deriveArgon2idKey(
67
+ { password: new Uint8Array([1]), salt: new Uint8Array(16), params: { m: 8, t: 1, p: 1 }, keyLength: 32, label: 'x' },
68
+ argon2idDeriverFrom(binding),
69
+ ).destroy();
70
+ expect(binding.calls[0]?.version).toBe(0x13);
71
+ });
72
+
73
+ it('refuses a binding that hands back a string', () => {
74
+ const stringy = {
75
+ hashRawSync: () => '$argon2id$v=19$m=65536,t=3,p=1$...' as unknown as Uint8Array,
76
+ } satisfies RawArgon2idBinding;
77
+
78
+ expect(() =>
79
+ deriveArgon2idKey(
80
+ { password: new Uint8Array([1]), salt: new Uint8Array(16), params: { m: 8, t: 1, p: 1 }, keyLength: 32, label: 'x' },
81
+ argon2idDeriverFrom(stringy),
82
+ ),
83
+ ).toThrow(/raw bytes/);
84
+ });
85
+
86
+ it('registerArgon2idBinding arms the seam and proves it', () => {
87
+ expect(hasArgon2idDeriver()).toBe(false);
88
+ registerArgon2idBinding(recordingBinding());
89
+ expect(hasArgon2idDeriver()).toBe(true);
90
+ });
91
+ });
92
+
93
+ describe('createSessionSigner', () => {
94
+ const identity = {
95
+ tenantId: 't_a',
96
+ userId: 'u_1',
97
+ role: 'OWNER' as const,
98
+ permissions: ['infra:write'],
99
+ privateScope: true,
100
+ };
101
+
102
+ it('mints a token its own verifier accepts', () => {
103
+ const signer = createSessionSigner();
104
+ const { token, claims } = signer.mint(identity);
105
+
106
+ const result = signer.verify(token);
107
+ expect(result.ok).toBe(true);
108
+ expect(result.ok && result.claims.jti).toBe(claims.jti);
109
+ });
110
+
111
+ it('rejects a token signed by a different key', () => {
112
+ const a = createSessionSigner();
113
+ const b = createSessionSigner();
114
+ const { token } = a.mint(identity);
115
+
116
+ expect(b.verify(token)).toMatchObject({ ok: false, failure: 'invalid-signature' });
117
+ });
118
+
119
+ it('rejects a tampered payload', () => {
120
+ const signer = createSessionSigner();
121
+ const { token } = signer.mint({ ...identity, permissions: [] });
122
+ const [, signature] = token.split('.');
123
+ const forged = Buffer.from(JSON.stringify({ ...identity, exp: 9_999_999_999, jti: 'x' })).toString('base64url');
124
+
125
+ expect(signer.verify(`${forged}.${signature}`)).toMatchObject({ ok: false, failure: 'invalid-signature' });
126
+ });
127
+
128
+ it('reports missing and malformed tokens distinctly', () => {
129
+ const signer = createSessionSigner();
130
+ expect(signer.verify(undefined)).toMatchObject({ failure: 'missing' });
131
+ expect(signer.verify('no-dot')).toMatchObject({ failure: 'malformed' });
132
+ });
133
+
134
+ it('treats expiry as expiry, whether or not anyone revoked it', () => {
135
+ let now = 1_000_000_000_000;
136
+ const signer = createSessionSigner({ ttlSeconds: 60, now: () => now });
137
+ const { token } = signer.mint(identity);
138
+
139
+ now += 61_000;
140
+ expect(signer.verify(token)).toMatchObject({ ok: false, failure: 'expired' });
141
+ });
142
+
143
+ it('honors a revoked jti until it would have expired, then forgets it', () => {
144
+ let now = 1_000_000_000_000;
145
+ const signer = createSessionSigner({ ttlSeconds: 60, now: () => now });
146
+ const { token, claims } = signer.mint(identity);
147
+
148
+ signer.revoke(claims.jti);
149
+ expect(signer.verify(token)).toMatchObject({ ok: false, failure: 'revoked' });
150
+ expect(signer.activeRevocations()).toContain(claims.jti);
151
+
152
+ // Past the horizon the entry is pointless — the signature check refuses the
153
+ // token anyway, so keeping it would be an unbounded leak.
154
+ now += 121_000;
155
+ expect(signer.activeRevocations()).toEqual([]);
156
+ });
157
+
158
+ it('records a revocation for a jti it never minted', () => {
159
+ // Another node in the fleet locked; this process must honor it.
160
+ const signer = createSessionSigner();
161
+ signer.revoke('jti_from_elsewhere');
162
+ expect(signer.isRevoked('jti_from_elsewhere')).toBe(true);
163
+ });
164
+
165
+ it('rejects a token carrying an unknown claim key', () => {
166
+ const signer = createSessionSigner();
167
+ const { token } = signer.mint(identity);
168
+ const [payload, signature] = token.split('.');
169
+ const decoded = JSON.parse(Buffer.from(payload as string, 'base64url').toString('utf8')) as Record<string, unknown>;
170
+ decoded.isAdmin = true;
171
+ const tampered = Buffer.from(JSON.stringify(decoded)).toString('base64url');
172
+
173
+ // Signature fails first here, but the `.strict()` parse is the backstop that
174
+ // stops a version-skewed token from smuggling an unrecognized grant.
175
+ expect(signer.verify(`${tampered}.${signature}`).ok).toBe(false);
176
+ });
177
+
178
+ it('a public-key-only signer verifies but refuses to mint', () => {
179
+ const minter = createSessionSigner();
180
+ const { token } = minter.mint(identity);
181
+ const verifier = createSessionSigner({ publicKey: minter.publicKeyPem() });
182
+
183
+ expect(verifier.verify(token).ok).toBe(true);
184
+ expect(() => verifier.mint(identity)).toThrow(/only a public key/);
185
+ });
186
+ });
187
+
188
+ describe('createSessionSigner — cross-node revocation store (03 §4a)', () => {
189
+ const identity = {
190
+ tenantId: 't_a',
191
+ userId: 'u_1',
192
+ role: 'OWNER' as const,
193
+ permissions: ['infra:write'],
194
+ privateScope: true,
195
+ };
196
+
197
+ it('the two-signer replay: revoke on A, verify refuses on B through the shared store (V7.3)', async () => {
198
+ // The exact window the consolidation promised closed: node A locks a session,
199
+ // node B is still serving requests with the same token. One shared store,
200
+ // two signers over one keypair, in one process.
201
+ const store = createMemoryAsyncRevocationStore();
202
+ const pair = generateKeyPairSync('ed25519');
203
+ const nodeA = createSessionSigner({ privateKey: pair.privateKey, revocationStore: store });
204
+ const nodeB = createSessionSigner({ publicKey: pair.publicKey, revocationStore: store });
205
+
206
+ const { token, claims } = nodeA.mint(identity);
207
+
208
+ nodeA.revoke(claims.jti); // the lock lands on A
209
+ await nodeA.flushRevocations(); // the write reaches the shared store
210
+
211
+ // B's very FIRST sight of the now-revoked jti is a cache miss: it answers
212
+ // from what it can prove locally (a valid signature, not yet known revoked)
213
+ // and reads the store off the hot path. This one-request window IS the seam's
214
+ // documented latency, asserted rather than hidden.
215
+ expect(nodeB.verify(token).ok).toBe(true);
216
+ await nodeB.flushRevocations(); // the read settles → B has learned it
217
+
218
+ // From here B refuses it, and keeps refusing without touching the store again.
219
+ expect(nodeB.verify(token)).toMatchObject({ ok: false, failure: 'revoked' });
220
+ expect(nodeB.isRevoked(claims.jti)).toBe(true);
221
+ });
222
+
223
+ it('revokeUser closes every session it minted for a user, fleet-wide through the store (02 §3)', async () => {
224
+ // The primitive behind a role change taking effect NOW: routes authorize
225
+ // from the token's claims, so a demotion that leaves the user's live tokens
226
+ // alive is a demotion that did not happen. Two devices here, plus a second
227
+ // node that must learn about both.
228
+ const store = createMemoryAsyncRevocationStore();
229
+ const pair = generateKeyPairSync('ed25519');
230
+ const nodeA = createSessionSigner({ privateKey: pair.privateKey, revocationStore: store });
231
+ const nodeB = createSessionSigner({ publicKey: pair.publicKey, revocationStore: store });
232
+
233
+ const laptop = nodeA.mint(identity);
234
+ const phone = nodeA.mint(identity);
235
+ const someoneElse = nodeA.mint({ ...identity, userId: 'u_2' });
236
+
237
+ const closed = nodeA.revokeUser('u_1');
238
+ expect([...closed].sort()).toEqual([laptop.claims.jti, phone.claims.jti].sort());
239
+ expect(nodeA.verify(laptop.token)).toMatchObject({ ok: false, failure: 'revoked' });
240
+ expect(nodeA.verify(phone.token)).toMatchObject({ ok: false, failure: 'revoked' });
241
+ // Another user's session is untouched — this is a per-user revoke, not a purge.
242
+ expect(nodeA.verify(someoneElse.token).ok).toBe(true);
243
+
244
+ // Each revoked jti wrote through, so the other node refuses them too.
245
+ await nodeA.flushRevocations();
246
+ nodeB.verify(laptop.token);
247
+ nodeB.verify(phone.token);
248
+ await nodeB.flushRevocations();
249
+ expect(nodeB.verify(laptop.token)).toMatchObject({ ok: false, failure: 'revoked' });
250
+ expect(nodeB.verify(phone.token)).toMatchObject({ ok: false, failure: 'revoked' });
251
+
252
+ // A user with no live session is a no-op, not a throw.
253
+ expect(nodeA.revokeUser('u_nobody')).toEqual([]);
254
+ });
255
+
256
+ it('sessionsOf lists the live sessions per device, dropping expired and revoked ones (02 §4)', () => {
257
+ let now = 1_700_000_000_000;
258
+ const signer = createSessionSigner({ ttlSeconds: 300, now: () => now });
259
+
260
+ const laptop = signer.mint(identity, { deviceLabel: 'laptop' });
261
+ now += 1000;
262
+ const phone = signer.mint(identity, { deviceLabel: 'phone' });
263
+ // A label is DISPLAY metadata, never a claim: the signed payload is exactly
264
+ // the strict claims schema, so a client-supplied string cannot ride inside it.
265
+ expect(Object.keys(laptop.claims)).not.toContain('deviceLabel');
266
+ // …and an over-long one is truncated rather than rejected (a login must not
267
+ // fail because a browser sent a long device string).
268
+ const verbose = signer.mint({ ...identity, userId: 'u_2' }, { deviceLabel: 'x'.repeat(200) });
269
+ expect(signer.sessionsOf('u_2')[0]?.deviceLabel).toHaveLength(64);
270
+ expect(verbose.claims.userId).toBe('u_2');
271
+
272
+ // Newest first, and only this user's.
273
+ expect(signer.sessionsOf('u_1').map((s) => s.deviceLabel)).toEqual(['phone', 'laptop']);
274
+ expect(signer.sessionsOf('u_1').every((s) => s.tenantId === identity.tenantId)).toBe(true);
275
+
276
+ // A revoked session is a dead session — listing it would offer "log this
277
+ // device out" on a device that is already out.
278
+ signer.revoke(phone.claims.jti);
279
+ expect(signer.sessionsOf('u_1').map((s) => s.jti)).toEqual([laptop.claims.jti]);
280
+
281
+ // And an expired one drops out on its own.
282
+ now += 400_000;
283
+ expect(signer.sessionsOf('u_1')).toEqual([]);
284
+ });
285
+
286
+ it('write-through: a revoke reaches the shared store, refused HERE immediately regardless', async () => {
287
+ const store = createMemoryAsyncRevocationStore();
288
+ const signer = createSessionSigner({ revocationStore: store });
289
+ const { token, claims } = signer.mint(identity);
290
+
291
+ signer.revoke(claims.jti);
292
+ // Local refusal is synchronous — the store write has not even resolved yet.
293
+ expect(signer.verify(token)).toMatchObject({ ok: false, failure: 'revoked' });
294
+
295
+ await signer.flushRevocations();
296
+ expect(await store.get(claims.jti)).not.toBeNull();
297
+ });
298
+
299
+ it('bounds cross-node staleness by the cache TTL, then re-reads', async () => {
300
+ let now = 1_000_000_000_000;
301
+ const store = createMemoryAsyncRevocationStore();
302
+ const verifier = createSessionSigner({
303
+ publicKey: createSessionSigner().publicKeyPem(), // any key — signature isn't the subject here
304
+ revocationStore: store,
305
+ revocationCacheTtlMs: 5000,
306
+ now: () => now,
307
+ });
308
+ // A token minted by a matching minter.
309
+ const minterPair = generateKeyPairSync('ed25519');
310
+ const minter = createSessionSigner({ privateKey: minterPair.privateKey, now: () => now });
311
+ const reader = createSessionSigner({
312
+ publicKey: minterPair.publicKey,
313
+ revocationStore: store,
314
+ revocationCacheTtlMs: 5000,
315
+ now: () => now,
316
+ });
317
+ const { token, claims } = minter.mint({ ...identity, exp: Math.floor(now / 1000) + 3600 });
318
+
319
+ // First verify populates a "not revoked" cache entry at t0.
320
+ expect(reader.verify(token).ok).toBe(true);
321
+ await reader.flushRevocations();
322
+
323
+ // A peer revokes directly in the store (as node A would, via its own signer).
324
+ await store.set(claims.jti, Math.floor(now / 1000) + 3600);
325
+
326
+ // WITHIN the TTL the cached "not revoked" verdict still stands — this is the
327
+ // bounded-staleness window the seam documents, not a bug.
328
+ now += 4000;
329
+ expect(reader.verify(token).ok).toBe(true);
330
+ await reader.flushRevocations();
331
+ expect(reader.verify(token).ok).toBe(true);
332
+
333
+ // PAST the TTL the next verify re-reads the store and picks up the revocation.
334
+ now += 2000; // total 6s > 5s TTL
335
+ expect(reader.verify(token).ok).toBe(true); // the triggering verify still answers from stale cache
336
+ await reader.flushRevocations(); // the re-read settles
337
+ expect(reader.verify(token)).toMatchObject({ ok: false, failure: 'revoked' });
338
+
339
+ void verifier; // constructed to prove the option shape; the reader is the subject
340
+ });
341
+
342
+ it('prunes the shared store of entries past their exp', async () => {
343
+ let now = 1_000_000_000_000;
344
+ const store = createMemoryAsyncRevocationStore();
345
+ const signer = createSessionSigner({ ttlSeconds: 60, revocationStore: store, now: () => now });
346
+
347
+ signer.revoke('jti_short');
348
+ await signer.flushRevocations();
349
+ expect(store.size()).toBe(1);
350
+
351
+ // Past the horizon, flush prunes the shared store just as the local Map is
352
+ // pruned — a denylist that only grows is a leak wearing a security hat.
353
+ now += 121_000;
354
+ await signer.flushRevocations();
355
+ expect(store.size()).toBe(0);
356
+ });
357
+
358
+ it('surfaces a failing store write without throwing out of revoke', async () => {
359
+ const errors: string[] = [];
360
+ const brokenStore = {
361
+ async set() {
362
+ throw new Error('redis down');
363
+ },
364
+ async get() {
365
+ return null;
366
+ },
367
+ async prune() {
368
+ return 0;
369
+ },
370
+ };
371
+ const signer = createSessionSigner({
372
+ revocationStore: brokenStore,
373
+ onRevocationStoreError: (op) => errors.push(op),
374
+ });
375
+ const { token, claims } = signer.mint(identity);
376
+
377
+ // revoke must never throw on the request path — the local Map still refuses.
378
+ expect(() => signer.revoke(claims.jti)).not.toThrow();
379
+ expect(signer.verify(token)).toMatchObject({ ok: false, failure: 'revoked' });
380
+ await signer.flushRevocations();
381
+ expect(errors).toContain('set');
382
+ });
383
+
384
+ it('behaves exactly as before when no store is wired (local profile)', () => {
385
+ const signer = createSessionSigner();
386
+ const { token, claims } = signer.mint(identity);
387
+ signer.revoke(claims.jti);
388
+ expect(signer.verify(token)).toMatchObject({ ok: false, failure: 'revoked' });
389
+ });
390
+ });
@@ -0,0 +1,6 @@
1
+ // The GuardEngine server wiring (02 §4): the argon2id binding adapter that arms
2
+ // cwip's KDF seam, and the Ed25519 session signer that mints and revokes.
3
+ export * from './argon2Binding';
4
+ export * from './verifier';
5
+ export * from './revocationStore';
6
+ export * from './sessionSigner';
@@ -0,0 +1,283 @@
1
+ /**
2
+ * The off-thread KDF path (05 §B6).
3
+ *
4
+ * Three things have to be true, and only the first is obvious:
5
+ *
6
+ * 1. the worker computes the SAME answer as the in-process path — an envelope
7
+ * sealed on one must open on the other, or turning the worker on would lock
8
+ * every existing user out;
9
+ * 2. no key material crosses the thread boundary, and the password does not
10
+ * outlive the post on either side;
11
+ * 3. the failure modes are loud. A worker that dies, hangs, or is disposed
12
+ * mid-flight must reject — the alternative is a login that never answers,
13
+ * which is indistinguishable from a hung request until the socket times out.
14
+ *
15
+ * The protocol cases drive an injected fake worker (no thread, no native argon2)
16
+ * because they are about plumbing, and paying a 250 ms derivation per case to
17
+ * test message routing would buy nothing. The interoperability case uses a REAL
18
+ * OS thread and the REAL binding, because that is the claim that matters and a
19
+ * fake cannot make it.
20
+ */
21
+ import { describe, expect, it } from 'bun:test';
22
+ import { setArgon2idDeriver } from 'cwip/guard';
23
+ import {
24
+ createKdfWorkerPasswordVerifier,
25
+ GUARD_KDF_WORKER_FAILED,
26
+ GUARD_KDF_WORKER_TIMEOUT,
27
+ type KdfWorkerRequest,
28
+ type KdfWorkerResponse,
29
+ kdfWorkerEntryUrl,
30
+ serveKdfWorker,
31
+ } from './kdfWorker';
32
+ import { createInProcessPasswordVerifier, verifyPasswordEnvelope } from './passwordVerifier';
33
+ import { registerNodeRsArgon2id } from './argon2Binding';
34
+
35
+ const password = (value: string): Uint8Array => new TextEncoder().encode(value);
36
+
37
+ /**
38
+ * A fake worker whose behavior the case chooses. `handle` returning `undefined`
39
+ * models a thread that never answers.
40
+ */
41
+ const fakeWorker = (handle: (request: KdfWorkerRequest) => KdfWorkerResponse | undefined) => {
42
+ const messageHandlers: ((data: unknown) => void)[] = [];
43
+ const errorHandlers: ((error: unknown) => void)[] = [];
44
+ const seen: KdfWorkerRequest[] = [];
45
+ return {
46
+ seen,
47
+ fail: (error: unknown) => {
48
+ for (const handler of errorHandlers) handler(error);
49
+ },
50
+ /** Push a reply in whatever order the case wants. */
51
+ deliver: (response: KdfWorkerResponse) => {
52
+ for (const handler of messageHandlers) handler(response);
53
+ },
54
+ spawn: () => ({
55
+ async post(data: unknown) {
56
+ const request = data as KdfWorkerRequest;
57
+ seen.push(request);
58
+ const response = handle(request);
59
+ if (response) for (const handler of messageHandlers) handler(response);
60
+ },
61
+ onMessage: (handler: (data: unknown) => void) => messageHandlers.push(handler),
62
+ onError: (handler: (error: unknown) => void) => errorHandlers.push(handler),
63
+ terminate: () => undefined,
64
+ }),
65
+ };
66
+ };
67
+
68
+ describe('createKdfWorkerPasswordVerifier — the protocol', () => {
69
+ it('zeroizes the host’s password copy once the thread has it, not when the verdict returns', async () => {
70
+ // The whole reason `HostWorker.post` is awaitable. The buffer the caller
71
+ // handed over must be blank by the time this assertion runs, which is while
72
+ // the derivation would still be in flight on a real thread — that window is
73
+ // where a heap snapshot or a crash dump would otherwise catch a plaintext
74
+ // password.
75
+ const buffer = password('hunter2-hunter2');
76
+ let observedByWorker: Uint8Array | null = null;
77
+ const fake = fakeWorker((request) => {
78
+ observedByWorker = new Uint8Array(request.password);
79
+ return { id: request.id, ok: true, value: true };
80
+ });
81
+
82
+ const port = createKdfWorkerPasswordVerifier({ entry: 'unused', spawn: fake.spawn });
83
+ await port.verify('u1', '{}', buffer);
84
+
85
+ expect(buffer.every((byte) => byte === 0)).toBe(true);
86
+ // …and the worker saw the REAL bytes, not the zeroed ones. Without the
87
+ // awaitable post this assertion is what fails: the queue would have handed
88
+ // over a buffer the client had already blanked, and every password would
89
+ // have derived from zeros.
90
+ expect(observedByWorker).not.toBeNull();
91
+ expect(new TextDecoder().decode(observedByWorker as unknown as Uint8Array)).toBe('hunter2-hunter2');
92
+ });
93
+
94
+ it('routes each verdict to its own caller when replies arrive OUT OF ORDER', async () => {
95
+ // One thread serving many logins is the entire point of a long-lived worker,
96
+ // and derivations do not finish in the order they started. A client that
97
+ // matched replies by arrival order rather than by id would hand one user's
98
+ // login the other user's verdict — an authentication bypass, not a mix-up.
99
+ // So: two requests, replies delivered in reverse, opposite answers.
100
+ const held: KdfWorkerRequest[] = [];
101
+ const fake = fakeWorker((request) => {
102
+ held.push(request);
103
+ return undefined;
104
+ });
105
+ const port = createKdfWorkerPasswordVerifier({ entry: 'unused', spawn: fake.spawn });
106
+
107
+ const first = port.verify('right-password', '{}', password('a'));
108
+ const second = port.verify('wrong-password', '{}', password('b'));
109
+ await Promise.resolve();
110
+ expect(held).toHaveLength(2);
111
+
112
+ for (const request of [...held].reverse())
113
+ fake.deliver({ id: request.id, ok: true, value: request.userId === 'right-password' });
114
+
115
+ expect(await first).toBe(true);
116
+ expect(await second).toBe(false);
117
+ });
118
+
119
+ it('rejects every request in flight when the thread dies', async () => {
120
+ const fake = fakeWorker(() => undefined);
121
+ const port = createKdfWorkerPasswordVerifier({ entry: 'unused', spawn: fake.spawn });
122
+
123
+ const inFlight = port.verify('u1', '{}', password('x'));
124
+ await Promise.resolve();
125
+ fake.fail(new Error('worker exited with code 1'));
126
+
127
+ await expect(inFlight).rejects.toThrow(/KDF worker failed/);
128
+ });
129
+
130
+ it('times out rather than hanging a login forever', async () => {
131
+ const port = createKdfWorkerPasswordVerifier({
132
+ entry: 'unused',
133
+ timeoutMs: 10,
134
+ spawn: fakeWorker(() => undefined).spawn,
135
+ });
136
+ await expect(port.verify('u1', '{}', password('x'))).rejects.toThrow(/did not answer within 10ms/);
137
+ });
138
+
139
+ it('refuses new work after dispose, and fails what was already in flight', async () => {
140
+ const port = createKdfWorkerPasswordVerifier({ entry: 'unused', spawn: fakeWorker(() => undefined).spawn });
141
+ const inFlight = port.verify('u1', '{}', password('x'));
142
+ await Promise.resolve();
143
+
144
+ await port.dispose?.();
145
+
146
+ await expect(inFlight).rejects.toThrow(/disposed while a request was in flight/);
147
+ await expect(port.verify('u1', '{}', password('y'))).rejects.toThrow(/has been disposed/);
148
+ });
149
+
150
+ it('preserves the worker’s own error code instead of flattening it to a thread failure', async () => {
151
+ // A corrupt stored envelope and a dead thread are different incidents with
152
+ // different responses; a client that reported both as GUARD_KDF_WORKER_FAILED
153
+ // would send an operator chasing a worker that is perfectly healthy.
154
+ const port = createKdfWorkerPasswordVerifier({
155
+ entry: 'unused',
156
+ spawn: fakeWorker((request) => ({
157
+ id: request.id,
158
+ ok: false,
159
+ code: 'SECRET_ENVELOPE_MALFORMED',
160
+ message: 'not an envelope',
161
+ })).spawn,
162
+ });
163
+
164
+ await expect(port.verify('u1', 'garbage', password('x'))).rejects.toMatchObject({
165
+ code: 'SECRET_ENVELOPE_MALFORMED',
166
+ });
167
+ expect(GUARD_KDF_WORKER_FAILED).toBe('GUARD_KDF_WORKER_FAILED');
168
+ expect(GUARD_KDF_WORKER_TIMEOUT).toBe('GUARD_KDF_WORKER_TIMEOUT');
169
+ });
170
+
171
+ it('refuses a reply of the wrong shape rather than handing a caller a lie', async () => {
172
+ // `verify` promising `boolean` and returning a string would be truthy at
173
+ // every call site in the product — i.e. a universal password accept.
174
+ const port = createKdfWorkerPasswordVerifier({
175
+ entry: 'unused',
176
+ spawn: fakeWorker((request) => ({ id: request.id, ok: true, value: 'yes' })).spawn,
177
+ });
178
+ await expect(port.verify('u1', '{}', password('x'))).rejects.toThrow(/non-boolean verdict/);
179
+ });
180
+ });
181
+
182
+ describe('serveKdfWorker — the worker loop', () => {
183
+ /** Drive the loop directly, with a fake scope and a cheap fake KDF. */
184
+ const driveLoop = async (): Promise<{
185
+ send: (request: KdfWorkerRequest) => Promise<KdfWorkerResponse>;
186
+ }> => {
187
+ let onMessage: ((data: unknown) => void) | null = null;
188
+ const outbox: KdfWorkerResponse[] = [];
189
+ await serveKdfWorker({
190
+ scope: {
191
+ postMessage: (data) => outbox.push(data as KdfWorkerResponse),
192
+ onMessage: (handler) => {
193
+ onMessage = handler;
194
+ },
195
+ },
196
+ // A deterministic stand-in: the loop under test is the message plumbing
197
+ // and the zeroize, not argon2 itself (which the KAT covers).
198
+ register: async () => {
199
+ setArgon2idDeriver(({ password: pw, salt, keyLength }) => {
200
+ const out = new Uint8Array(keyLength);
201
+ for (let i = 0; i < keyLength; i += 1) out[i] = ((pw[i % pw.length] ?? 0) ^ (salt[i % salt.length] ?? 0)) & 0xff;
202
+ return out;
203
+ });
204
+ },
205
+ });
206
+ return {
207
+ send: async (request) => {
208
+ outbox.length = 0;
209
+ (onMessage as unknown as (data: unknown) => void)(request);
210
+ return outbox[0] as KdfWorkerResponse;
211
+ },
212
+ };
213
+ };
214
+
215
+ it('seals and verifies, and a wrong password is a false rather than a throw', async () => {
216
+ const loop = await driveLoop();
217
+
218
+ const sealed = await loop.send({ id: 1, kind: 'seal', userId: 'u1', password: password('right') });
219
+ expect(sealed.ok).toBe(true);
220
+ const envelopeJson = (sealed as { value: string }).value;
221
+
222
+ const good = await loop.send({ id: 2, kind: 'verify', userId: 'u1', envelopeJson, password: password('right') });
223
+ expect(good).toMatchObject({ id: 2, ok: true, value: true });
224
+
225
+ const bad = await loop.send({ id: 3, kind: 'verify', userId: 'u1', envelopeJson, password: password('wrong') });
226
+ expect(bad).toMatchObject({ id: 3, ok: true, value: false });
227
+
228
+ setArgon2idDeriver(null);
229
+ });
230
+
231
+ it('zeroizes its own copy of the password even when the verify throws', async () => {
232
+ const loop = await driveLoop();
233
+ const buffer = password('secret-value');
234
+ // A malformed envelope is corruption, not a wrong password: it throws, and
235
+ // that is precisely the path on which a `finally`-less loop would leave a
236
+ // plaintext password sitting in worker RAM.
237
+ const response = await loop.send({ id: 1, kind: 'verify', userId: 'u1', envelopeJson: 'not json', password: buffer });
238
+ expect(response.ok).toBe(false);
239
+ expect(buffer.every((byte) => byte === 0)).toBe(true);
240
+ setArgon2idDeriver(null);
241
+ });
242
+
243
+ it('ignores a message with no request id instead of crashing the thread', async () => {
244
+ const loop = await driveLoop();
245
+ const response = await loop.send({ kind: 'verify' } as unknown as KdfWorkerRequest);
246
+ expect(response).toBeUndefined();
247
+ setArgon2idDeriver(null);
248
+ });
249
+ });
250
+
251
+ describe('the real thread — interoperability with the in-process path', () => {
252
+ it('opens an envelope the in-process path sealed, and vice versa', async () => {
253
+ // THE claim that makes this option safe to turn on in production: the worker
254
+ // is a different thread, not a different KDF. If these two ever disagreed,
255
+ // enabling the worker would reject every existing user's password and
256
+ // sealing on it would produce envelopes the local path cannot open.
257
+ await registerNodeRsArgon2id();
258
+ const inProcess = createInProcessPasswordVerifier();
259
+ const worker = createKdfWorkerPasswordVerifier({ entry: kdfWorkerEntryUrl(), timeoutMs: 30_000 });
260
+
261
+ try {
262
+ const sealedHere = await inProcess.seal('u-shared', new TextEncoder().encode('correct horse'));
263
+ expect(await worker.verify('u-shared', sealedHere, new TextEncoder().encode('correct horse'))).toBe(true);
264
+ expect(await worker.verify('u-shared', sealedHere, new TextEncoder().encode('wrong horse'))).toBe(false);
265
+
266
+ const sealedThere = await worker.seal('u-shared', new TextEncoder().encode('battery staple'));
267
+ expect(verifyPasswordEnvelope('u-shared', sealedThere, new TextEncoder().encode('battery staple'))).toBe(true);
268
+
269
+ // The user binding survives the trip: the envelope the WORKER sealed is
270
+ // bound to `cc-pw:u-shared` by its AAD, so reading it for another user is
271
+ // refused outright rather than answered `false`. That distinction is
272
+ // deliberate upstream — a relocated envelope is tampering, not a wrong
273
+ // password — and a worker that dropped `userId` on the wire would have
274
+ // sealed it to the wrong subject with nothing here to notice.
275
+ expect(() => verifyPasswordEnvelope('u-other', sealedThere, new TextEncoder().encode('battery staple'))).toThrow(
276
+ /bound to "cc-pw:u-shared"/,
277
+ );
278
+ } finally {
279
+ await worker.dispose?.();
280
+ setArgon2idDeriver(null);
281
+ }
282
+ }, 60_000);
283
+ });