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,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The guard โ one decision function, so a Hono app and a bare `Bun.serve` handler cannot
|
|
3
|
+
* end up enforcing two different rules.
|
|
4
|
+
*
|
|
5
|
+
* `handle(req)` answers a `Response` when it has taken responsibility for the request, and
|
|
6
|
+
* `null` when the app should serve it as usual. That shape is what lets it be middleware in
|
|
7
|
+
* one app and an early `return` in another without either of them re-deciding anything.
|
|
8
|
+
*
|
|
9
|
+
* ๐ด Registration order is the security property, exactly as it is for the SSO gate this
|
|
10
|
+
* sits behind. Mount it so that it runs BEFORE any route that serves content, and AFTER
|
|
11
|
+
* whatever proves the caller is allowed in the building at all โ the master password is a
|
|
12
|
+
* second wall, not a replacement for the first.
|
|
13
|
+
*/
|
|
14
|
+
import {
|
|
15
|
+
MASTER_LOCK_PATHS,
|
|
16
|
+
MASTER_LOCK_PREFIX,
|
|
17
|
+
MASTER_LOCK_STATE_HEADER,
|
|
18
|
+
masterLockCookieName,
|
|
19
|
+
} from "cursedbelt-core/master-lock";
|
|
20
|
+
import { LOCK_SCRIPT, LOCK_STYLE, type LockPageOptions, lockPageHtml } from "./lockPage";
|
|
21
|
+
import type { MasterLock } from "./masterLock";
|
|
22
|
+
|
|
23
|
+
export interface MasterLockGuardOptions extends LockPageOptions {
|
|
24
|
+
/**
|
|
25
|
+
* The lock this guard enforces.
|
|
26
|
+
*
|
|
27
|
+
* A {@link MasterLock} for an app with one app-wide password (collections, the
|
|
28
|
+
* binary-server inspector). A FUNCTION for an app where each person has their own
|
|
29
|
+
* (`principals.ts`): it is handed the request and returns that caller's lock.
|
|
30
|
+
*
|
|
31
|
+
* ๐ด The function must resolve the principal from the request's own verified session
|
|
32
|
+
* and NOTHING else. A resolver that reads a header, a query parameter or a body field
|
|
33
|
+
* hands the caller the choice of whose lock to be measured against, which is the one
|
|
34
|
+
* way this feature can be turned inside out. Returning `null` means "no principal" โ
|
|
35
|
+
* the guard then stands aside for ordinary paths (the app's own sign-in owns the
|
|
36
|
+
* request) and refuses `/__lock/*` outright, since there is nobody to unlock for.
|
|
37
|
+
*/
|
|
38
|
+
lock: MasterLock | ((req: Request) => MasterLock | null);
|
|
39
|
+
/**
|
|
40
|
+
* Paths that stay reachable while locked, beyond `/__lock/*`.
|
|
41
|
+
*
|
|
42
|
+
* ๐ด This is where a MACHINE surface is kept out of the lock, and getting it wrong is
|
|
43
|
+
* the one way this feature can break something else. binary-server's inspector is a
|
|
44
|
+
* UI and is locked whole; binary-server's STORE serves the fleet's bytes and is a
|
|
45
|
+
* different process that never mounts this at all. Within one process, a health check
|
|
46
|
+
* or a token-authenticated ingest route belongs here โ a page does not.
|
|
47
|
+
*/
|
|
48
|
+
allow?: (url: URL, req: Request) => boolean;
|
|
49
|
+
/**
|
|
50
|
+
* What makes this app's unlock cookie ITS OWN โ `master_lock_<appKey>`.
|
|
51
|
+
*
|
|
52
|
+
* ๐ด Set it on every app that can ever share a hostname with another locked app, which
|
|
53
|
+
* on this fleet means every app: they are all `localhost:<port>` on the owner's Mac, and
|
|
54
|
+
* a cookie is scoped by HOST, not by origin. Left unset the cookie is the bare
|
|
55
|
+
* `master_lock`, and two such apps overwrite each other's token โ see
|
|
56
|
+
* `masterLockCookieName` for what that silently breaks. Defaults to {@link appLabel},
|
|
57
|
+
* so an app that already names itself is fixed without being changed.
|
|
58
|
+
*/
|
|
59
|
+
appKey?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Is this connection HTTPS, so the cookie may carry `Secure`?
|
|
62
|
+
*
|
|
63
|
+
* Defaults to reading `x-forwarded-proto` then the URL โ right for both a satellite
|
|
64
|
+
* behind nginx and a loopback daemon speaking plain http, where a `Secure` cookie would
|
|
65
|
+
* simply never be stored and the site could never be unlocked.
|
|
66
|
+
*/
|
|
67
|
+
secure?: (url: URL, req: Request) => boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const noStore = { "cache-control": "no-store, no-cache, must-revalidate", pragma: "no-cache" } as const;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The lock page's own CSP. Strict enough to stand in front of `apps/vault`'s wall without
|
|
74
|
+
* loosening it: nothing loads from anywhere but this origin, the form posts nowhere (the
|
|
75
|
+
* script does the POST), and the page cannot be framed.
|
|
76
|
+
*/
|
|
77
|
+
const LOCK_CSP =
|
|
78
|
+
"default-src 'none'; style-src 'self'; script-src 'self'; connect-src 'self'; " +
|
|
79
|
+
"img-src data:; form-action 'none'; base-uri 'none'; frame-ancestors 'none'";
|
|
80
|
+
|
|
81
|
+
export interface MasterLockGuard {
|
|
82
|
+
/** `Response` when handled, `null` when the app should serve the request. */
|
|
83
|
+
handle(req: Request): Promise<Response | null>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createMasterLockGuard(options: MasterLockGuardOptions): MasterLockGuard {
|
|
87
|
+
const resolve: (req: Request) => MasterLock | null =
|
|
88
|
+
typeof options.lock === "function" ? options.lock : () => options.lock as MasterLock;
|
|
89
|
+
const isSecure = options.secure ?? defaultSecure;
|
|
90
|
+
const cookieName = masterLockCookieName(options.appKey ?? options.appLabel);
|
|
91
|
+
|
|
92
|
+
const cookie = (token: string, secure: boolean): string =>
|
|
93
|
+
// No Max-Age: a SESSION cookie, so closing the browser drops it. The server-side
|
|
94
|
+
// idle clock is the real expiry โ this only keeps the token out of a profile that
|
|
95
|
+
// somebody else might open tomorrow.
|
|
96
|
+
`${cookieName}=${token}; Path=/; HttpOnly; SameSite=Strict${secure ? "; Secure" : ""}`;
|
|
97
|
+
const clearCookie = (secure: boolean): string =>
|
|
98
|
+
`${cookieName}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0${secure ? "; Secure" : ""}`;
|
|
99
|
+
|
|
100
|
+
/** The ONE refusal. Identical for wrong, throttled, unconfigured and not-unlocked. */
|
|
101
|
+
const refuse = (retryAfterMs = 0): Response =>
|
|
102
|
+
Response.json(
|
|
103
|
+
{ error: "locked", retryAfterMs },
|
|
104
|
+
{ status: 401, headers: { ...noStore, [MASTER_LOCK_STATE_HEADER]: "locked" } },
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* ๐ด May THIS request through โ and the difference between the two modes is the whole
|
|
109
|
+
* security property of the per-person wall.
|
|
110
|
+
*
|
|
111
|
+
* **App-wide**: `unlocked` is enough. The site has one user and one unlock; the token
|
|
112
|
+
* is how a caller with no cookie jar says "it's still me", not a per-browser
|
|
113
|
+
* credential. Reading the ambient state is the behaviour `apps/collections` and the
|
|
114
|
+
* binary-server inspector have always had.
|
|
115
|
+
*
|
|
116
|
+
* **Per-person**: the token is REQUIRED. `unlocked` there means only "somebody's
|
|
117
|
+
* device is open", and honouring that would make the unlock ambient โ measured while
|
|
118
|
+
* building this (2026-08-25): with `unlocked ||` in front, one browser unlocking meant
|
|
119
|
+
* every OTHER browser signed in as that person walked straight through, having typed
|
|
120
|
+
* nothing. The owner's phone would ride his desk's unlock, and an invented
|
|
121
|
+
* `x-master-lock` value would be accepted because the check never looked at it. Two
|
|
122
|
+
* tests in `principals.spec.ts` fail on the `||`, which is why it is gone.
|
|
123
|
+
*/
|
|
124
|
+
const opens = (lock: MasterLock, req: Request): boolean =>
|
|
125
|
+
lock.isEnrollable ? lock.presents(req) : lock.unlocked || lock.presents(req);
|
|
126
|
+
|
|
127
|
+
async function handleOwnRoute(req: Request, url: URL, lock: MasterLock): Promise<Response> {
|
|
128
|
+
const path = url.pathname;
|
|
129
|
+
const secure = isSecure(url, req);
|
|
130
|
+
|
|
131
|
+
if (req.method === "GET" || req.method === "HEAD") {
|
|
132
|
+
if (path === MASTER_LOCK_PATHS.style) {
|
|
133
|
+
return new Response(LOCK_STYLE, {
|
|
134
|
+
headers: { "content-type": "text/css; charset=utf-8", ...noStore },
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (path === MASTER_LOCK_PATHS.script) {
|
|
138
|
+
return new Response(LOCK_SCRIPT, {
|
|
139
|
+
headers: { "content-type": "text/javascript; charset=utf-8", ...noStore },
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (path === MASTER_LOCK_PATHS.status) {
|
|
143
|
+
return Response.json(lock.status(), { headers: noStore });
|
|
144
|
+
}
|
|
145
|
+
if (path === MASTER_LOCK_PATHS.page || path === MASTER_LOCK_PREFIX) {
|
|
146
|
+
// Already open FOR THIS CALLER, so there is nothing to type โ send them to
|
|
147
|
+
// the app rather than showing a password box that would do nothing. Per
|
|
148
|
+
// person this must be the caller's own state, not the site's: bouncing a
|
|
149
|
+
// locked-out phone to `/` because the desk is open is an infinite loop.
|
|
150
|
+
if (opens(lock, req)) return Response.redirect(new URL("/", url), 303);
|
|
151
|
+
return lockPage(options, lock);
|
|
152
|
+
}
|
|
153
|
+
return new Response("not found", { status: 404, headers: noStore });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (req.method !== "POST") {
|
|
157
|
+
return new Response("method not allowed", { status: 405, headers: noStore });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (path === MASTER_LOCK_PATHS.enroll) {
|
|
161
|
+
const body = await readJson(req);
|
|
162
|
+
const result = await lock.enroll({
|
|
163
|
+
kdf: body?.kdf as never,
|
|
164
|
+
verifier: typeof body?.verifier === "string" ? body.verifier : "",
|
|
165
|
+
});
|
|
166
|
+
// Every refusal is the SAME refusal, as everywhere else here: "you may not
|
|
167
|
+
// enroll" and "somebody already did" must not be distinguishable, or the route
|
|
168
|
+
// becomes a way to ask whether a given account has a master password yet.
|
|
169
|
+
if (!result.ok) return refuse();
|
|
170
|
+
return Response.json(
|
|
171
|
+
{ ok: true, idleMs: lock.idleMs },
|
|
172
|
+
{ headers: { ...noStore, "set-cookie": cookie(result.token, secure) } },
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (path === MASTER_LOCK_PATHS.unlock) {
|
|
177
|
+
const body = await readJson(req);
|
|
178
|
+
const verifier = typeof body?.verifier === "string" ? body.verifier : "";
|
|
179
|
+
const result = await lock.unlock(verifier);
|
|
180
|
+
if (!result.ok) return refuse(result.retryAfterMs);
|
|
181
|
+
return Response.json(
|
|
182
|
+
{ ok: true, idleMs: lock.idleMs },
|
|
183
|
+
{ headers: { ...noStore, "set-cookie": cookie(result.token, secure) } },
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Everything below CHANGES something and therefore needs the site already open.
|
|
188
|
+
if (!lock.presents(req)) return refuse();
|
|
189
|
+
|
|
190
|
+
if (path === MASTER_LOCK_PATHS.ping) {
|
|
191
|
+
lock.touch(req);
|
|
192
|
+
return Response.json({ ok: true, remainingMs: lock.remainingMs }, { headers: noStore });
|
|
193
|
+
}
|
|
194
|
+
if (path === MASTER_LOCK_PATHS.lock) {
|
|
195
|
+
lock.lock();
|
|
196
|
+
return Response.json(
|
|
197
|
+
{ ok: true },
|
|
198
|
+
{ headers: { ...noStore, "set-cookie": clearCookie(secure) } },
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (path === MASTER_LOCK_PATHS.settings) {
|
|
202
|
+
const body = await readJson(req);
|
|
203
|
+
const idleMs = lock.setIdleMs(Number(body?.idleMs));
|
|
204
|
+
return Response.json({ ok: true, idleMs }, { headers: noStore });
|
|
205
|
+
}
|
|
206
|
+
if (path === MASTER_LOCK_PATHS.change) {
|
|
207
|
+
const body = await readJson(req);
|
|
208
|
+
const result = await lock.change({
|
|
209
|
+
currentVerifier: typeof body?.currentVerifier === "string" ? body.currentVerifier : "",
|
|
210
|
+
kdf: body?.kdf as never,
|
|
211
|
+
verifier: typeof body?.verifier === "string" ? body.verifier : "",
|
|
212
|
+
});
|
|
213
|
+
if (!result.ok) {
|
|
214
|
+
return Response.json({ error: result.reason }, { status: 400, headers: noStore });
|
|
215
|
+
}
|
|
216
|
+
// The rotation locked the site; drop the cookie with it so the page it came from
|
|
217
|
+
// does not sit there holding a token that stopped meaning anything.
|
|
218
|
+
return Response.json(
|
|
219
|
+
{ ok: true },
|
|
220
|
+
{ headers: { ...noStore, "set-cookie": clearCookie(secure) } },
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return new Response("not found", { status: 404, headers: noStore });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
async handle(req: Request): Promise<Response | null> {
|
|
228
|
+
const url = new URL(req.url);
|
|
229
|
+
const own =
|
|
230
|
+
url.pathname === MASTER_LOCK_PREFIX || url.pathname.startsWith(`${MASTER_LOCK_PREFIX}/`);
|
|
231
|
+
const lock = resolve(req);
|
|
232
|
+
if (!lock) {
|
|
233
|
+
// No principal. On the app's own paths the caller is not signed in and the
|
|
234
|
+
// app's sign-in owns the request; on `/__lock/*` there is nobody to unlock
|
|
235
|
+
// FOR, and answering the ordinary refusal keeps that indistinguishable from
|
|
236
|
+
// being locked.
|
|
237
|
+
return own ? refuse() : null;
|
|
238
|
+
}
|
|
239
|
+
if (own) return handleOwnRoute(req, url, lock);
|
|
240
|
+
if (options.allow?.(url, req)) return null;
|
|
241
|
+
if (opens(lock, req)) return null;
|
|
242
|
+
|
|
243
|
+
// A document request is answered IN PLACE with the lock page โ same URL, status
|
|
244
|
+
// 200 โ so unlocking reloads to where they were going instead of dumping them at
|
|
245
|
+
// the root. Everything else gets the uniform refusal.
|
|
246
|
+
if (wantsDocument(req)) return lockPage(options, lock);
|
|
247
|
+
return refuse();
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function lockPage(options: LockPageOptions, lock: MasterLock): Response {
|
|
253
|
+
return new Response(lockPageHtml({ ...options, enroll: lock.awaitingEnrollment }), {
|
|
254
|
+
status: 200,
|
|
255
|
+
headers: {
|
|
256
|
+
"content-type": "text/html; charset=utf-8",
|
|
257
|
+
"content-security-policy": LOCK_CSP,
|
|
258
|
+
"x-content-type-options": "nosniff",
|
|
259
|
+
[MASTER_LOCK_STATE_HEADER]: "locked",
|
|
260
|
+
...noStore,
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Is this the browser asking for a PAGE, as opposed to the app asking for data? */
|
|
266
|
+
function wantsDocument(req: Request): boolean {
|
|
267
|
+
if (req.method !== "GET") return false;
|
|
268
|
+
// `Sec-Fetch-Dest` is the browser's own answer and beats guessing from `Accept`; every
|
|
269
|
+
// engine this fleet runs on sends it. `Accept` is the fallback for curl and for a
|
|
270
|
+
// request that arrived through something that stripped it.
|
|
271
|
+
const dest = req.headers.get("sec-fetch-dest");
|
|
272
|
+
if (dest) return dest === "document" || dest === "iframe";
|
|
273
|
+
return (req.headers.get("accept") ?? "").includes("text/html");
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function readJson(req: Request): Promise<Record<string, unknown> | null> {
|
|
277
|
+
try {
|
|
278
|
+
const body = await req.json();
|
|
279
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) return null;
|
|
280
|
+
return body as Record<string, unknown>;
|
|
281
|
+
} catch {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function defaultSecure(url: URL, req: Request): boolean {
|
|
287
|
+
const forwarded = req.headers.get("x-forwarded-proto");
|
|
288
|
+
if (forwarded) return forwarded.split(",")[0]?.trim() === "https";
|
|
289
|
+
return url.protocol === "https:";
|
|
290
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cursedbelt-server/master-lock` โ the server half of the sitewide master-password lock.
|
|
3
|
+
*
|
|
4
|
+
* What the feature is, what it deliberately is NOT, and the four decisions behind it are in
|
|
5
|
+
* `masterLock.ts`'s module note. Read that first.
|
|
6
|
+
*
|
|
7
|
+
* Adopting it in an app is three things:
|
|
8
|
+
* 1. a {@link MasterLock} built over whatever key/value the app already has,
|
|
9
|
+
* 2. `createMasterLockGuard(...).handle(req)` in front of everything the app serves,
|
|
10
|
+
* 3. `cursedbelt/react/master-lock` in the header, for the manual lock and the rotation.
|
|
11
|
+
*
|
|
12
|
+
* Removing it is deleting those three. That is the shape the owner asked for: *"the reusable
|
|
13
|
+
* functionality for this should be in a place that makes it easy to share to add or remove
|
|
14
|
+
* from apps in the code, such as cb."*
|
|
15
|
+
*/
|
|
16
|
+
export { type MasterLockGuard, type MasterLockGuardOptions, createMasterLockGuard } from "./guard";
|
|
17
|
+
export { LOCK_SCRIPT, LOCK_STYLE, type LockPageOptions, lockPageHtml } from "./lockPage";
|
|
18
|
+
export {
|
|
19
|
+
MASTER_LOCK_PRINCIPAL_KEY_PREFIX,
|
|
20
|
+
MasterLockDirectory,
|
|
21
|
+
type MasterLockDirectoryOptions,
|
|
22
|
+
masterLockPrincipalKey,
|
|
23
|
+
} from "./principals";
|
|
24
|
+
export {
|
|
25
|
+
MasterLock,
|
|
26
|
+
type MasterLockEnrollResult,
|
|
27
|
+
type MasterLockOptions,
|
|
28
|
+
type MasterLockRecord,
|
|
29
|
+
type MasterLockStore,
|
|
30
|
+
type MasterLockUnlockResult,
|
|
31
|
+
readRecord,
|
|
32
|
+
} from "./masterLock";
|
|
33
|
+
export {
|
|
34
|
+
DEFAULT_SEED_IDLE_MS,
|
|
35
|
+
generateStagePassword,
|
|
36
|
+
type MintMasterLockSeedOptions,
|
|
37
|
+
mintMasterLockSeed,
|
|
38
|
+
serializeMasterLockSeed,
|
|
39
|
+
} from "./seed";
|
|
40
|
+
export {
|
|
41
|
+
MASTER_LOCK_SETTING_KEY,
|
|
42
|
+
createKvMasterLockStore,
|
|
43
|
+
createMemoryMasterLockStore,
|
|
44
|
+
} from "./store";
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ๐ด The one test that stops the whole feature from failing silently.
|
|
3
|
+
*
|
|
4
|
+
* `LOCK_SCRIPT` carries a HAND COPY of `deriveMasterLockVerifier`, because the lock page is
|
|
5
|
+
* served to a browser as plain text and cannot import anything. If the two drift, the page
|
|
6
|
+
* derives a verifier that can never match, and the symptom is the owner's correct master
|
|
7
|
+
* password being refused forever โ with every health check green, which is precisely how
|
|
8
|
+
* `apps/collections` lost a day in 2026-08-12.
|
|
9
|
+
*
|
|
10
|
+
* So this EVALUATES the function out of the shipped script source and compares it with the
|
|
11
|
+
* TypeScript one on the vault's pinned vector. Change either, and this fails until you
|
|
12
|
+
* change the other.
|
|
13
|
+
*/
|
|
14
|
+
import { describe, expect, test } from "bun:test";
|
|
15
|
+
import { MASTER_LOCK_PATHS, type MasterLockKdfParams, deriveMasterLockVerifier } from "cursedbelt-core/master-lock";
|
|
16
|
+
import { LOCK_SCRIPT, LOCK_STYLE, lockPageHtml } from "./lockPage";
|
|
17
|
+
|
|
18
|
+
const KDF: MasterLockKdfParams = {
|
|
19
|
+
v: 1,
|
|
20
|
+
alg: "PBKDF2-SHA256",
|
|
21
|
+
iter: 600_000,
|
|
22
|
+
salt: "AAECAwQFBgcICQoLDA0ODw",
|
|
23
|
+
};
|
|
24
|
+
const PASSWORD = "correct horse battery staple";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pull `deriveVerifier` out of the served script.
|
|
28
|
+
*
|
|
29
|
+
* The script assigns it to `globalThis.__masterLockDerive` for exactly this โ a seam that
|
|
30
|
+
* costs one line in the browser and is the difference between a pinned copy and a hopeful
|
|
31
|
+
* one. Everything after that assignment touches `document`, so the source is cut there.
|
|
32
|
+
*/
|
|
33
|
+
function extractDerive(): (password: string, kdf: MasterLockKdfParams) => Promise<string> {
|
|
34
|
+
const marker = "globalThis.__masterLockDerive = deriveVerifier;";
|
|
35
|
+
const cut = LOCK_SCRIPT.indexOf(marker);
|
|
36
|
+
expect(cut).toBeGreaterThan(0);
|
|
37
|
+
const source = LOCK_SCRIPT.slice(0, cut + marker.length);
|
|
38
|
+
// The prelude reads `document`, which does not exist under `bun test`; the slice above
|
|
39
|
+
// stops before it, but the leading `const` declarations still run โ so evaluate in a
|
|
40
|
+
// function scope with those globals stubbed.
|
|
41
|
+
const factory = new Function(
|
|
42
|
+
"document",
|
|
43
|
+
`${source}\nreturn globalThis.__masterLockDerive;`,
|
|
44
|
+
) as (doc: unknown) => (password: string, kdf: MasterLockKdfParams) => Promise<string>;
|
|
45
|
+
return factory({ getElementById: () => ({ addEventListener() {}, setAttribute() {}, removeAttribute() {} }) });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("the lock page's hand-copied derivation", () => {
|
|
49
|
+
test("๐ด derives EXACTLY what the TypeScript one does", async () => {
|
|
50
|
+
const pageDerive = extractDerive();
|
|
51
|
+
const fromPage = await pageDerive(PASSWORD, KDF);
|
|
52
|
+
const fromModule = await deriveMasterLockVerifier(PASSWORD, KDF);
|
|
53
|
+
expect(fromPage).toBe(fromModule);
|
|
54
|
+
// And both are still the vault's pinned vector, so a seeded app opens.
|
|
55
|
+
expect(fromPage).toBe("xnUVRdpRFD1tjzd7Il-Jd-YpvfVTGOs-6gAdsf2VfXc");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("agrees on a second, unrelated input too", async () => {
|
|
59
|
+
const pageDerive = extractDerive();
|
|
60
|
+
const kdf = { ...KDF, iter: 1, salt: "DwoNDAsKCQgHBgUEAwIB" };
|
|
61
|
+
expect(await pageDerive("another password", kdf)).toBe(
|
|
62
|
+
await deriveMasterLockVerifier("another password", kdf),
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe("the page itself", () => {
|
|
68
|
+
test("names the app, links only same-origin assets, and carries no inline script", () => {
|
|
69
|
+
const html = lockPageHtml({ appLabel: "Collections" });
|
|
70
|
+
expect(html).toContain("<title>Locked ยท Collections</title>");
|
|
71
|
+
expect(html).toContain(`href="${MASTER_LOCK_PATHS.style}"`);
|
|
72
|
+
expect(html).toContain(`src="${MASTER_LOCK_PATHS.script}"`);
|
|
73
|
+
// An inline <script> would need a CSP nonce; the guard's wall forbids it outright.
|
|
74
|
+
expect(html).not.toMatch(/<script(?![^>]*\bsrc=)/);
|
|
75
|
+
expect(html).toContain('name="robots" content="noindex, nofollow"');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("escapes the app label โ it is passed in by the consumer", () => {
|
|
79
|
+
const html = lockPageHtml({ appLabel: '<img src=x onerror="alert(1)">' });
|
|
80
|
+
expect(html).not.toContain("<img");
|
|
81
|
+
expect(html).toContain("<img");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("the stylesheet needs no font, image or origin from anywhere", () => {
|
|
85
|
+
expect(LOCK_STYLE).not.toContain("url(");
|
|
86
|
+
expect(LOCK_STYLE).not.toContain("@import");
|
|
87
|
+
});
|
|
88
|
+
});
|