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