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,57 @@
1
+ import { validationEnvelope } from '../errors';
2
+ /**
3
+ * Read the value a target validates against. The body targets (`json`/`form`) prefer
4
+ * the ctgr-decoded body so a tunnelled mutation validates its real payload; the
5
+ * URL-bound targets (`query`/`param`) are never tunnelled (GETs go direct) and read
6
+ * from the live request. A query object collapses single-valued keys to a string so
7
+ * `z.string()`/`z.coerce.number()` field schemas see a scalar (repeated keys stay an
8
+ * array) — matching Hono's own validator semantics.
9
+ */
10
+ async function readTarget(c, target) {
11
+ if (target === 'json' || target === 'form') {
12
+ const decoded = c.get('ctgrDecoded');
13
+ if (decoded)
14
+ return decoded.body;
15
+ if (target === 'json')
16
+ return c.req.json().catch(() => undefined);
17
+ return c.req.parseBody();
18
+ }
19
+ if (target === 'param')
20
+ return c.req.param();
21
+ return Object.fromEntries(Object.entries(c.req.queries()).map(([key, values]) => values.length === 1 ? [key, values[0]] : [key, values]));
22
+ }
23
+ /**
24
+ * Validate a request `target` against a zod `schema`. Mount per route ahead of the
25
+ * handler:
26
+ *
27
+ * ```ts
28
+ * register('post', '/notes', zValidator(CreateNoteSchema), (c) => {
29
+ * const body = c.req.valid('json'); // typed z.infer<typeof CreateNoteSchema>
30
+ * // …
31
+ * });
32
+ * ```
33
+ *
34
+ * On failure responds `400` with the canonical envelope (field errors under
35
+ * `context.issues`); never throws, so it produces the same body whether or not the
36
+ * app mounts the {@link errorEnvelope} `onError` handler. On success the parsed value
37
+ * is available as both `c.req.valid(target)` and `c.get('validatedBody')`.
38
+ */
39
+ export function zValidator(schema, target = 'json') {
40
+ const handler = async (c, next) => {
41
+ const raw = await readTarget(c, target);
42
+ const result = schema.safeParse(raw);
43
+ if (!result.success) {
44
+ return c.json(validationEnvelope(result.error), 400);
45
+ }
46
+ // Expose the parsed value both ways (typed `c.req.valid` + convenience getter).
47
+ c.req.addValidatedData(target, result.data);
48
+ c.set('validatedBody', result.data);
49
+ await next();
50
+ };
51
+ // The body is written against the concrete `CursedbeltEnv` (so the ctgr/validatedBody
52
+ // accessors type-check); the public signature is the generic, schema-typed
53
+ // MiddlewareHandler that flows `c.req.valid(target)` into the handler. Hono's Context
54
+ // is invariant in its Env, so this widening goes through `unknown` — the same cast
55
+ // a hand-rolled typed middleware (e.g. @hono/zod-validator) performs.
56
+ return handler;
57
+ }
@@ -0,0 +1,42 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ import type { Migration } from './types';
3
+ /**
4
+ * The versioned SQLite migration runner — a tiny `_migrations`-sentinel runner, NOT
5
+ * a schema-first ORM. It replaces the old scattered guarded-`ALTER` /
6
+ * `addColumnIfMissing` pattern with ordered, once-only migrations whose names are
7
+ * recorded once applied. Business tables stay raw (`db.query` + `data JSON`); only
8
+ * the *plumbing* tables get Kysely types (see `../db/kysely`).
9
+ */
10
+ /**
11
+ * Apply the recommended connection PRAGMAs. **Call this BEFORE {@link runMigrations}
12
+ * and OUTSIDE any transaction** — `PRAGMA foreign_keys` is silently ignored inside a
13
+ * transaction, and the runner wraps each migration in one. Idempotent; safe on boot.
14
+ *
15
+ * This is now a straight alias of cwip's `applyCcPragmas`: the CC storage tier pins
16
+ * ONE pragma profile monorepo-wide (`CC_PRAGMAS` — WAL + NORMAL + busy_timeout 5000 +
17
+ * foreign_keys ON + wal_autocheckpoint 1000), and the belt-local copy that used to
18
+ * live here was the second implementation the spec forbids. The per-pragma
19
+ * `PragmaOptions` overrides are gone with it — a handle that drifts from the profile
20
+ * is a `pragma-drift` violation, not a configuration choice.
21
+ *
22
+ * @deprecated Prefer cwip's `assertCcPragmas`, which additionally verifies each
23
+ * pragma took and installs the WAL transaction-lifetime guard.
24
+ */
25
+ export declare function applyRecommendedPragmas(db: Database): void;
26
+ /**
27
+ * Run every pending migration exactly once, in array order, each inside its own
28
+ * transaction, recording its `name` in `_migrations`. Returns the names actually
29
+ * applied this call (empty when already up to date — re-running is a no-op).
30
+ *
31
+ * Set the connection PRAGMAs first via {@link applyRecommendedPragmas} (foreign-key
32
+ * enforcement cannot be turned on inside the per-migration transaction).
33
+ */
34
+ export declare function runMigrations(db: Database, migrations: readonly Migration[]): string[];
35
+ /** Does `table` have a column named `column`? (via `PRAGMA table_info`). */
36
+ export declare function columnExists(db: Database, table: string, column: string): boolean;
37
+ /**
38
+ * Add a column if it is missing (idempotent). Returns true when it was added. A
39
+ * migration helper for the rare evolve-in-place case; the canonical schema is built
40
+ * fresh by `0001_initial`, so new migrations normally `CREATE` rather than `ALTER`.
41
+ */
42
+ export declare function addColumnIfMissing(db: Database, table: string, column: string, ddl: string): boolean;
@@ -0,0 +1,70 @@
1
+ import { applyCcPragmas } from 'cwip/sqlite';
2
+ /**
3
+ * The versioned SQLite migration runner — a tiny `_migrations`-sentinel runner, NOT
4
+ * a schema-first ORM. It replaces the old scattered guarded-`ALTER` /
5
+ * `addColumnIfMissing` pattern with ordered, once-only migrations whose names are
6
+ * recorded once applied. Business tables stay raw (`db.query` + `data JSON`); only
7
+ * the *plumbing* tables get Kysely types (see `../db/kysely`).
8
+ */
9
+ /**
10
+ * Apply the recommended connection PRAGMAs. **Call this BEFORE {@link runMigrations}
11
+ * and OUTSIDE any transaction** — `PRAGMA foreign_keys` is silently ignored inside a
12
+ * transaction, and the runner wraps each migration in one. Idempotent; safe on boot.
13
+ *
14
+ * This is now a straight alias of cwip's `applyCcPragmas`: the CC storage tier pins
15
+ * ONE pragma profile monorepo-wide (`CC_PRAGMAS` — WAL + NORMAL + busy_timeout 5000 +
16
+ * foreign_keys ON + wal_autocheckpoint 1000), and the belt-local copy that used to
17
+ * live here was the second implementation the spec forbids. The per-pragma
18
+ * `PragmaOptions` overrides are gone with it — a handle that drifts from the profile
19
+ * is a `pragma-drift` violation, not a configuration choice.
20
+ *
21
+ * @deprecated Prefer cwip's `assertCcPragmas`, which additionally verifies each
22
+ * pragma took and installs the WAL transaction-lifetime guard.
23
+ */
24
+ export function applyRecommendedPragmas(db) {
25
+ applyCcPragmas(db);
26
+ }
27
+ /**
28
+ * Run every pending migration exactly once, in array order, each inside its own
29
+ * transaction, recording its `name` in `_migrations`. Returns the names actually
30
+ * applied this call (empty when already up to date — re-running is a no-op).
31
+ *
32
+ * Set the connection PRAGMAs first via {@link applyRecommendedPragmas} (foreign-key
33
+ * enforcement cannot be turned on inside the per-migration transaction).
34
+ */
35
+ export function runMigrations(db, migrations) {
36
+ db.run('CREATE TABLE IF NOT EXISTS _migrations (name TEXT PRIMARY KEY, applied_at TEXT NOT NULL)');
37
+ const applied = new Set(db.query('SELECT name FROM _migrations').all().map((r) => r.name));
38
+ const insert = db.prepare('INSERT INTO _migrations (name, applied_at) VALUES (?, ?)');
39
+ const ran = [];
40
+ for (const m of migrations) {
41
+ if (applied.has(m.name))
42
+ continue;
43
+ const apply = db.transaction(() => {
44
+ m.up(db);
45
+ insert.run(m.name, new Date().toISOString());
46
+ });
47
+ apply();
48
+ ran.push(m.name);
49
+ }
50
+ return ran;
51
+ }
52
+ function quoteIdent(name) {
53
+ return `"${name.replace(/"/g, '""')}"`;
54
+ }
55
+ /** Does `table` have a column named `column`? (via `PRAGMA table_info`). */
56
+ export function columnExists(db, table, column) {
57
+ const rows = db.query(`PRAGMA table_info(${quoteIdent(table)})`).all();
58
+ return rows.some((r) => r.name === column);
59
+ }
60
+ /**
61
+ * Add a column if it is missing (idempotent). Returns true when it was added. A
62
+ * migration helper for the rare evolve-in-place case; the canonical schema is built
63
+ * fresh by `0001_initial`, so new migrations normally `CREATE` rather than `ALTER`.
64
+ */
65
+ export function addColumnIfMissing(db, table, column, ddl) {
66
+ if (columnExists(db, table, column))
67
+ return false;
68
+ db.run(`ALTER TABLE ${quoteIdent(table)} ADD COLUMN ${quoteIdent(column)} ${ddl}`);
69
+ return true;
70
+ }
@@ -0,0 +1,16 @@
1
+ import type { Database } from 'bun:sqlite';
2
+ /**
3
+ * One ordered, immutable schema migration. `name` is recorded in the `_migrations`
4
+ * sentinel once applied, so a migration's `up()` runs exactly once across the life
5
+ * of a database. Corrections land as NEW migrations — never edit a shipped one, and
6
+ * never rename one that has been applied (the name is its identity).
7
+ *
8
+ * `up()` runs inside the runner's per-migration transaction; throw to abort + roll
9
+ * back that migration (the sentinel row is not written, so it retries next boot).
10
+ */
11
+ export interface Migration {
12
+ /** Unique, lexically-ordered name, e.g. `0001_initial`. Immutable once applied. */
13
+ readonly name: string;
14
+ /** Apply the migration. Runs inside the runner's transaction. */
15
+ up(db: Database): void;
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ export declare const OUTBOUND_URL_BLOCKED = "OUTBOUND_URL_BLOCKED";
2
+ export interface OutboundGuardOptions {
3
+ /**
4
+ * Exact hostnames (case-insensitive) explicitly permitted even if they would
5
+ * otherwise be blocked — for a deliberately-configured internal integration.
6
+ * Empty by default: nothing internal is reachable unless a host opts in.
7
+ */
8
+ allowHosts?: readonly string[];
9
+ /** Permitted URL schemes. Default `['http:', 'https:']`. */
10
+ allowSchemes?: readonly string[];
11
+ }
12
+ /**
13
+ * Structural SSRF guard: parse `rawUrl`, enforce scheme, reject credentials, and
14
+ * refuse any host that is a private/loopback/link-local/reserved IP literal (in
15
+ * any encoding) or a well-known internal hostname. Returns the parsed URL.
16
+ *
17
+ * @throws {AppError} `OUTBOUND_URL_BLOCKED` on any violation.
18
+ */
19
+ export declare const assertPublicOutboundUrl: (rawUrl: string, options?: OutboundGuardOptions) => URL;
20
+ /**
21
+ * DNS-aware SSRF guard: the structural checks of {@link assertPublicOutboundUrl}
22
+ * PLUS a resolution pass — every address the hostname resolves to is re-checked,
23
+ * so a public name pointing at a private IP is refused. Still racy against an
24
+ * attacker who rebinds between this lookup and the actual connect, which is why
25
+ * the SaaS profile must also enforce network-layer egress; but it closes the
26
+ * naive `evil.example → 169.254.169.254` vector the structural pass cannot see.
27
+ *
28
+ * @throws {AppError} `OUTBOUND_URL_BLOCKED` on any violation.
29
+ */
30
+ export declare const assertResolvedPublicUrl: (rawUrl: string, options?: OutboundGuardOptions) => Promise<URL>;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * SSRF guard for server-side outbound HTTP (03 §2.4 H-4 adjudication).
3
+ *
4
+ * The Request Builder send (`/api/system/http`) is server-side BY DESIGN: the
5
+ * pinned CSP's `connect-src` would block a browser-side request to an arbitrary
6
+ * host, so the send is performed by the server to keep the tool working without
7
+ * widening a single header. But moving the send server-side creates a
8
+ * server-side request forgery surface the CSP argument never addressed — an
9
+ * authenticated caller can now make the SERVER fetch:
10
+ *
11
+ * - `http://169.254.169.254/latest/meta-data/…` → cloud metadata → credentials
12
+ * - `http://localhost:4747/…` / `http://127.0.0.1` → the station and sibling apps
13
+ * - `http://10.x` / `172.16.x` / `192.168.x` → anything on the private network
14
+ * - `http://<internal-rds-or-redis-or-admin>` → internal-only services
15
+ *
16
+ * This module is the boundary that refuses those. It is fail-CLOSED and lives in
17
+ * the framework (not in a leaf `sendHttp` impl) so no implementation can forget
18
+ * it: the mixin runs {@link assertPublicOutboundUrl} on the caller-supplied URL
19
+ * BEFORE any impl sees it.
20
+ *
21
+ * Two layers, because neither alone is sufficient:
22
+ * 1. {@link assertPublicOutboundUrl} — synchronous, structural: scheme +
23
+ * credential + literal-IP-range + dangerous-hostname checks. Catches every
24
+ * IP-literal vector (including the decimal/hex/octal encodings that
25
+ * `new URL()` preserves and `fetch` still resolves) and the well-known
26
+ * internal hostnames. This is what the request path calls on every send.
27
+ * 2. {@link assertResolvedPublicUrl} — async, DNS-aware: additionally resolves
28
+ * the hostname and re-checks every resolved address, which is the only
29
+ * defense against a public name that resolves to a private IP (DNS
30
+ * rebinding). Opt-in for impls that can afford the lookup; the SaaS profile
31
+ * must ALSO enforce egress at the network layer, since even this races a
32
+ * rebinding attacker between resolve and connect.
33
+ */
34
+ import { isIP } from 'node:net';
35
+ import { lookup } from 'node:dns/promises';
36
+ import { AppError } from '../errors';
37
+ export const OUTBOUND_URL_BLOCKED = 'OUTBOUND_URL_BLOCKED';
38
+ const blocked = (reason, context) => new AppError(`Outbound request refused: ${reason}.`, {
39
+ code: OUTBOUND_URL_BLOCKED,
40
+ status: 400,
41
+ category: 'security',
42
+ // Deliberately terse context — do not echo internal topology back.
43
+ context,
44
+ });
45
+ /** Hostnames and suffixes that always resolve to a non-public / internal target. */
46
+ const BLOCKED_HOST_EXACT = new Set(['localhost', 'metadata.google.internal', 'metadata']);
47
+ const BLOCKED_HOST_SUFFIXES = ['.localhost', '.local', '.internal', '.intranet', '.cluster.local', '.svc'];
48
+ /**
49
+ * Parse a hostname into an IPv4 the way a permissive resolver would, covering
50
+ * the encodings SSRF filters classically miss: dotted-decimal (`127.0.0.1`), a
51
+ * bare 32-bit integer (`2130706433`), and hex (`0x7f000001`). Returns the four
52
+ * octets, or `null` when the host is not an IPv4 literal in any of these forms.
53
+ */
54
+ const looseIpv4Octets = (host) => {
55
+ if (isIP(host) === 4) {
56
+ const parts = host.split('.').map(Number);
57
+ return [parts[0], parts[1], parts[2], parts[3]];
58
+ }
59
+ // Bare integer or hex integer → a single 32-bit value.
60
+ const asInt = /^0x[0-9a-f]+$/i.test(host)
61
+ ? Number.parseInt(host, 16)
62
+ : /^[0-9]+$/.test(host)
63
+ ? Number.parseInt(host, 10)
64
+ : Number.NaN;
65
+ if (Number.isInteger(asInt) && asInt >= 0 && asInt <= 0xffffffff)
66
+ return [(asInt >>> 24) & 0xff, (asInt >>> 16) & 0xff, (asInt >>> 8) & 0xff, asInt & 0xff];
67
+ return null;
68
+ };
69
+ /** Whether an IPv4 (as octets) is in a private, loopback, link-local or reserved range. */
70
+ const isBlockedIpv4 = ([a, b]) => {
71
+ if (a === 0 || a === 10 || a === 127)
72
+ return true; // this-host, private, loopback
73
+ if (a === 169 && b === 254)
74
+ return true; // link-local — INCLUDES 169.254.169.254 metadata
75
+ if (a === 172 && b >= 16 && b <= 31)
76
+ return true; // private
77
+ if (a === 192 && b === 168)
78
+ return true; // private
79
+ if (a === 100 && b >= 64 && b <= 127)
80
+ return true; // CGNAT (100.64/10)
81
+ if (a === 198 && (b === 18 || b === 19))
82
+ return true; // benchmarking
83
+ if (a >= 224)
84
+ return true; // multicast (224/4) + reserved (240/4) + 255.255.255.255
85
+ return false;
86
+ };
87
+ /**
88
+ * Extract the embedded IPv4 of an IPv4-mapped IPv6 address. `new URL` normalizes
89
+ * `::ffff:127.0.0.1` to the HEX form `::ffff:7f00:1`, so both the dotted and the
90
+ * two-hextet forms have to be handled.
91
+ */
92
+ const mappedV4Octets = (head) => {
93
+ const rest = /^::ffff:(.+)$/i.exec(head)?.[1];
94
+ if (!rest)
95
+ return null;
96
+ if (rest.includes('.'))
97
+ return looseIpv4Octets(rest);
98
+ const groups = rest.split(':');
99
+ if (groups.length !== 2)
100
+ return null;
101
+ const hi = Number.parseInt(groups[0], 16);
102
+ const lo = Number.parseInt(groups[1], 16);
103
+ if (!Number.isInteger(hi) || !Number.isInteger(lo))
104
+ return null;
105
+ return [(hi >> 8) & 0xff, hi & 0xff, (lo >> 8) & 0xff, lo & 0xff];
106
+ };
107
+ /** Whether an IPv6 literal (already `net.isIP`-classified) is non-public. */
108
+ const isBlockedIpv6 = (raw) => {
109
+ const head = raw.toLowerCase().replace(/^\[|\]$/g, '');
110
+ if (head === '::1' || head === '::')
111
+ return true; // loopback, unspecified
112
+ const mapped = mappedV4Octets(head);
113
+ if (mapped && isBlockedIpv4(mapped))
114
+ return true; // IPv4-mapped (`::ffff:…`)
115
+ // fe80::/10 link-local (fe80–febf) and fec0::/10 deprecated site-local (fec0–feff).
116
+ if (/^fe[89ab]/.test(head) || /^fe[c-f]/.test(head))
117
+ return true;
118
+ if (head.startsWith('fc') || head.startsWith('fd'))
119
+ return true; // fc00::/7 unique-local (incl. fd00:ec2::254 metadata)
120
+ if (head.startsWith('ff'))
121
+ return true; // ff00::/8 multicast
122
+ return false;
123
+ };
124
+ const isBlockedHostname = (host) => {
125
+ if (BLOCKED_HOST_EXACT.has(host))
126
+ return true;
127
+ return BLOCKED_HOST_SUFFIXES.some((suffix) => host.endsWith(suffix));
128
+ };
129
+ /**
130
+ * Structural SSRF guard: parse `rawUrl`, enforce scheme, reject credentials, and
131
+ * refuse any host that is a private/loopback/link-local/reserved IP literal (in
132
+ * any encoding) or a well-known internal hostname. Returns the parsed URL.
133
+ *
134
+ * @throws {AppError} `OUTBOUND_URL_BLOCKED` on any violation.
135
+ */
136
+ export const assertPublicOutboundUrl = (rawUrl, options = {}) => {
137
+ const allowSchemes = options.allowSchemes ?? ['http:', 'https:'];
138
+ const allowHosts = new Set((options.allowHosts ?? []).map((h) => h.toLowerCase()));
139
+ let url;
140
+ try {
141
+ url = new URL(rawUrl);
142
+ }
143
+ catch {
144
+ throw blocked('not a valid absolute URL', {});
145
+ }
146
+ if (!allowSchemes.includes(url.protocol))
147
+ throw blocked(`scheme '${url.protocol}' is not allowed`, { scheme: url.protocol });
148
+ // Credentials in the URL are a redirect/parsing-confusion vector and never
149
+ // legitimate for a tool send.
150
+ if (url.username !== '' || url.password !== '')
151
+ throw blocked('credentials in URL are not allowed', {});
152
+ const host = url.hostname.toLowerCase().replace(/^\[|\]$/g, '');
153
+ if (allowHosts.has(host))
154
+ return url; // explicit host opt-in bypasses the blocks
155
+ const v4 = looseIpv4Octets(host);
156
+ if (v4) {
157
+ if (isBlockedIpv4(v4))
158
+ throw blocked('target resolves to a private or reserved address', { host });
159
+ return url;
160
+ }
161
+ if (isIP(host) === 6) {
162
+ if (isBlockedIpv6(host))
163
+ throw blocked('target resolves to a private or reserved address', { host });
164
+ return url;
165
+ }
166
+ if (isBlockedHostname(host))
167
+ throw blocked('target host is internal-only', { host });
168
+ return url;
169
+ };
170
+ /**
171
+ * DNS-aware SSRF guard: the structural checks of {@link assertPublicOutboundUrl}
172
+ * PLUS a resolution pass — every address the hostname resolves to is re-checked,
173
+ * so a public name pointing at a private IP is refused. Still racy against an
174
+ * attacker who rebinds between this lookup and the actual connect, which is why
175
+ * the SaaS profile must also enforce network-layer egress; but it closes the
176
+ * naive `evil.example → 169.254.169.254` vector the structural pass cannot see.
177
+ *
178
+ * @throws {AppError} `OUTBOUND_URL_BLOCKED` on any violation.
179
+ */
180
+ export const assertResolvedPublicUrl = async (rawUrl, options = {}) => {
181
+ const url = assertPublicOutboundUrl(rawUrl, options);
182
+ const host = url.hostname.toLowerCase().replace(/^\[|\]$/g, '');
183
+ // A literal IP or an explicitly-allowed host needs no resolution recheck.
184
+ if (isIP(host) !== 0 || looseIpv4Octets(host) || (options.allowHosts ?? []).some((h) => h.toLowerCase() === host))
185
+ return url;
186
+ let addresses;
187
+ try {
188
+ addresses = await lookup(host, { all: true });
189
+ }
190
+ catch {
191
+ throw blocked('target hostname did not resolve', { host });
192
+ }
193
+ for (const { address } of addresses) {
194
+ const octets = looseIpv4Octets(address);
195
+ if (octets ? isBlockedIpv4(octets) : isIP(address) === 6 && isBlockedIpv6(address))
196
+ throw blocked('target hostname resolves to a private or reserved address', { host });
197
+ }
198
+ return url;
199
+ };
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Live delivery for the notification center — the half that makes a bell badge
3
+ * change without anybody clicking anything.
4
+ *
5
+ * The center already has the seam (`NotificationSink`); what was missing was a
6
+ * transport behind it. This is that transport, as **SSE** rather than a
7
+ * WebSocket, and the reason is the shape of the traffic: notifications are
8
+ * server→client only, they must survive a laptop lid closing, and a browser
9
+ * reconnects an `EventSource` by itself. A WebSocket would mean writing the
10
+ * reconnect loop, the heartbeat and the backoff that `EventSource` already has
11
+ * — and `cursedbelt-server/ws` remains the right answer for anything the client
12
+ * needs to send back up.
13
+ *
14
+ * 🔴 **A hub is a cache of who is listening, never a queue.** A notification is
15
+ * already durable (it is a row) before a sink is called, so a subscriber that
16
+ * is asleep, disconnected or behind a proxy that ate the stream loses nothing:
17
+ * the next list/poll returns the same row. That is why every emit here is
18
+ * fire-and-forget and a failing subscriber is dropped rather than retried — the
19
+ * poll transport in `cursedbelt/react/notifications` is the backstop, and it is
20
+ * not optional.
21
+ */
22
+ import type { AppNotification, NotificationSink } from "cwip/notifications";
23
+ /** What a subscriber receives. `unread` rides alongside so a bell badge never
24
+ * has to make a second request to learn its own number. */
25
+ export type NotificationHubEvent = {
26
+ kind: "notification";
27
+ notification: AppNotification;
28
+ } | {
29
+ kind: "unread";
30
+ userId: string;
31
+ count: number;
32
+ };
33
+ export type NotificationHubListener = (event: NotificationHubEvent) => void;
34
+ export interface NotificationHubOptions {
35
+ /**
36
+ * Comment-line heartbeat cadence, in ms. Default 25s — under the 30s idle
37
+ * timeout nginx and Cloudflare both default to, which is what silently kills
38
+ * an otherwise healthy SSE stream. 0 disables it (tests).
39
+ */
40
+ heartbeatMs?: number;
41
+ /**
42
+ * Max concurrent streams per user. Default 8. A tab is a stream, and a
43
+ * browser left open for a week accumulates them; without a cap one person's
44
+ * tab habit is an unbounded server-side array.
45
+ */
46
+ maxStreamsPerUser?: number;
47
+ /** Reported when a listener throws. Never rethrown — see the module doc. */
48
+ onError?: (error: unknown, context: string) => void;
49
+ }
50
+ export interface NotificationHub {
51
+ /**
52
+ * Hand this to `createNotificationCenter({ sink })`. Every `notify*` then
53
+ * reaches the connected tabs of exactly the addressed user.
54
+ */
55
+ readonly sink: NotificationSink;
56
+ /** Register a listener for one user's events. Returns an unsubscribe fn. */
57
+ subscribe(userId: string, listener: NotificationHubListener): () => void;
58
+ /** An SSE `Response` for one user — the thing a route returns. */
59
+ stream(userId: string, options?: {
60
+ signal?: AbortSignal;
61
+ }): Response;
62
+ /** How many live subscribers a user has (all users when omitted). */
63
+ subscriberCount(userId?: string): number;
64
+ /** Drop every subscriber and stop every heartbeat (shutdown, and tests). */
65
+ close(): void;
66
+ }
67
+ export declare function createNotificationHub(options?: NotificationHubOptions): NotificationHub;
@@ -0,0 +1,187 @@
1
+ const SSE_HEADERS = {
2
+ "content-type": "text/event-stream; charset=utf-8",
3
+ "cache-control": "no-store, no-transform",
4
+ connection: "keep-alive",
5
+ // nginx buffers proxied responses by default, which turns a live stream into
6
+ // one delivered when it ends. The fleet's box is behind nginx; this is the
7
+ // header that opts out, and it is inert everywhere else.
8
+ "x-accel-buffering": "no",
9
+ };
10
+ const encodeEvent = (event) => {
11
+ const name = event.kind;
12
+ const data = event.kind === "notification" ? event.notification : { count: event.count };
13
+ return `event: ${name}\ndata: ${JSON.stringify(data)}\n\n`;
14
+ };
15
+ export function createNotificationHub(options = {}) {
16
+ const heartbeatMs = options.heartbeatMs ?? 25_000;
17
+ const maxStreamsPerUser = options.maxStreamsPerUser ?? 8;
18
+ const onError = options.onError ?? (() => { });
19
+ /** userId → listeners. An empty set is deleted, so the map never grows with
20
+ * the user table — only with who is actually connected right now. */
21
+ const listeners = new Map();
22
+ let closed = false;
23
+ const emit = (userId, event) => {
24
+ const set = listeners.get(userId);
25
+ if (!set)
26
+ return;
27
+ // Snapshot: a listener may unsubscribe itself while being called (a
28
+ // stream that has just been cancelled does exactly that).
29
+ for (const listener of [...set]) {
30
+ try {
31
+ listener(event);
32
+ }
33
+ catch (error) {
34
+ onError(error, "notificationHub.listener");
35
+ set.delete(listener);
36
+ }
37
+ }
38
+ if (set.size === 0)
39
+ listeners.delete(userId);
40
+ };
41
+ const subscribe = (userId, listener) => {
42
+ if (closed)
43
+ return () => { };
44
+ let set = listeners.get(userId);
45
+ if (!set) {
46
+ set = new Set();
47
+ listeners.set(userId, set);
48
+ }
49
+ // Oldest-first eviction: the tab that has been open longest is the one
50
+ // least likely to be looked at, and dropping it costs nothing (the poll
51
+ // backstop still runs there).
52
+ while (set.size >= maxStreamsPerUser) {
53
+ const oldest = set.values().next().value;
54
+ if (!oldest)
55
+ break;
56
+ set.delete(oldest);
57
+ try {
58
+ oldest({ kind: "unread", userId, count: -1 });
59
+ }
60
+ catch (error) {
61
+ onError(error, "notificationHub.evict");
62
+ }
63
+ }
64
+ set.add(listener);
65
+ return () => {
66
+ const current = listeners.get(userId);
67
+ if (!current)
68
+ return;
69
+ current.delete(listener);
70
+ if (current.size === 0)
71
+ listeners.delete(userId);
72
+ };
73
+ };
74
+ return {
75
+ sink: {
76
+ onNotification(notification) {
77
+ emit(notification.userId, { kind: "notification", notification });
78
+ },
79
+ onUnread(userId, count) {
80
+ emit(userId, { kind: "unread", userId, count });
81
+ },
82
+ },
83
+ subscribe,
84
+ stream(userId, streamOptions = {}) {
85
+ let unsubscribe;
86
+ let heartbeat;
87
+ let onAbort;
88
+ const encoder = new TextEncoder();
89
+ const body = new ReadableStream({
90
+ start(controller) {
91
+ let live = true;
92
+ const send = (chunk) => {
93
+ if (!live)
94
+ return;
95
+ try {
96
+ controller.enqueue(encoder.encode(chunk));
97
+ }
98
+ catch (error) {
99
+ // The peer went away between our check and the write. Tear the
100
+ // subscription down rather than logging once per event forever.
101
+ live = false;
102
+ onError(error, "notificationHub.enqueue");
103
+ teardown();
104
+ }
105
+ };
106
+ const teardown = () => {
107
+ live = false;
108
+ unsubscribe?.();
109
+ unsubscribe = undefined;
110
+ if (heartbeat)
111
+ clearInterval(heartbeat);
112
+ heartbeat = undefined;
113
+ if (onAbort)
114
+ streamOptions.signal?.removeEventListener("abort", onAbort);
115
+ onAbort = undefined;
116
+ };
117
+ // A comment line first: it flushes proxy buffers and tells the
118
+ // client the stream is genuinely open before anything happens.
119
+ send(": connected\n\n");
120
+ unsubscribe = subscribe(userId, (event) => {
121
+ // The eviction poke (count -1) is not a real event — it means
122
+ // "this stream lost its slot", so close rather than report it.
123
+ if (event.kind === "unread" && event.count < 0) {
124
+ teardown();
125
+ try {
126
+ controller.close();
127
+ }
128
+ catch {
129
+ /* already closed */
130
+ }
131
+ return;
132
+ }
133
+ send(encodeEvent(event));
134
+ });
135
+ if (heartbeatMs > 0) {
136
+ heartbeat = setInterval(() => send(": ping\n\n"), heartbeatMs);
137
+ // Never hold the process open for a heartbeat — a satellite that
138
+ // is shutting down must not wait out someone's open tab.
139
+ heartbeat.unref?.();
140
+ }
141
+ if (streamOptions.signal) {
142
+ onAbort = () => {
143
+ teardown();
144
+ try {
145
+ controller.close();
146
+ }
147
+ catch {
148
+ /* already closed */
149
+ }
150
+ };
151
+ streamOptions.signal.addEventListener("abort", onAbort, { once: true });
152
+ }
153
+ },
154
+ cancel() {
155
+ unsubscribe?.();
156
+ unsubscribe = undefined;
157
+ if (heartbeat)
158
+ clearInterval(heartbeat);
159
+ heartbeat = undefined;
160
+ },
161
+ });
162
+ return new Response(body, { status: 200, headers: SSE_HEADERS });
163
+ },
164
+ subscriberCount(userId) {
165
+ if (userId !== undefined)
166
+ return listeners.get(userId)?.size ?? 0;
167
+ let total = 0;
168
+ for (const set of listeners.values())
169
+ total += set.size;
170
+ return total;
171
+ },
172
+ close() {
173
+ closed = true;
174
+ for (const [userId, set] of [...listeners]) {
175
+ for (const listener of [...set]) {
176
+ try {
177
+ listener({ kind: "unread", userId, count: -1 });
178
+ }
179
+ catch (error) {
180
+ onError(error, "notificationHub.close");
181
+ }
182
+ }
183
+ }
184
+ listeners.clear();
185
+ },
186
+ };
187
+ }