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.
- package/LICENSE +15 -0
- package/dist/server/activity/index.d.ts +36 -0
- package/dist/server/activity/index.js +36 -0
- package/dist/server/activity/migrations.d.ts +21 -0
- package/dist/server/activity/migrations.js +55 -0
- package/dist/server/activity/shareSource.d.ts +11 -0
- package/dist/server/activity/shareSource.js +45 -0
- package/dist/server/activity/store.d.ts +123 -0
- package/dist/server/activity/store.js +178 -0
- package/dist/server/app.d.ts +56 -0
- package/dist/server/app.js +45 -0
- package/dist/server/audit/index.d.ts +2 -0
- package/dist/server/audit/index.js +2 -0
- package/dist/server/audit/migrations.d.ts +2 -0
- package/dist/server/audit/migrations.js +79 -0
- package/dist/server/audit/sqliteSink.d.ts +36 -0
- package/dist/server/audit/sqliteSink.js +95 -0
- package/dist/server/audit/sqliteSink.test.d.ts +1 -0
- package/dist/server/audit/sqliteSink.test.js +180 -0
- package/dist/server/auth/cookie.d.ts +22 -0
- package/dist/server/auth/cookie.js +35 -0
- package/dist/server/auth/csrf.d.ts +42 -0
- package/dist/server/auth/csrf.js +36 -0
- package/dist/server/auth/fileTokenSigner.d.ts +10 -0
- package/dist/server/auth/fileTokenSigner.js +22 -0
- package/dist/server/auth/getOnly.d.ts +20 -0
- package/dist/server/auth/getOnly.js +27 -0
- package/dist/server/auth/index.d.ts +12 -0
- package/dist/server/auth/index.js +15 -0
- package/dist/server/auth/jwt.d.ts +18 -0
- package/dist/server/auth/jwt.js +56 -0
- package/dist/server/auth/loginThrottle.d.ts +118 -0
- package/dist/server/auth/loginThrottle.js +204 -0
- package/dist/server/auth/middleware.d.ts +14 -0
- package/dist/server/auth/middleware.js +47 -0
- package/dist/server/auth/password.d.ts +14 -0
- package/dist/server/auth/password.js +65 -0
- package/dist/server/auth/sessionStore.d.ts +19 -0
- package/dist/server/auth/sessionStore.js +221 -0
- package/dist/server/auth/totp.d.ts +34 -0
- package/dist/server/auth/totp.js +81 -0
- package/dist/server/auth/types.d.ts +133 -0
- package/dist/server/auth/types.js +1 -0
- package/dist/server/auth/vault.d.ts +27 -0
- package/dist/server/auth/vault.js +57 -0
- package/dist/server/context.d.ts +35 -0
- package/dist/server/context.js +1 -0
- package/dist/server/ctgr.d.ts +32 -0
- package/dist/server/ctgr.js +69 -0
- package/dist/server/db/kysely.d.ts +14 -0
- package/dist/server/db/kysely.js +16 -0
- package/dist/server/db/plumbingTypes.d.ts +116 -0
- package/dist/server/db/plumbingTypes.js +12 -0
- package/dist/server/dropzone/duplicates.d.ts +82 -0
- package/dist/server/dropzone/duplicates.js +90 -0
- package/dist/server/dropzone/index.d.ts +13 -0
- package/dist/server/dropzone/index.js +13 -0
- package/dist/server/dropzone/scan.d.ts +166 -0
- package/dist/server/dropzone/scan.js +272 -0
- package/dist/server/errors.d.ts +7 -0
- package/dist/server/errors.js +7 -0
- package/dist/server/guard/argon2Binding.d.ts +97 -0
- package/dist/server/guard/argon2Binding.js +138 -0
- package/dist/server/guard/index.d.ts +4 -0
- package/dist/server/guard/index.js +6 -0
- package/dist/server/guard/kdfWorker.d.ts +101 -0
- package/dist/server/guard/kdfWorker.js +301 -0
- package/dist/server/guard/kdfWorkerEntry.d.ts +1 -0
- package/dist/server/guard/kdfWorkerEntry.js +15 -0
- package/dist/server/guard/passwordVerifier.d.ts +65 -0
- package/dist/server/guard/passwordVerifier.js +122 -0
- package/dist/server/guard/revocationStore.d.ts +132 -0
- package/dist/server/guard/revocationStore.js +211 -0
- package/dist/server/guard/sessionSigner.d.ts +132 -0
- package/dist/server/guard/sessionSigner.js +303 -0
- package/dist/server/guard/verifier.d.ts +21 -0
- package/dist/server/guard/verifier.js +1 -0
- package/dist/server/incidents/channels.d.ts +161 -0
- package/dist/server/incidents/channels.js +357 -0
- package/dist/server/incidents/checks.d.ts +61 -0
- package/dist/server/incidents/checks.js +55 -0
- package/dist/server/incidents/index.d.ts +51 -0
- package/dist/server/incidents/index.js +51 -0
- package/dist/server/incidents/migrations.d.ts +20 -0
- package/dist/server/incidents/migrations.js +58 -0
- package/dist/server/incidents/notifier.d.ts +79 -0
- package/dist/server/incidents/notifier.js +141 -0
- package/dist/server/incidents/reporter.d.ts +60 -0
- package/dist/server/incidents/reporter.js +49 -0
- package/dist/server/incidents/routes.d.ts +91 -0
- package/dist/server/incidents/routes.js +202 -0
- package/dist/server/incidents/store.d.ts +75 -0
- package/dist/server/incidents/store.js +176 -0
- package/dist/server/incidents/types.d.ts +107 -0
- package/dist/server/incidents/types.js +32 -0
- package/dist/server/index.d.ts +25 -0
- package/dist/server/index.js +53 -0
- package/dist/server/jobs/fireQueue.d.ts +12 -0
- package/dist/server/jobs/fireQueue.js +37 -0
- package/dist/server/jobs/index.d.ts +6 -0
- package/dist/server/jobs/index.js +10 -0
- package/dist/server/jobs/logger.d.ts +10 -0
- package/dist/server/jobs/logger.js +14 -0
- package/dist/server/jobs/plainjobAdapter.d.ts +19 -0
- package/dist/server/jobs/plainjobAdapter.js +48 -0
- package/dist/server/jobs/polling.d.ts +17 -0
- package/dist/server/jobs/polling.js +79 -0
- package/dist/server/jobs/runner.d.ts +24 -0
- package/dist/server/jobs/runner.js +186 -0
- package/dist/server/jobs/timeout.d.ts +28 -0
- package/dist/server/jobs/timeout.js +47 -0
- package/dist/server/jobs/types.d.ts +167 -0
- package/dist/server/jobs/types.js +12 -0
- package/dist/server/master-lock/guard.d.ts +53 -0
- package/dist/server/master-lock/guard.js +205 -0
- package/dist/server/master-lock/index.d.ts +21 -0
- package/dist/server/master-lock/index.js +21 -0
- package/dist/server/master-lock/lockPage.d.ts +34 -0
- package/dist/server/master-lock/lockPage.js +281 -0
- package/dist/server/master-lock/masterLock.d.ts +294 -0
- package/dist/server/master-lock/masterLock.js +523 -0
- package/dist/server/master-lock/principals.d.ts +76 -0
- package/dist/server/master-lock/principals.js +157 -0
- package/dist/server/master-lock/seed.d.ts +47 -0
- package/dist/server/master-lock/seed.js +93 -0
- package/dist/server/master-lock/store.d.ts +19 -0
- package/dist/server/master-lock/store.js +20 -0
- package/dist/server/media-bun/image.d.ts +7 -0
- package/dist/server/media-bun/image.js +96 -0
- package/dist/server/media-bun/index.d.ts +4 -0
- package/dist/server/media-bun/index.js +10 -0
- package/dist/server/media-bun/streaming.d.ts +86 -0
- package/dist/server/media-bun/streaming.js +237 -0
- package/dist/server/media-bun/video.d.ts +35 -0
- package/dist/server/media-bun/video.js +181 -0
- package/dist/server/metrics/metricsBuffer.d.ts +39 -0
- package/dist/server/metrics/metricsBuffer.js +36 -0
- package/dist/server/metrics/normalizeRoute.d.ts +5 -0
- package/dist/server/metrics/normalizeRoute.js +58 -0
- package/dist/server/middleware/correlationId.d.ts +9 -0
- package/dist/server/middleware/correlationId.js +20 -0
- package/dist/server/middleware/corsAllowlist.d.ts +38 -0
- package/dist/server/middleware/corsAllowlist.js +37 -0
- package/dist/server/middleware/errorEnvelope.d.ts +17 -0
- package/dist/server/middleware/errorEnvelope.js +32 -0
- package/dist/server/middleware/index.d.ts +9 -0
- package/dist/server/middleware/index.js +10 -0
- package/dist/server/middleware/localOrigin.d.ts +71 -0
- package/dist/server/middleware/localOrigin.js +119 -0
- package/dist/server/middleware/rateLimit.d.ts +56 -0
- package/dist/server/middleware/rateLimit.js +86 -0
- package/dist/server/middleware/requestLogger.d.ts +29 -0
- package/dist/server/middleware/requestLogger.js +47 -0
- package/dist/server/middleware/securityHeaders.d.ts +29 -0
- package/dist/server/middleware/securityHeaders.js +44 -0
- package/dist/server/middleware/zValidator.d.ts +29 -0
- package/dist/server/middleware/zValidator.js +57 -0
- package/dist/server/migration/runner.d.ts +42 -0
- package/dist/server/migration/runner.js +70 -0
- package/dist/server/migration/types.d.ts +16 -0
- package/dist/server/migration/types.js +1 -0
- package/dist/server/net/outboundGuard.d.ts +30 -0
- package/dist/server/net/outboundGuard.js +199 -0
- package/dist/server/notifications/hub.d.ts +67 -0
- package/dist/server/notifications/hub.js +187 -0
- package/dist/server/notifications/index.d.ts +47 -0
- package/dist/server/notifications/index.js +46 -0
- package/dist/server/notifications/migrations.d.ts +13 -0
- package/dist/server/notifications/migrations.js +20 -0
- package/dist/server/notifications/routes.d.ts +58 -0
- package/dist/server/notifications/routes.js +113 -0
- package/dist/server/notifications/service.d.ts +58 -0
- package/dist/server/notifications/service.js +41 -0
- package/dist/server/paths/appDataHome.d.ts +19 -0
- package/dist/server/paths/appDataHome.js +68 -0
- package/dist/server/private-media/crypto.d.ts +37 -0
- package/dist/server/private-media/crypto.js +111 -0
- package/dist/server/private-media/index-store.d.ts +31 -0
- package/dist/server/private-media/index-store.js +36 -0
- package/dist/server/private-media/mountPrivateRoutes.d.ts +26 -0
- package/dist/server/private-media/mountPrivateRoutes.js +219 -0
- package/dist/server/private-media/pipeline.d.ts +37 -0
- package/dist/server/private-media/pipeline.js +97 -0
- package/dist/server/private-media/unlock.d.ts +52 -0
- package/dist/server/private-media/unlock.js +55 -0
- package/dist/server/resume/index.d.ts +2 -0
- package/dist/server/resume/index.js +2 -0
- package/dist/server/resume/routes.d.ts +41 -0
- package/dist/server/resume/routes.js +50 -0
- package/dist/server/resume/store.d.ts +37 -0
- package/dist/server/resume/store.js +38 -0
- package/dist/server/retention/archive.d.ts +78 -0
- package/dist/server/retention/archive.js +284 -0
- package/dist/server/retention/fleet.d.ts +50 -0
- package/dist/server/retention/fleet.js +66 -0
- package/dist/server/retention/index.d.ts +38 -0
- package/dist/server/retention/index.js +38 -0
- package/dist/server/retention/policy.d.ts +263 -0
- package/dist/server/retention/policy.js +264 -0
- package/dist/server/retention/store.d.ts +101 -0
- package/dist/server/retention/store.js +251 -0
- package/dist/server/retention/sweeper.d.ts +69 -0
- package/dist/server/retention/sweeper.js +289 -0
- package/dist/server/retention/target.d.ts +143 -0
- package/dist/server/retention/target.js +438 -0
- package/dist/server/search/index.d.ts +2 -0
- package/dist/server/search/index.js +6 -0
- package/dist/server/search/pgSearchProvider.d.ts +91 -0
- package/dist/server/search/pgSearchProvider.js +330 -0
- package/dist/server/search/pgSearchProvider.pg.test.d.ts +1 -0
- package/dist/server/search/pgSearchProvider.pg.test.js +338 -0
- package/dist/server/search/pgSearchSchema.d.ts +81 -0
- package/dist/server/search/pgSearchSchema.js +89 -0
- package/dist/server/sharing/index.d.ts +18 -0
- package/dist/server/sharing/index.js +16 -0
- package/dist/server/sharing/migrations.d.ts +6 -0
- package/dist/server/sharing/migrations.js +79 -0
- package/dist/server/sharing/router.d.ts +111 -0
- package/dist/server/sharing/router.js +93 -0
- package/dist/server/sharing/sql.d.ts +67 -0
- package/dist/server/sharing/sql.js +88 -0
- package/dist/server/sharing/store.d.ts +173 -0
- package/dist/server/sharing/store.js +484 -0
- package/dist/server/sqlite/backup.d.ts +48 -0
- package/dist/server/sqlite/backup.js +97 -0
- package/dist/server/sqlite/browse.d.ts +42 -0
- package/dist/server/sqlite/browse.js +84 -0
- package/dist/server/sqlite/export.d.ts +32 -0
- package/dist/server/sqlite/export.js +42 -0
- package/dist/server/sqlite/fts5.d.ts +106 -0
- package/dist/server/sqlite/fts5.js +154 -0
- package/dist/server/sqlite/health.d.ts +44 -0
- package/dist/server/sqlite/health.js +52 -0
- package/dist/server/sqlite/import.d.ts +36 -0
- package/dist/server/sqlite/import.js +108 -0
- package/dist/server/sqlite/index.d.ts +9 -0
- package/dist/server/sqlite/index.js +15 -0
- package/dist/server/sqlite/readonlySql.d.ts +15 -0
- package/dist/server/sqlite/readonlySql.js +65 -0
- package/dist/server/sqlite/stats.d.ts +22 -0
- package/dist/server/sqlite/stats.js +32 -0
- package/dist/server/sqlite/viewer.d.ts +17 -0
- package/dist/server/sqlite/viewer.js +56 -0
- package/dist/server/storage/binaryServerProcessing.d.ts +23 -0
- package/dist/server/storage/binaryServerProcessing.js +81 -0
- package/dist/server/storage/binaryServerStore.d.ts +3 -0
- package/dist/server/storage/binaryServerStore.js +95 -0
- package/dist/server/storage/diskStore.d.ts +2 -0
- package/dist/server/storage/diskStore.js +88 -0
- package/dist/server/storage/files/adapter.d.ts +33 -0
- package/dist/server/storage/files/adapter.js +203 -0
- package/dist/server/storage/files/catalogue.d.ts +72 -0
- package/dist/server/storage/files/catalogue.js +1033 -0
- package/dist/server/storage/files/chunkedUpload.d.ts +56 -0
- package/dist/server/storage/files/chunkedUpload.js +70 -0
- package/dist/server/storage/files/fileRoutes.d.ts +51 -0
- package/dist/server/storage/files/fileRoutes.js +405 -0
- package/dist/server/storage/files/index.d.ts +9 -0
- package/dist/server/storage/files/index.js +11 -0
- package/dist/server/storage/files/migrations.d.ts +2 -0
- package/dist/server/storage/files/migrations.js +163 -0
- package/dist/server/storage/files/mimeByExt.d.ts +1 -0
- package/dist/server/storage/files/mimeByExt.js +13 -0
- package/dist/server/storage/files/stack.d.ts +136 -0
- package/dist/server/storage/files/stack.js +132 -0
- package/dist/server/storage/files/storageOps.d.ts +54 -0
- package/dist/server/storage/files/storageOps.js +110 -0
- package/dist/server/storage/files/types.d.ts +261 -0
- package/dist/server/storage/files/types.js +22 -0
- package/dist/server/storage/imageThumbnail.d.ts +3 -0
- package/dist/server/storage/imageThumbnail.js +50 -0
- package/dist/server/storage/index.d.ts +15 -0
- package/dist/server/storage/index.js +27 -0
- package/dist/server/storage/mountMediaRoutes.d.ts +28 -0
- package/dist/server/storage/mountMediaRoutes.js +141 -0
- package/dist/server/storage/objectStoreMediaStore.d.ts +4 -0
- package/dist/server/storage/objectStoreMediaStore.js +57 -0
- package/dist/server/storage/s3ObjectStore.d.ts +63 -0
- package/dist/server/storage/s3ObjectStore.js +187 -0
- package/dist/server/storage/sources.d.ts +47 -0
- package/dist/server/storage/sources.js +148 -0
- package/dist/server/storage/streamRange.d.ts +19 -0
- package/dist/server/storage/streamRange.js +69 -0
- package/dist/server/storage/streamToken.d.ts +16 -0
- package/dist/server/storage/streamToken.js +54 -0
- package/dist/server/storage/transcodeJobs.d.ts +65 -0
- package/dist/server/storage/transcodeJobs.js +104 -0
- package/dist/server/storage/types.d.ts +193 -0
- package/dist/server/storage/types.js +41 -0
- package/dist/server/storage/videoDerivativeKeys.d.ts +127 -0
- package/dist/server/storage/videoDerivativeKeys.js +149 -0
- package/dist/server/storage/videoProcessor.d.ts +30 -0
- package/dist/server/storage/videoProcessor.js +148 -0
- package/dist/server/sync/alarm.d.ts +34 -0
- package/dist/server/sync/alarm.js +91 -0
- package/dist/server/sync/commands.d.ts +87 -0
- package/dist/server/sync/commands.js +241 -0
- package/dist/server/sync/engine.d.ts +62 -0
- package/dist/server/sync/engine.js +184 -0
- package/dist/server/sync/http.d.ts +106 -0
- package/dist/server/sync/http.js +243 -0
- package/dist/server/sync/index.d.ts +41 -0
- package/dist/server/sync/index.js +41 -0
- package/dist/server/sync/opLog.d.ts +61 -0
- package/dist/server/sync/opLog.js +96 -0
- package/dist/server/sync/planner.d.ts +31 -0
- package/dist/server/sync/planner.js +30 -0
- package/dist/server/sync/status.d.ts +63 -0
- package/dist/server/sync/status.js +49 -0
- package/dist/server/sync/timer.d.ts +52 -0
- package/dist/server/sync/timer.js +170 -0
- package/dist/server/sync/tokens.d.ts +25 -0
- package/dist/server/sync/tokens.js +51 -0
- package/dist/server/sync/types.d.ts +101 -0
- package/dist/server/sync/types.js +29 -0
- package/dist/server/ws/eventBus.d.ts +14 -0
- package/dist/server/ws/eventBus.js +42 -0
- package/dist/server/ws/hub.d.ts +20 -0
- package/dist/server/ws/hub.js +154 -0
- package/dist/server/ws/index.d.ts +5 -0
- package/dist/server/ws/index.js +7 -0
- package/dist/server/ws/presence.d.ts +11 -0
- package/dist/server/ws/presence.js +20 -0
- package/dist/server/ws/room.d.ts +17 -0
- package/dist/server/ws/room.js +23 -0
- package/dist/server/ws/types.d.ts +122 -0
- package/dist/server/ws/types.js +1 -0
- package/package.json +234 -0
- package/src/declaredDepsAreImported.spec.ts +87 -0
- package/src/leafSubpathsImportNothing.spec.ts +358 -0
- package/src/lockfileIsTracked.spec.ts +50 -0
- package/src/namedSubpathsResolve.spec.ts +122 -0
- package/src/noPathDeps.spec.ts +24 -0
- package/src/publishShape.spec.ts +89 -0
- package/src/server/activity/index.ts +59 -0
- package/src/server/activity/migrations.ts +67 -0
- package/src/server/activity/shareSource.ts +57 -0
- package/src/server/activity/store.spec.ts +255 -0
- package/src/server/activity/store.ts +368 -0
- package/src/server/app.spec.ts +243 -0
- package/src/server/app.ts +89 -0
- package/src/server/audit/index.ts +2 -0
- package/src/server/audit/migrations.ts +88 -0
- package/src/server/audit/sqliteSink.test.ts +200 -0
- package/src/server/audit/sqliteSink.ts +162 -0
- package/src/server/auth/cookie.spec.ts +46 -0
- package/src/server/auth/cookie.ts +42 -0
- package/src/server/auth/csrf.spec.ts +81 -0
- package/src/server/auth/csrf.ts +83 -0
- package/src/server/auth/fileTokenSigner.ts +40 -0
- package/src/server/auth/getOnly.spec.ts +53 -0
- package/src/server/auth/getOnly.ts +32 -0
- package/src/server/auth/index.ts +51 -0
- package/src/server/auth/jwt.spec.ts +106 -0
- package/src/server/auth/jwt.ts +75 -0
- package/src/server/auth/loginThrottle.spec.ts +213 -0
- package/src/server/auth/loginThrottle.ts +261 -0
- package/src/server/auth/middleware.spec.ts +148 -0
- package/src/server/auth/middleware.ts +56 -0
- package/src/server/auth/password.spec.ts +48 -0
- package/src/server/auth/password.ts +71 -0
- package/src/server/auth/sessionStore.spec.ts +189 -0
- package/src/server/auth/sessionStore.ts +261 -0
- package/src/server/auth/totp.spec.ts +73 -0
- package/src/server/auth/totp.ts +92 -0
- package/src/server/auth/types.ts +146 -0
- package/src/server/auth/vault.spec.ts +51 -0
- package/src/server/auth/vault.ts +71 -0
- package/src/server/context.ts +37 -0
- package/src/server/ctgr.spec.ts +138 -0
- package/src/server/ctgr.ts +108 -0
- package/src/server/db/kysely.spec.ts +62 -0
- package/src/server/db/kysely.ts +21 -0
- package/src/server/db/plumbingTypes.ts +126 -0
- package/src/server/dropzone/duplicates.ts +121 -0
- package/src/server/dropzone/index.ts +30 -0
- package/src/server/dropzone/scan.spec.ts +255 -0
- package/src/server/dropzone/scan.ts +342 -0
- package/src/server/errors.spec.ts +76 -0
- package/src/server/errors.ts +22 -0
- package/src/server/guard/argon2Binding.spec.ts +117 -0
- package/src/server/guard/argon2Binding.ts +195 -0
- package/src/server/guard/argon2Kat.spec.ts +250 -0
- package/src/server/guard/guard.spec.ts +390 -0
- package/src/server/guard/index.ts +6 -0
- package/src/server/guard/kdfWorker.spec.ts +283 -0
- package/src/server/guard/kdfWorker.ts +367 -0
- package/src/server/guard/kdfWorkerEntry.ts +16 -0
- package/src/server/guard/passwordVerifier.spec.ts +71 -0
- package/src/server/guard/passwordVerifier.ts +175 -0
- package/src/server/guard/revocationStore.spec.ts +135 -0
- package/src/server/guard/revocationStore.ts +291 -0
- package/src/server/guard/sessionSigner.ts +425 -0
- package/src/server/guard/verifier.ts +27 -0
- package/src/server/incidents/channels.ts +487 -0
- package/src/server/incidents/checks.ts +120 -0
- package/src/server/incidents/incidents.spec.ts +463 -0
- package/src/server/incidents/index.ts +96 -0
- package/src/server/incidents/migrations.ts +66 -0
- package/src/server/incidents/notifier.ts +215 -0
- package/src/server/incidents/reporter.ts +110 -0
- package/src/server/incidents/routes.ts +314 -0
- package/src/server/incidents/store.ts +329 -0
- package/src/server/incidents/types.ts +116 -0
- package/src/server/index.ts +248 -0
- package/src/server/jobs/fireQueue.spec.ts +85 -0
- package/src/server/jobs/fireQueue.ts +37 -0
- package/src/server/jobs/index.ts +26 -0
- package/src/server/jobs/logger.ts +16 -0
- package/src/server/jobs/plainjobAdapter.spec.ts +34 -0
- package/src/server/jobs/plainjobAdapter.ts +54 -0
- package/src/server/jobs/polling.spec.ts +219 -0
- package/src/server/jobs/polling.ts +81 -0
- package/src/server/jobs/runner.spec.ts +213 -0
- package/src/server/jobs/runner.ts +213 -0
- package/src/server/jobs/timeout.spec.ts +35 -0
- package/src/server/jobs/timeout.ts +53 -0
- package/src/server/jobs/types.ts +188 -0
- package/src/server/master-lock/guard.spec.ts +347 -0
- package/src/server/master-lock/guard.ts +290 -0
- package/src/server/master-lock/index.ts +44 -0
- package/src/server/master-lock/lockPage.spec.ts +88 -0
- package/src/server/master-lock/lockPage.ts +313 -0
- package/src/server/master-lock/masterLock.spec.ts +338 -0
- package/src/server/master-lock/masterLock.ts +627 -0
- package/src/server/master-lock/principals.spec.ts +378 -0
- package/src/server/master-lock/principals.ts +175 -0
- package/src/server/master-lock/seed.spec.ts +69 -0
- package/src/server/master-lock/seed.ts +112 -0
- package/src/server/master-lock/store.ts +35 -0
- package/src/server/media-bun/image.spec.ts +115 -0
- package/src/server/media-bun/image.ts +134 -0
- package/src/server/media-bun/index.ts +52 -0
- package/src/server/media-bun/streaming.spec.ts +134 -0
- package/src/server/media-bun/streaming.ts +347 -0
- package/src/server/media-bun/video.spec.ts +115 -0
- package/src/server/media-bun/video.ts +232 -0
- package/src/server/metrics/metricsBuffer.ts +92 -0
- package/src/server/metrics/normalizeRoute.spec.ts +51 -0
- package/src/server/metrics/normalizeRoute.ts +63 -0
- package/src/server/middleware/correlationId.ts +24 -0
- package/src/server/middleware/corsAllowlist.spec.ts +118 -0
- package/src/server/middleware/corsAllowlist.ts +69 -0
- package/src/server/middleware/errorEnvelope.ts +38 -0
- package/src/server/middleware/index.ts +19 -0
- package/src/server/middleware/localOrigin.spec.ts +168 -0
- package/src/server/middleware/localOrigin.ts +139 -0
- package/src/server/middleware/rateLimit.spec.ts +72 -0
- package/src/server/middleware/rateLimit.ts +125 -0
- package/src/server/middleware/requestLogger.ts +95 -0
- package/src/server/middleware/securityHeaders.ts +78 -0
- package/src/server/middleware/zValidator.spec.ts +251 -0
- package/src/server/middleware/zValidator.ts +114 -0
- package/src/server/migration/runner.spec.ts +88 -0
- package/src/server/migration/runner.ts +87 -0
- package/src/server/migration/types.ts +17 -0
- package/src/server/net/outboundGuard.spec.ts +90 -0
- package/src/server/net/outboundGuard.ts +205 -0
- package/src/server/notifications/hub.ts +245 -0
- package/src/server/notifications/index.ts +77 -0
- package/src/server/notifications/migrations.ts +39 -0
- package/src/server/notifications/routes.ts +177 -0
- package/src/server/notifications/service.spec.ts +305 -0
- package/src/server/notifications/service.ts +112 -0
- package/src/server/paths/appDataHome.spec.ts +87 -0
- package/src/server/paths/appDataHome.ts +84 -0
- package/src/server/private-media/blobRoute.spec.ts +165 -0
- package/src/server/private-media/crypto.spec.ts +81 -0
- package/src/server/private-media/crypto.ts +155 -0
- package/src/server/private-media/gatePolicy.spec.ts +98 -0
- package/src/server/private-media/index-store.ts +83 -0
- package/src/server/private-media/mountPrivateRoutes.ts +265 -0
- package/src/server/private-media/pipeline.spec.ts +111 -0
- package/src/server/private-media/pipeline.ts +170 -0
- package/src/server/private-media/routes.spec.ts +265 -0
- package/src/server/private-media/unlock.ts +112 -0
- package/src/server/resume/index.ts +8 -0
- package/src/server/resume/resume.spec.ts +118 -0
- package/src/server/resume/routes.ts +92 -0
- package/src/server/resume/store.ts +82 -0
- package/src/server/retention/archive.spec.ts +181 -0
- package/src/server/retention/archive.ts +353 -0
- package/src/server/retention/fleet.ts +104 -0
- package/src/server/retention/index.ts +101 -0
- package/src/server/retention/policy.spec.ts +132 -0
- package/src/server/retention/policy.ts +392 -0
- package/src/server/retention/store.spec.ts +236 -0
- package/src/server/retention/store.ts +338 -0
- package/src/server/retention/sweeper.spec.ts +321 -0
- package/src/server/retention/sweeper.ts +367 -0
- package/src/server/retention/target.spec.ts +592 -0
- package/src/server/retention/target.ts +613 -0
- package/src/server/search/index.ts +12 -0
- package/src/server/search/pgSearchProvider.pg.test.ts +392 -0
- package/src/server/search/pgSearchProvider.ts +421 -0
- package/src/server/search/pgSearchSchema.ts +147 -0
- package/src/server/sharing/index.ts +65 -0
- package/src/server/sharing/migrations.ts +128 -0
- package/src/server/sharing/router.spec.ts +167 -0
- package/src/server/sharing/router.ts +194 -0
- package/src/server/sharing/sql.ts +109 -0
- package/src/server/sharing/store.spec.ts +408 -0
- package/src/server/sharing/store.ts +737 -0
- package/src/server/sqlite/backup.spec.ts +114 -0
- package/src/server/sqlite/backup.ts +154 -0
- package/src/server/sqlite/browse.spec.ts +70 -0
- package/src/server/sqlite/browse.ts +120 -0
- package/src/server/sqlite/export.spec.ts +43 -0
- package/src/server/sqlite/export.ts +74 -0
- package/src/server/sqlite/fts5.spec.ts +175 -0
- package/src/server/sqlite/fts5.ts +255 -0
- package/src/server/sqlite/health.spec.ts +79 -0
- package/src/server/sqlite/health.ts +105 -0
- package/src/server/sqlite/import.spec.ts +75 -0
- package/src/server/sqlite/import.ts +141 -0
- package/src/server/sqlite/index.ts +64 -0
- package/src/server/sqlite/readonlySql.spec.ts +58 -0
- package/src/server/sqlite/readonlySql.ts +67 -0
- package/src/server/sqlite/stats.spec.ts +21 -0
- package/src/server/sqlite/stats.ts +61 -0
- package/src/server/sqlite/viewer.spec.ts +127 -0
- package/src/server/sqlite/viewer.ts +72 -0
- package/src/server/storage/binaryServerProcessing.integration.spec.ts +222 -0
- package/src/server/storage/binaryServerProcessing.spec.ts +150 -0
- package/src/server/storage/binaryServerProcessing.ts +127 -0
- package/src/server/storage/binaryServerStore.ts +102 -0
- package/src/server/storage/chunked.integration.spec.ts +185 -0
- package/src/server/storage/diskStore.ts +83 -0
- package/src/server/storage/files/adapter.photo.display.spec.ts +169 -0
- package/src/server/storage/files/adapter.photo.spec.ts +128 -0
- package/src/server/storage/files/adapter.spec.ts +121 -0
- package/src/server/storage/files/adapter.ts +255 -0
- package/src/server/storage/files/catalogue.media-host.spec.ts +184 -0
- package/src/server/storage/files/catalogue.orderings.spec.ts +157 -0
- package/src/server/storage/files/catalogue.private.spec.ts +106 -0
- package/src/server/storage/files/catalogue.recover.spec.ts +131 -0
- package/src/server/storage/files/catalogue.sort.spec.ts +154 -0
- package/src/server/storage/files/catalogue.spec.ts +133 -0
- package/src/server/storage/files/catalogue.trash-import.spec.ts +125 -0
- package/src/server/storage/files/catalogue.ts +1326 -0
- package/src/server/storage/files/chunkedUpload.spec.ts +111 -0
- package/src/server/storage/files/chunkedUpload.ts +146 -0
- package/src/server/storage/files/collections.spec.ts +294 -0
- package/src/server/storage/files/fileRoutes.replace.spec.ts +88 -0
- package/src/server/storage/files/fileRoutes.spec.ts +327 -0
- package/src/server/storage/files/fileRoutes.ts +462 -0
- package/src/server/storage/files/index.ts +66 -0
- package/src/server/storage/files/migrations.ts +187 -0
- package/src/server/storage/files/mimeByExt.ts +14 -0
- package/src/server/storage/files/stack.spec.ts +258 -0
- package/src/server/storage/files/stack.ts +284 -0
- package/src/server/storage/files/storageOps.spec.ts +126 -0
- package/src/server/storage/files/storageOps.ts +213 -0
- package/src/server/storage/files/types.ts +285 -0
- package/src/server/storage/imageThumbnail.spec.ts +81 -0
- package/src/server/storage/imageThumbnail.ts +60 -0
- package/src/server/storage/index.ts +122 -0
- package/src/server/storage/mountMediaRoutes.ts +231 -0
- package/src/server/storage/objectStore.spec.ts +272 -0
- package/src/server/storage/objectStoreMediaStore.ts +63 -0
- package/src/server/storage/s3ObjectStore.ts +239 -0
- package/src/server/storage/sources.ts +245 -0
- package/src/server/storage/storage.spec.ts +219 -0
- package/src/server/storage/streamRange.ts +92 -0
- package/src/server/storage/streamToken.ts +76 -0
- package/src/server/storage/transcode.integration.spec.ts +134 -0
- package/src/server/storage/transcodeJobs.spec.ts +152 -0
- package/src/server/storage/transcodeJobs.ts +171 -0
- package/src/server/storage/types.ts +230 -0
- package/src/server/storage/videoDerivativeKeys.spec.ts +112 -0
- package/src/server/storage/videoDerivativeKeys.ts +172 -0
- package/src/server/storage/videoProcessor.posterOnly.spec.ts +106 -0
- package/src/server/storage/videoProcessor.ts +229 -0
- package/src/server/sync/alarm.spec.ts +149 -0
- package/src/server/sync/alarm.ts +137 -0
- package/src/server/sync/commands.spec.ts +145 -0
- package/src/server/sync/commands.ts +361 -0
- package/src/server/sync/engine.spec.ts +496 -0
- package/src/server/sync/engine.ts +255 -0
- package/src/server/sync/http.ts +316 -0
- package/src/server/sync/httpRemote.spec.ts +158 -0
- package/src/server/sync/index.ts +90 -0
- package/src/server/sync/opLog.spec.ts +110 -0
- package/src/server/sync/opLog.ts +189 -0
- package/src/server/sync/planner.spec.ts +53 -0
- package/src/server/sync/planner.ts +62 -0
- package/src/server/sync/status.spec.ts +92 -0
- package/src/server/sync/status.ts +108 -0
- package/src/server/sync/timer.spec.ts +150 -0
- package/src/server/sync/timer.ts +207 -0
- package/src/server/sync/tokens.spec.ts +38 -0
- package/src/server/sync/tokens.ts +94 -0
- package/src/server/sync/types.ts +108 -0
- package/src/server/ws/eventBus.spec.ts +67 -0
- package/src/server/ws/eventBus.ts +46 -0
- package/src/server/ws/hub.integration.spec.ts +93 -0
- package/src/server/ws/hub.spec.ts +282 -0
- package/src/server/ws/hub.ts +168 -0
- package/src/server/ws/index.ts +20 -0
- package/src/server/ws/presence.spec.ts +27 -0
- package/src/server/ws/presence.ts +26 -0
- package/src/server/ws/room.spec.ts +36 -0
- package/src/server/ws/room.ts +29 -0
- package/src/server/ws/types.ts +134 -0
- package/src/skippedTestsAreAnswered.spec.ts +215 -0
- package/src/testTmpRoot.spec.ts +102 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The cursedbelt job runner's public types — the engine both apps share.
|
|
5
|
+
*
|
|
6
|
+
* cursedbelt owns the *mechanism* (durable cron scheduling, restart-surviving
|
|
7
|
+
* pollers, the due-row fire queue, run auditing); the *job definitions* —
|
|
8
|
+
* reminder delivery, recurrence materialization, backup, metrics flush, caSync —
|
|
9
|
+
* stay app-business and consume these primitives.
|
|
10
|
+
*
|
|
11
|
+
* Three primitives cover every in-process scheduled-work shape:
|
|
12
|
+
* - {@link JobRunner} — durable cron jobs (plainjob-backed; survive restart).
|
|
13
|
+
* - PollingJob — a periodic timer that records its last successful run.
|
|
14
|
+
* - FireQueue — the "select due rows → deliver → mark sent" pattern.
|
|
15
|
+
*
|
|
16
|
+
* (cwip/taskq is the *other* model — lease-locked, multi-process, cross-machine —
|
|
17
|
+
* and stays in cwip. These primitives are single-server / in-process only.)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A minimal structured logger (winston-compatible, so it can be passed straight
|
|
22
|
+
* through to the underlying plainjob queue/worker). Consumers inject their app
|
|
23
|
+
* logger; the default ({@link defaultJobLogger}) writes warn/info/error to the
|
|
24
|
+
* console and silences `debug` (plainjob logs at debug every poll).
|
|
25
|
+
*/
|
|
26
|
+
export interface JobLogger {
|
|
27
|
+
error(message: string, ...meta: unknown[]): void;
|
|
28
|
+
warn(message: string, ...meta: unknown[]): void;
|
|
29
|
+
info(message: string, ...meta: unknown[]): void;
|
|
30
|
+
debug(message: string, ...meta: unknown[]): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Everything a job handler is handed when it runs. */
|
|
34
|
+
export interface JobContext {
|
|
35
|
+
/** The shared SQLite connection (same instance the app uses). */
|
|
36
|
+
db: Database;
|
|
37
|
+
/**
|
|
38
|
+
* Aborted when the runner/poller is stopped, or when a {@link JobDef.timeout}
|
|
39
|
+
* elapses. Long-running handlers should observe it and short-circuit; a promise
|
|
40
|
+
* cannot be force-killed, so cooperation is the only way to stop work early.
|
|
41
|
+
*/
|
|
42
|
+
signal: AbortSignal;
|
|
43
|
+
logger: JobLogger;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A durable, cron-scheduled job registered with a {@link JobRunner}. */
|
|
47
|
+
export interface JobDef {
|
|
48
|
+
/** Unique name — the audit key (`job_runs.job_name`) and plainjob job type. */
|
|
49
|
+
name: string;
|
|
50
|
+
/**
|
|
51
|
+
* Cron expression. 5-field (minute precision) or 6-field with a leading
|
|
52
|
+
* seconds column — e.g. `0 3 * * *` is 3am daily, and a seconds step of the
|
|
53
|
+
* form `(asterisk-slash-10) * * * * *` fires every 10s. Omit for a job that is
|
|
54
|
+
* only ever fired manually via {@link JobRunner.runOnce}.
|
|
55
|
+
*/
|
|
56
|
+
cron?: string;
|
|
57
|
+
/** The work. Rejecting (or timing out) records an `error` run. */
|
|
58
|
+
run(context: JobContext): Promise<void> | void;
|
|
59
|
+
/**
|
|
60
|
+
* Max run time in ms before the context signal is aborted and the run is
|
|
61
|
+
* recorded as a timeout error. Default {@link DEFAULT_JOB_TIMEOUT_MS}. Set a
|
|
62
|
+
* larger value (or `0` to disable) for legitimately long jobs like backups.
|
|
63
|
+
*/
|
|
64
|
+
timeout?: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A single row of a job's run history (newest first via {@link JobRunner.getLastRun}). */
|
|
68
|
+
export interface JobRunRecord {
|
|
69
|
+
startedAt: string;
|
|
70
|
+
/** Null while the run is still in flight (or if the process died mid-run). */
|
|
71
|
+
endedAt?: string;
|
|
72
|
+
/** `running` | `ok` | `error`. */
|
|
73
|
+
status: string;
|
|
74
|
+
/** Present only on an `error` run. */
|
|
75
|
+
error?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface CreateJobRunnerOptions {
|
|
79
|
+
logger?: JobLogger;
|
|
80
|
+
/**
|
|
81
|
+
* How often each worker polls for due cron jobs, in ms. Default 1000. Set lower
|
|
82
|
+
* only if you need sub-second cron precision; the cost is one cheap SQLite query
|
|
83
|
+
* per worker per interval.
|
|
84
|
+
*/
|
|
85
|
+
pollIntervalMs?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Bounds on the runner's persisted history. Without bounds a high-frequency
|
|
88
|
+
* cron (e.g. a 5s engine janitor) grows `job_runs` and plainjob's internal
|
|
89
|
+
* `plainjob_jobs` forever — thousands of rows per day on a completely idle
|
|
90
|
+
* daemon. Retention is ON by default ({@link DEFAULT_JOB_RETENTION});
|
|
91
|
+
* override a field to tune it, or set a field to `0` to disable that bound.
|
|
92
|
+
*/
|
|
93
|
+
retention?: JobRetentionOptions;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Retention bounds for {@link JobRunner} history. `0` disables that bound. */
|
|
97
|
+
export interface JobRetentionOptions {
|
|
98
|
+
/** Delete `job_runs` audit rows older than this. Default 7 days. */
|
|
99
|
+
auditMaxAgeMs?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Keep at most this many newest `job_runs` rows PER job name — the bound that
|
|
102
|
+
* matters for sub-minute crons, where an age window alone still admits tens of
|
|
103
|
+
* thousands of rows. Default 2000.
|
|
104
|
+
*/
|
|
105
|
+
auditMaxRowsPerJob?: number;
|
|
106
|
+
/** Minimum gap between prune passes (pruning is lazy, after a run finishes). Default 1 hour. */
|
|
107
|
+
pruneIntervalMs?: number;
|
|
108
|
+
/** plainjob maintenance: remove its internal done-job rows older than this. Default 6 hours. */
|
|
109
|
+
doneJobsMaxAgeMs?: number;
|
|
110
|
+
/** plainjob maintenance: remove its internal failed-job rows older than this. Default 7 days. */
|
|
111
|
+
failedJobsMaxAgeMs?: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Default {@link JobRetentionOptions} — bounded history out of the box. */
|
|
115
|
+
export const DEFAULT_JOB_RETENTION: Required<JobRetentionOptions> = {
|
|
116
|
+
auditMaxAgeMs: 7 * 24 * 60 * 60 * 1000,
|
|
117
|
+
auditMaxRowsPerJob: 2000,
|
|
118
|
+
pruneIntervalMs: 60 * 60 * 1000,
|
|
119
|
+
doneJobsMaxAgeMs: 6 * 60 * 60 * 1000,
|
|
120
|
+
failedJobsMaxAgeMs: 7 * 24 * 60 * 60 * 1000,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The durable cron job runner — a thin facade over plainjob (SQLite-backed, no
|
|
125
|
+
* Redis). Schedules persist in the shared DB, so a registered cron survives a
|
|
126
|
+
* restart. (plainjob does NOT retroactively fire a cron missed while the process
|
|
127
|
+
* was down; for "must catch up on boot" work use a PollingJob with `runOnBoot`.)
|
|
128
|
+
*/
|
|
129
|
+
export interface JobRunner {
|
|
130
|
+
/** Register a job. Call before {@link start}; registering a cron after start spins its worker up immediately. */
|
|
131
|
+
register(job: JobDef): void;
|
|
132
|
+
/** Begin processing: one worker per cron job starts polling. Idempotent. */
|
|
133
|
+
start(): void;
|
|
134
|
+
/** Stop all workers (signaling in-flight handlers) and release the queue. Does NOT close the shared DB. */
|
|
135
|
+
stop(): Promise<void>;
|
|
136
|
+
/** Run a registered job once, right now (admin/manual trigger). Bypasses the schedule; still audited. Throws ApiError(404) if unknown. */
|
|
137
|
+
runOnce(name: string): Promise<void>;
|
|
138
|
+
/** The most recent run of `name`, or null if it has never run. */
|
|
139
|
+
getLastRun(name: string): JobRunRecord | null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** A periodic timer started/stopped as a unit. Returned by `createPollingJob`/`createFireQueue`. */
|
|
143
|
+
export interface PollingJob {
|
|
144
|
+
name: string;
|
|
145
|
+
/** Start the timer (and fire once immediately unless `runOnBoot` is false). Idempotent. */
|
|
146
|
+
start(): void;
|
|
147
|
+
/** Stop the timer and signal any in-flight run via its abort signal. */
|
|
148
|
+
stop(): void;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface PollingJobOpts {
|
|
152
|
+
name: string;
|
|
153
|
+
/** Fire cadence in ms. */
|
|
154
|
+
intervalMs: number;
|
|
155
|
+
/** Fire once immediately on {@link PollingJob.start}. Default true. */
|
|
156
|
+
runOnBoot?: boolean;
|
|
157
|
+
/** The shared SQLite connection. `job_state.last_run_at` is upserted after each successful run. */
|
|
158
|
+
db: Database;
|
|
159
|
+
run(context: JobContext): Promise<void> | void;
|
|
160
|
+
logger?: JobLogger;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** A {@link PollingJob} specialized for the "deliver every due row" pattern. */
|
|
164
|
+
export type FireQueue = PollingJob;
|
|
165
|
+
|
|
166
|
+
export interface FireQueueOpts<T> {
|
|
167
|
+
name: string;
|
|
168
|
+
db: Database;
|
|
169
|
+
/** Poll cadence in ms. Default {@link DEFAULT_FIRE_INTERVAL_MS} (30s). */
|
|
170
|
+
intervalMs?: number;
|
|
171
|
+
/** Fire once immediately on start. Default true. */
|
|
172
|
+
runOnBoot?: boolean;
|
|
173
|
+
/** Return the rows currently due for delivery. */
|
|
174
|
+
getDue(db: Database): T[];
|
|
175
|
+
/** Deliver one item. Throwing skips `markSent` for that item and routes to `onError`; the loop continues. */
|
|
176
|
+
deliver(item: T, db: Database): Promise<void> | void;
|
|
177
|
+
/** Mark one item delivered (only called after a successful `deliver`). */
|
|
178
|
+
markSent(item: T, db: Database): void;
|
|
179
|
+
/** Per-item failure hook (the loop still continues to the next item). */
|
|
180
|
+
onError?(item: T, err: unknown): void;
|
|
181
|
+
logger?: JobLogger;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Default per-run timeout for a cron {@link JobDef} (1 minute). */
|
|
185
|
+
export const DEFAULT_JOB_TIMEOUT_MS = 60_000;
|
|
186
|
+
|
|
187
|
+
/** Default poll cadence for a {@link FireQueue} (30 seconds). */
|
|
188
|
+
export const DEFAULT_FIRE_INTERVAL_MS = 30_000;
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The guard's decisions, over a real {@link MasterLock}.
|
|
3
|
+
*
|
|
4
|
+
* The property under test is the owner's sentence: *"until the master password is entered a
|
|
5
|
+
* user logged in as me cannot see anything about the site, not even text or images, nothing
|
|
6
|
+
* at all."* So the assertions are mostly about what does NOT come back.
|
|
7
|
+
*/
|
|
8
|
+
import { beforeAll, describe, expect, test } from "bun:test";
|
|
9
|
+
import {
|
|
10
|
+
MASTER_LOCK_PATHS,
|
|
11
|
+
MASTER_LOCK_STATE_HEADER,
|
|
12
|
+
type MasterLockKdfParams,
|
|
13
|
+
deriveMasterLockVerifier,
|
|
14
|
+
newMasterLockKdfParams,
|
|
15
|
+
} from "cursedbelt-core/master-lock";
|
|
16
|
+
import { createMasterLockGuard } from "./guard";
|
|
17
|
+
import { MasterLock } from "./masterLock";
|
|
18
|
+
import { createMemoryMasterLockStore } from "./store";
|
|
19
|
+
|
|
20
|
+
const KDF: MasterLockKdfParams = {
|
|
21
|
+
v: 1,
|
|
22
|
+
alg: "PBKDF2-SHA256",
|
|
23
|
+
iter: 1,
|
|
24
|
+
salt: "AAECAwQFBgcICQoLDA0ODw",
|
|
25
|
+
};
|
|
26
|
+
let VERIFIER = "";
|
|
27
|
+
let SEED = "";
|
|
28
|
+
|
|
29
|
+
beforeAll(async () => {
|
|
30
|
+
VERIFIER = await deriveMasterLockVerifier("open sesame", KDF);
|
|
31
|
+
SEED = JSON.stringify({
|
|
32
|
+
kdf: KDF,
|
|
33
|
+
verifierHash: await Bun.password.hash(VERIFIER, { algorithm: "argon2id" }),
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function build(allow?: (url: URL) => boolean) {
|
|
38
|
+
const lock = new MasterLock({ store: createMemoryMasterLockStore(), seedJson: SEED });
|
|
39
|
+
const guard = createMasterLockGuard({
|
|
40
|
+
lock,
|
|
41
|
+
appLabel: "Test App",
|
|
42
|
+
...(allow ? { allow } : {}),
|
|
43
|
+
});
|
|
44
|
+
return { lock, guard };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const doc = (path: string, cookie?: string): Request =>
|
|
48
|
+
new Request(`http://app.test${path}`, {
|
|
49
|
+
headers: {
|
|
50
|
+
"sec-fetch-dest": "document",
|
|
51
|
+
...(cookie ? { cookie } : {}),
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
const xhr = (path: string, cookie?: string): Request =>
|
|
55
|
+
new Request(`http://app.test${path}`, {
|
|
56
|
+
headers: { "sec-fetch-dest": "empty", ...(cookie ? { cookie } : {}) },
|
|
57
|
+
});
|
|
58
|
+
const post = (path: string, body: unknown, cookie?: string): Request =>
|
|
59
|
+
new Request(`http://app.test${path}`, {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "content-type": "application/json", ...(cookie ? { cookie } : {}) },
|
|
62
|
+
body: JSON.stringify(body),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/** Unlock and return the cookie header the browser would send back. */
|
|
66
|
+
async function open(guard: ReturnType<typeof build>["guard"]): Promise<string> {
|
|
67
|
+
const res = await guard.handle(post(MASTER_LOCK_PATHS.unlock, { verifier: VERIFIER }));
|
|
68
|
+
expect(res?.status).toBe(200);
|
|
69
|
+
const setCookie = res?.headers.get("set-cookie") ?? "";
|
|
70
|
+
// The NAME as well as the value: it is per app now, and a helper that hardcoded
|
|
71
|
+
// `master_lock=` would keep passing on the day the guard stopped setting that.
|
|
72
|
+
const pair = setCookie.split(";")[0] ?? "";
|
|
73
|
+
expect((pair.split("=")[1] ?? "").length).toBeGreaterThan(10);
|
|
74
|
+
return pair;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
describe("while locked", () => {
|
|
78
|
+
test("a page request gets the lock page IN PLACE — same url, no app", async () => {
|
|
79
|
+
const { guard } = build();
|
|
80
|
+
const res = await guard.handle(doc("/some/deep/page?q=1"));
|
|
81
|
+
expect(res?.status).toBe(200);
|
|
82
|
+
expect(res?.headers.get(MASTER_LOCK_STATE_HEADER)).toBe("locked");
|
|
83
|
+
const html = (await res?.text()) ?? "";
|
|
84
|
+
expect(html).toContain("Master password");
|
|
85
|
+
expect(html).toContain("Test App");
|
|
86
|
+
// Nothing of the app, and nothing fetched from anywhere else: every `src`/`href` is a
|
|
87
|
+
// same-origin path or an inline `data:` URI. Checked on the ATTRIBUTES rather than on
|
|
88
|
+
// the string, because the inline favicon legitimately contains the SVG namespace URI —
|
|
89
|
+
// which is an identifier, not a request, and a naive "no http" grep fails on it.
|
|
90
|
+
expect(html).not.toContain("<script>");
|
|
91
|
+
for (const [, value] of html.matchAll(/\b(?:src|href)="([^"]*)"/g)) {
|
|
92
|
+
expect(value.startsWith("/") || value.startsWith("data:")).toBe(true);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("everything else is the SAME 401, whatever it asked for", async () => {
|
|
97
|
+
const { guard } = build();
|
|
98
|
+
for (const request of [
|
|
99
|
+
xhr("/api/items"),
|
|
100
|
+
xhr("/media/secret.jpg"),
|
|
101
|
+
post("/api/delete", {}),
|
|
102
|
+
new Request("http://app.test/anything"),
|
|
103
|
+
]) {
|
|
104
|
+
const res = await guard.handle(request);
|
|
105
|
+
expect(res?.status).toBe(401);
|
|
106
|
+
expect(res?.headers.get(MASTER_LOCK_STATE_HEADER)).toBe("locked");
|
|
107
|
+
expect(await res?.json()).toEqual({ error: "locked", retryAfterMs: 0 });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("nothing it serves may be cached", async () => {
|
|
112
|
+
const { guard } = build();
|
|
113
|
+
const page = await guard.handle(doc("/"));
|
|
114
|
+
expect(page?.headers.get("cache-control")).toContain("no-store");
|
|
115
|
+
const status = await guard.handle(xhr(MASTER_LOCK_PATHS.status));
|
|
116
|
+
expect(status?.headers.get("cache-control")).toContain("no-store");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("the lock page carries a wall of its own", async () => {
|
|
120
|
+
const { guard } = build();
|
|
121
|
+
const res = await guard.handle(doc("/"));
|
|
122
|
+
const csp = res?.headers.get("content-security-policy") ?? "";
|
|
123
|
+
expect(csp).toContain("default-src 'none'");
|
|
124
|
+
expect(csp).toContain("frame-ancestors 'none'");
|
|
125
|
+
expect(res?.headers.get("x-content-type-options")).toBe("nosniff");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("its own page, style, script and status stay reachable — they are how you unlock", async () => {
|
|
129
|
+
const { guard } = build();
|
|
130
|
+
for (const path of [
|
|
131
|
+
MASTER_LOCK_PATHS.page,
|
|
132
|
+
MASTER_LOCK_PATHS.style,
|
|
133
|
+
MASTER_LOCK_PATHS.script,
|
|
134
|
+
MASTER_LOCK_PATHS.status,
|
|
135
|
+
]) {
|
|
136
|
+
const res = await guard.handle(xhr(path));
|
|
137
|
+
expect(res?.status).toBe(200);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("status discloses the params and no more", async () => {
|
|
142
|
+
const { guard } = build();
|
|
143
|
+
const res = await guard.handle(xhr(MASTER_LOCK_PATHS.status));
|
|
144
|
+
const body = (await res?.json()) as Record<string, unknown>;
|
|
145
|
+
expect(body).toEqual({
|
|
146
|
+
configured: true,
|
|
147
|
+
enrollable: false,
|
|
148
|
+
locked: true,
|
|
149
|
+
kdf: KDF,
|
|
150
|
+
idleMs: 5 * 60_000,
|
|
151
|
+
remainingMs: 0,
|
|
152
|
+
retryAfterMs: 0,
|
|
153
|
+
});
|
|
154
|
+
expect(JSON.stringify(body)).not.toContain("argon2");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("an `allow` path is passed through — that is where a machine surface lives", async () => {
|
|
158
|
+
const { guard } = build((url) => url.pathname === "/healthz");
|
|
159
|
+
expect(await guard.handle(xhr("/healthz"))).toBeNull();
|
|
160
|
+
expect((await guard.handle(xhr("/api/items")))?.status).toBe(401);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("a mutation route refuses before it can change anything", async () => {
|
|
164
|
+
const { lock, guard } = build();
|
|
165
|
+
expect((await guard.handle(post(MASTER_LOCK_PATHS.lock, {})))?.status).toBe(401);
|
|
166
|
+
expect((await guard.handle(post(MASTER_LOCK_PATHS.settings, { idleMs: 60_000 })))?.status).toBe(401);
|
|
167
|
+
expect(lock.idleMs).toBe(5 * 60_000);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("unlocking", () => {
|
|
172
|
+
test("the right verifier mints a cookie and the app is served again", async () => {
|
|
173
|
+
const { guard } = build();
|
|
174
|
+
const cookie = await open(guard);
|
|
175
|
+
expect(await guard.handle(doc("/", cookie))).toBeNull();
|
|
176
|
+
expect(await guard.handle(xhr("/api/items", cookie))).toBeNull();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("the cookie is HttpOnly + SameSite=Strict, and Secure only on https", async () => {
|
|
180
|
+
const { guard } = build();
|
|
181
|
+
const plain = await guard.handle(post(MASTER_LOCK_PATHS.unlock, { verifier: VERIFIER }));
|
|
182
|
+
const header = plain?.headers.get("set-cookie") ?? "";
|
|
183
|
+
expect(header).toContain("HttpOnly");
|
|
184
|
+
expect(header).toContain("SameSite=Strict");
|
|
185
|
+
expect(header).not.toContain("Secure");
|
|
186
|
+
// A session cookie — closing the browser drops it; the server clock is the real expiry.
|
|
187
|
+
expect(header).not.toContain("Max-Age");
|
|
188
|
+
|
|
189
|
+
const { guard: guard2 } = build();
|
|
190
|
+
const forwarded = new Request(`http://app.test${MASTER_LOCK_PATHS.unlock}`, {
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: { "content-type": "application/json", "x-forwarded-proto": "https" },
|
|
193
|
+
body: JSON.stringify({ verifier: VERIFIER }),
|
|
194
|
+
});
|
|
195
|
+
expect((await guard2.handle(forwarded))?.headers.get("set-cookie")).toContain("Secure");
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("a wrong verifier answers the same refusal as being locked out", async () => {
|
|
199
|
+
const { guard } = build();
|
|
200
|
+
const res = await guard.handle(post(MASTER_LOCK_PATHS.unlock, { verifier: "nope" }));
|
|
201
|
+
expect(res?.status).toBe(401);
|
|
202
|
+
expect(await res?.json()).toEqual({ error: "locked", retryAfterMs: 0 });
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("a garbage body is refused, not crashed on", async () => {
|
|
206
|
+
const { guard } = build();
|
|
207
|
+
const raw = new Request(`http://app.test${MASTER_LOCK_PATHS.unlock}`, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
body: "not json",
|
|
210
|
+
});
|
|
211
|
+
expect((await guard.handle(raw))?.status).toBe(401);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("visiting the lock page while unlocked sends you to the app", async () => {
|
|
215
|
+
const { guard } = build();
|
|
216
|
+
const cookie = await open(guard);
|
|
217
|
+
const res = await guard.handle(doc(MASTER_LOCK_PATHS.page, cookie));
|
|
218
|
+
expect(res?.status).toBe(303);
|
|
219
|
+
expect(res?.headers.get("location")).toBe("http://app.test/");
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe("while unlocked", () => {
|
|
224
|
+
test("the manual lock closes the site and clears the cookie", async () => {
|
|
225
|
+
const { guard } = build();
|
|
226
|
+
const cookie = await open(guard);
|
|
227
|
+
const res = await guard.handle(post(MASTER_LOCK_PATHS.lock, {}, cookie));
|
|
228
|
+
expect(res?.status).toBe(200);
|
|
229
|
+
expect(res?.headers.get("set-cookie")).toContain("Max-Age=0");
|
|
230
|
+
expect((await guard.handle(xhr("/api/items", cookie)))?.status).toBe(401);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("the ping reports how much idle time is left", async () => {
|
|
234
|
+
const { guard } = build();
|
|
235
|
+
const cookie = await open(guard);
|
|
236
|
+
const res = await guard.handle(post(MASTER_LOCK_PATHS.ping, {}, cookie));
|
|
237
|
+
const body = (await res?.json()) as { remainingMs: number };
|
|
238
|
+
expect(body.remainingMs).toBeGreaterThan(4 * 60_000);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("the timeout is settable and clamped", async () => {
|
|
242
|
+
const { guard, lock } = build();
|
|
243
|
+
const cookie = await open(guard);
|
|
244
|
+
const res = await guard.handle(post(MASTER_LOCK_PATHS.settings, { idleMs: 1 }, cookie));
|
|
245
|
+
expect(await res?.json()).toEqual({ ok: true, idleMs: 30_000 });
|
|
246
|
+
expect(lock.idleMs).toBe(30_000);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("rotation needs the current password and locks the site behind it", async () => {
|
|
250
|
+
const { guard } = build();
|
|
251
|
+
const cookie = await open(guard);
|
|
252
|
+
const nextKdf = newMasterLockKdfParams();
|
|
253
|
+
const nextVerifier = await deriveMasterLockVerifier("something else", nextKdf);
|
|
254
|
+
|
|
255
|
+
const refused = await guard.handle(
|
|
256
|
+
post(MASTER_LOCK_PATHS.change, { currentVerifier: "no", kdf: nextKdf, verifier: nextVerifier }, cookie),
|
|
257
|
+
);
|
|
258
|
+
expect(refused?.status).toBe(400);
|
|
259
|
+
expect(await refused?.json()).toEqual({ error: "wrong" });
|
|
260
|
+
|
|
261
|
+
const done = await guard.handle(
|
|
262
|
+
post(
|
|
263
|
+
MASTER_LOCK_PATHS.change,
|
|
264
|
+
{ currentVerifier: VERIFIER, kdf: nextKdf, verifier: nextVerifier },
|
|
265
|
+
cookie,
|
|
266
|
+
),
|
|
267
|
+
);
|
|
268
|
+
expect(done?.status).toBe(200);
|
|
269
|
+
expect(done?.headers.get("set-cookie")).toContain("Max-Age=0");
|
|
270
|
+
expect((await guard.handle(xhr("/api/items", cookie)))?.status).toBe(401);
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("an app with no master password configured", () => {
|
|
275
|
+
test("is served normally — an unopenable lock would brick it", async () => {
|
|
276
|
+
const lock = new MasterLock({ store: createMemoryMasterLockStore(), seedJson: null });
|
|
277
|
+
const guard = createMasterLockGuard({ lock, appLabel: "Test App" });
|
|
278
|
+
expect(await guard.handle(doc("/"))).toBeNull();
|
|
279
|
+
const status = await guard.handle(xhr(MASTER_LOCK_PATHS.status));
|
|
280
|
+
expect(await status?.json()).toMatchObject({ configured: false, locked: false, kdf: null });
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* 🔴 Two locked apps on ONE HOST — the bug a browser found and a unit test never could.
|
|
286
|
+
*
|
|
287
|
+
* A cookie is scoped by host, not by origin, so `localhost:3358` and `localhost:3098` share
|
|
288
|
+
* one jar. While both apps wrote a cookie called `master_lock`, whichever was unlocked last
|
|
289
|
+
* owned it, and the other app's `presents(req)` began answering false — which silently
|
|
290
|
+
* disabled "Lock this site", the idle-timeout setting, the password rotation and the
|
|
291
|
+
* activity ping on an app that still LOOKED open. See `masterLockCookieName`.
|
|
292
|
+
*/
|
|
293
|
+
describe("two locked apps sharing a hostname", () => {
|
|
294
|
+
const buildNamed = (appLabel: string) => {
|
|
295
|
+
const lock = new MasterLock({ store: createMemoryMasterLockStore(), seedJson: SEED });
|
|
296
|
+
return { lock, guard: createMasterLockGuard({ lock, appLabel }) };
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
test("each app names its own cookie, so neither can overwrite the other", async () => {
|
|
300
|
+
const first = await open(buildNamed("Collections").guard);
|
|
301
|
+
const second = await open(buildNamed("binary-server · file inspector").guard);
|
|
302
|
+
expect(first.split("=")[0]).toBe("master_lock_collections");
|
|
303
|
+
expect(second.split("=")[0]).toBe("master_lock_binary-server-file-inspector");
|
|
304
|
+
expect(first.split("=")[0]).not.toBe(second.split("=")[0]);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* ONE host's cookie jar, behaving as a browser's does: a `Set-Cookie` REPLACES any
|
|
309
|
+
* cookie of the same name. That replacement is the whole bug, so a test that joins two
|
|
310
|
+
* `Set-Cookie` strings with `; ` proves nothing — `presentedTokens` reads both halves
|
|
311
|
+
* and every assertion passes on the broken code too. Measured while writing this.
|
|
312
|
+
*/
|
|
313
|
+
const jarOf = (...pairs: string[]): string => {
|
|
314
|
+
const jar = new Map<string, string>();
|
|
315
|
+
for (const pair of pairs) {
|
|
316
|
+
const [name, ...value] = pair.split("=");
|
|
317
|
+
jar.set(name ?? "", value.join("="));
|
|
318
|
+
}
|
|
319
|
+
return [...jar].map(([name, value]) => `${name}=${value}`).join("; ");
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
test("a browser holding BOTH cookies still works each app's own controls", async () => {
|
|
323
|
+
const a = buildNamed("Collections");
|
|
324
|
+
const b = buildNamed("binary-server · file inspector");
|
|
325
|
+
const jar = jarOf(await open(a.guard), await open(b.guard));
|
|
326
|
+
|
|
327
|
+
// The exact four routes the collision broke, asked of the app that was unlocked FIRST
|
|
328
|
+
// — i.e. the one whose cookie the old shared name would have thrown away.
|
|
329
|
+
expect((await a.guard.handle(post(MASTER_LOCK_PATHS.ping, {}, jar)))?.status).toBe(200);
|
|
330
|
+
expect(
|
|
331
|
+
(await a.guard.handle(post(MASTER_LOCK_PATHS.settings, { idleMs: 60_000 }, jar)))?.status,
|
|
332
|
+
).toBe(200);
|
|
333
|
+
expect((await a.guard.handle(post(MASTER_LOCK_PATHS.lock, {}, jar)))?.status).toBe(200);
|
|
334
|
+
expect(a.lock.unlocked).toBe(false);
|
|
335
|
+
// ...and locking one app left the other exactly as it was.
|
|
336
|
+
expect(b.lock.unlocked).toBe(true);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("one app's token is not the other's key", async () => {
|
|
340
|
+
const a = buildNamed("Collections");
|
|
341
|
+
const b = buildNamed("binary-server · file inspector");
|
|
342
|
+
const onlyB = jarOf(await open(b.guard));
|
|
343
|
+
// `a` was never unlocked, so a jar carrying only b's cookie must not open it.
|
|
344
|
+
expect(a.lock.presents(post(MASTER_LOCK_PATHS.ping, {}, onlyB))).toBe(false);
|
|
345
|
+
expect((await a.guard.handle(xhr("/api/items", onlyB)))?.status).toBe(401);
|
|
346
|
+
});
|
|
347
|
+
});
|