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,313 @@
1
+ /**
2
+ * The lock page — the ONLY thing a locked app serves.
3
+ *
4
+ * ── Why it is authored here as strings ──────────────────────────────────────
5
+ * It has to render before any of the app exists: no bundle, no framework, no design system,
6
+ * no fonts, nothing fetched from anywhere. That is the requirement — *"a user logged in as
7
+ * me cannot see anything about the site, not even text or images, nothing at all"* — and the
8
+ * honest way to meet it is for the locked response to have nothing of the app in it.
9
+ *
10
+ * ── Why the CSS and JS are separate routes, not inline ──────────────────────
11
+ * `apps/vault`'s CSP wall forbids inline script, and any app adopting this lock deserves to
12
+ * keep a wall that strict. Two same-origin files cost one round trip each and let the page
13
+ * ship under `script-src 'self'` with no nonce plumbing.
14
+ *
15
+ * ── 🔴 The derivation in `LOCK_SCRIPT` is a HAND COPY of `deriveMasterLockVerifier` ──────
16
+ * It cannot import it: this file is served to a browser as plain text, outside every build.
17
+ * A copy that drifts derives a verifier that can never match, and the symptom would be "my
18
+ * correct password is refused" — the same shape of bug that cost `apps/collections` a day.
19
+ * So `lockPage.spec.ts` EVALUATES the script's function out of this source and asserts it
20
+ * agrees with the TypeScript one on a fixed vector. Change one, the spec fails until you
21
+ * change the other.
22
+ */
23
+ import { MASTER_LOCK_PATHS } from "cursedbelt-core/master-lock";
24
+
25
+ /** Everything the page needs to know about the app it is standing in front of. */
26
+ export interface LockPageOptions {
27
+ /** What to call this app — "Collections", "binary-server inspector". */
28
+ appLabel: string;
29
+ /** Shown under the title. Defaults to the sentence that explains the second wall. */
30
+ note?: string;
31
+ /**
32
+ * Render the CHOOSE-a-password form rather than the type-it form.
33
+ *
34
+ * Set by the guard from `lock.awaitingEnrollment`, i.e. per-person mode with no record
35
+ * for this account yet. It is a rendering hint and nothing more — the server refuses
36
+ * `POST /__lock/enroll` on its own record's state, so a page asking to enroll cannot
37
+ * make enrollment legal.
38
+ */
39
+ enroll?: boolean;
40
+ }
41
+
42
+ const escapeHtml = (value: string): string =>
43
+ value.replace(
44
+ /[&<>"']/g,
45
+ (ch) =>
46
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch] ?? ch,
47
+ );
48
+
49
+ /**
50
+ * The document. Deliberately says the app's NAME and nothing else about it — whoever is
51
+ * looking already reached the origin, so the hostname told them that much, and a page that
52
+ * would not say which app it is cannot tell the owner which password to type.
53
+ */
54
+ export function lockPageHtml(options: LockPageOptions): string {
55
+ const label = escapeHtml(options.appLabel);
56
+ const enroll = options.enroll === true;
57
+ const note = escapeHtml(
58
+ options.note ??
59
+ (enroll
60
+ ? "You are signed in, but this site needs a master password of your own. " +
61
+ "Choose one now. Nobody else can see it and nobody else can reset it — " +
62
+ "if you forget it, it cannot be recovered."
63
+ : "This site is locked. Signing in is not enough — enter the master password to continue."),
64
+ );
65
+ return `<!doctype html>
66
+ <html lang="en">
67
+ <head>
68
+ <meta charset="utf-8">
69
+ <meta name="viewport" content="width=device-width, initial-scale=1">
70
+ <meta name="robots" content="noindex, nofollow">
71
+ <title>Locked · ${label}</title>
72
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🔒</text></svg>">
73
+ <link rel="stylesheet" href="${MASTER_LOCK_PATHS.style}">
74
+ </head>
75
+ <body data-mode="${enroll ? "enroll" : "unlock"}">
76
+ <main class="card" aria-labelledby="ml-title">
77
+ <div class="glyph" aria-hidden="true">${enroll ? "🔑" : "🔒"}</div>
78
+ <h1 id="ml-title">${label}</h1>
79
+ <p class="note">${note}</p>
80
+ <form id="ml-form" autocomplete="off">
81
+ <label class="field">
82
+ <span>${enroll ? "New master password" : "Master password"}</span>
83
+ <input id="ml-input" type="password" name="master-password"
84
+ autocomplete="${enroll ? "new-password" : "current-password"}"
85
+ autofocus required spellcheck="false" enterkeyhint="${enroll ? "next" : "go"}"${
86
+ enroll ? ' minlength="10"' : ""
87
+ }>
88
+ </label>${
89
+ enroll
90
+ ? `
91
+ <label class="field">
92
+ <span>Confirm master password</span>
93
+ <input id="ml-confirm" type="password" name="confirm-master-password"
94
+ autocomplete="new-password" required spellcheck="false" enterkeyhint="go"
95
+ minlength="10">
96
+ </label>`
97
+ : ""
98
+ }
99
+ <button id="ml-submit" type="submit">${enroll ? "Set master password" : "Unlock"}</button>
100
+ </form>
101
+ <p id="ml-status" class="status" role="status" aria-live="polite"></p>
102
+ </main>
103
+ <script type="module" src="${MASTER_LOCK_PATHS.script}"></script>
104
+ </body>
105
+ </html>
106
+ `;
107
+ }
108
+
109
+ /** The stylesheet. Follows the viewer's theme and commits to nothing else. */
110
+ export const LOCK_STYLE = `:root{color-scheme:light dark;
111
+ --ml-bg:#f4f5f7;--ml-card:#ffffff;--ml-text:#14171c;--ml-muted:#69707c;--ml-border:#d9dde3;
112
+ --ml-accent:#1f6feb;--ml-accent-text:#ffffff;--ml-danger:#b42318;
113
+ --ml-shadow:0 18px 48px rgba(16,18,22,.14)}
114
+ @media (prefers-color-scheme:dark){:root{
115
+ --ml-bg:#0d0f13;--ml-card:#171a20;--ml-text:#e7eaef;--ml-muted:#8b93a1;--ml-border:#2b3039;
116
+ --ml-accent:#4f8cf7;--ml-accent-text:#0b0d11;--ml-danger:#f97066;
117
+ --ml-shadow:0 18px 48px rgba(0,0,0,.55)}}
118
+ *{box-sizing:border-box}
119
+ body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px;
120
+ background:var(--ml-bg);color:var(--ml-text);
121
+ font:15px/1.55 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}
122
+ .card{width:100%;max-width:23rem;background:var(--ml-card);border:1px solid var(--ml-border);
123
+ border-radius:16px;padding:28px 26px 24px;box-shadow:var(--ml-shadow);text-align:center}
124
+ .glyph{font-size:30px;line-height:1;margin-bottom:10px}
125
+ h1{margin:0 0 6px;font-size:18px;font-weight:650;letter-spacing:-.01em}
126
+ .note{margin:0 0 20px;font-size:13px;color:var(--ml-muted)}
127
+ .field{display:block;text-align:left;margin-bottom:14px}
128
+ .field span{display:block;font-size:12px;font-weight:600;color:var(--ml-muted);margin-bottom:6px}
129
+ input{width:100%;height:40px;padding:0 12px;border:1px solid var(--ml-border);border-radius:9px;
130
+ background:var(--ml-bg);color:inherit;font:inherit}
131
+ input:focus{outline:2px solid var(--ml-accent);outline-offset:1px;border-color:transparent}
132
+ button{width:100%;height:40px;border:0;border-radius:9px;background:var(--ml-accent);
133
+ color:var(--ml-accent-text);font:inherit;font-weight:600;cursor:pointer}
134
+ button[disabled]{opacity:.6;cursor:progress}
135
+ .status{margin:14px 0 0;min-height:1.2em;font-size:12.5px;color:var(--ml-muted)}
136
+ .status[data-tone="error"]{color:var(--ml-danger)}
137
+ `;
138
+
139
+ /**
140
+ * The page's script.
141
+ *
142
+ * Derives in the browser and sends only the verifier — the password itself never crosses
143
+ * the network, which is what makes "saved nowhere" true rather than aspirational.
144
+ *
145
+ * 600,000 PBKDF2 rounds take a beat on a phone, so the button says so; a page that looks
146
+ * frozen gets clicked again, and every extra click is another 600k rounds.
147
+ */
148
+ export const LOCK_SCRIPT = `const paths = ${JSON.stringify(MASTER_LOCK_PATHS)};
149
+
150
+ const form = document.getElementById("ml-form");
151
+ const input = document.getElementById("ml-input");
152
+ const confirmInput = document.getElementById("ml-confirm");
153
+ const submit = document.getElementById("ml-submit");
154
+ const status = document.getElementById("ml-status");
155
+
156
+ function say(message, tone) {
157
+ status.textContent = message;
158
+ if (tone) status.setAttribute("data-tone", tone);
159
+ else status.removeAttribute("data-tone");
160
+ }
161
+
162
+ // ── derivation ───────────────────────────────────────────────────────────────
163
+ // 🔴 Byte-identical to deriveMasterLockVerifier in cursedbelt-core/master-lock. Pinned by
164
+ // lockPage.spec.ts, which evaluates THIS function and compares. Do not "tidy" it apart.
165
+ function unb64u(text) {
166
+ const padded = text.replace(/-/g, "+").replace(/_/g, "/");
167
+ const binary = atob(padded + "=".repeat((4 - (padded.length % 4)) % 4));
168
+ const out = new Uint8Array(binary.length);
169
+ for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
170
+ return out;
171
+ }
172
+ function b64u(bytes) {
173
+ let binary = "";
174
+ for (const byte of bytes) binary += String.fromCharCode(byte);
175
+ return btoa(binary).replace(/\\+/g, "-").replace(/\\//g, "_").replace(/=+$/, "");
176
+ }
177
+ async function pbkdf2(material, salt, iterations) {
178
+ const key = await crypto.subtle.importKey("raw", material, "PBKDF2", false, ["deriveBits"]);
179
+ const bits = await crypto.subtle.deriveBits(
180
+ { name: "PBKDF2", salt: salt, iterations: iterations, hash: "SHA-256" }, key, 256);
181
+ return new Uint8Array(bits);
182
+ }
183
+ async function deriveVerifier(password, kdf) {
184
+ const salt = unb64u(kdf.salt);
185
+ const bytes = new TextEncoder().encode(password);
186
+ const kek = await pbkdf2(bytes, salt, kdf.iter);
187
+ const joined = new Uint8Array(kek.length + bytes.length);
188
+ joined.set(kek, 0);
189
+ joined.set(bytes, kek.length);
190
+ return b64u(await pbkdf2(joined, salt, 1));
191
+ }
192
+ globalThis.__masterLockDerive = deriveVerifier;
193
+
194
+ // ── the page ─────────────────────────────────────────────────────────────────
195
+ // 🔴 Everything below this line touches \`document\` beyond \`getElementById\`, and
196
+ // lockPage.spec.ts cuts the source at the marker above to evaluate the derivation with a
197
+ // stub. Keep page state here, not in the prelude, or that test throws instead of running.
198
+ //
199
+ // Rendered by the server from THIS principal's own record. The server re-decides it on
200
+ // every POST, so this only chooses which form to submit — it cannot grant anything.
201
+ const enrolling = document.body.dataset.mode === "enroll";
202
+
203
+ /** The shortest password worth calling one. Matched by the input's own minlength, and
204
+ * checked here too because a pasted value can beat the constraint on some engines. */
205
+ const MIN_LENGTH = 10;
206
+
207
+ let kdf = null;
208
+ let busy = false;
209
+
210
+ async function loadStatus() {
211
+ try {
212
+ const res = await fetch(paths.status, { credentials: "same-origin" });
213
+ if (!res.ok) return;
214
+ const body = await res.json();
215
+ kdf = body.kdf;
216
+ if (body.enrollable) { /* the choose-a-password form is already rendered */ }
217
+ else if (!body.configured) say("No master password is set for this app.", "error");
218
+ else if (body.retryAfterMs > 0) countdown(body.retryAfterMs);
219
+ else if (!body.locked) location.reload();
220
+ } catch { /* offline: the submit below reports it */ }
221
+ }
222
+
223
+ let timer = null;
224
+ function countdown(ms) {
225
+ if (timer) clearInterval(timer);
226
+ let left = Math.ceil(ms / 1000);
227
+ const tick = () => {
228
+ if (left <= 0) { clearInterval(timer); timer = null; say(""); submit.disabled = false; return; }
229
+ say("Too many attempts. Try again in " + left + "s.", "error");
230
+ left -= 1;
231
+ };
232
+ submit.disabled = true;
233
+ tick();
234
+ timer = setInterval(tick, 1000);
235
+ }
236
+
237
+ /** Fresh params for a password nobody has chosen before. Byte-identical to
238
+ * newMasterLockKdfParams — pinned by lockPage.spec.ts alongside the derivation. */
239
+ function newKdf() {
240
+ const salt = new Uint8Array(16);
241
+ crypto.getRandomValues(salt);
242
+ return { v: 1, alg: "PBKDF2-SHA256", iter: 600000, salt: b64u(salt) };
243
+ }
244
+
245
+ async function doEnroll(password) {
246
+ const params = newKdf();
247
+ const verifier = await deriveVerifier(password, params);
248
+ const res = await fetch(paths.enroll, {
249
+ method: "POST",
250
+ credentials: "same-origin",
251
+ headers: { "content-type": "application/json" },
252
+ body: JSON.stringify({ kdf: params, verifier: verifier }),
253
+ });
254
+ if (res.ok) { say("Master password set."); location.reload(); return true; }
255
+ // The server collapses every enrollment refusal into one, so this cannot say more
256
+ // than "reload and see" without inventing a distinction the server refused to draw.
257
+ say("This site could not accept that right now. Reload and try again.", "error");
258
+ return false;
259
+ }
260
+
261
+ async function doUnlock(password) {
262
+ const verifier = await deriveVerifier(password, kdf);
263
+ const res = await fetch(paths.unlock, {
264
+ method: "POST",
265
+ credentials: "same-origin",
266
+ headers: { "content-type": "application/json" },
267
+ body: JSON.stringify({ verifier: verifier }),
268
+ });
269
+ if (res.ok) { say("Unlocked."); location.reload(); return true; }
270
+ const body = await res.json().catch(() => ({}));
271
+ input.value = "";
272
+ input.focus();
273
+ if (body && body.retryAfterMs > 0) { countdown(body.retryAfterMs); return false; }
274
+ say("That is not the master password.", "error");
275
+ return false;
276
+ }
277
+
278
+ form.addEventListener("submit", async (event) => {
279
+ event.preventDefault();
280
+ if (busy || submit.disabled) return;
281
+ const password = input.value;
282
+ if (!password) return;
283
+ if (enrolling) {
284
+ if (password.length < MIN_LENGTH) {
285
+ say("Use at least " + MIN_LENGTH + " characters.", "error");
286
+ return;
287
+ }
288
+ if (confirmInput && confirmInput.value !== password) {
289
+ say("Those two do not match.", "error");
290
+ confirmInput.value = "";
291
+ confirmInput.focus();
292
+ return;
293
+ }
294
+ } else if (!kdf) {
295
+ await loadStatus();
296
+ if (!kdf) { say("Cannot reach this site right now.", "error"); return; }
297
+ }
298
+ busy = true;
299
+ submit.disabled = true;
300
+ say(enrolling ? "Setting…" : "Checking…");
301
+ try {
302
+ const done = enrolling ? await doEnroll(password) : await doUnlock(password);
303
+ if (!done) submit.disabled = false;
304
+ } catch {
305
+ say("Cannot reach this site right now.", "error");
306
+ submit.disabled = false;
307
+ } finally {
308
+ busy = false;
309
+ }
310
+ });
311
+
312
+ loadStatus();
313
+ `;
@@ -0,0 +1,338 @@
1
+ /**
2
+ * The engine: seeding, the idle clock, the throttle, rotation.
3
+ *
4
+ * Real argon2id runs on every unlock here (that is the thing being tested), so this leans on
5
+ * the repo's per-test budget rather than trying to be fast.
6
+ */
7
+ import { beforeEach, describe, expect, test } from "bun:test";
8
+ import {
9
+ DEFAULT_IDLE_MS,
10
+ MAX_IDLE_MS,
11
+ MIN_IDLE_MS,
12
+ type MasterLockKdfParams,
13
+ deriveMasterLockVerifier,
14
+ newMasterLockKdfParams,
15
+ } from "cursedbelt-core/master-lock";
16
+ import { MasterLock, readRecord } from "./masterLock";
17
+ import { createMemoryMasterLockStore } from "./store";
18
+
19
+ const KDF: MasterLockKdfParams = {
20
+ v: 1,
21
+ alg: "PBKDF2-SHA256",
22
+ iter: 1, // 600k rounds x N tests is minutes; the ROUND COUNT is pinned in kdf.spec.ts
23
+ salt: "AAECAwQFBgcICQoLDA0ODw",
24
+ };
25
+ const PASSWORD = "the owner's vault master password";
26
+
27
+ let VERIFIER = "";
28
+ let SEED = "";
29
+
30
+ beforeEach(async () => {
31
+ if (VERIFIER) return;
32
+ VERIFIER = await deriveMasterLockVerifier(PASSWORD, KDF);
33
+ SEED = JSON.stringify({
34
+ kdf: KDF,
35
+ verifierHash: await Bun.password.hash(VERIFIER, { algorithm: "argon2id" }),
36
+ });
37
+ });
38
+
39
+ /** A lock with a fake clock the test drives. */
40
+ function build(options: { seed?: string | null; stored?: string | null; now?: () => number } = {}) {
41
+ const store = createMemoryMasterLockStore(options.stored ?? null);
42
+ const locked: boolean[] = [];
43
+ const lock = new MasterLock({
44
+ store,
45
+ seedJson: options.seed === undefined ? SEED : options.seed,
46
+ ...(options.now ? { now: options.now } : {}),
47
+ onLockedChange: (value) => locked.push(value),
48
+ });
49
+ return { lock, store, locked };
50
+ }
51
+
52
+ const req = (token?: string): Request =>
53
+ new Request("http://x/", token ? { headers: { cookie: `master_lock=${token}` } } : undefined);
54
+
55
+ describe("seeding", () => {
56
+ test("adopts MASTER_LOCK_SEED when the store is empty, and persists it", () => {
57
+ const { lock, store } = build();
58
+ expect(lock.configured).toBe(true);
59
+ expect(store.read()).toBeTruthy();
60
+ expect(readRecord(store.read())?.kdf.salt).toBe(KDF.salt);
61
+ });
62
+
63
+ test("a STORED record wins over the seed — a rotation is permanent", async () => {
64
+ const otherKdf = newMasterLockKdfParams();
65
+ const stored = JSON.stringify({
66
+ kdf: otherKdf,
67
+ verifierHash: await Bun.password.hash("whatever", { algorithm: "argon2id" }),
68
+ idleMs: 90_000,
69
+ });
70
+ const { lock } = build({ stored });
71
+ expect(lock.kdf?.salt).toBe(otherKdf.salt);
72
+ expect(lock.idleMs).toBe(90_000);
73
+ });
74
+
75
+ test("an unusable seed leaves the lock unconfigured rather than half-armed", () => {
76
+ const { lock } = build({ seed: '{"kdf":{"v":1},"verifierHash":"nope"}' });
77
+ expect(lock.configured).toBe(false);
78
+ });
79
+
80
+ test("🔴 an UNCONFIGURED app reads as unlocked — a lock nobody can open is not protection", () => {
81
+ const { lock } = build({ seed: null });
82
+ expect(lock.configured).toBe(false);
83
+ expect(lock.unlocked).toBe(true);
84
+ });
85
+
86
+ test("a seed whose hash arrived shell-quoted is repaired, not refused", async () => {
87
+ const hash = await Bun.password.hash(VERIFIER, { algorithm: "argon2id" });
88
+ const { lock } = build({ seed: JSON.stringify({ kdf: KDF, verifierHash: `'${hash}'` }) });
89
+ expect(lock.configured).toBe(true);
90
+ expect((await lock.unlock(VERIFIER)).ok).toBe(true);
91
+ });
92
+
93
+ test("🔴 a seed whose WHOLE BLOB arrived shell-quoted is repaired too", async () => {
94
+ // The value is written single-quoted into a file a shell sources, because an argon2id
95
+ // hash contains `$`. Every reader in the fleet strips that pair — and one of them was
96
+ // wrong once, for a day, in production. Both shapes have to work.
97
+ const { lock } = build({ seed: `'${SEED}'` });
98
+ expect(lock.configured).toBe(true);
99
+ expect((await lock.unlock(VERIFIER)).ok).toBe(true);
100
+ });
101
+ });
102
+
103
+ describe("unlock", () => {
104
+ test("the right verifier opens it; the wrong one does not", async () => {
105
+ const { lock, locked } = build();
106
+ expect(lock.unlocked).toBe(false);
107
+ expect((await lock.unlock("not it")).ok).toBe(false);
108
+ expect(lock.unlocked).toBe(false);
109
+
110
+ const result = await lock.unlock(VERIFIER);
111
+ expect(result.ok).toBe(true);
112
+ expect(lock.unlocked).toBe(true);
113
+ expect(locked).toEqual([false]);
114
+ });
115
+
116
+ test("the minted token is what `presents` recognises, and nothing else is", async () => {
117
+ const { lock } = build();
118
+ const result = await lock.unlock(VERIFIER);
119
+ if (!result.ok) throw new Error("expected an unlock");
120
+ expect(lock.presents(req(result.token))).toBe(true);
121
+ expect(lock.presents(req("a-different-token-of-the-same-length"))).toBe(false);
122
+ expect(lock.presents(req())).toBe(false);
123
+ });
124
+
125
+ test("a hash this build cannot parse answers 'no' without counting a failure", async () => {
126
+ const { lock } = build({
127
+ seed: JSON.stringify({ kdf: KDF, verifierHash: "$argon2id$this-is-not-a-hash" }),
128
+ });
129
+ for (let i = 0; i < 8; i += 1) expect((await lock.unlock(VERIFIER)).ok).toBe(false);
130
+ // Never throttled: the owner is not guessing, the machine is broken.
131
+ expect(lock.status().retryAfterMs).toBe(0);
132
+ });
133
+ });
134
+
135
+ describe("the idle clock", () => {
136
+ test("🔴 an ordinary request does NOT slide it — only interaction does", async () => {
137
+ let now = 1_000_000;
138
+ const { lock } = build({ now: () => now });
139
+ const result = await lock.unlock(VERIFIER);
140
+ if (!result.ok) throw new Error("expected an unlock");
141
+ const token = result.token;
142
+
143
+ // Nine minutes of a page polling every thirty seconds, with a real read of the lock
144
+ // state on every tick — i.e. exactly what react-query does to an idle tab. A clock
145
+ // slid by traffic would still be open at the end; the assertion is that it is not.
146
+ const start = now;
147
+ let sawLocked = false;
148
+ for (let i = 0; i < 18; i += 1) {
149
+ now += 30_000;
150
+ const open = lock.presents(req(token));
151
+ expect(open).toBe(now - start <= DEFAULT_IDLE_MS);
152
+ sawLocked ||= !open;
153
+ }
154
+ expect(sawLocked).toBe(true);
155
+ expect(lock.unlocked).toBe(false);
156
+ });
157
+
158
+ test("touch() slides it, so an active session never lapses", async () => {
159
+ let now = 1_000_000;
160
+ const { lock } = build({ now: () => now });
161
+ const result = await lock.unlock(VERIFIER);
162
+ if (!result.ok) throw new Error("expected an unlock");
163
+ const token = result.token;
164
+
165
+ for (let i = 0; i < 20; i += 1) {
166
+ now += DEFAULT_IDLE_MS - 1_000;
167
+ expect(lock.touch(req(token))).toBe(true);
168
+ }
169
+ expect(lock.unlocked).toBe(true);
170
+ });
171
+
172
+ test("🔴 touch() never REVIVES a lapsed unlock", async () => {
173
+ let now = 1_000_000;
174
+ const { lock } = build({ now: () => now });
175
+ const result = await lock.unlock(VERIFIER);
176
+ if (!result.ok) throw new Error("expected an unlock");
177
+ now += DEFAULT_IDLE_MS + 1;
178
+ expect(lock.touch(req(result.token))).toBe(false);
179
+ expect(lock.unlocked).toBe(false);
180
+ });
181
+
182
+ test("lapsing fires onLockedChange, so a route swap can follow it", async () => {
183
+ let now = 1_000_000;
184
+ const { lock, locked } = build({ now: () => now });
185
+ await lock.unlock(VERIFIER);
186
+ now += DEFAULT_IDLE_MS + 1;
187
+ expect(lock.unlocked).toBe(false);
188
+ expect(locked).toEqual([false, true]);
189
+ });
190
+
191
+ test("the manual lock closes it at once", async () => {
192
+ const { lock, locked } = build();
193
+ await lock.unlock(VERIFIER);
194
+ lock.lock();
195
+ expect(lock.unlocked).toBe(false);
196
+ expect(locked).toEqual([false, true]);
197
+ // Idempotent — a second lock is not a second event.
198
+ lock.lock();
199
+ expect(locked).toEqual([false, true]);
200
+ });
201
+
202
+ test("setIdleMs clamps and persists", () => {
203
+ const { lock, store } = build();
204
+ expect(lock.setIdleMs(60_000)).toBe(60_000);
205
+ expect(readRecord(store.read())?.idleMs).toBe(60_000);
206
+ expect(lock.setIdleMs(1)).toBe(MIN_IDLE_MS);
207
+ expect(lock.setIdleMs(999 * 60 * 60_000)).toBe(MAX_IDLE_MS);
208
+ expect(lock.setIdleMs(Number.NaN)).toBe(DEFAULT_IDLE_MS);
209
+ });
210
+ });
211
+
212
+ /**
213
+ * 🔴 The two tests below declare their own budget, and it is not padding.
214
+ *
215
+ * Every `unlock()` runs a real argon2id verification — that IS the thing under test — and
216
+ * these two are the only ones in the file that do it in bulk: 9 attempts and 17 attempts.
217
+ * Measured 2026-09-13 on this machine: 4.5s and 7.2s against bun's DEFAULT per-test budget
218
+ * of 5000ms, so the second failed on every run and the first was one hiccup behind it. The
219
+ * file's header says it "leans on the repo's per-test budget"; this repo's `bunfig.toml`
220
+ * declares none, so that budget is 5s and the claim was simply false.
221
+ *
222
+ * Stated per test rather than raised in `bunfig.toml`, deliberately: a repo-wide 20s would
223
+ * buy these two nothing the line below does not, and would cost all 5,200 other tests the
224
+ * fast failure that tells you something is HUNG rather than slow.
225
+ */
226
+ const KDF_BULK_MS = 20_000;
227
+
228
+ describe("the guess throttle", () => {
229
+ test("five failures are free, the sixth costs a wait, and a success clears it", async () => {
230
+ let now = 1_000_000;
231
+ const { lock } = build({ now: () => now });
232
+ for (let i = 0; i < 5; i += 1) {
233
+ expect((await lock.unlock("wrong")).ok).toBe(false);
234
+ expect(lock.status().retryAfterMs).toBe(0);
235
+ }
236
+ const sixth = await lock.unlock("wrong");
237
+ expect(sixth.ok).toBe(false);
238
+ expect(sixth.ok === false && sixth.retryAfterMs).toBe(30_000);
239
+
240
+ // 🔴 Inside the window even the CORRECT verifier is refused — otherwise the throttle
241
+ // would be an oracle that says "that one was right".
242
+ const early = await lock.unlock(VERIFIER);
243
+ expect(early.ok).toBe(false);
244
+ expect(lock.unlocked).toBe(false);
245
+
246
+ now += 30_001;
247
+ expect((await lock.unlock(VERIFIER)).ok).toBe(true);
248
+ expect(lock.status().retryAfterMs).toBe(0);
249
+ }, KDF_BULK_MS);
250
+
251
+ test("a windowed refusal does not advance the counter", async () => {
252
+ let now = 1_000_000;
253
+ const { lock } = build({ now: () => now });
254
+ for (let i = 0; i < 6; i += 1) await lock.unlock("wrong");
255
+ for (let i = 0; i < 10; i += 1) await lock.unlock("wrong");
256
+ now += 30_001;
257
+ // Still the 6th failure's delay, not the 16th's — the hammering bought nothing and
258
+ // cost nothing.
259
+ const next = await lock.unlock("wrong");
260
+ expect(next.ok === false && next.retryAfterMs).toBe(60_000);
261
+ }, KDF_BULK_MS);
262
+ });
263
+
264
+ describe("rotation", () => {
265
+ test("needs the current password, rewrites the record, and locks the site", async () => {
266
+ const { lock, store, locked } = build();
267
+ await lock.unlock(VERIFIER);
268
+
269
+ const nextKdf = newMasterLockKdfParams();
270
+ const nextVerifier = await deriveMasterLockVerifier("a different one", nextKdf);
271
+
272
+ const refused = await lock.change({
273
+ currentVerifier: "wrong",
274
+ kdf: nextKdf,
275
+ verifier: nextVerifier,
276
+ });
277
+ expect(refused).toEqual({ ok: false, reason: "wrong" });
278
+ expect(lock.unlocked).toBe(true);
279
+
280
+ const done = await lock.change({
281
+ currentVerifier: VERIFIER,
282
+ kdf: nextKdf,
283
+ verifier: nextVerifier,
284
+ });
285
+ expect(done).toEqual({ ok: true });
286
+ // 🔴 Every open page dies with the old password.
287
+ expect(lock.unlocked).toBe(false);
288
+ expect(locked.at(-1)).toBe(true);
289
+
290
+ // The OLD one no longer opens it; the new one does.
291
+ expect((await lock.unlock(VERIFIER)).ok).toBe(false);
292
+ expect((await lock.unlock(nextVerifier)).ok).toBe(true);
293
+ expect(readRecord(store.read())?.kdf.salt).toBe(nextKdf.salt);
294
+ });
295
+
296
+ test("refuses params it could not derive under, before touching the record", async () => {
297
+ const { lock, store } = build();
298
+ const before = store.read();
299
+ const result = await lock.change({
300
+ currentVerifier: VERIFIER,
301
+ kdf: { v: 2 } as unknown as MasterLockKdfParams,
302
+ verifier: "x",
303
+ });
304
+ expect(result).toEqual({ ok: false, reason: "invalid" });
305
+ expect(store.read()).toBe(before);
306
+ });
307
+
308
+ test("keeps this app's idle timeout across a rotation", async () => {
309
+ const { lock, store } = build();
310
+ lock.setIdleMs(120_000);
311
+ await lock.unlock(VERIFIER);
312
+ const nextKdf = newMasterLockKdfParams();
313
+ await lock.change({
314
+ currentVerifier: VERIFIER,
315
+ kdf: nextKdf,
316
+ verifier: await deriveMasterLockVerifier("next", nextKdf),
317
+ });
318
+ expect(readRecord(store.read())?.idleMs).toBe(120_000);
319
+ });
320
+ });
321
+
322
+ describe("readRecord", () => {
323
+ test("rejects everything that is not a usable record", () => {
324
+ expect(readRecord(null)).toBeNull();
325
+ expect(readRecord("")).toBeNull();
326
+ expect(readRecord("not json")).toBeNull();
327
+ expect(readRecord("[]")).toBeNull();
328
+ expect(readRecord(JSON.stringify({ kdf: KDF }))).toBeNull();
329
+ expect(readRecord(JSON.stringify({ kdf: KDF, verifierHash: "plaintext" }))).toBeNull();
330
+ expect(readRecord(JSON.stringify({ verifierHash: "$argon2id$x" }))).toBeNull();
331
+ });
332
+
333
+ test("defaults a missing or absurd idleMs rather than inheriting it", () => {
334
+ const base = { kdf: KDF, verifierHash: "$argon2id$v=19$m=1,t=1,p=1$c2FsdA$aGFzaA" };
335
+ expect(readRecord(JSON.stringify(base))?.idleMs).toBe(DEFAULT_IDLE_MS);
336
+ expect(readRecord(JSON.stringify({ ...base, idleMs: -5 }))?.idleMs).toBe(MIN_IDLE_MS);
337
+ });
338
+ });