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,261 @@
1
+ import type { PlumbingDb } from '../db/kysely';
2
+ import type { AuthSessionRow } from '../db/plumbingTypes';
3
+ import type { CreateSessionInput, Session, SessionStore } from './types';
4
+
5
+ /**
6
+ * Kysely-backed, revocable, restart-surviving session store (task 26).
7
+ *
8
+ * The in-memory map is the hot-path source of truth for token validity — the auth
9
+ * middleware reads it synchronously, no per-request DB round-trip. The
10
+ * `auth_sessions` table exists only so the map can be rebuilt after a restart
11
+ * ({@link SessionStore.rehydrate}); DB writes for sliding `touch`es are throttled
12
+ * (memory is always current). Single-server deployment, so there is no
13
+ * cross-instance coherence to manage.
14
+ */
15
+
16
+ const HOUR_MS = 60 * 60 * 1000;
17
+ const DAY_MS = 24 * HOUR_MS;
18
+
19
+ export interface SessionStoreOptions {
20
+ /** Sliding refresh window — how long after the last activity a session stays
21
+ * valid. Default 7 days. */
22
+ refreshWindowMs?: number;
23
+ /** Retention before a stale (lapsed) row is pruned from memory + DB. Default 30
24
+ * days. Must be ≥ refreshWindowMs to keep recently-expired rows for the audit view. */
25
+ retentionMs?: number;
26
+ /** Throttle window for sliding-`touch` DB writes. Memory is always current; the
27
+ * row is flushed at most this often (unless a token re-issue forces it). Default 60s. */
28
+ flushIntervalMs?: number;
29
+ /** Start a background prune timer. Default false — wire pruning through the job
30
+ * runner (task 29), or call {@link SessionStore.prune} yourself. */
31
+ autoPrune?: boolean;
32
+ /** Auto-prune cadence when {@link autoPrune} is on. Default 1 hour. */
33
+ pruneIntervalMs?: number;
34
+ }
35
+
36
+ const rowToSession = (r: AuthSessionRow): Session => ({
37
+ sessionId: r.session_id,
38
+ userId: r.user_id,
39
+ getOnly: r.get_only === 1,
40
+ createdAt: r.created_at,
41
+ lastSeenAt: r.last_seen_at,
42
+ refreshExpiresAt: r.refresh_expires_at,
43
+ accessExpiresAt: r.access_expires_at,
44
+ disabled: r.disabled === 1,
45
+ disabledAt: r.disabled_at,
46
+ disabledBy: r.disabled_by,
47
+ userAgent: r.user_agent,
48
+ ip: r.ip,
49
+ });
50
+
51
+ export function createSessionStore(ky: PlumbingDb, opts: SessionStoreOptions = {}): SessionStore {
52
+ const refreshWindowMs = opts.refreshWindowMs ?? 7 * DAY_MS;
53
+ const retentionMs = opts.retentionMs ?? 30 * DAY_MS;
54
+ const flushIntervalMs = opts.flushIntervalMs ?? 60_000;
55
+ const pruneIntervalMs = opts.pruneIntervalMs ?? HOUR_MS;
56
+
57
+ const map = new Map<string, Session>();
58
+ const lastFlushAt = new Map<string, number>();
59
+ let pruneTimer: ReturnType<typeof setInterval> | undefined;
60
+
61
+ const isFresh = (s: Session | undefined, now = Date.now()): s is Session =>
62
+ !!s && !s.disabled && Date.parse(s.refreshExpiresAt) >= now;
63
+
64
+ const store: SessionStore = {
65
+ async rehydrate() {
66
+ // Drop ancient rows first so the map starts trim, then load only currently-valid
67
+ // sessions (disabled/expired rows stay in the DB for the audit view).
68
+ await store.prune();
69
+ const rows = await ky.selectFrom('auth_sessions').selectAll().execute();
70
+ const now = Date.now();
71
+ map.clear();
72
+ lastFlushAt.clear();
73
+ for (const r of rows) {
74
+ const s = rowToSession(r);
75
+ if (isFresh(s, now)) {
76
+ map.set(s.sessionId, s);
77
+ lastFlushAt.set(s.sessionId, now);
78
+ }
79
+ }
80
+ },
81
+
82
+ async create(input: CreateSessionInput) {
83
+ const now = Date.now();
84
+ const nowIso = new Date(now).toISOString();
85
+ const session: Session = {
86
+ sessionId: input.sessionId,
87
+ userId: input.userId,
88
+ getOnly: input.getOnly ?? false,
89
+ createdAt: nowIso,
90
+ lastSeenAt: nowIso,
91
+ refreshExpiresAt: input.refreshExpiresAt ?? new Date(now + refreshWindowMs).toISOString(),
92
+ accessExpiresAt: input.accessExpiresAt ?? null,
93
+ disabled: false,
94
+ disabledAt: null,
95
+ disabledBy: null,
96
+ userAgent: input.userAgent ?? null,
97
+ ip: input.ip ?? null,
98
+ };
99
+ map.set(session.sessionId, session);
100
+ lastFlushAt.set(session.sessionId, now);
101
+ await ky
102
+ .insertInto('auth_sessions')
103
+ .values({
104
+ session_id: session.sessionId,
105
+ user_id: session.userId,
106
+ get_only: session.getOnly ? 1 : 0,
107
+ created_at: session.createdAt,
108
+ last_seen_at: session.lastSeenAt,
109
+ refresh_expires_at: session.refreshExpiresAt,
110
+ access_expires_at: session.accessExpiresAt,
111
+ disabled: 0,
112
+ disabled_at: null,
113
+ disabled_by: null,
114
+ user_agent: session.userAgent,
115
+ ip: session.ip,
116
+ })
117
+ .execute();
118
+ return session;
119
+ },
120
+
121
+ get(sessionId) {
122
+ return map.get(sessionId);
123
+ },
124
+
125
+ isValid(session): session is Session {
126
+ return isFresh(session);
127
+ },
128
+
129
+ async touch(sessionId, opts2) {
130
+ const s = map.get(sessionId);
131
+ if (!s) return;
132
+ const now = Date.now();
133
+ s.lastSeenAt = new Date(now).toISOString();
134
+ s.refreshExpiresAt = new Date(now + refreshWindowMs).toISOString();
135
+ const reissued = opts2?.accessExpiresAt !== undefined;
136
+ if (reissued) s.accessExpiresAt = opts2?.accessExpiresAt ?? null;
137
+
138
+ const last = lastFlushAt.get(sessionId) ?? 0;
139
+ if (reissued || now - last > flushIntervalMs) {
140
+ await ky
141
+ .updateTable('auth_sessions')
142
+ .set({
143
+ last_seen_at: s.lastSeenAt,
144
+ refresh_expires_at: s.refreshExpiresAt,
145
+ access_expires_at: s.accessExpiresAt,
146
+ })
147
+ .where('session_id', '=', sessionId)
148
+ .execute();
149
+ lastFlushAt.set(sessionId, now);
150
+ }
151
+ },
152
+
153
+ async disable(sessionId, disabledBy = null) {
154
+ const s = map.get(sessionId);
155
+ const nowIso = new Date().toISOString();
156
+ if (s) {
157
+ s.disabled = true;
158
+ s.disabledAt = nowIso;
159
+ s.disabledBy = disabledBy;
160
+ }
161
+ const res = await ky
162
+ .updateTable('auth_sessions')
163
+ .set({ disabled: 1, disabled_at: nowIso, disabled_by: disabledBy })
164
+ .where('session_id', '=', sessionId)
165
+ .where('disabled', '=', 0)
166
+ .executeTakeFirst();
167
+ return s !== undefined || Number(res?.numUpdatedRows ?? 0) > 0;
168
+ },
169
+
170
+ async disableAllForUser(userId, disabledBy = null) {
171
+ const nowIso = new Date().toISOString();
172
+ const affected: string[] = [];
173
+ for (const s of map.values()) {
174
+ if (s.userId === userId && !s.disabled) {
175
+ s.disabled = true;
176
+ s.disabledAt = nowIso;
177
+ s.disabledBy = disabledBy;
178
+ affected.push(s.sessionId);
179
+ }
180
+ }
181
+ await ky
182
+ .updateTable('auth_sessions')
183
+ .set({ disabled: 1, disabled_at: nowIso, disabled_by: disabledBy })
184
+ .where('user_id', '=', userId)
185
+ .where('disabled', '=', 0)
186
+ .execute();
187
+ return affected;
188
+ },
189
+
190
+ async remove(sessionId) {
191
+ map.delete(sessionId);
192
+ lastFlushAt.delete(sessionId);
193
+ await ky.deleteFrom('auth_sessions').where('session_id', '=', sessionId).execute();
194
+ },
195
+
196
+ async deleteByUser(userId) {
197
+ for (const [id, s] of map) {
198
+ if (s.userId === userId) {
199
+ map.delete(id);
200
+ lastFlushAt.delete(id);
201
+ }
202
+ }
203
+ await ky.deleteFrom('auth_sessions').where('user_id', '=', userId).execute();
204
+ },
205
+
206
+ async listByUser(userId) {
207
+ const rows = await ky
208
+ .selectFrom('auth_sessions')
209
+ .selectAll()
210
+ .where('user_id', '=', userId)
211
+ .orderBy('created_at', 'desc')
212
+ .execute();
213
+ return rows.map(rowToSession);
214
+ },
215
+
216
+ listAll() {
217
+ return Array.from(map.values());
218
+ },
219
+
220
+ getLastSeenForUser(userId) {
221
+ let latest: string | null = null;
222
+ for (const s of map.values()) {
223
+ if (s.userId !== userId || s.disabled) continue;
224
+ if (!latest || s.lastSeenAt > latest) latest = s.lastSeenAt;
225
+ }
226
+ return latest;
227
+ },
228
+
229
+ async prune() {
230
+ const cutoff = Date.now() - retentionMs;
231
+ const cutoffIso = new Date(cutoff).toISOString();
232
+ let removed = 0;
233
+ for (const [id, s] of map) {
234
+ if (Date.parse(s.lastSeenAt) < cutoff) {
235
+ map.delete(id);
236
+ lastFlushAt.delete(id);
237
+ removed++;
238
+ }
239
+ }
240
+ await ky.deleteFrom('auth_sessions').where('last_seen_at', '<', cutoffIso).execute();
241
+ return removed;
242
+ },
243
+
244
+ stop() {
245
+ if (pruneTimer) {
246
+ clearInterval(pruneTimer);
247
+ pruneTimer = undefined;
248
+ }
249
+ },
250
+ };
251
+
252
+ if (opts.autoPrune) {
253
+ pruneTimer = setInterval(() => {
254
+ void store.prune();
255
+ }, pruneIntervalMs);
256
+ // Never keep the process alive for a prune tick.
257
+ pruneTimer.unref?.();
258
+ }
259
+
260
+ return store;
261
+ }
@@ -0,0 +1,73 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { generateSync } from 'otplib';
3
+ import {
4
+ generateRecoveryCodes,
5
+ generateTotpSecret,
6
+ getTotpUri,
7
+ verifyRecoveryCode,
8
+ verifyTotpCode,
9
+ } from './totp';
10
+
11
+ describe('TOTP', () => {
12
+ test('generates a base32 secret', () => {
13
+ const secret = generateTotpSecret();
14
+ expect(secret).toMatch(/^[A-Z2-7]+$/);
15
+ expect(secret.length).toBeGreaterThanOrEqual(16);
16
+ });
17
+
18
+ test('builds an otpauth:// URI with issuer + label + secret', () => {
19
+ const secret = generateTotpSecret();
20
+ const uri = getTotpUri('user@example.com', secret, 'CursedAlchemy');
21
+ expect(uri.startsWith('otpauth://totp/')).toBe(true);
22
+ expect(uri).toContain('issuer=CursedAlchemy');
23
+ expect(uri).toContain(`secret=${secret}`);
24
+ });
25
+
26
+ test('verifies the current code, rejects a wrong one', () => {
27
+ const secret = generateTotpSecret();
28
+ const code = generateSync({ secret });
29
+ expect(verifyTotpCode(code, secret)).toBe(true);
30
+ expect(verifyTotpCode('000000', secret)).toBe(false);
31
+ });
32
+
33
+ test('tolerates whitespace and rejects non-6-digit input', () => {
34
+ const secret = generateTotpSecret();
35
+ const code = generateSync({ secret });
36
+ expect(verifyTotpCode(` ${code} `, secret)).toBe(true);
37
+ expect(verifyTotpCode('12345', secret)).toBe(false);
38
+ expect(verifyTotpCode('abcdef', secret)).toBe(false);
39
+ expect(verifyTotpCode('', secret)).toBe(false);
40
+ });
41
+
42
+ test('accepts a code from the previous 30s step (±1 drift)', () => {
43
+ const secret = generateTotpSecret();
44
+ const prev = generateSync({ secret, epoch: Math.floor(Date.now() / 1000) - 30 });
45
+ expect(verifyTotpCode(prev, secret)).toBe(true);
46
+ });
47
+ });
48
+
49
+ describe('recovery codes', () => {
50
+ test('generates N readable codes + argon2id hashes; never stores plaintext', async () => {
51
+ const { plaintext, hashed } = await generateRecoveryCodes(8);
52
+ expect(plaintext).toHaveLength(8);
53
+ expect(hashed).toHaveLength(8);
54
+ for (const h of hashed) expect(h.startsWith('$argon2id$')).toBe(true);
55
+ // a hash must not be the plaintext
56
+ expect(hashed[0]).not.toContain(plaintext[0]);
57
+ });
58
+
59
+ test('verifyRecoveryCode returns the matching index, tolerant of formatting', async () => {
60
+ const { plaintext, hashed } = await generateRecoveryCodes(5);
61
+ const idx = await verifyRecoveryCode(plaintext[2].toLowerCase().replace('-', ''), hashed);
62
+ expect(idx).toBe(2);
63
+ });
64
+
65
+ test('returns -1 for an unknown code, and after a code is consumed', async () => {
66
+ const { plaintext, hashed } = await generateRecoveryCodes(3);
67
+ expect(await verifyRecoveryCode('ZZZZZ-ZZZZZ', hashed)).toBe(-1);
68
+ // consume index 1 (the caller splices it out) → it no longer matches
69
+ const remaining = hashed.filter((_, i) => i !== 1);
70
+ expect(await verifyRecoveryCode(plaintext[1], remaining)).toBe(-1);
71
+ expect(await verifyRecoveryCode(plaintext[0], remaining)).toBe(0);
72
+ });
73
+ });
@@ -0,0 +1,92 @@
1
+ import { generateSecret, generateURI, verifySync } from 'otplib';
2
+
3
+ /**
4
+ * TOTP (RFC 6238) on `otplib` — standard authenticator-app parameters (base32
5
+ * secret, SHA-1, 6 digits, 30s step). otplib's default plugins are pure-JS
6
+ * (`@noble/hashes` + `@scure/base`), so this runs natively on Bun with no native
7
+ * deps. Plus single-use recovery codes hashed with argon2id (never stored plain).
8
+ *
9
+ * The toolkit provides the crypto; enabling/disabling 2FA, persisting the secret +
10
+ * recovery hashes, and the login-flow ordering stay app-business (task 32).
11
+ */
12
+
13
+ export const DEFAULT_TOTP_ISSUER = 'CursedAlchemy';
14
+
15
+ // One 30s step of clock-skew tolerance on each side (±1 step). otplib v13 defaults
16
+ // to ZERO tolerance, so we set it explicitly (period = 30s).
17
+ const DRIFT_SECONDS = 30;
18
+
19
+ /** A fresh random base32 TOTP secret (20 bytes / 160 bits — the standard). */
20
+ export function generateTotpSecret(): string {
21
+ return generateSecret();
22
+ }
23
+
24
+ /** The `otpauth://` URI an authenticator app scans / imports (for QR rendering). */
25
+ export function getTotpUri(account: string, secret: string, issuer = DEFAULT_TOTP_ISSUER): string {
26
+ return generateURI({ issuer, label: account, secret });
27
+ }
28
+
29
+ /**
30
+ * Verify a user-entered 6-digit code against a base32 secret, allowing ±1 step of
31
+ * drift. Tolerates surrounding whitespace; rejects anything that isn't six digits.
32
+ */
33
+ export function verifyTotpCode(token: string, secret: string): boolean {
34
+ const clean = String(token).replace(/\s+/g, '');
35
+ if (!/^\d{6}$/.test(clean)) return false;
36
+ try {
37
+ return verifySync({ secret, token: clean, epochTolerance: DRIFT_SECONDS }).valid;
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ // ── Recovery codes ──────────────────────────────────────────────────────────────
44
+
45
+ // No easily-confused characters (no 0/O, 1/I/L) — readable + dictatable.
46
+ const CODE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
47
+
48
+ /** Strip separators + uppercase, so display formatting never affects matching. */
49
+ const normalizeCode = (code: string): string =>
50
+ String(code)
51
+ .replace(/[^a-zA-Z0-9]/g, '')
52
+ .toUpperCase();
53
+
54
+ const randomCode = (): string => {
55
+ const bytes = crypto.getRandomValues(new Uint8Array(10));
56
+ let s = '';
57
+ for (const b of bytes) s += CODE_ALPHABET[b % CODE_ALPHABET.length];
58
+ return `${s.slice(0, 5)}-${s.slice(5)}`; // e.g. "A1B2C-D3E4F"
59
+ };
60
+
61
+ /**
62
+ * Generate `count` single-use recovery codes. Returns `plaintext` (shown to the user
63
+ * exactly once) and `hashed` (argon2id PHC strings — persist these as a JSON array,
64
+ * never the plaintext).
65
+ */
66
+ export async function generateRecoveryCodes(
67
+ count = 10,
68
+ ): Promise<{ plaintext: string[]; hashed: string[] }> {
69
+ const plaintext = Array.from({ length: count }, randomCode);
70
+ const hashed = await Promise.all(
71
+ plaintext.map((code) => Bun.password.hash(normalizeCode(code), { algorithm: 'argon2id' })),
72
+ );
73
+ return { plaintext, hashed };
74
+ }
75
+
76
+ /**
77
+ * Verify a candidate recovery code against the stored argon2id hashes. Returns the
78
+ * index of the matching hash, or -1 if none match. The caller consumes it (splice
79
+ * that index out and persist the shortened array) so each code works exactly once.
80
+ */
81
+ export async function verifyRecoveryCode(input: string, hashed: string[]): Promise<number> {
82
+ const candidate = normalizeCode(input);
83
+ if (!candidate) return -1;
84
+ for (let i = 0; i < hashed.length; i++) {
85
+ try {
86
+ if (await Bun.password.verify(candidate, hashed[i])) return i;
87
+ } catch {
88
+ // ignore a malformed stored hash — treat as a non-match
89
+ }
90
+ }
91
+ return -1;
92
+ }
@@ -0,0 +1,146 @@
1
+ import type { JWTPayload } from 'jose';
2
+ import type { CursedbeltVariables } from '../context';
3
+
4
+ /**
5
+ * Shared auth types for the cursedbelt server auth toolkit.
6
+ *
7
+ * cursedbelt owns the *mechanism* (sign/verify, session validity, the getOnly
8
+ * gate, CSRF, throttling, TOTP, vault crypto). The *model* — which roles may do
9
+ * what, the `/f`-vs-`/login` policy, what a "session" means to the product — stays
10
+ * in the consuming app. The toolkit carries `roles`/`getOnly`/`purpose` opaquely
11
+ * and never interprets them beyond the documented gates.
12
+ */
13
+
14
+ /** Token purposes the apps mint. `access` is the login session; the rest are
15
+ * short-lived single-feature unlock/state tokens (held in page memory, never a
16
+ * cookie). cursedbelt does not enforce these — {@link AppJwtPayload.purpose} is an
17
+ * opaque string the app sets and checks (via `verifyPurposeToken`). */
18
+ export type TokenPurpose =
19
+ | 'access'
20
+ | 'refresh'
21
+ | 'vault-unlock'
22
+ | 'pm-unlock'
23
+ | 'files-unlock'
24
+ | 'yt-oauth';
25
+
26
+ /**
27
+ * The decoded JWT payload shape the apps use. `userId`/`sessionId` tie a token to
28
+ * its user + its row in the session store (the kill switch); `roles` + `getOnly`
29
+ * ride along for the auth middleware to surface on `c.get('user')`.
30
+ */
31
+ export interface AppJwtPayload extends JWTPayload {
32
+ userId: string;
33
+ sessionId: string;
34
+ roles: string[];
35
+ /** GET-only (`/f`) session marker. Absent on tokens minted before the claim
36
+ * existed → treated as `false` (see {@link AuthUser.getOnly}). */
37
+ getOnly?: boolean;
38
+ /** See {@link TokenPurpose}. Absent ⇒ a plain access token. */
39
+ purpose?: TokenPurpose | (string & {});
40
+ }
41
+
42
+ /**
43
+ * The authenticated principal the auth middleware sets on `c.get('user')`. This is
44
+ * the *normalized* view (getOnly defaulted), distinct from the raw {@link AppJwtPayload}.
45
+ */
46
+ export interface AuthUser {
47
+ id: string;
48
+ roles: string[];
49
+ /** Normalized: a missing `getOnly` claim becomes `false` here. */
50
+ getOnly: boolean;
51
+ sessionId: string;
52
+ }
53
+
54
+ /** Hono `Variables` the auth middleware contributes. Composed with the base
55
+ * {@link CursedbeltVariables} into {@link AuthEnv}. */
56
+ export interface AuthVariables {
57
+ /** Set by `createAuthMiddleware` once the token + session are validated. */
58
+ user: AuthUser;
59
+ }
60
+
61
+ /** The Hono env an app gets once it mounts the cursedbelt auth middleware: the
62
+ * base plumbing variables plus the authenticated `user`. `user` is typed required
63
+ * because the gate runs before any handler that reads it; mount auth before the
64
+ * routes that depend on it. */
65
+ export interface AuthEnv {
66
+ Variables: CursedbeltVariables & AuthVariables;
67
+ }
68
+
69
+ /** A persisted login session — the in-memory + DB-backed record the auth middleware
70
+ * checks on every request (revocation + sliding-refresh expiry). */
71
+ export interface Session {
72
+ sessionId: string;
73
+ userId: string;
74
+ /** GET-only (`/f`) session. Copied onto the access token's `getOnly` claim. */
75
+ getOnly: boolean;
76
+ createdAt: string;
77
+ lastSeenAt: string;
78
+ /** Sliding deadline: a session is valid while now < refreshExpiresAt. */
79
+ refreshExpiresAt: string;
80
+ /** When the *current* access token expires (informational; the cookie/JWT is the
81
+ * real gate). Null until first issued. */
82
+ accessExpiresAt: string | null;
83
+ disabled: boolean;
84
+ disabledAt: string | null;
85
+ disabledBy: string | null;
86
+ userAgent: string | null;
87
+ ip: string | null;
88
+ }
89
+
90
+ /** Inputs to mint a session. The store computes `createdAt`/`lastSeenAt` and (unless
91
+ * overridden) `refreshExpiresAt` from its configured window — callers never hand-roll
92
+ * timestamps. */
93
+ export interface CreateSessionInput {
94
+ sessionId: string;
95
+ userId: string;
96
+ getOnly?: boolean;
97
+ accessExpiresAt?: string | null;
98
+ userAgent?: string | null;
99
+ ip?: string | null;
100
+ /** Override the computed `now + refreshWindowMs` deadline. */
101
+ refreshExpiresAt?: string;
102
+ }
103
+
104
+ /**
105
+ * Revocable, restart-surviving session store. The in-memory map is the hot-path
106
+ * source of truth for token validity (no per-request DB read); the `auth_sessions`
107
+ * table exists so the map can be rebuilt after a restart ({@link SessionStore.rehydrate}).
108
+ * Single-server deployment — no cross-instance coherence to manage.
109
+ */
110
+ export interface SessionStore {
111
+ /** Load the table into memory on boot (prunes expired/old rows first). */
112
+ rehydrate(): Promise<void>;
113
+ /** Persist + cache a new session. */
114
+ create(input: CreateSessionInput): Promise<Session>;
115
+ /** Sync hot-path read from memory. Returns the LIVE cached object (not a copy) —
116
+ * treat it read-only; mutate only through the store's methods. */
117
+ get(sessionId: string): Session | undefined;
118
+ /** Is this session usable right now (present, not disabled, inside its window)? */
119
+ isValid(session: Session | undefined): session is Session;
120
+ /** Slide the refresh deadline forward on activity (and optionally record a freshly
121
+ * issued access-token expiry). DB write is throttled unless `accessExpiresAt` is
122
+ * passed (a re-issue must persist immediately so a restart reflects it). */
123
+ touch(sessionId: string, opts?: { accessExpiresAt?: string | null }): Promise<void>;
124
+ /** Revoke one session (keeps the audit row). Returns false if unknown. */
125
+ disable(sessionId: string, disabledBy?: string | null): Promise<boolean>;
126
+ /** Revoke every active session for a user; returns the affected ids (e.g. to drop
127
+ * matching live websockets). */
128
+ disableAllForUser(userId: string, disabledBy?: string | null): Promise<string[]>;
129
+ /** Forget a single session entirely (logout — drops the audit row too). */
130
+ remove(sessionId: string): Promise<void>;
131
+ /** Hard-delete every session for a user (memory + DB). */
132
+ deleteByUser(userId: string): Promise<void>;
133
+ /** All sessions for a user (incl. disabled audit rows), newest first — for an
134
+ * account/admin "your devices" view. */
135
+ listByUser(userId: string): Promise<Session[]>;
136
+ /** All cached sessions (sync; admin live list). */
137
+ listAll(): Session[];
138
+ /** Most-recent activity across a user's active sessions, or null — a cheap presence
139
+ * signal. */
140
+ getLastSeenForUser(userId: string): string | null;
141
+ /** Drop sessions whose last activity predates the retention window (memory + DB).
142
+ * Returns the count removed. */
143
+ prune(): Promise<number>;
144
+ /** Stop the optional auto-prune timer (no-op if none was started). */
145
+ stop(): void;
146
+ }
@@ -0,0 +1,51 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { decryptVaultItem, deriveVaultKey, encryptVaultItem } from './vault';
3
+
4
+ const SECRET = 'server-secret-used-for-vault-key-derivation';
5
+
6
+ describe('vault item crypto (AES-256-GCM + HKDF)', () => {
7
+ test('encrypt → decrypt roundtrips the plaintext', async () => {
8
+ const key = await deriveVaultKey(SECRET, 'user-1');
9
+ const plaintext = 'sꜱh key: hunter2 🔐 — multi-byte ✓';
10
+ const envelope = await encryptVaultItem(key, plaintext);
11
+ expect(await decryptVaultItem(key, envelope)).toBe(plaintext);
12
+ });
13
+
14
+ test('each encryption uses a fresh IV (ciphertext differs)', async () => {
15
+ const key = await deriveVaultKey(SECRET, 'user-1');
16
+ const a = await encryptVaultItem(key, 'same');
17
+ const b = await encryptVaultItem(key, 'same');
18
+ expect(a).not.toBe(b);
19
+ expect(await decryptVaultItem(key, a)).toBe('same');
20
+ expect(await decryptVaultItem(key, b)).toBe('same');
21
+ });
22
+
23
+ test('the envelope starts with the version byte (1)', async () => {
24
+ const key = await deriveVaultKey(SECRET, 'user-1');
25
+ const envelope = await encryptVaultItem(key, 'x');
26
+ expect(new Uint8Array(Buffer.from(envelope, 'base64'))[0]).toBe(1);
27
+ });
28
+
29
+ test('a different user derives a different key — cross-user decrypt fails', async () => {
30
+ const k1 = await deriveVaultKey(SECRET, 'user-1');
31
+ const k2 = await deriveVaultKey(SECRET, 'user-2');
32
+ const envelope = await encryptVaultItem(k1, 'secret');
33
+ await expect(decryptVaultItem(k2, envelope)).rejects.toThrow();
34
+ });
35
+
36
+ test('a tampered envelope fails the GCM auth check', async () => {
37
+ const key = await deriveVaultKey(SECRET, 'user-1');
38
+ const bytes = new Uint8Array(Buffer.from(await encryptVaultItem(key, 'secret'), 'base64'));
39
+ bytes[bytes.length - 1] ^= 0xff; // flip a ciphertext bit
40
+ await expect(decryptVaultItem(key, Buffer.from(bytes).toString('base64'))).rejects.toThrow();
41
+ });
42
+
43
+ test('rejects an unknown version byte', async () => {
44
+ const key = await deriveVaultKey(SECRET, 'user-1');
45
+ const bytes = new Uint8Array(Buffer.from(await encryptVaultItem(key, 'secret'), 'base64'));
46
+ bytes[0] = 2;
47
+ await expect(decryptVaultItem(key, Buffer.from(bytes).toString('base64'))).rejects.toThrow(
48
+ /version/,
49
+ );
50
+ });
51
+ });