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,367 @@
1
+ /**
2
+ * The off-thread KDF (05 §B6) — a long-lived worker that owns every byte of key
3
+ * material it produces.
4
+ *
5
+ * ## The problem
6
+ *
7
+ * argon2id at the platform parameters is ~150–250 ms of *pure CPU*. The KDF seam
8
+ * is synchronous by design (see `Argon2idDeriver` — the async bindings return
9
+ * un-zeroizable PHC strings), so that time is spent on the event loop with
10
+ * nothing else able to run. There is no race and no correctness bug; there is a
11
+ * hard throughput ceiling. A saas node handling a burst of logins serializes
12
+ * them AND stalls every unrelated request for the duration, so one tenant's
13
+ * login storm is every other tenant's latency spike.
14
+ *
15
+ * ## The design, and the one thing that makes it worth building
16
+ *
17
+ * The naive off-thread KDF derives in a worker and posts the KEY BACK. That
18
+ * moves the CPU but weakens custody: the key crosses a structured-clone boundary
19
+ * (a copy the sender cannot zeroize, because clone semantics leave the original
20
+ * behind and the receiver adopts a second allocation), and it lands in a host
21
+ * that must remember to destroy it.
22
+ *
23
+ * **Nothing here posts a key.** The worker runs the SHIPPED
24
+ * `sealPasswordVerifier` / `verifyPasswordEnvelope` — the same functions, the
25
+ * same envelope format, the same one KDF — so the derived key is born, used and
26
+ * zeroized entirely inside the worker thread. What crosses back is a boolean or
27
+ * the sealed *ciphertext*. The password crosses inward, because it arrives from
28
+ * the request and must; the worker zeroizes its copy on every path, and the host
29
+ * zeroizes its own.
30
+ *
31
+ * That is why this is a worker for the whole VERIFY rather than a worker for the
32
+ * derivation: relocating the derivation alone would have traded a throughput
33
+ * problem for a custody problem.
34
+ *
35
+ * ## Long-lived, never per-call
36
+ *
37
+ * Thread startup plus module load (a native argon2 binding!) is tens of
38
+ * milliseconds, and a spawn-per-login design lets a login storm fork unbounded
39
+ * OS threads — which is the same denial of service the ceiling above, only
40
+ * worse. One thread, multiplexed by request id. This is the mistake
41
+ * `createMarshalWorkerClient` documents in `core/wire/marshalWorker.ts`; it is
42
+ * repeated here deliberately in the same shape so both read alike.
43
+ */
44
+ import { AppError } from '../errors';
45
+ import {
46
+ createInProcessPasswordVerifier,
47
+ type PasswordVerifierPort,
48
+ sealPasswordVerifier,
49
+ verifyPasswordEnvelope,
50
+ } from './passwordVerifier';
51
+ import { registerNodeRsArgon2id } from './argon2Binding';
52
+
53
+ /** Raised when the worker thread dies, fails to start, or is disposed mid-flight. */
54
+ export const GUARD_KDF_WORKER_FAILED = 'GUARD_KDF_WORKER_FAILED';
55
+ /** Raised when the worker does not answer within the configured budget. */
56
+ export const GUARD_KDF_WORKER_TIMEOUT = 'GUARD_KDF_WORKER_TIMEOUT';
57
+
58
+ /**
59
+ * Default per-request ceiling. Generous relative to a ~250 ms derivation on
60
+ * purpose: this is a liveness backstop for a wedged thread, not a latency SLO,
61
+ * and a tight budget here would turn a loaded machine into a login outage.
62
+ */
63
+ export const KDF_WORKER_DEFAULT_TIMEOUT_MS = 15_000;
64
+
65
+ /* ------------------------------------------------------------- the protocol */
66
+
67
+ export type KdfWorkerRequest =
68
+ | { id: number; kind: 'seal'; userId: string; password: Uint8Array }
69
+ | { id: number; kind: 'verify'; userId: string; envelopeJson: string; password: Uint8Array };
70
+
71
+ export type KdfWorkerResponse =
72
+ | { id: number; ok: true; value: string | boolean }
73
+ | { id: number; ok: false; code: string; message: string };
74
+
75
+ /* ---------------------------------------------------------- the worker side */
76
+
77
+ /** The subset of a worker-thread global this module needs, in either runtime. */
78
+ interface WorkerScope {
79
+ postMessage(data: unknown): void;
80
+ onMessage(handler: (data: unknown) => void): void;
81
+ }
82
+
83
+ const nodeWorkerScope = async (): Promise<WorkerScope> => {
84
+ const { parentPort } = await import('node:worker_threads');
85
+ if (parentPort === null) throw new Error('serveKdfWorker() was called outside a worker thread.');
86
+ return {
87
+ postMessage: (data) => parentPort.postMessage(data),
88
+ onMessage: (handler) => parentPort.on('message', handler),
89
+ };
90
+ };
91
+
92
+ /**
93
+ * Run the KDF worker's message loop. A worker entry module is this call and
94
+ * nothing else (see `kdfWorkerEntry.ts`).
95
+ *
96
+ * `register` is injectable so the spec can drive the loop with a cheap fake
97
+ * binding — registering the real native argon2 in a test that only checks
98
+ * message plumbing would pay 250 ms per case to prove nothing about the
99
+ * plumbing. The DEFAULT is the real binding, because a worker that silently ran
100
+ * a fake KDF would be a catastrophe rather than a bug.
101
+ */
102
+ export async function serveKdfWorker(options: { scope?: WorkerScope; register?: () => Promise<void> } = {}): Promise<void> {
103
+ const scope = options.scope ?? (await nodeWorkerScope());
104
+ // Registration happens ONCE, at worker start, before any request is served —
105
+ // not lazily per request, or the first login of every thread pays a native
106
+ // module load inside its own timeout budget.
107
+ await (options.register ?? registerNodeRsArgon2id)();
108
+
109
+ scope.onMessage((data: unknown) => {
110
+ const request = data as KdfWorkerRequest;
111
+ if (typeof request?.id !== 'number') return;
112
+ // The password arrives as a structured-clone copy owned by THIS thread.
113
+ // Re-wrap anything that is not already a Uint8Array rather than trusting the
114
+ // wire type — a plain `{0:..,1:..}` would silently derive from the wrong
115
+ // bytes instead of failing — but keep the inbound view too, because it is a
116
+ // second plaintext copy this thread is responsible for.
117
+ const inbound = request.password;
118
+ const password = inbound instanceof Uint8Array ? inbound : new Uint8Array(inbound);
119
+ try {
120
+ const value =
121
+ request.kind === 'seal'
122
+ ? sealPasswordVerifier(request.userId, password)
123
+ : verifyPasswordEnvelope(request.userId, request.envelopeJson, password);
124
+ scope.postMessage({ id: request.id, ok: true, value } satisfies KdfWorkerResponse);
125
+ } catch (error) {
126
+ // Only the CODE and message cross back. An error `context` in this module
127
+ // can hold envelope fields, so the envelope itself never leaves.
128
+ scope.postMessage({
129
+ id: request.id,
130
+ ok: false,
131
+ code: (error instanceof AppError ? error.code : undefined) ?? GUARD_KDF_WORKER_FAILED,
132
+ message: error instanceof Error ? error.message : String(error),
133
+ } satisfies KdfWorkerResponse);
134
+ } finally {
135
+ // Both `sealPasswordVerifier` and `verifyPasswordEnvelope` consume (and so
136
+ // zeroize) the buffer they are handed — but they are the far side of a
137
+ // contract, and a throw BEFORE they are reached (a malformed envelope, an
138
+ // unregistered deriver) leaves a plaintext password in worker RAM. Clearing
139
+ // again is idempotent and costs a memset.
140
+ //
141
+ // EVERY view, not just the one that was used: when the wire type is wrong
142
+ // the re-wrap above makes a second allocation, and zeroizing only the copy
143
+ // we derived from would leave the structured clone this thread was handed
144
+ // sitting in memory for the life of the worker.
145
+ password.fill(0);
146
+ if (inbound !== password && inbound instanceof Uint8Array) inbound.fill(0);
147
+ }
148
+ });
149
+ }
150
+
151
+ /* ------------------------------------------------------------- the host side */
152
+
153
+ /**
154
+ * The subset of both runtimes' Worker the client needs.
155
+ *
156
+ * `post` RESOLVES ONCE THE DATA HAS BEEN HANDED TO THE THREAD, and that is not a
157
+ * stylistic choice. A worker is constructed asynchronously, so early requests
158
+ * are queued; the queue holds the caller's `Uint8Array` by reference. A client
159
+ * that zeroized the password right after a fire-and-forget `post()` would blank
160
+ * the buffer the queue is still holding, and the worker would then derive a key
161
+ * from 32 zero bytes — accepting every wrong password that happened to be
162
+ * presented during startup. Awaiting the post is what makes "zeroize as early as
163
+ * possible" and "send the real bytes" compatible.
164
+ */
165
+ interface HostWorker {
166
+ post(data: unknown): Promise<void>;
167
+ onMessage(handler: (data: unknown) => void): void;
168
+ onError(handler: (error: unknown) => void): void;
169
+ terminate(): void;
170
+ }
171
+
172
+ export interface KdfWorkerOptions {
173
+ /** Module specifier / URL of an entry that calls {@link serveKdfWorker}. */
174
+ entry: string | URL;
175
+ /** Per-request ceiling. Defaults to {@link KDF_WORKER_DEFAULT_TIMEOUT_MS}. */
176
+ timeoutMs?: number;
177
+ /** Spawn override — the spec uses it to drive the protocol without a real thread. */
178
+ spawn?: (entry: string | URL) => HostWorker;
179
+ }
180
+
181
+ function spawnHostWorker(entry: string | URL): HostWorker {
182
+ // Callers await `settled` rather than seeing the construction race. It is
183
+ // RESOLVED, never rejected, on every exit path including a failed spawn: a
184
+ // rejection here would surface as an unhandled rejection on a request that the
185
+ // error handler below is already failing properly.
186
+ let realPost: ((data: unknown) => void) | null = null;
187
+ let markSettled: () => void = () => undefined;
188
+ const settled = new Promise<void>((resolve) => {
189
+ markSettled = resolve;
190
+ });
191
+ let terminated = false;
192
+ const messageHandlers: ((data: unknown) => void)[] = [];
193
+ const errorHandlers: ((error: unknown) => void)[] = [];
194
+ let terminateReal: (() => void) | null = null;
195
+
196
+ void (async () => {
197
+ try {
198
+ const { Worker } = await import('node:worker_threads');
199
+ if (terminated) return;
200
+ const worker = new Worker(entry as string | URL);
201
+ worker.on('message', (data: unknown) => {
202
+ for (const handler of messageHandlers) handler(data);
203
+ });
204
+ worker.on('error', (error: unknown) => {
205
+ for (const handler of errorHandlers) handler(error);
206
+ });
207
+ // ANY exit is a failure: this worker is long-lived by design, so it should
208
+ // outlive every request. A cleanly-exited thread that went unnoticed would
209
+ // leave each in-flight login waiting out its full timeout, and every later
210
+ // one posting into a dead thread to do the same.
211
+ worker.on('exit', (code: number) => {
212
+ const how = code === 0 ? 'exited cleanly while still in use' : `exited with code ${code}`;
213
+ for (const handler of errorHandlers) handler(new Error(`KDF worker ${how}`));
214
+ });
215
+ realPost = (data) => worker.postMessage(data);
216
+ terminateReal = () => void worker.terminate();
217
+ } catch (error) {
218
+ for (const handler of errorHandlers) handler(error);
219
+ } finally {
220
+ markSettled();
221
+ }
222
+ })();
223
+
224
+ return {
225
+ async post(data) {
226
+ if (realPost === null) await settled;
227
+ // Still null ⇒ the spawn failed or we were disposed mid-construction. The
228
+ // error handler has already failed (or will fail) the request; dropping
229
+ // the message is correct, and the caller may now zeroize.
230
+ realPost?.(data);
231
+ },
232
+ onMessage: (handler) => messageHandlers.push(handler),
233
+ onError: (handler) => errorHandlers.push(handler),
234
+ terminate: () => {
235
+ terminated = true;
236
+ markSettled();
237
+ terminateReal?.();
238
+ },
239
+ };
240
+ }
241
+
242
+ /**
243
+ * A {@link PasswordVerifierPort} backed by one long-lived worker thread.
244
+ *
245
+ * Hand it to `createAccountService({ passwords })` on a host that expects login
246
+ * concurrency. Local single-user hosts should NOT — they would pay a thread and
247
+ * a native module load to serialize a workload that was never contended.
248
+ */
249
+ export function createKdfWorkerPasswordVerifier(options: KdfWorkerOptions): PasswordVerifierPort {
250
+ const timeoutMs = options.timeoutMs ?? KDF_WORKER_DEFAULT_TIMEOUT_MS;
251
+ const worker = (options.spawn ?? spawnHostWorker)(options.entry);
252
+
253
+ interface Pending {
254
+ resolve(value: string | boolean): void;
255
+ reject(error: unknown): void;
256
+ timer: ReturnType<typeof setTimeout>;
257
+ }
258
+ const pending = new Map<number, Pending>();
259
+ let nextId = 1;
260
+ let disposed = false;
261
+
262
+ const settle = (id: number, apply: (p: Pending) => void): void => {
263
+ const entry = pending.get(id);
264
+ if (!entry) return; // already timed out — a late reply is discarded
265
+ pending.delete(id);
266
+ clearTimeout(entry.timer);
267
+ apply(entry);
268
+ };
269
+
270
+ const failure = (message: string, code: string, status: number): AppError =>
271
+ new AppError(message, { code, status, category: 'security', context: { worker: String(options.entry) } });
272
+
273
+ worker.onMessage((data) => {
274
+ const response = data as KdfWorkerResponse;
275
+ if (typeof response?.id !== 'number') return;
276
+ settle(response.id, (p) => {
277
+ if (response.ok) {
278
+ p.resolve(response.value);
279
+ return;
280
+ }
281
+ // Preserve the worker's own code: `SECRET_ENVELOPE_*` failures are a
282
+ // corrupt row, not a dead thread, and a caller distinguishing them must
283
+ // still be able to.
284
+ p.reject(failure(response.message, response.code, 500));
285
+ });
286
+ });
287
+
288
+ // A dead worker takes every request in flight with it — failing them now beats
289
+ // each one waiting out its own timeout.
290
+ worker.onError((error) => {
291
+ const message = error instanceof Error ? error.message : String(error);
292
+ for (const id of [...pending.keys()])
293
+ settle(id, (p) => p.reject(failure(`KDF worker failed: ${message}`, GUARD_KDF_WORKER_FAILED, 500)));
294
+ });
295
+
296
+ /**
297
+ * Post the request, zeroize the host's copy of the password as soon as the
298
+ * thread has it, then await the verdict.
299
+ *
300
+ * The zeroize placement is the point. The worker holds a structured-clone
301
+ * copy, so the host's buffer is dead weight the instant the post lands —
302
+ * and the await that follows lasts the whole ~250 ms derivation, which is
303
+ * exactly the window in which a heap snapshot, a core dump or a crash reporter
304
+ * would capture a plaintext password. Clearing it before the await instead of
305
+ * in a `finally` after it removes that window entirely.
306
+ */
307
+ const send = async (build: (id: number) => KdfWorkerRequest, password: Uint8Array): Promise<string | boolean> => {
308
+ if (disposed) throw failure('The KDF worker has been disposed.', GUARD_KDF_WORKER_FAILED, 500);
309
+ const id = nextId++;
310
+ const answer = new Promise<string | boolean>((resolve, reject) => {
311
+ const timer = setTimeout(() => {
312
+ pending.delete(id);
313
+ reject(failure(`The KDF worker did not answer within ${timeoutMs}ms.`, GUARD_KDF_WORKER_TIMEOUT, 504));
314
+ }, timeoutMs);
315
+ pending.set(id, { resolve, reject, timer });
316
+ });
317
+
318
+ try {
319
+ await worker.post(build(id));
320
+ } finally {
321
+ password.fill(0);
322
+ }
323
+ return await answer;
324
+ };
325
+
326
+ return {
327
+ async seal(userId, password) {
328
+ const value = await send((id) => ({ id, kind: 'seal', userId, password }), password);
329
+ if (typeof value !== 'string')
330
+ throw failure('The KDF worker returned a non-string envelope for seal().', GUARD_KDF_WORKER_FAILED, 500);
331
+ return value;
332
+ },
333
+
334
+ async verify(userId, envelopeJson, password) {
335
+ const value = await send((id) => ({ id, kind: 'verify', userId, envelopeJson, password }), password);
336
+ if (typeof value !== 'boolean')
337
+ throw failure('The KDF worker returned a non-boolean verdict for verify().', GUARD_KDF_WORKER_FAILED, 500);
338
+ return value;
339
+ },
340
+
341
+ async dispose() {
342
+ disposed = true;
343
+ for (const id of [...pending.keys()])
344
+ settle(id, (p) =>
345
+ p.reject(failure('The KDF worker was disposed while a request was in flight.', GUARD_KDF_WORKER_FAILED, 503)),
346
+ );
347
+ worker.terminate();
348
+ },
349
+ };
350
+ }
351
+
352
+ /**
353
+ * The URL of the entry module {@link createKdfWorkerPasswordVerifier} spawns.
354
+ *
355
+ * Resolved relative to THIS module rather than hard-coded by the host, so an app
356
+ * never has to know where inside the package the entry lives:
357
+ *
358
+ * createKdfWorkerPasswordVerifier({ entry: kdfWorkerEntryUrl() })
359
+ *
360
+ * Under the `bun` export condition this resolves to the TypeScript source, which
361
+ * is what every first-party consumer runs (see the monorepo's zero-build-lag
362
+ * contract). A non-Bun host consuming built `dist/` names its own built entry.
363
+ */
364
+ export const kdfWorkerEntryUrl = (): URL => new URL('./kdfWorkerEntry.ts', import.meta.url);
365
+
366
+ /** Re-exported so a host wiring the guard tier needs one import. */
367
+ export { createInProcessPasswordVerifier, type PasswordVerifierPort };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The SHIPPED KDF worker entry (05 §B6).
3
+ *
4
+ * Unlike a marshalling worker — where every marshaller declares its own entry
5
+ * because every marshaller has its own schema — there is exactly ONE password
6
+ * KDF in the platform (Law #7), so there is exactly one entry and the framework
7
+ * ships it. A host does not write this file; it points
8
+ * `createKdfWorkerPasswordVerifier({ entry: kdfWorkerEntryUrl() })` at it.
9
+ *
10
+ * The whole module is one call on purpose: everything a worker entry does that
11
+ * is not "start the loop" is code running on a thread with a live argon2id
12
+ * binding and a plaintext password in scope.
13
+ */
14
+ import { serveKdfWorker } from './kdfWorker';
15
+
16
+ await serveKdfWorker();
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Password verifiers over the ONE KDF (02 §2).
3
+ *
4
+ * Proves the verifier IS a `SecretEnvelope`: a right password opens it, a wrong
5
+ * one fails GCM authentication (→ `false`, not a throw), the envelope is bound to
6
+ * its user (a lifted envelope is refused), a malformed row is corruption (throws),
7
+ * and every entry point CONSUMES the password buffer (byte custody, 01 §3.4).
8
+ */
9
+ import { describe, expect, it } from 'bun:test';
10
+ import type { Argon2idDeriver } from 'cwip/guard';
11
+ import { sealPasswordVerifier, verifyPasswordEnvelope } from './passwordVerifier';
12
+
13
+ // A deterministic, PASSWORD-SENSITIVE stand-in for argon2id (the real seam mixes
14
+ // far more): the output must depend on the password, or a "wrong password" would
15
+ // derive the same key and the verifier would be meaningless.
16
+ const stubDeriver: Argon2idDeriver = ({ password, salt, keyLength }) => {
17
+ const out = new Uint8Array(keyLength);
18
+ for (let i = 0; i < keyLength; i += 1)
19
+ out[i] = ((password[i % password.length] ?? 0) ^ (salt[i % salt.length] ?? 0) ^ (i & 0xff)) & 0xff;
20
+ return out;
21
+ };
22
+
23
+ const bytes = (value: string): Uint8Array => new TextEncoder().encode(value);
24
+ const allZero = (buffer: Uint8Array): boolean => buffer.every((b) => b === 0);
25
+
26
+ describe('passwordVerifier', () => {
27
+ it('round-trips: the sealed password verifies', () => {
28
+ const envelope = sealPasswordVerifier('u_ann', bytes('correct horse battery'), stubDeriver);
29
+ expect(verifyPasswordEnvelope('u_ann', envelope, bytes('correct horse battery'), stubDeriver)).toBe(true);
30
+ });
31
+
32
+ it('rejects a wrong password with false (not a throw)', () => {
33
+ const envelope = sealPasswordVerifier('u_ann', bytes('correct horse battery'), stubDeriver);
34
+ expect(verifyPasswordEnvelope('u_ann', envelope, bytes('wrong password here!!'), stubDeriver)).toBe(false);
35
+ });
36
+
37
+ it('is bound to its user — a lifted envelope is refused, not silently accepted', () => {
38
+ const envelope = sealPasswordVerifier('u_ann', bytes('shared-secret-value'), stubDeriver);
39
+ // Same password, wrong user id: the AAD (`cc-pw:u_ann`) no longer matches, so
40
+ // the envelope refuses rather than authenticating u_eve with u_ann's verifier.
41
+ expect(() => verifyPasswordEnvelope('u_eve', envelope, bytes('shared-secret-value'), stubDeriver)).toThrow(
42
+ /bound to|relocated|SecretEnvelope/i,
43
+ );
44
+ });
45
+
46
+ it('two users with the same password get different envelopes (fresh per-user salt)', () => {
47
+ const a = sealPasswordVerifier('u_a', bytes('identical-pw'), stubDeriver);
48
+ const b = sealPasswordVerifier('u_b', bytes('identical-pw'), stubDeriver);
49
+ expect(a).not.toEqual(b);
50
+ });
51
+
52
+ it('throws on a malformed stored envelope (corruption, never "wrong password")', () => {
53
+ expect(() => verifyPasswordEnvelope('u_ann', '{"not":"an envelope"}', bytes('pw'), stubDeriver)).toThrow();
54
+ });
55
+
56
+ it('CONSUMES the password buffer on seal, on a correct verify, and on a wrong verify', () => {
57
+ const p1 = bytes('to-be-sealed-pw');
58
+ sealPasswordVerifier('u_ann', p1, stubDeriver);
59
+ expect(allZero(p1)).toBe(true);
60
+
61
+ const envelope = sealPasswordVerifier('u_ann', bytes('to-be-sealed-pw'), stubDeriver);
62
+
63
+ const p2 = bytes('to-be-sealed-pw');
64
+ verifyPasswordEnvelope('u_ann', envelope, p2, stubDeriver);
65
+ expect(allZero(p2)).toBe(true);
66
+
67
+ const p3 = bytes('the-wrong-password');
68
+ verifyPasswordEnvelope('u_ann', envelope, p3, stubDeriver);
69
+ expect(allZero(p3)).toBe(true);
70
+ });
71
+ });
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Password verifiers over the ONE KDF (02 §2, Law #7).
3
+ *
4
+ * A login password check needs a *verifier* — something stored per user that a
5
+ * presented password can be checked against — WITHOUT introducing a second hash
6
+ * function or a second at-rest secret format. The platform already ships exactly
7
+ * one of each: the argon2id seam (`cwip/.../guard/kdf.ts`) and the
8
+ * `SecretEnvelope` (`cwip/.../guard/secretEnvelope.ts`). This module builds the
9
+ * verifier out of those, adding nothing.
10
+ *
11
+ * The trick: a verifier is a `SecretEnvelope` that seals a FIXED known plaintext
12
+ * under a key argon2id-derived from the user's password. To check a password,
13
+ * re-derive the one-shot key from the presented password and try to open the
14
+ * envelope — GCM's authentication tag IS the password check. A wrong password
15
+ * derives a wrong key and GCM refuses (`SECRET_ENVELOPE_AUTH_FAILED`), which we
16
+ * translate to `false`; a right password opens and the recovered plaintext
17
+ * matches the known constant. No password hash is ever stored, compared, or even
18
+ * computed outside the KDF, and the envelope is the same `pw_envelope` string the
19
+ * registry `users.pw_envelope` column already types.
20
+ *
21
+ * Two properties that are not incidental:
22
+ *
23
+ * - **The verifier is bound to its user.** The envelope AAD is `cc-pw:<userId>`,
24
+ * so a `pw_envelope` lifted out of user A's row into user B's fails GCM
25
+ * authentication rather than letting B log in with A's password (the same
26
+ * swap defense `SecretEnvelope` gives every secret).
27
+ * - **Byte custody (01 §3.4).** Both entry points CONSUME the password buffer:
28
+ * the underlying manager zeroizes it the moment the KDF is done with it, on
29
+ * every path including a thrown KDF. Callers hand over ownership and must not
30
+ * touch the buffer afterwards.
31
+ */
32
+ import { timingSafeEqual } from 'node:crypto';
33
+ import {
34
+ type Argon2idDeriver,
35
+ createSecretEnvelopeManager,
36
+ mintEnvelopeSalt,
37
+ parseSecretEnvelope,
38
+ serializeSecretEnvelope,
39
+ zeroize,
40
+ } from 'cwip/guard';
41
+ import { isAppError } from '../errors';
42
+
43
+ /**
44
+ * The reserved AAD app namespace for password verifiers. `SecretEnvelope` forbids
45
+ * a ':' in the appId (it separates the AAD fields), so this is a plain token; the
46
+ * userId rides the AAD `key` half.
47
+ */
48
+ const PW_APP = 'cc-pw';
49
+
50
+ /** The fixed plaintext every verifier seals — its recovery on open is the success signal. */
51
+ const VERIFIER_PLAINTEXT = new TextEncoder().encode('cc-guard-pw-verifier-v1');
52
+
53
+ /** Bind an envelope to one user: AAD becomes `cc-pw:<userId>`. */
54
+ const refFor = (userId: string): { appId: string; key: string } => ({ appId: PW_APP, key: userId });
55
+
56
+ /**
57
+ * Seal a password verifier for `userId`, returning the serialized `SecretEnvelope`
58
+ * to store in `users.pw_envelope`. **Consumes `password`** (zeroized before return).
59
+ *
60
+ * A fresh per-user KDF salt is minted for each call, so two users with the same
61
+ * password produce different envelopes and re-deriving one never helps the other.
62
+ */
63
+ export function sealPasswordVerifier(userId: string, password: Uint8Array, deriver?: Argon2idDeriver): string {
64
+ const manager = createSecretEnvelopeManager({ salt: mintEnvelopeSalt(), deriver });
65
+ try {
66
+ // unlock derives the master key from (password, salt) and zeroizes `password`
67
+ // in its own finally — including if the KDF throws.
68
+ manager.unlock(password);
69
+ return serializeSecretEnvelope(manager.seal(refFor(userId), VERIFIER_PLAINTEXT));
70
+ } finally {
71
+ manager.lock();
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Check `password` against the stored `envelopeJson` for `userId`. **Consumes
77
+ * `password`** (zeroized before return). Returns `true` iff the password is
78
+ * correct; `false` for a wrong password (GCM authentication failure). A malformed
79
+ * stored envelope is corruption, not a wrong password, and re-throws.
80
+ *
81
+ * The comparison is constant-time in the sensitive part: argon2id derivation
82
+ * dominates the timing, and GCM tag verification plus the final `timingSafeEqual`
83
+ * are constant-time, so a right and a wrong password of equal length are
84
+ * indistinguishable by timing.
85
+ */
86
+ export function verifyPasswordEnvelope(
87
+ userId: string,
88
+ envelopeJson: string,
89
+ password: Uint8Array,
90
+ deriver?: Argon2idDeriver,
91
+ ): boolean {
92
+ let envelope: ReturnType<typeof parseSecretEnvelope>;
93
+ try {
94
+ envelope = parseSecretEnvelope(envelopeJson);
95
+ } catch (error) {
96
+ // A stored envelope that will not parse is a tampered/corrupt row — never
97
+ // silently treat it as "wrong password". Zeroize the buffer we were handed
98
+ // before propagating (the manager never got a chance to).
99
+ zeroize(password);
100
+ throw error;
101
+ }
102
+
103
+ const manager = createSecretEnvelopeManager({ salt: mintEnvelopeSalt(), deriver });
104
+ let opened: Uint8Array | null = null;
105
+ try {
106
+ // openWithPassphrase re-derives a one-shot key from the envelope's own salt +
107
+ // the presented password, opens, and zeroizes `password` on every path.
108
+ opened = manager.openWithPassphrase(envelope, refFor(userId), password);
109
+ return opened.byteLength === VERIFIER_PLAINTEXT.byteLength && timingSafeEqual(opened, VERIFIER_PLAINTEXT);
110
+ } catch (error) {
111
+ // GCM's rejection is the wrong-password verdict — the ONLY error we swallow.
112
+ if (isAppError(error) && error.code === 'SECRET_ENVELOPE_AUTH_FAILED') return false;
113
+ throw error;
114
+ } finally {
115
+ if (opened) zeroize(opened);
116
+ manager.lock();
117
+ }
118
+ }
119
+
120
+ /* ------------------------------------------------------------------ the port */
121
+
122
+ /**
123
+ * Where password derivation RUNS (05 §B6).
124
+ *
125
+ * The two functions above are synchronous on purpose — the KDF seam is, and for
126
+ * the reason documented on `Argon2idDeriver`: every async argon2id binding hands
127
+ * back a promise AND a base64/PHC *string*, which is key material that cannot be
128
+ * zeroized. The cost is that one derivation is ~150–250 ms of pure CPU on the
129
+ * event loop, so concurrent logins SERIALIZE and stall every unrelated request
130
+ * on the process for that window. A single-user local app never notices; a saas
131
+ * node taking a burst of logins does, and so does every tenant sharing it.
132
+ *
133
+ * This port is the seam that lets a host move that burn off the request thread
134
+ * WITHOUT introducing a second KDF, a second envelope format, or an async
135
+ * deriver. Both implementations call the exact same `sealPasswordVerifier` /
136
+ * `verifyPasswordEnvelope` above; they differ only in which thread runs them.
137
+ *
138
+ * local / default {@link createInProcessPasswordVerifier} — direct calls,
139
+ * byte-for-byte the behavior that shipped before this port
140
+ * existed.
141
+ * saas / opt-in `createKdfWorkerPasswordVerifier` (`./kdfWorker`) — a
142
+ * long-lived worker thread. **No key material crosses the
143
+ * thread boundary in either direction**: the password goes
144
+ * in, the derived key is born, used and zeroized inside the
145
+ * worker, and only a boolean (or the sealed ciphertext)
146
+ * comes back.
147
+ *
148
+ * Both entry points CONSUME `password` — ownership transfers on the call, and
149
+ * the buffer is zeroized on every path including a throw.
150
+ */
151
+ export interface PasswordVerifierPort {
152
+ /** Seal a verifier for `userId`, returning the serialized envelope. Consumes `password`. */
153
+ seal(userId: string, password: Uint8Array): Promise<string>;
154
+ /** `true` iff `password` matches `envelopeJson`. Consumes `password`. */
155
+ verify(userId: string, envelopeJson: string, password: Uint8Array): Promise<boolean>;
156
+ /** Release any thread/handle the implementation owns. In-process: a no-op. */
157
+ dispose?(): Promise<void>;
158
+ }
159
+
160
+ /**
161
+ * The default port: derive on the calling thread, exactly as every caller did
162
+ * before the port existed. Nothing about the crypto changes — this is the same
163
+ * two functions behind a promise, so a host that never opts into the worker
164
+ * path pays no async penalty beyond one already-resolved microtask.
165
+ */
166
+ export function createInProcessPasswordVerifier(deriver?: Argon2idDeriver): PasswordVerifierPort {
167
+ return {
168
+ async seal(userId, password) {
169
+ return sealPasswordVerifier(userId, password, deriver);
170
+ },
171
+ async verify(userId, envelopeJson, password) {
172
+ return verifyPasswordEnvelope(userId, envelopeJson, password, deriver);
173
+ },
174
+ };
175
+ }