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,281 @@
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
+ const escapeHtml = (value) => value.replace(/[&<>"']/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch] ?? ch);
25
+ /**
26
+ * The document. Deliberately says the app's NAME and nothing else about it — whoever is
27
+ * looking already reached the origin, so the hostname told them that much, and a page that
28
+ * would not say which app it is cannot tell the owner which password to type.
29
+ */
30
+ export function lockPageHtml(options) {
31
+ const label = escapeHtml(options.appLabel);
32
+ const enroll = options.enroll === true;
33
+ const note = escapeHtml(options.note ??
34
+ (enroll
35
+ ? "You are signed in, but this site needs a master password of your own. " +
36
+ "Choose one now. Nobody else can see it and nobody else can reset it — " +
37
+ "if you forget it, it cannot be recovered."
38
+ : "This site is locked. Signing in is not enough — enter the master password to continue."));
39
+ return `<!doctype html>
40
+ <html lang="en">
41
+ <head>
42
+ <meta charset="utf-8">
43
+ <meta name="viewport" content="width=device-width, initial-scale=1">
44
+ <meta name="robots" content="noindex, nofollow">
45
+ <title>Locked · ${label}</title>
46
+ <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>">
47
+ <link rel="stylesheet" href="${MASTER_LOCK_PATHS.style}">
48
+ </head>
49
+ <body data-mode="${enroll ? "enroll" : "unlock"}">
50
+ <main class="card" aria-labelledby="ml-title">
51
+ <div class="glyph" aria-hidden="true">${enroll ? "🔑" : "🔒"}</div>
52
+ <h1 id="ml-title">${label}</h1>
53
+ <p class="note">${note}</p>
54
+ <form id="ml-form" autocomplete="off">
55
+ <label class="field">
56
+ <span>${enroll ? "New master password" : "Master password"}</span>
57
+ <input id="ml-input" type="password" name="master-password"
58
+ autocomplete="${enroll ? "new-password" : "current-password"}"
59
+ autofocus required spellcheck="false" enterkeyhint="${enroll ? "next" : "go"}"${enroll ? ' minlength="10"' : ""}>
60
+ </label>${enroll
61
+ ? `
62
+ <label class="field">
63
+ <span>Confirm master password</span>
64
+ <input id="ml-confirm" type="password" name="confirm-master-password"
65
+ autocomplete="new-password" required spellcheck="false" enterkeyhint="go"
66
+ minlength="10">
67
+ </label>`
68
+ : ""}
69
+ <button id="ml-submit" type="submit">${enroll ? "Set master password" : "Unlock"}</button>
70
+ </form>
71
+ <p id="ml-status" class="status" role="status" aria-live="polite"></p>
72
+ </main>
73
+ <script type="module" src="${MASTER_LOCK_PATHS.script}"></script>
74
+ </body>
75
+ </html>
76
+ `;
77
+ }
78
+ /** The stylesheet. Follows the viewer's theme and commits to nothing else. */
79
+ export const LOCK_STYLE = `:root{color-scheme:light dark;
80
+ --ml-bg:#f4f5f7;--ml-card:#ffffff;--ml-text:#14171c;--ml-muted:#69707c;--ml-border:#d9dde3;
81
+ --ml-accent:#1f6feb;--ml-accent-text:#ffffff;--ml-danger:#b42318;
82
+ --ml-shadow:0 18px 48px rgba(16,18,22,.14)}
83
+ @media (prefers-color-scheme:dark){:root{
84
+ --ml-bg:#0d0f13;--ml-card:#171a20;--ml-text:#e7eaef;--ml-muted:#8b93a1;--ml-border:#2b3039;
85
+ --ml-accent:#4f8cf7;--ml-accent-text:#0b0d11;--ml-danger:#f97066;
86
+ --ml-shadow:0 18px 48px rgba(0,0,0,.55)}}
87
+ *{box-sizing:border-box}
88
+ body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px;
89
+ background:var(--ml-bg);color:var(--ml-text);
90
+ font:15px/1.55 ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif}
91
+ .card{width:100%;max-width:23rem;background:var(--ml-card);border:1px solid var(--ml-border);
92
+ border-radius:16px;padding:28px 26px 24px;box-shadow:var(--ml-shadow);text-align:center}
93
+ .glyph{font-size:30px;line-height:1;margin-bottom:10px}
94
+ h1{margin:0 0 6px;font-size:18px;font-weight:650;letter-spacing:-.01em}
95
+ .note{margin:0 0 20px;font-size:13px;color:var(--ml-muted)}
96
+ .field{display:block;text-align:left;margin-bottom:14px}
97
+ .field span{display:block;font-size:12px;font-weight:600;color:var(--ml-muted);margin-bottom:6px}
98
+ input{width:100%;height:40px;padding:0 12px;border:1px solid var(--ml-border);border-radius:9px;
99
+ background:var(--ml-bg);color:inherit;font:inherit}
100
+ input:focus{outline:2px solid var(--ml-accent);outline-offset:1px;border-color:transparent}
101
+ button{width:100%;height:40px;border:0;border-radius:9px;background:var(--ml-accent);
102
+ color:var(--ml-accent-text);font:inherit;font-weight:600;cursor:pointer}
103
+ button[disabled]{opacity:.6;cursor:progress}
104
+ .status{margin:14px 0 0;min-height:1.2em;font-size:12.5px;color:var(--ml-muted)}
105
+ .status[data-tone="error"]{color:var(--ml-danger)}
106
+ `;
107
+ /**
108
+ * The page's script.
109
+ *
110
+ * Derives in the browser and sends only the verifier — the password itself never crosses
111
+ * the network, which is what makes "saved nowhere" true rather than aspirational.
112
+ *
113
+ * 600,000 PBKDF2 rounds take a beat on a phone, so the button says so; a page that looks
114
+ * frozen gets clicked again, and every extra click is another 600k rounds.
115
+ */
116
+ export const LOCK_SCRIPT = `const paths = ${JSON.stringify(MASTER_LOCK_PATHS)};
117
+
118
+ const form = document.getElementById("ml-form");
119
+ const input = document.getElementById("ml-input");
120
+ const confirmInput = document.getElementById("ml-confirm");
121
+ const submit = document.getElementById("ml-submit");
122
+ const status = document.getElementById("ml-status");
123
+
124
+ function say(message, tone) {
125
+ status.textContent = message;
126
+ if (tone) status.setAttribute("data-tone", tone);
127
+ else status.removeAttribute("data-tone");
128
+ }
129
+
130
+ // ── derivation ───────────────────────────────────────────────────────────────
131
+ // 🔴 Byte-identical to deriveMasterLockVerifier in cursedbelt-core/master-lock. Pinned by
132
+ // lockPage.spec.ts, which evaluates THIS function and compares. Do not "tidy" it apart.
133
+ function unb64u(text) {
134
+ const padded = text.replace(/-/g, "+").replace(/_/g, "/");
135
+ const binary = atob(padded + "=".repeat((4 - (padded.length % 4)) % 4));
136
+ const out = new Uint8Array(binary.length);
137
+ for (let i = 0; i < binary.length; i += 1) out[i] = binary.charCodeAt(i);
138
+ return out;
139
+ }
140
+ function b64u(bytes) {
141
+ let binary = "";
142
+ for (const byte of bytes) binary += String.fromCharCode(byte);
143
+ return btoa(binary).replace(/\\+/g, "-").replace(/\\//g, "_").replace(/=+$/, "");
144
+ }
145
+ async function pbkdf2(material, salt, iterations) {
146
+ const key = await crypto.subtle.importKey("raw", material, "PBKDF2", false, ["deriveBits"]);
147
+ const bits = await crypto.subtle.deriveBits(
148
+ { name: "PBKDF2", salt: salt, iterations: iterations, hash: "SHA-256" }, key, 256);
149
+ return new Uint8Array(bits);
150
+ }
151
+ async function deriveVerifier(password, kdf) {
152
+ const salt = unb64u(kdf.salt);
153
+ const bytes = new TextEncoder().encode(password);
154
+ const kek = await pbkdf2(bytes, salt, kdf.iter);
155
+ const joined = new Uint8Array(kek.length + bytes.length);
156
+ joined.set(kek, 0);
157
+ joined.set(bytes, kek.length);
158
+ return b64u(await pbkdf2(joined, salt, 1));
159
+ }
160
+ globalThis.__masterLockDerive = deriveVerifier;
161
+
162
+ // ── the page ─────────────────────────────────────────────────────────────────
163
+ // 🔴 Everything below this line touches \`document\` beyond \`getElementById\`, and
164
+ // lockPage.spec.ts cuts the source at the marker above to evaluate the derivation with a
165
+ // stub. Keep page state here, not in the prelude, or that test throws instead of running.
166
+ //
167
+ // Rendered by the server from THIS principal's own record. The server re-decides it on
168
+ // every POST, so this only chooses which form to submit — it cannot grant anything.
169
+ const enrolling = document.body.dataset.mode === "enroll";
170
+
171
+ /** The shortest password worth calling one. Matched by the input's own minlength, and
172
+ * checked here too because a pasted value can beat the constraint on some engines. */
173
+ const MIN_LENGTH = 10;
174
+
175
+ let kdf = null;
176
+ let busy = false;
177
+
178
+ async function loadStatus() {
179
+ try {
180
+ const res = await fetch(paths.status, { credentials: "same-origin" });
181
+ if (!res.ok) return;
182
+ const body = await res.json();
183
+ kdf = body.kdf;
184
+ if (body.enrollable) { /* the choose-a-password form is already rendered */ }
185
+ else if (!body.configured) say("No master password is set for this app.", "error");
186
+ else if (body.retryAfterMs > 0) countdown(body.retryAfterMs);
187
+ else if (!body.locked) location.reload();
188
+ } catch { /* offline: the submit below reports it */ }
189
+ }
190
+
191
+ let timer = null;
192
+ function countdown(ms) {
193
+ if (timer) clearInterval(timer);
194
+ let left = Math.ceil(ms / 1000);
195
+ const tick = () => {
196
+ if (left <= 0) { clearInterval(timer); timer = null; say(""); submit.disabled = false; return; }
197
+ say("Too many attempts. Try again in " + left + "s.", "error");
198
+ left -= 1;
199
+ };
200
+ submit.disabled = true;
201
+ tick();
202
+ timer = setInterval(tick, 1000);
203
+ }
204
+
205
+ /** Fresh params for a password nobody has chosen before. Byte-identical to
206
+ * newMasterLockKdfParams — pinned by lockPage.spec.ts alongside the derivation. */
207
+ function newKdf() {
208
+ const salt = new Uint8Array(16);
209
+ crypto.getRandomValues(salt);
210
+ return { v: 1, alg: "PBKDF2-SHA256", iter: 600000, salt: b64u(salt) };
211
+ }
212
+
213
+ async function doEnroll(password) {
214
+ const params = newKdf();
215
+ const verifier = await deriveVerifier(password, params);
216
+ const res = await fetch(paths.enroll, {
217
+ method: "POST",
218
+ credentials: "same-origin",
219
+ headers: { "content-type": "application/json" },
220
+ body: JSON.stringify({ kdf: params, verifier: verifier }),
221
+ });
222
+ if (res.ok) { say("Master password set."); location.reload(); return true; }
223
+ // The server collapses every enrollment refusal into one, so this cannot say more
224
+ // than "reload and see" without inventing a distinction the server refused to draw.
225
+ say("This site could not accept that right now. Reload and try again.", "error");
226
+ return false;
227
+ }
228
+
229
+ async function doUnlock(password) {
230
+ const verifier = await deriveVerifier(password, kdf);
231
+ const res = await fetch(paths.unlock, {
232
+ method: "POST",
233
+ credentials: "same-origin",
234
+ headers: { "content-type": "application/json" },
235
+ body: JSON.stringify({ verifier: verifier }),
236
+ });
237
+ if (res.ok) { say("Unlocked."); location.reload(); return true; }
238
+ const body = await res.json().catch(() => ({}));
239
+ input.value = "";
240
+ input.focus();
241
+ if (body && body.retryAfterMs > 0) { countdown(body.retryAfterMs); return false; }
242
+ say("That is not the master password.", "error");
243
+ return false;
244
+ }
245
+
246
+ form.addEventListener("submit", async (event) => {
247
+ event.preventDefault();
248
+ if (busy || submit.disabled) return;
249
+ const password = input.value;
250
+ if (!password) return;
251
+ if (enrolling) {
252
+ if (password.length < MIN_LENGTH) {
253
+ say("Use at least " + MIN_LENGTH + " characters.", "error");
254
+ return;
255
+ }
256
+ if (confirmInput && confirmInput.value !== password) {
257
+ say("Those two do not match.", "error");
258
+ confirmInput.value = "";
259
+ confirmInput.focus();
260
+ return;
261
+ }
262
+ } else if (!kdf) {
263
+ await loadStatus();
264
+ if (!kdf) { say("Cannot reach this site right now.", "error"); return; }
265
+ }
266
+ busy = true;
267
+ submit.disabled = true;
268
+ say(enrolling ? "Setting…" : "Checking…");
269
+ try {
270
+ const done = enrolling ? await doEnroll(password) : await doUnlock(password);
271
+ if (!done) submit.disabled = false;
272
+ } catch {
273
+ say("Cannot reach this site right now.", "error");
274
+ submit.disabled = false;
275
+ } finally {
276
+ busy = false;
277
+ }
278
+ });
279
+
280
+ loadStatus();
281
+ `;
@@ -0,0 +1,294 @@
1
+ /**
2
+ * The sitewide MASTER LOCK — a second wall, in front of a whole app, that the app's own
3
+ * sign-in cannot open.
4
+ *
5
+ * ── What the owner asked for (2026-08-15, their words) ──────────────────────
6
+ * *"I want to be able to share my laptop without certain apps being at risk of other people
7
+ * viewing them while I am still logged in … Until the master password is entered a user
8
+ * logged in as me cannot see anything about the site, not even text or images, nothing at
9
+ * all."*
10
+ *
11
+ * So: standard auth still happens FIRST and is unchanged. This sits behind it. A request
12
+ * that has passed the app's own gate and carries no live unlock is answered with the lock
13
+ * page (for a document) or a 401 (for anything else) — never with the app.
14
+ *
15
+ * ── 🔴 BE HONEST ABOUT WHAT THIS BUYS ───────────────────────────────────────
16
+ * In `apps/vault` the master password IS the encryption key: the server holds ciphertext
17
+ * and a total breach yields noise. **This cannot work that way, and must never be described
18
+ * as if it does.** The apps it guards serve their bytes to browsers; those bytes are
19
+ * plaintext at rest by nature. This is a LOCK ON THE DOOR — an unlocked laptop, a borrowed
20
+ * phone, a hijacked session still cannot open the UI. The owner said the same thing back:
21
+ * *"I am aware that a tech savvy user of my laptop could still get around it by changing
22
+ * code. This work does not worry about that, we are only securing app UI access."*
23
+ * The same distinction is written in `fleet/rules/owner-uis-are-one-ui.md` and in
24
+ * binary-server's `src/admin/passcode.ts`, which is this feature's nearest relative.
25
+ *
26
+ * ── The four decisions that are not obvious ─────────────────────────────────
27
+ *
28
+ * **1. Sessions live in MEMORY, so a restart re-locks.** The correct default for a surface
29
+ * whose whole risk model is "the browser is somewhere it should not be". Same call as the
30
+ * inspector passcode.
31
+ *
32
+ * **2. The idle clock is slid by INTERACTION, never by traffic.** The owner said *"if I
33
+ * leave the site up but don't interact with it"* — and every app here polls. Sliding on any
34
+ * request would let a background refetch hold the lock open all night with nobody at the
35
+ * desk, which is exactly the scenario the feature exists for. So an ordinary request only
36
+ * READS the clock; `touch()` — reached solely by `POST /__lock/ping`, which the browser
37
+ * sends on real pointer/key/touch activity — is the only thing that moves it.
38
+ *
39
+ * **3. The password is never stored, and this process never sees it.** The browser derives
40
+ * `verifier = PBKDF2(KEK ‖ password)` (see `../../core/master-lock/kdf.ts`); the record
41
+ * holds `argon2id(verifier)`. It is seeded from the vault's OWN record so the owner's
42
+ * existing vault master password works on day one without anyone knowing it.
43
+ *
44
+ * **4. Every refusal is the same refusal.** Wrong verifier, throttled, unconfigured and
45
+ * "no unlock" answer with the identical status and body, and a dummy argon2 verify runs on
46
+ * every path so the TIMING does not separate them either. The one thing that varies is the
47
+ * `retryAfterMs` a throttled caller is told — which they already know, since they are the
48
+ * one who has been guessing.
49
+ */
50
+ import { type MasterLockKdfParams, type MasterLockStatus } from "cursedbelt-core/master-lock";
51
+ /** The persisted record. Three public values — none of them is a secret, and the two that
52
+ * look like one are a salt and a one-way hash. */
53
+ export interface MasterLockRecord {
54
+ kdf: MasterLockKdfParams;
55
+ /** `argon2id(verifier)`. The PHC string is self-describing, so there is no salt column. */
56
+ verifierHash: string;
57
+ /** The owner's configured idle timeout for THIS app. */
58
+ idleMs: number;
59
+ }
60
+ /**
61
+ * Where the record rests. One blob, so an app can keep it in whatever it already has —
62
+ * binary-server's `settings` table, a satellite's `app_settings` row, a JSON file.
63
+ *
64
+ * Synchronous because every consumer's storage is `bun:sqlite`, and an async read here
65
+ * would make the request-path guard async for no reason any caller could observe.
66
+ */
67
+ export interface MasterLockStore {
68
+ read(): string | null;
69
+ write(json: string): void;
70
+ }
71
+ export interface MasterLockOptions {
72
+ store: MasterLockStore;
73
+ /**
74
+ * The BOOTSTRAP record, as JSON, used only when the store is empty — the same shape as
75
+ * {@link MasterLockRecord} with `idleMs` optional.
76
+ *
77
+ * It arrives as `MASTER_LOCK_SEED` in the app's own 0600 deploy-secrets env file,
78
+ * copied from the vault's own `vault_keys` row, so the lock is armed from the first
79
+ * request rather than from whenever somebody remembers to set one. A lock that has to
80
+ * be configured before it protects anything protects nothing on the day it matters —
81
+ * the reasoning `bootstrapPasscode` records in binary-server.
82
+ *
83
+ * A change made in the app afterwards WINS, permanently: this is consulted only when
84
+ * the store has nothing.
85
+ */
86
+ seedJson?: string | null;
87
+ /** Injected clock. The tests drive it; production omits it. */
88
+ now?: () => number;
89
+ /**
90
+ * Called whenever this app flips locked ⇄ unlocked, including when the idle timer fires.
91
+ *
92
+ * `binary-server`'s inspector needs it: its `/` is a Bun `HTMLBundle` matched BEFORE the
93
+ * `fetch` handler, so the only way to keep the app shell off the wire while locked is to
94
+ * swap that route with `server.reload()` — which needs a signal, not a poll.
95
+ */
96
+ onLockedChange?: (locked: boolean) => void;
97
+ /** Where to log the few things worth a line (a seed adopted, a rotation). */
98
+ log?: (line: string) => void;
99
+ /**
100
+ * PER-PERSON mode. Inverts decision 5 below: with no record this lock reads as
101
+ * LOCKED rather than open, and {@link MasterLock.enroll} is the way out.
102
+ *
103
+ * 🔴 It is an option and not the default because the two modes fail in opposite
104
+ * directions, and each is right for exactly one situation:
105
+ *
106
+ * - **App-wide (`false`, the default).** One record for the whole app, seeded
107
+ * from `MASTER_LOCK_SEED`. Unconfigured must read as OPEN, or a missing seed
108
+ * bricks an app nobody can get into — see `unlocked`.
109
+ * - **Per-person (`true`).** One record per signed-in account, and there is no
110
+ * seed: each person chooses their own. Unconfigured must read as LOCKED,
111
+ * because "this person has not chosen a password yet" is the state of every
112
+ * NEW account, and treating it as open would mean the wall admits everybody
113
+ * it has never met — the precise opposite of the feature.
114
+ *
115
+ * Nothing bricks in per-person mode, because enrollment is always available to
116
+ * a principal with no record. That is what makes failing closed affordable here
117
+ * and unaffordable there.
118
+ */
119
+ enrollable?: boolean;
120
+ }
121
+ /** What an unlock attempt answers. `ok` carries the token to set as a cookie. */
122
+ export type MasterLockUnlockResult = {
123
+ ok: true;
124
+ token: string;
125
+ } | {
126
+ ok: false;
127
+ retryAfterMs: number;
128
+ };
129
+ /** What choosing a FIRST master password answers. `ok` carries the unlock, so the
130
+ * person who just set it is not asked to type it again in the next breath. */
131
+ export type MasterLockEnrollResult = {
132
+ ok: true;
133
+ token: string;
134
+ } | {
135
+ ok: false;
136
+ reason: "not-enrollable" | "already-configured" | "invalid";
137
+ };
138
+ /**
139
+ * One app's lock. Constructed once at boot and held for the process's life.
140
+ *
141
+ * There is exactly ONE unlock at a time, not one per browser session. That is deliberate
142
+ * and it is the owner's model: *"entering it in one site will only apply it to that site"* —
143
+ * the unit is the SITE, and these apps have exactly one user. Minting per-session unlocks
144
+ * would mean the phone in someone else's hand stays open after the owner locks the desk,
145
+ * which inverts the manual-lock requirement (*"then no one can see anything until the master
146
+ * password is entered again"*).
147
+ */
148
+ export declare class MasterLock {
149
+ private readonly store;
150
+ private readonly now;
151
+ private readonly onLockedChange;
152
+ private readonly log;
153
+ /** Per-person mode — see {@link MasterLockOptions.enrollable}. */
154
+ private readonly enrollable;
155
+ private record;
156
+ /**
157
+ * The live unlocks: token → when that token last saw real interaction. Moved only by
158
+ * {@link touch}.
159
+ *
160
+ * 🔴 A MAP, not one session, since 2026-08-25 — and the reason is the per-person mode.
161
+ * App-wide, one unlock was right: one person, one site, and the owner's *"entering it
162
+ * in one site will only apply it to that site"*. Per person it is wrong twice over.
163
+ * The owner asked to *"put in prompts for the orch and monitor it from our phones and
164
+ * other devices"* — so the desk and the phone are both his, both open at once, and a
165
+ * single-token model would have each unlock silently evict the other. And the guard
166
+ * now demands a PRESENTED token rather than reading `unlocked` (see the guard's own
167
+ * note), which is only possible if every device that typed the password holds one.
168
+ *
169
+ * {@link lock} still clears them ALL, which is what keeps the owner's original
170
+ * requirement true: *"then no one can see anything until the master password is
171
+ * entered again"* — one button, every device.
172
+ */
173
+ private sessions;
174
+ private expiryTimer;
175
+ private failures;
176
+ private lastFailureAt;
177
+ constructor(options: MasterLockOptions);
178
+ /** Is a master password set at all? An app with `false` here refuses nothing. */
179
+ get configured(): boolean;
180
+ /** The params the browser must derive under, or null. Public by design. */
181
+ get kdf(): MasterLockKdfParams | null;
182
+ get idleMs(): number;
183
+ /** Is this lock in per-person mode? */
184
+ get isEnrollable(): boolean;
185
+ /** May a first password be chosen right now? Per-person mode, and no record yet. */
186
+ get awaitingEnrollment(): boolean;
187
+ /**
188
+ * Is the site open right now?
189
+ *
190
+ * 🔴 An UNCONFIGURED app-wide lock reads as UNLOCKED, and that is the deliberate
191
+ * choice. A lock with no password cannot be opened by anybody, including the owner —
192
+ * failing closed would brick an app rather than protect it, and this guards a UI whose
193
+ * data is already behind the app's own owner-only auth. `/healthz` reports
194
+ * `configured` so a smoke can catch the state externally instead of the owner
195
+ * discovering it.
196
+ *
197
+ * 🔴 In PER-PERSON mode the same reasoning runs the other way and this reads as
198
+ * LOCKED. Nobody is bricked — {@link enroll} is open to a principal with no record —
199
+ * and "has not chosen a password yet" describes every new account, so opening for it
200
+ * would admit exactly the people the wall exists to stop. See
201
+ * {@link MasterLockOptions.enrollable}.
202
+ */
203
+ get unlocked(): boolean;
204
+ /** Milliseconds of idleness left before the unlock lapses; 0 when locked. */
205
+ /** Milliseconds of idleness left before the LONGEST-lived unlock lapses; 0 when none
206
+ * is live. The status page counts down with it, so it is the most generous of the
207
+ * open devices rather than an arbitrary one. */
208
+ get remainingMs(): number;
209
+ status(): MasterLockStatus;
210
+ /** Does this request carry the live unlock? The cookie, or the header for a caller with
211
+ * no cookie jar. */
212
+ presents(req: Request): boolean;
213
+ /**
214
+ * Evaluate a verifier and, on a match, mint the unlock.
215
+ *
216
+ * Runs an argon2 verify on EVERY path — no record, throttled, wrong — so the answer's
217
+ * latency says nothing the answer itself does not.
218
+ */
219
+ unlock(verifier: string): Promise<MasterLockUnlockResult>;
220
+ /**
221
+ * Choose the FIRST master password for this lock — per-person mode only.
222
+ *
223
+ * ── Why this is not a hole ─────────────────────────────────────────────────
224
+ * It looks like "anyone who can reach the endpoint sets the password", and the three
225
+ * things that make it not are all checked here rather than at the caller:
226
+ *
227
+ * 1. **It refuses once a record exists.** There is no overwrite branch and no force
228
+ * flag; rotation is {@link change}, which demands the current password re-typed.
229
+ * So this can only ever create the password that was never set.
230
+ * 2. **It refuses outside per-person mode**, so mounting the route in an app-wide
231
+ * app cannot silently arm a lock the owner did not choose.
232
+ * 3. **The principal is resolved from the app's own session, not from the request
233
+ * body.** That is the guard's job (`resolve`), and it is why a stranger cannot
234
+ * enroll on somebody else's behalf: to reach this at all they must already hold a
235
+ * live signed-in session for the account they are enrolling, and if they hold
236
+ * that, the master lock was never what was standing between them and the app.
237
+ *
238
+ * The unlock is minted with the record, deliberately: being asked to type a password
239
+ * back at the person who just chose it, twice, teaches them the app is broken.
240
+ */
241
+ enroll(input: {
242
+ kdf: MasterLockKdfParams;
243
+ verifier: string;
244
+ }): Promise<MasterLockEnrollResult>;
245
+ /**
246
+ * Record real user interaction. The ONE thing that moves the idle clock — see decision 2
247
+ * in the module note.
248
+ *
249
+ * A no-op on a lapsed session, deliberately: activity must never REVIVE an unlock, or a
250
+ * page left open would resurrect itself the moment the cat walked across the keyboard.
251
+ */
252
+ touch(req: Request): boolean;
253
+ /** The manual lock — the owner's *"I want a lock option to turn the site back to needing
254
+ * the master password"*. Also what a change of password does to every open page. */
255
+ lock(): void;
256
+ /**
257
+ * Rotate this app's master password. Requires the CURRENT one, re-typed — an open session
258
+ * proves somebody is at the keyboard, not that it is the owner, and the whole feature
259
+ * exists for the minutes when it is not.
260
+ *
261
+ * Per-app by construction: the record this writes is this app's own. The owner asked for
262
+ * exactly that, and for there to be no way to change them all at once.
263
+ */
264
+ change(input: {
265
+ currentVerifier: string;
266
+ kdf: MasterLockKdfParams;
267
+ verifier: string;
268
+ }): Promise<{
269
+ ok: true;
270
+ } | {
271
+ ok: false;
272
+ reason: "wrong" | "invalid" | "unconfigured";
273
+ }>;
274
+ /** The owner's *"customizable by me"* idle timeout, clamped to the usable band. */
275
+ setIdleMs(value: number): number;
276
+ /** Drop the in-memory unlock without notifying — for tests between cases. */
277
+ resetForTest(): void;
278
+ /** Retire a session whose idle window has passed. Called on every read of the state, so
279
+ * correctness never depends on the timer having fired. */
280
+ private sweep;
281
+ /**
282
+ * Wake up exactly when the unlock lapses.
283
+ *
284
+ * The sweep above already makes every READ correct, so this is not what enforces the
285
+ * timeout — it is what lets `onLockedChange` fire with nobody asking, which the
286
+ * binary-server route swap needs. `unref` where the runtime has it, so a timer never
287
+ * keeps a process alive.
288
+ */
289
+ private scheduleExpiry;
290
+ private clearExpiry;
291
+ }
292
+ /** Parse a stored/seeded record, or null. Every field is validated because all three
293
+ * sources — a database, an env seed, an older build — are outside this module. */
294
+ export declare function readRecord(json: string | null | undefined): MasterLockRecord | null;