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,18 @@
1
+ import { type JWTPayload } from 'jose';
2
+ import type { AppJwtPayload } from './types';
3
+ /**
4
+ * Sign an HS256 token. `expiresIn` is a jose duration string (`'15m'`, `'24h'`) or a
5
+ * number of seconds-from-now. `iat`/`exp` are set automatically — pass everything
6
+ * else (`userId`, `sessionId`, `roles`, `getOnly`, optional `purpose`).
7
+ */
8
+ export declare function signToken(payload: Omit<AppJwtPayload, 'iat' | 'exp'>, secret: string, expiresIn: string | number): Promise<string>;
9
+ /** Verify signature + claims and return the decoded payload (throws on bad
10
+ * signature / expiry / malformed). */
11
+ export declare function verifyToken<T extends JWTPayload = AppJwtPayload>(token: string, secret: string): Promise<T>;
12
+ /**
13
+ * Verify a single-purpose token (vault/private-media/files/yt-oauth) AND assert its
14
+ * `purpose` claim matches — so an access token (or a token for a *different*
15
+ * feature) can never be replayed as, say, a vault-unlock token. Throws on a bad
16
+ * signature, expiry, or a purpose mismatch.
17
+ */
18
+ export declare function verifyPurposeToken<T extends AppJwtPayload = AppJwtPayload>(token: string, secret: string, purpose: string): Promise<T>;
@@ -0,0 +1,56 @@
1
+ import { jwtVerify, SignJWT } from 'jose';
2
+ /**
3
+ * JWT sign/verify on `jose` (Web Crypto HMAC — native on Bun + Node, no native
4
+ * deps; replaces `jsonwebtoken`). Every token system in the apps — the access
5
+ * token plus the short-lived vault/private-media/files/yt-oauth unlock tokens —
6
+ * goes through these, so there is exactly ONE jose integration to reason about.
7
+ *
8
+ * jose verifies any standard HS256 JWT regardless of which library signed it, so a
9
+ * token previously minted by `jsonwebtoken` validates here unchanged — that is the
10
+ * whole cutover guarantee (no dual-verify shim or `jsonwebtoken` dependency is
11
+ * carried into this from-scratch toolkit). A pre-`getOnly` token simply has no
12
+ * `getOnly` claim; the auth middleware reads it as `false`.
13
+ *
14
+ * NOTE: Web Crypto HMAC is async, so these are async (jsonwebtoken was sync — every
15
+ * caller becomes async/await).
16
+ */
17
+ const keyOf = (secret) => new TextEncoder().encode(secret);
18
+ /**
19
+ * Normalize jose's expiration input. jose treats a bare **number** as an absolute
20
+ * UNIX timestamp (seconds) — so `3600` would mean "1970" and expire instantly. We
21
+ * treat a number as RELATIVE seconds-from-now (the obviously-intended meaning) by
22
+ * handing jose a `"<n>s"` duration; a string (`'15m'`, `'24h'`) passes through as a
23
+ * jose duration unchanged.
24
+ */
25
+ const asDuration = (expiresIn) => typeof expiresIn === 'number' ? `${expiresIn}s` : expiresIn;
26
+ /**
27
+ * Sign an HS256 token. `expiresIn` is a jose duration string (`'15m'`, `'24h'`) or a
28
+ * number of seconds-from-now. `iat`/`exp` are set automatically — pass everything
29
+ * else (`userId`, `sessionId`, `roles`, `getOnly`, optional `purpose`).
30
+ */
31
+ export async function signToken(payload, secret, expiresIn) {
32
+ return new SignJWT(payload)
33
+ .setProtectedHeader({ alg: 'HS256' })
34
+ .setIssuedAt()
35
+ .setExpirationTime(asDuration(expiresIn))
36
+ .sign(keyOf(secret));
37
+ }
38
+ /** Verify signature + claims and return the decoded payload (throws on bad
39
+ * signature / expiry / malformed). */
40
+ export async function verifyToken(token, secret) {
41
+ const { payload } = await jwtVerify(token, keyOf(secret));
42
+ return payload;
43
+ }
44
+ /**
45
+ * Verify a single-purpose token (vault/private-media/files/yt-oauth) AND assert its
46
+ * `purpose` claim matches — so an access token (or a token for a *different*
47
+ * feature) can never be replayed as, say, a vault-unlock token. Throws on a bad
48
+ * signature, expiry, or a purpose mismatch.
49
+ */
50
+ export async function verifyPurposeToken(token, secret, purpose) {
51
+ const payload = await verifyToken(token, secret);
52
+ if (payload.purpose !== purpose) {
53
+ throw new Error(`token purpose mismatch: expected '${purpose}', got '${payload.purpose}'`);
54
+ }
55
+ return payload;
56
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Failed-login backoff for every password door in the fleet — cc's `guard` mixin and
3
+ * the satellites' own auth stacks alike.
4
+ *
5
+ * ── Why this shape (consolidated 2026-08-02) ────────────────────────────────
6
+ * There were two of these. This file used to hold a fixed-window lockout (N failures
7
+ * in a window → locked for M minutes); `cursed-satellites` grew a second, better one
8
+ * in `satellite-kit` because at the time `cursed-core` was treated as read-only from
9
+ * there. That precedent is repealed, so the EVOLVED implementation lands here and the
10
+ * satellites import it back. Everything below that reads like over-engineering is a
11
+ * lesson the fixed-window version had already lost.
12
+ *
13
+ * ── The trust model, which is the whole design ───────────────────────────────
14
+ * A client-supplied `X-Forwarded-For` is forgeable, so per-IP limiting alone is
15
+ * defeated by rotating the header. Two things follow, and both are deliberate:
16
+ *
17
+ * 1. **The IP is read from the LAST hop, not the first.** Behind nginx,
18
+ * `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for` APPENDS the socket
19
+ * peer to whatever the client sent. The final entry is therefore the address our
20
+ * own proxy observed; the first is whatever the client typed. Reading the first is
21
+ * the classic version of this bug.
22
+ * 2. **The GLOBAL counter is the actual defense.** For a single-principal door
23
+ * ("somebody is failing a lot" is enough to slow everybody down) it cannot be
24
+ * evaded by rotating a header at all. Per-IP limiting is the courtesy layer that
25
+ * keeps one noisy client from being the reason the legitimate user waits.
26
+ *
27
+ * ── The lockout-vs-DoS trade, stated plainly ─────────────────────────────────
28
+ * A global lock means an attacker can make a legitimate user wait. That is why the
29
+ * global ceiling is 60s while the per-IP ceiling is 15 minutes: a minute of delay per
30
+ * attempt reduces a distributed brute force to a rounding error against argon2id, and
31
+ * never locks someone out of their own dashboard for a quarter of an hour. A
32
+ * successful sign-in clears both counters.
33
+ *
34
+ * ── Exponential, not fixed-window ───────────────────────────────────────────
35
+ * Delay is `base·2^(n-1)` past the threshold rather than a flat lockout, so an honest
36
+ * typo costs a second and a scripted run costs progressively everything. A quiet
37
+ * window forgives: someone who mistyped twice last week starts fresh.
38
+ *
39
+ * ── Why in-memory, and why bounded ──────────────────────────────────────────
40
+ * The state is per-process and does not survive a restart. That is acceptable: an
41
+ * attacker cannot cause a restart, and the only thing that does — a deploy — is
42
+ * operator-initiated and rare. What is NOT acceptable is an unbounded map keyed by
43
+ * attacker-controlled input, so the table has a hard key cap and evicts the
44
+ * least-recently-seen entry when full. The cap is a real limit, not a comment: the
45
+ * global counter keeps working after eviction, which is precisely the case where it
46
+ * matters most.
47
+ *
48
+ * Pair with the coarse per-IP `createRateLimiter` (`../middleware/rateLimit`); this is
49
+ * the strict per-identity layer.
50
+ */
51
+ export interface LoginThrottleOptions {
52
+ /** Injected clock, so backoff is testable without waiting. */
53
+ now?: () => number;
54
+ /**
55
+ * Failures that incur NO delay. Default 5.
56
+ *
57
+ * Read it precisely: with `threshold: 5`, failures 1–5 are answered 401 with no
58
+ * lock, failure 6 is ALSO answered 401 and is the one that arms the first delay, so
59
+ * attempt 7 is the first refusal. The extra attempt is deliberate — the alternative
60
+ * (lock on the Nth failure itself) means a 429 whose cause the user cannot
61
+ * distinguish from a wrong password.
62
+ */
63
+ threshold?: number;
64
+ /** First delay after the threshold, doubling per further failure. Default 1s. */
65
+ baseDelayMs?: number;
66
+ /** Ceiling on the per-key delay. Default 15 minutes. */
67
+ maxDelayMs?: number;
68
+ /** Free failures, across ALL clients, before everyone is delayed. Default 20. */
69
+ globalThreshold?: number;
70
+ /** Ceiling on the global delay — deliberately lower; see the header. Default 60s. */
71
+ globalMaxDelayMs?: number;
72
+ /** A key with no failures for this long is forgotten. Default 1 hour. */
73
+ windowMs?: number;
74
+ /** Hard cap on tracked keys, so the table cannot be a memory vector. Default 4096. */
75
+ maxKeys?: number;
76
+ }
77
+ export type ThrottleVerdict = {
78
+ allowed: true;
79
+ } | {
80
+ allowed: false;
81
+ retryAfterSeconds: number;
82
+ scope: 'ip' | 'global';
83
+ };
84
+ export interface LoginThrottle {
85
+ /** May this key attempt a login right now? */
86
+ check(key: string): ThrottleVerdict;
87
+ /** Record a failed attempt. Returns the verdict the NEXT attempt would get. */
88
+ recordFailure(key: string): ThrottleVerdict;
89
+ /** Record a success — clears this key and the global counter. */
90
+ recordSuccess(key: string): void;
91
+ /** Clear one key without touching the global counter (admin unlock, verified reset). */
92
+ reset(key: string): void;
93
+ /** Tracked-key count, for the bounded-growth test. */
94
+ readonly size: number;
95
+ }
96
+ /**
97
+ * The client address to key on — the LAST `X-Forwarded-For` entry (what our proxy
98
+ * saw), falling back to `X-Real-IP`, the socket peer, and finally a shared constant.
99
+ *
100
+ * The fallback deliberately collapses to ONE key rather than inventing a unique one
101
+ * per request. A per-request key would make every attempt look like a first attempt
102
+ * from a new client, i.e. it would silently disable the per-IP layer. Collapsing means
103
+ * an unidentifiable client shares a budget with every other unidentifiable client,
104
+ * which is the conservative direction.
105
+ */
106
+ export declare function clientKeyOf(req: Request, socketAddress?: string | null): string;
107
+ /**
108
+ * The same, from inside a Hono handler.
109
+ *
110
+ * Under Bun, Hono's `env` IS the `Server` (the second argument to `app.fetch`), and
111
+ * `Server.requestIP(req)` is the only source of the real socket peer. Every step is
112
+ * defensive because that plumbing is the least stable thing here: `env` is `undefined`
113
+ * under `app.request()` in tests, and a future adapter could pass something else
114
+ * entirely. A missing address is not an error — `clientKeyOf` falls back to the
115
+ * headers, which is what production actually uses behind nginx.
116
+ */
117
+ export declare function clientKeyOfContext(req: Request, env: unknown): string;
118
+ export declare function createLoginThrottle(options?: LoginThrottleOptions): LoginThrottle;
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Failed-login backoff for every password door in the fleet — cc's `guard` mixin and
3
+ * the satellites' own auth stacks alike.
4
+ *
5
+ * ── Why this shape (consolidated 2026-08-02) ────────────────────────────────
6
+ * There were two of these. This file used to hold a fixed-window lockout (N failures
7
+ * in a window → locked for M minutes); `cursed-satellites` grew a second, better one
8
+ * in `satellite-kit` because at the time `cursed-core` was treated as read-only from
9
+ * there. That precedent is repealed, so the EVOLVED implementation lands here and the
10
+ * satellites import it back. Everything below that reads like over-engineering is a
11
+ * lesson the fixed-window version had already lost.
12
+ *
13
+ * ── The trust model, which is the whole design ───────────────────────────────
14
+ * A client-supplied `X-Forwarded-For` is forgeable, so per-IP limiting alone is
15
+ * defeated by rotating the header. Two things follow, and both are deliberate:
16
+ *
17
+ * 1. **The IP is read from the LAST hop, not the first.** Behind nginx,
18
+ * `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for` APPENDS the socket
19
+ * peer to whatever the client sent. The final entry is therefore the address our
20
+ * own proxy observed; the first is whatever the client typed. Reading the first is
21
+ * the classic version of this bug.
22
+ * 2. **The GLOBAL counter is the actual defense.** For a single-principal door
23
+ * ("somebody is failing a lot" is enough to slow everybody down) it cannot be
24
+ * evaded by rotating a header at all. Per-IP limiting is the courtesy layer that
25
+ * keeps one noisy client from being the reason the legitimate user waits.
26
+ *
27
+ * ── The lockout-vs-DoS trade, stated plainly ─────────────────────────────────
28
+ * A global lock means an attacker can make a legitimate user wait. That is why the
29
+ * global ceiling is 60s while the per-IP ceiling is 15 minutes: a minute of delay per
30
+ * attempt reduces a distributed brute force to a rounding error against argon2id, and
31
+ * never locks someone out of their own dashboard for a quarter of an hour. A
32
+ * successful sign-in clears both counters.
33
+ *
34
+ * ── Exponential, not fixed-window ───────────────────────────────────────────
35
+ * Delay is `base·2^(n-1)` past the threshold rather than a flat lockout, so an honest
36
+ * typo costs a second and a scripted run costs progressively everything. A quiet
37
+ * window forgives: someone who mistyped twice last week starts fresh.
38
+ *
39
+ * ── Why in-memory, and why bounded ──────────────────────────────────────────
40
+ * The state is per-process and does not survive a restart. That is acceptable: an
41
+ * attacker cannot cause a restart, and the only thing that does — a deploy — is
42
+ * operator-initiated and rare. What is NOT acceptable is an unbounded map keyed by
43
+ * attacker-controlled input, so the table has a hard key cap and evicts the
44
+ * least-recently-seen entry when full. The cap is a real limit, not a comment: the
45
+ * global counter keeps working after eviction, which is precisely the case where it
46
+ * matters most.
47
+ *
48
+ * Pair with the coarse per-IP `createRateLimiter` (`../middleware/rateLimit`); this is
49
+ * the strict per-identity layer.
50
+ */
51
+ /**
52
+ * The client address to key on — the LAST `X-Forwarded-For` entry (what our proxy
53
+ * saw), falling back to `X-Real-IP`, the socket peer, and finally a shared constant.
54
+ *
55
+ * The fallback deliberately collapses to ONE key rather than inventing a unique one
56
+ * per request. A per-request key would make every attempt look like a first attempt
57
+ * from a new client, i.e. it would silently disable the per-IP layer. Collapsing means
58
+ * an unidentifiable client shares a budget with every other unidentifiable client,
59
+ * which is the conservative direction.
60
+ */
61
+ export function clientKeyOf(req, socketAddress) {
62
+ const forwarded = req.headers.get('x-forwarded-for');
63
+ if (forwarded) {
64
+ const hops = forwarded
65
+ .split(',')
66
+ .map((h) => h.trim())
67
+ .filter((h) => h.length > 0);
68
+ // LAST, not first — see the header.
69
+ const observed = hops[hops.length - 1];
70
+ if (observed)
71
+ return observed;
72
+ }
73
+ const real = req.headers.get('x-real-ip')?.trim();
74
+ if (real)
75
+ return real;
76
+ return socketAddress?.trim() || 'unknown';
77
+ }
78
+ /**
79
+ * The same, from inside a Hono handler.
80
+ *
81
+ * Under Bun, Hono's `env` IS the `Server` (the second argument to `app.fetch`), and
82
+ * `Server.requestIP(req)` is the only source of the real socket peer. Every step is
83
+ * defensive because that plumbing is the least stable thing here: `env` is `undefined`
84
+ * under `app.request()` in tests, and a future adapter could pass something else
85
+ * entirely. A missing address is not an error — `clientKeyOf` falls back to the
86
+ * headers, which is what production actually uses behind nginx.
87
+ */
88
+ export function clientKeyOfContext(req, env) {
89
+ let address = null;
90
+ const requestIP = env?.requestIP;
91
+ if (typeof requestIP === 'function') {
92
+ try {
93
+ address = requestIP.call(env, req)?.address ?? null;
94
+ }
95
+ catch {
96
+ address = null;
97
+ }
98
+ }
99
+ return clientKeyOf(req, address);
100
+ }
101
+ /**
102
+ * Keys are compared case- and whitespace-insensitively.
103
+ *
104
+ * This is NOT cosmetic, and it is the one behavior the satellites' version had lost:
105
+ * the recommended key is `"<email>:<ip>"`, and an email address is case-insensitive,
106
+ * so without this `Victim@example.com` and `victim@example.com` are separate budgets
107
+ * and an attacker evades the per-account layer by varying the case of a letter. The
108
+ * global counter still caught that, but only after 20 free guesses per attacker.
109
+ */
110
+ const normalizeKey = (key) => key.trim().toLowerCase();
111
+ export function createLoginThrottle(options = {}) {
112
+ const now = options.now ?? Date.now;
113
+ const threshold = options.threshold ?? 5;
114
+ const baseDelayMs = options.baseDelayMs ?? 1_000;
115
+ const maxDelayMs = options.maxDelayMs ?? 15 * 60_000;
116
+ const globalThreshold = options.globalThreshold ?? 20;
117
+ const globalMaxDelayMs = options.globalMaxDelayMs ?? 60_000;
118
+ const windowMs = options.windowMs ?? 60 * 60_000;
119
+ const maxKeys = options.maxKeys ?? 4096;
120
+ const attempts = new Map();
121
+ const global = { failures: 0, lockedUntil: 0, lastSeen: 0 };
122
+ /** Delay for the nth failure past a threshold: base·2^(n-1), capped. */
123
+ const delayFor = (failures, limit, cap) => {
124
+ const past = failures - limit;
125
+ if (past <= 0)
126
+ return 0;
127
+ // `Math.min` before the shift would still overflow for large `past`; clamp the
128
+ // exponent instead so a client that keeps hammering cannot reach Infinity.
129
+ const exponent = Math.min(past - 1, 32);
130
+ return Math.min(baseDelayMs * 2 ** exponent, cap);
131
+ };
132
+ /** Drop stale entries; then, if still over the cap, evict least-recently-seen. */
133
+ const prune = (nowMs) => {
134
+ for (const [key, entry] of attempts) {
135
+ if (nowMs - entry.lastSeen > windowMs && entry.lockedUntil <= nowMs)
136
+ attempts.delete(key);
137
+ }
138
+ if (attempts.size <= maxKeys)
139
+ return;
140
+ const byAge = [...attempts.entries()].sort((a, b) => a[1].lastSeen - b[1].lastSeen);
141
+ for (const [key] of byAge.slice(0, attempts.size - maxKeys))
142
+ attempts.delete(key);
143
+ };
144
+ const verdict = (rawKey, nowMs) => {
145
+ const key = normalizeKey(rawKey);
146
+ // Global first: it is the layer that cannot be evaded by rotating a header, so it
147
+ // should also be the one that decides.
148
+ if (global.lockedUntil > nowMs) {
149
+ return {
150
+ allowed: false,
151
+ scope: 'global',
152
+ retryAfterSeconds: Math.ceil((global.lockedUntil - nowMs) / 1000),
153
+ };
154
+ }
155
+ const entry = attempts.get(key);
156
+ if (entry && entry.lockedUntil > nowMs) {
157
+ return {
158
+ allowed: false,
159
+ scope: 'ip',
160
+ retryAfterSeconds: Math.ceil((entry.lockedUntil - nowMs) / 1000),
161
+ };
162
+ }
163
+ return { allowed: true };
164
+ };
165
+ return {
166
+ check(key) {
167
+ return verdict(key, now());
168
+ },
169
+ recordFailure(rawKey) {
170
+ const key = normalizeKey(rawKey);
171
+ const nowMs = now();
172
+ const entry = attempts.get(key) ?? { failures: 0, lockedUntil: 0, lastSeen: nowMs };
173
+ // A quiet window forgives: someone who mistyped twice last week starts fresh,
174
+ // while a run of failures keeps compounding.
175
+ if (nowMs - entry.lastSeen > windowMs)
176
+ entry.failures = 0;
177
+ entry.failures += 1;
178
+ entry.lastSeen = nowMs;
179
+ entry.lockedUntil = nowMs + delayFor(entry.failures, threshold, maxDelayMs);
180
+ attempts.set(key, entry);
181
+ if (nowMs - global.lastSeen > windowMs)
182
+ global.failures = 0;
183
+ global.failures += 1;
184
+ global.lastSeen = nowMs;
185
+ global.lockedUntil = nowMs + delayFor(global.failures, globalThreshold, globalMaxDelayMs);
186
+ prune(nowMs);
187
+ return verdict(key, nowMs);
188
+ },
189
+ recordSuccess(rawKey) {
190
+ attempts.delete(normalizeKey(rawKey));
191
+ // The user got in, so whatever the global counter was reacting to is over.
192
+ // Leaving it armed would punish the legitimate user for the attack.
193
+ global.failures = 0;
194
+ global.lockedUntil = 0;
195
+ global.lastSeen = now();
196
+ },
197
+ reset(rawKey) {
198
+ attempts.delete(normalizeKey(rawKey));
199
+ },
200
+ get size() {
201
+ return attempts.size;
202
+ },
203
+ };
204
+ }
@@ -0,0 +1,14 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ import type { AuthEnv, SessionStore } from './types';
3
+ /**
4
+ * The composed auth middleware. Reads the httpOnly `access_token` cookie, verifies
5
+ * the JWT, checks the session is live (present, not revoked, inside its sliding
6
+ * window), and sets the normalized {@link AuthUser} on `c.get('user')` (plus
7
+ * `userId` for request-log attribution). Every failure maps to a 401 in the
8
+ * canonical error envelope.
9
+ *
10
+ * Deliberately minimal: it does NOT slide/re-issue the token — that (and the
11
+ * `/f`-vs-`/login` policy) is app business (task 32). Mount {@link
12
+ * ./getOnly.getOnlyEnforcement} AFTER this to add the GET-only gate.
13
+ */
14
+ export declare function createAuthMiddleware(sessionStore: SessionStore, jwtSecret: string): MiddlewareHandler<AuthEnv>;
@@ -0,0 +1,47 @@
1
+ import { ApiError, toErrorEnvelope } from '../errors';
2
+ import { readAuthCookie } from './cookie';
3
+ import { verifyToken } from './jwt';
4
+ /**
5
+ * The composed auth middleware. Reads the httpOnly `access_token` cookie, verifies
6
+ * the JWT, checks the session is live (present, not revoked, inside its sliding
7
+ * window), and sets the normalized {@link AuthUser} on `c.get('user')` (plus
8
+ * `userId` for request-log attribution). Every failure maps to a 401 in the
9
+ * canonical error envelope.
10
+ *
11
+ * Deliberately minimal: it does NOT slide/re-issue the token — that (and the
12
+ * `/f`-vs-`/login` policy) is app business (task 32). Mount {@link
13
+ * ./getOnly.getOnlyEnforcement} AFTER this to add the GET-only gate.
14
+ */
15
+ export function createAuthMiddleware(sessionStore, jwtSecret) {
16
+ return async (c, next) => {
17
+ const token = readAuthCookie(c);
18
+ if (!token) {
19
+ return c.json(toErrorEnvelope(new ApiError(401, 'Unauthorized')), 401);
20
+ }
21
+ let payload;
22
+ try {
23
+ payload = await verifyToken(token, jwtSecret);
24
+ }
25
+ catch {
26
+ return c.json(toErrorEnvelope(new ApiError(401, 'Invalid token')), 401);
27
+ }
28
+ const session = sessionStore.get(payload.sessionId);
29
+ if (!session || session.disabled) {
30
+ return c.json(toErrorEnvelope(new ApiError(401, 'Session revoked')), 401);
31
+ }
32
+ if (new Date(session.refreshExpiresAt) < new Date()) {
33
+ return c.json(toErrorEnvelope(new ApiError(401, 'Session expired')), 401);
34
+ }
35
+ const user = {
36
+ id: payload.userId,
37
+ roles: payload.roles ?? [],
38
+ // A token minted before the claim existed has no `getOnly` → treat as false,
39
+ // preserving access for pre-existing cookies.
40
+ getOnly: payload.getOnly ?? false,
41
+ sessionId: payload.sessionId,
42
+ };
43
+ c.set('user', user);
44
+ c.set('userId', user.id);
45
+ await next();
46
+ };
47
+ }
@@ -0,0 +1,14 @@
1
+ /** Mint an argon2id password hash. Returns the self-describing PHC string. */
2
+ export declare function hashPassword(plain: string): Promise<string>;
3
+ /**
4
+ * Verify a candidate against a stored hash, handling BOTH formats: an argon2id PHC
5
+ * string (new) via `Bun.password.verify`; a legacy `"<salt>:<pbkdf2-hex>"` digest by
6
+ * re-deriving PBKDF2-SHA512 with the stored salt and comparing in constant time.
7
+ */
8
+ export declare function verifyPassword(plain: string, stored: string): Promise<boolean>;
9
+ /**
10
+ * True when a stored hash is a legacy (non-argon2id) digest and should be upgraded.
11
+ * Call AFTER a successful {@link verifyPassword} to opportunistically re-hash with
12
+ * argon2id — never block auth on the upgrade.
13
+ */
14
+ export declare function needsRehash(stored: string): boolean;
@@ -0,0 +1,65 @@
1
+ import { pbkdf2 as pbkdf2Cb, timingSafeEqual } from 'node:crypto';
2
+ import { promisify } from 'node:util';
3
+ /**
4
+ * Password hashing on `Bun.password` (argon2id — memory-hard, thread-pooled, zero
5
+ * deps, the current OWASP default). The argon2id hash is self-describing
6
+ * (`$argon2id$v=19$m=…$<salt>$<hash>`) so there is NO separate salt column — the new
7
+ * schema stores just the hash string.
8
+ *
9
+ * Legacy upgrade path: the old app used PBKDF2-SHA512 @ 1000 iterations with a
10
+ * separate salt; the ETL (task 43) brings those forward as a combined `"<salt>:<hex>"`
11
+ * string in the password column (it can't re-hash — it lacks the plaintext).
12
+ * {@link verifyPassword} transparently verifies BOTH shapes; on a legacy match the
13
+ * caller re-hashes with argon2id ({@link needsRehash}) — a silent per-login upgrade,
14
+ * no forced reset.
15
+ */
16
+ // Legacy PBKDF2-SHA512 parameters — used ONLY to verify pre-argon2id hashes; never
17
+ // to mint new ones. (1000 iterations is far below modern guidance — exactly why we
18
+ // moved off it.)
19
+ const LEGACY_ITERATIONS = 1000;
20
+ const LEGACY_KEYLEN = 64;
21
+ const LEGACY_DIGEST = 'sha512';
22
+ const pbkdf2 = promisify(pbkdf2Cb);
23
+ const isArgon2 = (hash) => hash.startsWith('$argon2');
24
+ /** Mint an argon2id password hash. Returns the self-describing PHC string. */
25
+ export function hashPassword(plain) {
26
+ return Bun.password.hash(plain, { algorithm: 'argon2id' });
27
+ }
28
+ /**
29
+ * Verify a candidate against a stored hash, handling BOTH formats: an argon2id PHC
30
+ * string (new) via `Bun.password.verify`; a legacy `"<salt>:<pbkdf2-hex>"` digest by
31
+ * re-deriving PBKDF2-SHA512 with the stored salt and comparing in constant time.
32
+ */
33
+ export async function verifyPassword(plain, stored) {
34
+ if (isArgon2(stored)) {
35
+ try {
36
+ return await Bun.password.verify(plain, stored);
37
+ }
38
+ catch {
39
+ return false;
40
+ }
41
+ }
42
+ // Legacy: split on the FIRST colon — salt (hex) : derived (hex). A malformed value
43
+ // (no colon) is simply a non-match.
44
+ const sep = stored.indexOf(':');
45
+ if (sep === -1)
46
+ return false;
47
+ const salt = stored.slice(0, sep);
48
+ const expectedHex = stored.slice(sep + 1);
49
+ if (!salt || !expectedHex)
50
+ return false;
51
+ const derived = (await pbkdf2(plain, salt, LEGACY_ITERATIONS, LEGACY_KEYLEN, LEGACY_DIGEST)).toString('hex');
52
+ // timingSafeEqual throws on length mismatch; a length diff is already a non-match
53
+ // and short-circuiting leaks nothing a compare wouldn't.
54
+ if (derived.length !== expectedHex.length)
55
+ return false;
56
+ return timingSafeEqual(Buffer.from(derived), Buffer.from(expectedHex));
57
+ }
58
+ /**
59
+ * True when a stored hash is a legacy (non-argon2id) digest and should be upgraded.
60
+ * Call AFTER a successful {@link verifyPassword} to opportunistically re-hash with
61
+ * argon2id — never block auth on the upgrade.
62
+ */
63
+ export function needsRehash(stored) {
64
+ return !isArgon2(stored);
65
+ }
@@ -0,0 +1,19 @@
1
+ import type { PlumbingDb } from '../db/kysely';
2
+ import type { SessionStore } from './types';
3
+ export interface SessionStoreOptions {
4
+ /** Sliding refresh window — how long after the last activity a session stays
5
+ * valid. Default 7 days. */
6
+ refreshWindowMs?: number;
7
+ /** Retention before a stale (lapsed) row is pruned from memory + DB. Default 30
8
+ * days. Must be ≥ refreshWindowMs to keep recently-expired rows for the audit view. */
9
+ retentionMs?: number;
10
+ /** Throttle window for sliding-`touch` DB writes. Memory is always current; the
11
+ * row is flushed at most this often (unless a token re-issue forces it). Default 60s. */
12
+ flushIntervalMs?: number;
13
+ /** Start a background prune timer. Default false — wire pruning through the job
14
+ * runner (task 29), or call {@link SessionStore.prune} yourself. */
15
+ autoPrune?: boolean;
16
+ /** Auto-prune cadence when {@link autoPrune} is on. Default 1 hour. */
17
+ pruneIntervalMs?: number;
18
+ }
19
+ export declare function createSessionStore(ky: PlumbingDb, opts?: SessionStoreOptions): SessionStore;