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,148 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
3
+ import { Hono } from 'hono';
4
+ import { createKysely } from '../db/kysely';
5
+ import { AUTH_COOKIE_NAME } from './cookie';
6
+ import { getOnlyEnforcement } from './getOnly';
7
+ import { signToken } from './jwt';
8
+ import { createAuthMiddleware } from './middleware';
9
+ import { createSessionStore } from './sessionStore';
10
+ import type { AuthEnv, SessionStore } from './types';
11
+
12
+ const SECRET = 'integration-test-secret-32-chars-min!';
13
+ const REQ = (path: string, init?: RequestInit) => new Request(`http://localhost${path}`, init);
14
+ const withCookie = (token: string): RequestInit => ({
15
+ headers: { cookie: `${AUTH_COOKIE_NAME}=${token}` },
16
+ });
17
+
18
+ const DDL = `CREATE TABLE auth_sessions (
19
+ session_id TEXT PRIMARY KEY, user_id TEXT NOT NULL, created_at TEXT NOT NULL,
20
+ last_seen_at TEXT NOT NULL, refresh_expires_at TEXT NOT NULL, access_expires_at TEXT,
21
+ get_only INTEGER NOT NULL DEFAULT 0, disabled INTEGER NOT NULL DEFAULT 0,
22
+ disabled_at TEXT, disabled_by TEXT, user_agent TEXT, ip TEXT)`;
23
+
24
+ let db: Database;
25
+ let store: SessionStore;
26
+
27
+ const buildApp = () => {
28
+ const app = new Hono<AuthEnv>();
29
+ app.use('*', createAuthMiddleware(store, SECRET));
30
+ app.use('*', getOnlyEnforcement());
31
+ app.get('/me', (c) => {
32
+ const user = c.get('user');
33
+ return c.json({ user, attributedUserId: c.get('userId') });
34
+ });
35
+ app.post('/notes', (c) => c.json({ ok: true }));
36
+ app.get('/ctgrpo/notes', (c) => c.json({ ok: true }));
37
+ return app;
38
+ };
39
+
40
+ beforeEach(() => {
41
+ db = new Database(':memory:');
42
+ db.run(DDL);
43
+ store = createSessionStore(createKysely(db));
44
+ });
45
+ afterEach(() => {
46
+ store.stop();
47
+ db.close();
48
+ });
49
+
50
+ describe('createAuthMiddleware', () => {
51
+ test('valid cookie + live session → sets c.get("user") and userId', async () => {
52
+ await store.create({ sessionId: 's1', userId: 'u1', getOnly: false });
53
+ const token = await signToken(
54
+ { userId: 'u1', sessionId: 's1', roles: ['owner'], getOnly: false },
55
+ SECRET,
56
+ '15m',
57
+ );
58
+ const res = await buildApp().fetch(REQ('/me', withCookie(token)));
59
+ expect(res.status).toBe(200);
60
+ const body = await res.json();
61
+ expect(body.user).toEqual({ id: 'u1', roles: ['owner'], getOnly: false, sessionId: 's1' });
62
+ expect(body.attributedUserId).toBe('u1');
63
+ });
64
+
65
+ test('no cookie → 401 Unauthorized', async () => {
66
+ const res = await buildApp().fetch(REQ('/me'));
67
+ expect(res.status).toBe(401);
68
+ expect((await res.json()).error.message).toBe('Unauthorized');
69
+ });
70
+
71
+ test('garbage / wrong-secret token → 401 Invalid token', async () => {
72
+ await store.create({ sessionId: 's1', userId: 'u1' });
73
+ const bad = await signToken(
74
+ { userId: 'u1', sessionId: 's1', roles: [], getOnly: false },
75
+ 'a-different-secret-32-characters!!',
76
+ '15m',
77
+ );
78
+ const res = await buildApp().fetch(REQ('/me', withCookie(bad)));
79
+ expect(res.status).toBe(401);
80
+ expect((await res.json()).error.message).toBe('Invalid token');
81
+ });
82
+
83
+ test('unknown session → 401 Session revoked', async () => {
84
+ const token = await signToken(
85
+ { userId: 'u1', sessionId: 'ghost', roles: [], getOnly: false },
86
+ SECRET,
87
+ '15m',
88
+ );
89
+ const res = await buildApp().fetch(REQ('/me', withCookie(token)));
90
+ expect(res.status).toBe(401);
91
+ expect((await res.json()).error.message).toBe('Session revoked');
92
+ });
93
+
94
+ test('disabled session → 401 Session revoked', async () => {
95
+ await store.create({ sessionId: 's1', userId: 'u1' });
96
+ await store.disable('s1');
97
+ const token = await signToken(
98
+ { userId: 'u1', sessionId: 's1', roles: [], getOnly: false },
99
+ SECRET,
100
+ '15m',
101
+ );
102
+ const res = await buildApp().fetch(REQ('/me', withCookie(token)));
103
+ expect(res.status).toBe(401);
104
+ expect((await res.json()).error.message).toBe('Session revoked');
105
+ });
106
+
107
+ test('expired refresh window → 401 Session expired', async () => {
108
+ await store.create({
109
+ sessionId: 's1',
110
+ userId: 'u1',
111
+ refreshExpiresAt: new Date(Date.now() - 1000).toISOString(),
112
+ });
113
+ const token = await signToken(
114
+ { userId: 'u1', sessionId: 's1', roles: [], getOnly: false },
115
+ SECRET,
116
+ '15m',
117
+ );
118
+ const res = await buildApp().fetch(REQ('/me', withCookie(token)));
119
+ expect(res.status).toBe(401);
120
+ expect((await res.json()).error.message).toBe('Session expired');
121
+ });
122
+
123
+ test('a missing getOnly claim normalizes to getOnly:false on c.get("user")', async () => {
124
+ await store.create({ sessionId: 's1', userId: 'u1' });
125
+ // sign WITHOUT a getOnly key (cast — simulates a pre-getOnly token)
126
+ const token = await signToken(
127
+ { userId: 'u1', sessionId: 's1', roles: [] } as never,
128
+ SECRET,
129
+ '15m',
130
+ );
131
+ const body = await (await buildApp().fetch(REQ('/me', withCookie(token)))).json();
132
+ expect(body.user.getOnly).toBe(false);
133
+ });
134
+
135
+ test('getOnly session: raw POST 403 but a ctgr GET-twin passes', async () => {
136
+ await store.create({ sessionId: 's1', userId: 'u1', getOnly: true });
137
+ const token = await signToken(
138
+ { userId: 'u1', sessionId: 's1', roles: [], getOnly: true },
139
+ SECRET,
140
+ '15m',
141
+ );
142
+ const app = buildApp();
143
+ expect((await app.fetch(REQ('/notes', { method: 'POST', ...withCookie(token) }))).status).toBe(
144
+ 403,
145
+ );
146
+ expect((await app.fetch(REQ('/ctgrpo/notes', withCookie(token)))).status).toBe(200);
147
+ });
148
+ });
@@ -0,0 +1,56 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ import { ApiError, toErrorEnvelope } from '../errors';
3
+ import { readAuthCookie } from './cookie';
4
+ import { verifyToken } from './jwt';
5
+ import type { AppJwtPayload, AuthEnv, AuthUser, SessionStore } from './types';
6
+
7
+ /**
8
+ * The composed auth middleware. Reads the httpOnly `access_token` cookie, verifies
9
+ * the JWT, checks the session is live (present, not revoked, inside its sliding
10
+ * window), and sets the normalized {@link AuthUser} on `c.get('user')` (plus
11
+ * `userId` for request-log attribution). Every failure maps to a 401 in the
12
+ * canonical error envelope.
13
+ *
14
+ * Deliberately minimal: it does NOT slide/re-issue the token — that (and the
15
+ * `/f`-vs-`/login` policy) is app business (task 32). Mount {@link
16
+ * ./getOnly.getOnlyEnforcement} AFTER this to add the GET-only gate.
17
+ */
18
+ export function createAuthMiddleware(
19
+ sessionStore: SessionStore,
20
+ jwtSecret: string,
21
+ ): MiddlewareHandler<AuthEnv> {
22
+ return async (c, next) => {
23
+ const token = readAuthCookie(c);
24
+ if (!token) {
25
+ return c.json(toErrorEnvelope(new ApiError(401, 'Unauthorized')), 401);
26
+ }
27
+
28
+ let payload: AppJwtPayload;
29
+ try {
30
+ payload = await verifyToken<AppJwtPayload>(token, jwtSecret);
31
+ } catch {
32
+ return c.json(toErrorEnvelope(new ApiError(401, 'Invalid token')), 401);
33
+ }
34
+
35
+ const session = sessionStore.get(payload.sessionId);
36
+ if (!session || session.disabled) {
37
+ return c.json(toErrorEnvelope(new ApiError(401, 'Session revoked')), 401);
38
+ }
39
+ if (new Date(session.refreshExpiresAt) < new Date()) {
40
+ return c.json(toErrorEnvelope(new ApiError(401, 'Session expired')), 401);
41
+ }
42
+
43
+ const user: AuthUser = {
44
+ id: payload.userId,
45
+ roles: payload.roles ?? [],
46
+ // A token minted before the claim existed has no `getOnly` → treat as false,
47
+ // preserving access for pre-existing cookies.
48
+ getOnly: payload.getOnly ?? false,
49
+ sessionId: payload.sessionId,
50
+ };
51
+ c.set('user', user);
52
+ c.set('userId', user.id);
53
+
54
+ await next();
55
+ };
56
+ }
@@ -0,0 +1,48 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { pbkdf2Sync, randomBytes } from 'node:crypto';
3
+ import { hashPassword, needsRehash, verifyPassword } from './password';
4
+
5
+ // Reproduce the OLD app's legacy hash shape: PBKDF2-SHA512 @ 1000 iterations, the
6
+ // salt + derived hex joined as "<salt>:<hex>" (what the ETL stores in the password
7
+ // column for un-upgraded users).
8
+ const legacyHash = (plain: string, salt = randomBytes(16).toString('hex')): string => {
9
+ const hex = pbkdf2Sync(plain, salt, 1000, 64, 'sha512').toString('hex');
10
+ return `${salt}:${hex}`;
11
+ };
12
+
13
+ describe('hashPassword / verifyPassword (argon2id)', () => {
14
+ test('mints a self-describing argon2id hash and verifies it', async () => {
15
+ const hash = await hashPassword('hunter2');
16
+ expect(hash.startsWith('$argon2id$')).toBe(true);
17
+ expect(await verifyPassword('hunter2', hash)).toBe(true);
18
+ expect(await verifyPassword('wrong', hash)).toBe(false);
19
+ });
20
+
21
+ test('two hashes of the same password differ (random salt) yet both verify', async () => {
22
+ const a = await hashPassword('same');
23
+ const b = await hashPassword('same');
24
+ expect(a).not.toBe(b);
25
+ expect(await verifyPassword('same', a)).toBe(true);
26
+ expect(await verifyPassword('same', b)).toBe(true);
27
+ });
28
+ });
29
+
30
+ describe('verifyPassword (legacy PBKDF2)', () => {
31
+ test('verifies a correct password against a legacy salt:hex digest', async () => {
32
+ const stored = legacyHash('password');
33
+ expect(await verifyPassword('password', stored)).toBe(true);
34
+ expect(await verifyPassword('nope', stored)).toBe(false);
35
+ });
36
+
37
+ test('a malformed (colon-less) legacy value is a non-match, not a throw', async () => {
38
+ expect(await verifyPassword('x', 'no-colon-here')).toBe(false);
39
+ expect(await verifyPassword('x', '')).toBe(false);
40
+ });
41
+ });
42
+
43
+ describe('needsRehash', () => {
44
+ test('true for legacy, false for argon2id', async () => {
45
+ expect(needsRehash(legacyHash('p'))).toBe(true);
46
+ expect(needsRehash(await hashPassword('p'))).toBe(false);
47
+ });
48
+ });
@@ -0,0 +1,71 @@
1
+ import { pbkdf2 as pbkdf2Cb, timingSafeEqual } from 'node:crypto';
2
+ import { promisify } from 'node:util';
3
+
4
+ /**
5
+ * Password hashing on `Bun.password` (argon2id — memory-hard, thread-pooled, zero
6
+ * deps, the current OWASP default). The argon2id hash is self-describing
7
+ * (`$argon2id$v=19$m=…$<salt>$<hash>`) so there is NO separate salt column — the new
8
+ * schema stores just the hash string.
9
+ *
10
+ * Legacy upgrade path: the old app used PBKDF2-SHA512 @ 1000 iterations with a
11
+ * separate salt; the ETL (task 43) brings those forward as a combined `"<salt>:<hex>"`
12
+ * string in the password column (it can't re-hash — it lacks the plaintext).
13
+ * {@link verifyPassword} transparently verifies BOTH shapes; on a legacy match the
14
+ * caller re-hashes with argon2id ({@link needsRehash}) — a silent per-login upgrade,
15
+ * no forced reset.
16
+ */
17
+
18
+ // Legacy PBKDF2-SHA512 parameters — used ONLY to verify pre-argon2id hashes; never
19
+ // to mint new ones. (1000 iterations is far below modern guidance — exactly why we
20
+ // moved off it.)
21
+ const LEGACY_ITERATIONS = 1000;
22
+ const LEGACY_KEYLEN = 64;
23
+ const LEGACY_DIGEST = 'sha512';
24
+
25
+ const pbkdf2 = promisify(pbkdf2Cb);
26
+
27
+ const isArgon2 = (hash: string): boolean => hash.startsWith('$argon2');
28
+
29
+ /** Mint an argon2id password hash. Returns the self-describing PHC string. */
30
+ export function hashPassword(plain: string): Promise<string> {
31
+ return Bun.password.hash(plain, { algorithm: 'argon2id' });
32
+ }
33
+
34
+ /**
35
+ * Verify a candidate against a stored hash, handling BOTH formats: an argon2id PHC
36
+ * string (new) via `Bun.password.verify`; a legacy `"<salt>:<pbkdf2-hex>"` digest by
37
+ * re-deriving PBKDF2-SHA512 with the stored salt and comparing in constant time.
38
+ */
39
+ export async function verifyPassword(plain: string, stored: string): Promise<boolean> {
40
+ if (isArgon2(stored)) {
41
+ try {
42
+ return await Bun.password.verify(plain, stored);
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+ // Legacy: split on the FIRST colon — salt (hex) : derived (hex). A malformed value
48
+ // (no colon) is simply a non-match.
49
+ const sep = stored.indexOf(':');
50
+ if (sep === -1) return false;
51
+ const salt = stored.slice(0, sep);
52
+ const expectedHex = stored.slice(sep + 1);
53
+ if (!salt || !expectedHex) return false;
54
+
55
+ const derived = (
56
+ await pbkdf2(plain, salt, LEGACY_ITERATIONS, LEGACY_KEYLEN, LEGACY_DIGEST)
57
+ ).toString('hex');
58
+ // timingSafeEqual throws on length mismatch; a length diff is already a non-match
59
+ // and short-circuiting leaks nothing a compare wouldn't.
60
+ if (derived.length !== expectedHex.length) return false;
61
+ return timingSafeEqual(Buffer.from(derived), Buffer.from(expectedHex));
62
+ }
63
+
64
+ /**
65
+ * True when a stored hash is a legacy (non-argon2id) digest and should be upgraded.
66
+ * Call AFTER a successful {@link verifyPassword} to opportunistically re-hash with
67
+ * argon2id — never block auth on the upgrade.
68
+ */
69
+ export function needsRehash(stored: string): boolean {
70
+ return !isArgon2(stored);
71
+ }
@@ -0,0 +1,189 @@
1
+ import { Database } from 'bun:sqlite';
2
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
3
+ import { createKysely, type PlumbingDb } from '../db/kysely';
4
+ import { createSessionStore } from './sessionStore';
5
+ import type { SessionStore } from './types';
6
+
7
+ const DDL = `CREATE TABLE auth_sessions (
8
+ session_id TEXT PRIMARY KEY,
9
+ user_id TEXT NOT NULL,
10
+ created_at TEXT NOT NULL,
11
+ last_seen_at TEXT NOT NULL,
12
+ refresh_expires_at TEXT NOT NULL,
13
+ access_expires_at TEXT,
14
+ get_only INTEGER NOT NULL DEFAULT 0,
15
+ disabled INTEGER NOT NULL DEFAULT 0,
16
+ disabled_at TEXT,
17
+ disabled_by TEXT,
18
+ user_agent TEXT,
19
+ ip TEXT
20
+ )`;
21
+
22
+ let db: Database;
23
+ let ky: PlumbingDb;
24
+ let store: SessionStore;
25
+
26
+ beforeEach(() => {
27
+ db = new Database(':memory:');
28
+ db.run(DDL);
29
+ ky = createKysely(db);
30
+ store = createSessionStore(ky);
31
+ });
32
+
33
+ afterEach(() => {
34
+ store.stop();
35
+ db.close();
36
+ });
37
+
38
+ const rowCount = (): number =>
39
+ (db.query('SELECT COUNT(*) AS n FROM auth_sessions').get() as { n: number }).n;
40
+
41
+ describe('createSessionStore', () => {
42
+ test('create caches in memory AND persists to the DB', async () => {
43
+ const s = await store.create({ sessionId: 's1', userId: 'u1', getOnly: true, ip: '1.2.3.4' });
44
+ expect(s.getOnly).toBe(true);
45
+ expect(s.disabled).toBe(false);
46
+ expect(store.get('s1')).toEqual(s);
47
+ expect(rowCount()).toBe(1);
48
+ const row = db
49
+ .query('SELECT get_only, ip FROM auth_sessions WHERE session_id = ?')
50
+ .get('s1') as {
51
+ get_only: number;
52
+ ip: string;
53
+ };
54
+ expect(row.get_only).toBe(1);
55
+ expect(row.ip).toBe('1.2.3.4');
56
+ });
57
+
58
+ test('isValid is true for a fresh session, false when disabled or expired', async () => {
59
+ await store.create({ sessionId: 's1', userId: 'u1' });
60
+ expect(store.isValid(store.get('s1'))).toBe(true);
61
+ expect(store.isValid(undefined)).toBe(false);
62
+
63
+ await store.create({
64
+ sessionId: 'sExpired',
65
+ userId: 'u1',
66
+ refreshExpiresAt: new Date(Date.now() - 1000).toISOString(),
67
+ });
68
+ expect(store.isValid(store.get('sExpired'))).toBe(false);
69
+ });
70
+
71
+ test('touch slides the refresh deadline and updates lastSeenAt', async () => {
72
+ const created = await store.create({
73
+ sessionId: 's1',
74
+ userId: 'u1',
75
+ refreshExpiresAt: new Date(Date.now() + 1000).toISOString(),
76
+ });
77
+ // `create`/`get` hand back the live map object, so capture the string by value
78
+ // before `touch` mutates it.
79
+ const beforeRefresh = created.refreshExpiresAt;
80
+ await new Promise((r) => setTimeout(r, 5));
81
+ await store.touch('s1', { accessExpiresAt: '2099-01-01T00:00:00.000Z' }); // forces a flush
82
+ const after = store.get('s1');
83
+ expect(after).toBeDefined();
84
+ expect(Date.parse(after?.refreshExpiresAt ?? '')).toBeGreaterThan(Date.parse(beforeRefresh));
85
+ expect(after?.accessExpiresAt).toBe('2099-01-01T00:00:00.000Z');
86
+ // forced flush persisted the access expiry
87
+ const row = db
88
+ .query('SELECT access_expires_at FROM auth_sessions WHERE session_id = ?')
89
+ .get('s1') as {
90
+ access_expires_at: string;
91
+ };
92
+ expect(row.access_expires_at).toBe('2099-01-01T00:00:00.000Z');
93
+ });
94
+
95
+ test('disable revokes in memory + DB (audit row kept)', async () => {
96
+ await store.create({ sessionId: 's1', userId: 'u1' });
97
+ expect(await store.disable('s1', 'admin-7')).toBe(true);
98
+ expect(store.get('s1')?.disabled).toBe(true);
99
+ expect(store.isValid(store.get('s1'))).toBe(false);
100
+ const row = db
101
+ .query('SELECT disabled, disabled_by FROM auth_sessions WHERE session_id = ?')
102
+ .get('s1') as {
103
+ disabled: number;
104
+ disabled_by: string;
105
+ };
106
+ expect(row.disabled).toBe(1);
107
+ expect(row.disabled_by).toBe('admin-7');
108
+ });
109
+
110
+ test('disableAllForUser returns affected ids and revokes them', async () => {
111
+ await store.create({ sessionId: 'a', userId: 'u1' });
112
+ await store.create({ sessionId: 'b', userId: 'u1' });
113
+ await store.create({ sessionId: 'c', userId: 'u2' });
114
+ const affected = await store.disableAllForUser('u1', 'admin');
115
+ expect(affected.sort()).toEqual(['a', 'b']);
116
+ expect(store.get('a')?.disabled).toBe(true);
117
+ expect(store.get('c')?.disabled).toBe(false);
118
+ });
119
+
120
+ test('remove forgets the session entirely (memory + DB)', async () => {
121
+ await store.create({ sessionId: 's1', userId: 'u1' });
122
+ await store.remove('s1');
123
+ expect(store.get('s1')).toBeUndefined();
124
+ expect(rowCount()).toBe(0);
125
+ });
126
+
127
+ test('deleteByUser hard-deletes all of a user’s sessions', async () => {
128
+ await store.create({ sessionId: 'a', userId: 'u1' });
129
+ await store.create({ sessionId: 'b', userId: 'u1' });
130
+ await store.create({ sessionId: 'c', userId: 'u2' });
131
+ await store.deleteByUser('u1');
132
+ expect(store.get('a')).toBeUndefined();
133
+ expect(store.get('c')).toBeDefined();
134
+ expect(rowCount()).toBe(1);
135
+ });
136
+
137
+ test('listByUser returns all rows (incl. disabled) newest-first from the DB', async () => {
138
+ await store.create({
139
+ sessionId: 'a',
140
+ userId: 'u1',
141
+ refreshExpiresAt: new Date(Date.now() + 1e6).toISOString(),
142
+ });
143
+ await new Promise((r) => setTimeout(r, 5));
144
+ await store.create({ sessionId: 'b', userId: 'u1' });
145
+ await store.disable('a');
146
+ const list = await store.listByUser('u1');
147
+ expect(list.map((s) => s.sessionId)).toEqual(['b', 'a']); // created_at desc
148
+ expect(list.find((s) => s.sessionId === 'a')?.disabled).toBe(true);
149
+ });
150
+
151
+ test('getLastSeenForUser tracks the most recent active session', async () => {
152
+ await store.create({ sessionId: 'a', userId: 'u1' });
153
+ await new Promise((r) => setTimeout(r, 5));
154
+ await store.touch('a', { accessExpiresAt: null });
155
+ const last = store.getLastSeenForUser('u1');
156
+ expect(last).toBe(store.get('a')?.lastSeenAt ?? null);
157
+ expect(store.getLastSeenForUser('nobody')).toBeNull();
158
+ });
159
+
160
+ test('rehydrate rebuilds the in-memory map with only currently-valid sessions', async () => {
161
+ await store.create({ sessionId: 'live', userId: 'u1' });
162
+ await store.create({
163
+ sessionId: 'expired',
164
+ userId: 'u1',
165
+ refreshExpiresAt: new Date(Date.now() - 1000).toISOString(),
166
+ });
167
+ await store.create({ sessionId: 'dead', userId: 'u1' });
168
+ await store.disable('dead');
169
+
170
+ // A fresh store over the SAME db simulates a restart.
171
+ const fresh = createSessionStore(ky);
172
+ await fresh.rehydrate();
173
+ expect(fresh.get('live')).toBeDefined();
174
+ expect(fresh.get('expired')).toBeUndefined();
175
+ expect(fresh.get('dead')).toBeUndefined();
176
+ fresh.stop();
177
+ });
178
+
179
+ test('prune drops rows past the retention window', async () => {
180
+ const shortStore = createSessionStore(ky, { retentionMs: 10 });
181
+ await shortStore.create({ sessionId: 's1', userId: 'u1' });
182
+ await new Promise((r) => setTimeout(r, 30));
183
+ const removed = await shortStore.prune();
184
+ expect(removed).toBe(1);
185
+ expect(shortStore.get('s1')).toBeUndefined();
186
+ expect(rowCount()).toBe(0);
187
+ shortStore.stop();
188
+ });
189
+ });