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,135 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import {
|
|
3
|
+
createAsyncBackedRevocationStore,
|
|
4
|
+
createMemoryAsyncRevocationStore,
|
|
5
|
+
createMemoryRevocationStore,
|
|
6
|
+
createSqliteRevocationStore,
|
|
7
|
+
} from './revocationStore';
|
|
8
|
+
|
|
9
|
+
describe('sync memory revocation store', () => {
|
|
10
|
+
it('records, answers, and sweeps by ms expiry', () => {
|
|
11
|
+
const store = createMemoryRevocationStore();
|
|
12
|
+
expect(store.isRevoked('a')).toBe(false);
|
|
13
|
+
store.revoke('a', 10_000);
|
|
14
|
+
expect(store.isRevoked('a')).toBe(true);
|
|
15
|
+
expect(store.size()).toBe(1);
|
|
16
|
+
// sweep drops entries whose expiry has passed, keeps the rest
|
|
17
|
+
store.revoke('b', 30_000);
|
|
18
|
+
store.sweep(20_000);
|
|
19
|
+
expect(store.isRevoked('a')).toBe(false);
|
|
20
|
+
expect(store.isRevoked('b')).toBe(true);
|
|
21
|
+
expect(store.size()).toBe(1);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('sqlite revocation store', () => {
|
|
26
|
+
it('records, answers, sweeps, and closes', () => {
|
|
27
|
+
const store = createSqliteRevocationStore(':memory:');
|
|
28
|
+
try {
|
|
29
|
+
store.revoke('sess-1', 50_000);
|
|
30
|
+
expect(store.isRevoked('sess-1')).toBe(true);
|
|
31
|
+
expect(store.isRevoked('sess-2')).toBe(false);
|
|
32
|
+
store.sweep(60_000); // past the expiry → gone
|
|
33
|
+
expect(store.isRevoked('sess-1')).toBe(false);
|
|
34
|
+
} finally {
|
|
35
|
+
store.close();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('re-revoking an id is idempotent (INSERT OR REPLACE)', () => {
|
|
40
|
+
const store = createSqliteRevocationStore(':memory:');
|
|
41
|
+
try {
|
|
42
|
+
store.revoke('id', 10_000);
|
|
43
|
+
store.revoke('id', 20_000);
|
|
44
|
+
expect(store.isRevoked('id')).toBe(true);
|
|
45
|
+
store.sweep(15_000); // the second, later expiry wins → still revoked
|
|
46
|
+
expect(store.isRevoked('id')).toBe(true);
|
|
47
|
+
} finally {
|
|
48
|
+
store.close();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('async-backed revocation store (the cross-node bridge)', () => {
|
|
54
|
+
it('refuses an id synchronously the instant revoke() returns, before the write settles', () => {
|
|
55
|
+
const backing = createMemoryAsyncRevocationStore();
|
|
56
|
+
let clock = 1_000_000;
|
|
57
|
+
const store = createAsyncBackedRevocationStore(backing, { now: () => clock });
|
|
58
|
+
store.revoke('jti', clock + 60_000);
|
|
59
|
+
// No await: the local cache must already refuse it.
|
|
60
|
+
expect(store.isRevoked('jti')).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('a revoke on node A is seen on node B through the shared backing (V7.3-style)', async () => {
|
|
64
|
+
const shared = createMemoryAsyncRevocationStore();
|
|
65
|
+
let clock = 2_000_000;
|
|
66
|
+
const now = () => clock;
|
|
67
|
+
const nodeA = createAsyncBackedRevocationStore(shared, { now });
|
|
68
|
+
const nodeB = createAsyncBackedRevocationStore(shared, { now });
|
|
69
|
+
|
|
70
|
+
nodeA.revoke('jti', clock + 60_000);
|
|
71
|
+
await nodeA.flush(); // the write reaches the shared backing
|
|
72
|
+
|
|
73
|
+
// B has not read it yet → still allowed. The read is scheduled, not awaited.
|
|
74
|
+
expect(nodeB.isRevoked('jti')).toBe(false);
|
|
75
|
+
await nodeB.flush(); // the scheduled read settles → B has learned it
|
|
76
|
+
expect(nodeB.isRevoked('jti')).toBe(true);
|
|
77
|
+
|
|
78
|
+
// ms→s conversion round-trips: the backing holds epoch SECONDS.
|
|
79
|
+
expect(await shared.get('jti')).toBe(Math.ceil((clock + 60_000) / 1000));
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('a stale cache entry triggers a background refresh past cacheTtlMs', async () => {
|
|
83
|
+
const shared = createMemoryAsyncRevocationStore();
|
|
84
|
+
let clock = 3_000_000;
|
|
85
|
+
const reader = createAsyncBackedRevocationStore(shared, { now: () => clock, cacheTtlMs: 5_000 });
|
|
86
|
+
|
|
87
|
+
expect(reader.isRevoked('jti')).toBe(false); // schedules first read (miss)
|
|
88
|
+
await reader.flush();
|
|
89
|
+
|
|
90
|
+
// A peer revokes directly in the shared backing.
|
|
91
|
+
await shared.set('jti', Math.ceil((clock + 60_000) / 1000));
|
|
92
|
+
|
|
93
|
+
// Within the cache window the reader still trusts its cached miss.
|
|
94
|
+
clock += 4_000;
|
|
95
|
+
expect(reader.isRevoked('jti')).toBe(false);
|
|
96
|
+
|
|
97
|
+
// Past the window it re-reads.
|
|
98
|
+
clock += 2_000;
|
|
99
|
+
expect(reader.isRevoked('jti')).toBe(false); // returns cached, schedules refresh
|
|
100
|
+
await reader.flush();
|
|
101
|
+
expect(reader.isRevoked('jti')).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('sweep prunes the shared backing and drops expired local cache entries', async () => {
|
|
105
|
+
const shared = createMemoryAsyncRevocationStore();
|
|
106
|
+
let clock = 4_000_000;
|
|
107
|
+
const store = createAsyncBackedRevocationStore(shared, { now: () => clock });
|
|
108
|
+
store.revoke('old', clock + 1_000);
|
|
109
|
+
store.revoke('new', clock + 60_000);
|
|
110
|
+
await store.flush();
|
|
111
|
+
expect(shared.size()).toBe(2);
|
|
112
|
+
|
|
113
|
+
clock += 30_000;
|
|
114
|
+
store.sweep(clock);
|
|
115
|
+
await store.flush();
|
|
116
|
+
expect(shared.size()).toBe(1); // 'old' pruned from the shared backing
|
|
117
|
+
expect(store.isRevoked('new')).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('a backing failure is reported, never thrown out of the request path', async () => {
|
|
121
|
+
const ops: string[] = [];
|
|
122
|
+
const boom: import('./revocationStore').AsyncRevocationStore = {
|
|
123
|
+
set: () => Promise.reject(new Error('set failed')),
|
|
124
|
+
get: () => Promise.reject(new Error('get failed')),
|
|
125
|
+
prune: () => Promise.reject(new Error('prune failed')),
|
|
126
|
+
};
|
|
127
|
+
const store = createAsyncBackedRevocationStore(boom, { onError: (op) => ops.push(op) });
|
|
128
|
+
expect(() => store.revoke('x', 10_000)).not.toThrow();
|
|
129
|
+
// A different, uncached id so `isRevoked` actually schedules a backing read.
|
|
130
|
+
expect(() => store.isRevoked('y')).not.toThrow();
|
|
131
|
+
expect(() => store.sweep(10_000)).not.toThrow();
|
|
132
|
+
await store.flush();
|
|
133
|
+
expect(ops.sort()).toEqual(['get', 'prune', 'set']);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one revocation store, in two shapes for two hot paths (DEDUP-7).
|
|
3
|
+
*
|
|
4
|
+
* "Was this session/token withdrawn early?" is a single concern, but it is
|
|
5
|
+
* consulted from two verify paths whose constraints do not meet in the middle,
|
|
6
|
+
* so this file settles ONE contract per constraint and gives the async one a
|
|
7
|
+
* bridge back to the sync one:
|
|
8
|
+
*
|
|
9
|
+
* • {@link RevocationStore} — SYNCHRONOUS (`isRevoked`/`revoke`/`sweep`, epoch
|
|
10
|
+
* **milliseconds**). The single-owner satellites' `createOwnerAuth` consults
|
|
11
|
+
* it inside `isAuthenticated`, which is called on every request and cannot
|
|
12
|
+
* become a promise without turning every route async. Its durable
|
|
13
|
+
* implementation is a local sqlite file — the denylist a redeploy must not
|
|
14
|
+
* forget (`apps/roms`'s deploy smoke caught a logout that did not log out).
|
|
15
|
+
*
|
|
16
|
+
* • {@link AsyncRevocationStore} — ASYNCHRONOUS (`set`/`get`/`prune`, epoch
|
|
17
|
+
* **seconds**). The cross-node seam (03 §4a, audit G6): a Redis hash or a
|
|
18
|
+
* Postgres row that every node in a saas deployment reads, so a `lock()` on
|
|
19
|
+
* node A closes the token on node B too. `createSessionSigner` consumes THIS
|
|
20
|
+
* one and keeps its own read-through cache so its `verify` stays sync.
|
|
21
|
+
*
|
|
22
|
+
* Before DEDUP-7 these lived in two packages, BOTH exporting a
|
|
23
|
+
* `createMemoryRevocationStore` of a different shape — a live footgun the moment
|
|
24
|
+
* a file imported from the wrong one. They are one file now; `satellite-kit`
|
|
25
|
+
* re-exports the sync half so its callers are unchanged.
|
|
26
|
+
*
|
|
27
|
+
* The bridge between them is {@link createAsyncBackedRevocationStore}: a SYNC
|
|
28
|
+
* store whose durable backing is an async cross-node store, using the same
|
|
29
|
+
* read-through-cache trick the signer does. It is what lets a satellite opt into
|
|
30
|
+
* the cross-node seam without its `isAuthenticated` becoming a promise.
|
|
31
|
+
*/
|
|
32
|
+
import { mkdirSync } from 'node:fs';
|
|
33
|
+
import { dirname } from 'node:path';
|
|
34
|
+
import { Database } from 'bun:sqlite';
|
|
35
|
+
import { assertCcPragmas } from 'cwip/sqlite';
|
|
36
|
+
|
|
37
|
+
/* ============================================================ sync contract */
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Somewhere to record the session/token ids that must no longer be accepted,
|
|
41
|
+
* answered synchronously.
|
|
42
|
+
*
|
|
43
|
+
* Deliberately tiny, and deliberately not a session store: the token still
|
|
44
|
+
* carries its own expiry and signature, so this only ever answers "was this one
|
|
45
|
+
* withdrawn early". `sweep` exists so the table cannot grow forever — every
|
|
46
|
+
* entry becomes meaningless the moment its session would have expired anyway.
|
|
47
|
+
*
|
|
48
|
+
* Epoch **milliseconds** throughout, matching `Date.now()` and the satellites'
|
|
49
|
+
* cookie expiry. (The cross-node {@link AsyncRevocationStore} works in seconds,
|
|
50
|
+
* matching the signer's `exp`; {@link createAsyncBackedRevocationStore} bridges
|
|
51
|
+
* the two.)
|
|
52
|
+
*/
|
|
53
|
+
export interface RevocationStore {
|
|
54
|
+
/** Has this id been revoked? Called on every authenticated request. */
|
|
55
|
+
isRevoked(id: string): boolean;
|
|
56
|
+
/** Record a revocation until `expiresAtMs`, after which it is moot. */
|
|
57
|
+
revoke(id: string, expiresAtMs: number): void;
|
|
58
|
+
/** Drop entries whose sessions have expired. Called opportunistically. */
|
|
59
|
+
sweep(nowMs: number): void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* An in-memory sync {@link RevocationStore}.
|
|
64
|
+
*
|
|
65
|
+
* For tests, and for a single-process caller that does not need durability.
|
|
66
|
+
* NOT for a deployed satellite: an in-memory table would be worse than none —
|
|
67
|
+
* sign out, redeploy, and the session works again, which reads as the feature
|
|
68
|
+
* being broken rather than absent. Give a deployed door
|
|
69
|
+
* {@link createSqliteRevocationStore} (durable) or nothing.
|
|
70
|
+
*/
|
|
71
|
+
export function createMemoryRevocationStore(): RevocationStore & { size(): number } {
|
|
72
|
+
const entries = new Map<string, number>();
|
|
73
|
+
return {
|
|
74
|
+
isRevoked: (id) => entries.has(id),
|
|
75
|
+
revoke: (id, expiresAtMs) => void entries.set(id, expiresAtMs),
|
|
76
|
+
sweep: (nowMs) => {
|
|
77
|
+
for (const [id, expiresAt] of entries) if (expiresAt <= nowMs) entries.delete(id);
|
|
78
|
+
},
|
|
79
|
+
size: () => entries.size,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* A durable sync {@link RevocationStore} backed by a sqlite file.
|
|
85
|
+
*
|
|
86
|
+
* Because an in-memory table would be worse than none (see above), and the
|
|
87
|
+
* deploy engine persists each release's data directory, so durability costs one
|
|
88
|
+
* file. The table is self-limiting: an entry is meaningless once the session it
|
|
89
|
+
* names would have expired anyway, and `sweep` drops those on logout — rare,
|
|
90
|
+
* cheap, and exactly when the table has just grown, so there is no background
|
|
91
|
+
* timer to own.
|
|
92
|
+
*
|
|
93
|
+
* Its OWN file, not a table in an app's database: keeping a credential surface
|
|
94
|
+
* out of an app's data file bounds the blast radius of a corrupt or deleted
|
|
95
|
+
* file to "everyone signs in again".
|
|
96
|
+
*/
|
|
97
|
+
export function createSqliteRevocationStore(dbPath: string): RevocationStore & { close(): void } {
|
|
98
|
+
if (dbPath !== ':memory:' && !dbPath.startsWith('file:')) {
|
|
99
|
+
mkdirSync(dirname(dbPath), { recursive: true });
|
|
100
|
+
}
|
|
101
|
+
const db = new Database(dbPath, { create: true });
|
|
102
|
+
// Route pragmas through cwip's one controller (applies WAL + busy_timeout +
|
|
103
|
+
// foreign_keys + wal_autocheckpoint and re-reads to prove each took), rather
|
|
104
|
+
// than hand-rolling a lone `journal_mode = WAL` the pragma sweep rejects.
|
|
105
|
+
assertCcPragmas(db);
|
|
106
|
+
db.exec(`
|
|
107
|
+
CREATE TABLE IF NOT EXISTS revoked_sessions (
|
|
108
|
+
id TEXT PRIMARY KEY,
|
|
109
|
+
expires_at INTEGER NOT NULL
|
|
110
|
+
)
|
|
111
|
+
`);
|
|
112
|
+
const has = db.query<{ id: string }, [string]>('SELECT id FROM revoked_sessions WHERE id = ?');
|
|
113
|
+
const insert = db.query<unknown, [string, number]>(
|
|
114
|
+
'INSERT OR REPLACE INTO revoked_sessions (id, expires_at) VALUES (?, ?)',
|
|
115
|
+
);
|
|
116
|
+
const prune = db.query<unknown, [number]>('DELETE FROM revoked_sessions WHERE expires_at <= ?');
|
|
117
|
+
return {
|
|
118
|
+
isRevoked: (id) => has.get(id) !== null,
|
|
119
|
+
revoke: (id, expiresAtMs) => void insert.run(id, expiresAtMs),
|
|
120
|
+
sweep: (nowMs) => void prune.run(nowMs),
|
|
121
|
+
close: () => db.close(),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* ================================================ async cross-node contract */
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Where revoked jtis live so every node sees them (03 §4a). Local profile:
|
|
129
|
+
* unused (a per-process `Map` is enough for one process). SaaS: a Redis hash
|
|
130
|
+
* with per-entry TTL, or a row in the shared Postgres — the contract is
|
|
131
|
+
* identical, which is the point.
|
|
132
|
+
*
|
|
133
|
+
* Only `set`/`get`/`prune` and no bulk `list`, because the consumer's hot path
|
|
134
|
+
* is a SYNCHRONOUS `verify` (see {@link createSessionSigner}) that consults the
|
|
135
|
+
* store read-through by id off the hot path — never a full scan on every verify.
|
|
136
|
+
*
|
|
137
|
+
* Epoch **seconds** throughout, matching a token's own `exp`.
|
|
138
|
+
*/
|
|
139
|
+
export interface AsyncRevocationStore {
|
|
140
|
+
/**
|
|
141
|
+
* Record `jti` as revoked until `expEpochSeconds`. Idempotent; a re-revoke of
|
|
142
|
+
* the same jti is a no-op (or extends the exp, which is harmless — a denylist
|
|
143
|
+
* entry that outlives its token only wastes a little space until `prune`).
|
|
144
|
+
*/
|
|
145
|
+
set(jti: string, expEpochSeconds: number): Promise<void>;
|
|
146
|
+
/** The `exp` of a revoked jti (epoch seconds), or `null` when it is not revoked. */
|
|
147
|
+
get(jti: string): Promise<number | null>;
|
|
148
|
+
/** Drop every entry whose `exp <= nowEpochSeconds`. Returns the count removed. */
|
|
149
|
+
prune(nowEpochSeconds: number): Promise<number>;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* An in-memory {@link AsyncRevocationStore}.
|
|
154
|
+
*
|
|
155
|
+
* Two uses: a single-process saas test that wants the seam exercised without a
|
|
156
|
+
* real Redis, and — crucially — the **two-signer replay probe**, where one
|
|
157
|
+
* shared instance is handed to signer A and signer B so "revoke on A, verify on
|
|
158
|
+
* B" becomes provable in one process (V7.3). It is NOT a cross-process store:
|
|
159
|
+
* two OS processes do not share this Map, which is the whole reason a real
|
|
160
|
+
* deployment injects Redis/Postgres instead.
|
|
161
|
+
*
|
|
162
|
+
* (Named `…Async…` to stand apart from the sync {@link createMemoryRevocationStore};
|
|
163
|
+
* before DEDUP-7 both shapes shared the bare name across two packages and that
|
|
164
|
+
* collision was the footgun this file exists to remove.)
|
|
165
|
+
*/
|
|
166
|
+
export function createMemoryAsyncRevocationStore(): AsyncRevocationStore & { size(): number } {
|
|
167
|
+
const revoked = new Map<string, number>();
|
|
168
|
+
return {
|
|
169
|
+
async set(jti, expEpochSeconds) {
|
|
170
|
+
const existing = revoked.get(jti);
|
|
171
|
+
// Keep the later exp: two nodes revoking the same jti with slightly
|
|
172
|
+
// different clocks must not shorten the window.
|
|
173
|
+
if (existing === undefined || expEpochSeconds > existing) revoked.set(jti, expEpochSeconds);
|
|
174
|
+
},
|
|
175
|
+
async get(jti) {
|
|
176
|
+
return revoked.get(jti) ?? null;
|
|
177
|
+
},
|
|
178
|
+
async prune(nowEpochSeconds) {
|
|
179
|
+
let removed = 0;
|
|
180
|
+
for (const [jti, exp] of revoked) {
|
|
181
|
+
if (exp <= nowEpochSeconds) {
|
|
182
|
+
revoked.delete(jti);
|
|
183
|
+
removed += 1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return removed;
|
|
187
|
+
},
|
|
188
|
+
size() {
|
|
189
|
+
return revoked.size;
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* ==================================================== the bridge: async → sync */
|
|
195
|
+
|
|
196
|
+
export interface AsyncBackedRevocationStoreOptions {
|
|
197
|
+
/** Injected clock in **milliseconds** (default `Date.now`), for tests. */
|
|
198
|
+
now?: () => number;
|
|
199
|
+
/**
|
|
200
|
+
* How long a store verdict is trusted before `isRevoked` re-reads it (ms).
|
|
201
|
+
* This is the cross-node revocation LATENCY BOUND: a revocation written by
|
|
202
|
+
* another node is seen here within this window (plus the store's own read
|
|
203
|
+
* latency), because `isRevoked` stays synchronous — it answers from the cache
|
|
204
|
+
* and refreshes it off the hot path rather than awaiting a round trip. Default
|
|
205
|
+
* 5000 (03 §4a's ≤5s).
|
|
206
|
+
*/
|
|
207
|
+
cacheTtlMs?: number;
|
|
208
|
+
/**
|
|
209
|
+
* Reports a backing read/write that failed. A failed write-through means a
|
|
210
|
+
* revocation did not reach other nodes — the local cache still refuses the id
|
|
211
|
+
* HERE, but the host may want to retry or alarm. Default: swallow, because a
|
|
212
|
+
* sync store must never throw out of `isRevoked`/`revoke` on the request path.
|
|
213
|
+
*/
|
|
214
|
+
onError?: (op: 'get' | 'set' | 'prune', error: unknown) => void;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* A SYNC {@link RevocationStore} whose durable backing is an async cross-node
|
|
219
|
+
* {@link AsyncRevocationStore} — the bridge that gives a satellite the cross-node
|
|
220
|
+
* seam without its `isAuthenticated` becoming a promise.
|
|
221
|
+
*
|
|
222
|
+
* It is the same read-through-cache trick {@link createSessionSigner} keeps
|
|
223
|
+
* internally, packaged so a plain sync consumer can use it: `revoke` writes
|
|
224
|
+
* through fire-and-forget and caches locally so the id is refused HERE the
|
|
225
|
+
* instant it returns; `isRevoked` answers from the cache and schedules a refresh
|
|
226
|
+
* when the entry is missing or older than `cacheTtlMs`, never blocking. Ids and
|
|
227
|
+
* expiries cross the ms↔seconds boundary here (the sync contract is ms, the
|
|
228
|
+
* async one seconds).
|
|
229
|
+
*
|
|
230
|
+
* {@link flush} settles the fire-and-forget work — for shutdown/handover (a node
|
|
231
|
+
* draining must not lose an unwritten revocation) and for tests that need the
|
|
232
|
+
* cross-node state to have settled.
|
|
233
|
+
*/
|
|
234
|
+
export function createAsyncBackedRevocationStore(
|
|
235
|
+
backing: AsyncRevocationStore,
|
|
236
|
+
options: AsyncBackedRevocationStoreOptions = {},
|
|
237
|
+
): RevocationStore & { flush(): Promise<void> } {
|
|
238
|
+
const now = options.now ?? (() => Date.now());
|
|
239
|
+
const cacheTtlMs = options.cacheTtlMs ?? 5000;
|
|
240
|
+
const onError = options.onError ?? (() => undefined);
|
|
241
|
+
|
|
242
|
+
/** id → the last backing verdict (exp in ms, or null) and when it was read. */
|
|
243
|
+
const cache = new Map<string, { expMs: number | null; readAtMs: number }>();
|
|
244
|
+
const pending = new Set<Promise<unknown>>();
|
|
245
|
+
/** Reads in flight per id, so a burst of `isRevoked` coalesces into one read. */
|
|
246
|
+
const inflightReads = new Map<string, Promise<void>>();
|
|
247
|
+
|
|
248
|
+
const track = (work: Promise<unknown>): void => {
|
|
249
|
+
pending.add(work);
|
|
250
|
+
void work.finally(() => pending.delete(work));
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const refresh = (id: string): void => {
|
|
254
|
+
if (inflightReads.has(id)) return;
|
|
255
|
+
const read = backing
|
|
256
|
+
.get(id)
|
|
257
|
+
.then((expSec) => {
|
|
258
|
+
cache.set(id, { expMs: expSec === null ? null : expSec * 1000, readAtMs: now() });
|
|
259
|
+
})
|
|
260
|
+
.catch((error) => onError('get', error))
|
|
261
|
+
.finally(() => inflightReads.delete(id));
|
|
262
|
+
inflightReads.set(id, read);
|
|
263
|
+
track(read);
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
isRevoked(id) {
|
|
268
|
+
const cached = cache.get(id);
|
|
269
|
+
if (cached === undefined || now() - cached.readAtMs >= cacheTtlMs) refresh(id);
|
|
270
|
+
// A fresh cached exp still in the future ⇒ revoked. An expired cached exp
|
|
271
|
+
// is treated as not-revoked: the token's own expiry already refuses it, so
|
|
272
|
+
// the denylist has nothing left to add.
|
|
273
|
+
return cached?.expMs != null && cached.expMs > now();
|
|
274
|
+
},
|
|
275
|
+
revoke(id, expiresAtMs) {
|
|
276
|
+
// Cache locally FIRST so this node refuses the id the instant this returns,
|
|
277
|
+
// regardless of whether the write-through has propagated yet.
|
|
278
|
+
cache.set(id, { expMs: expiresAtMs, readAtMs: now() });
|
|
279
|
+
track(backing.set(id, Math.ceil(expiresAtMs / 1000)).catch((error) => onError('set', error)));
|
|
280
|
+
},
|
|
281
|
+
sweep(nowMs) {
|
|
282
|
+
for (const [id, cached] of cache) if (cached.expMs !== null && cached.expMs <= nowMs) cache.delete(id);
|
|
283
|
+
track(backing.prune(Math.floor(nowMs / 1000)).catch((error) => onError('prune', error)));
|
|
284
|
+
},
|
|
285
|
+
async flush() {
|
|
286
|
+
// Settle in-flight work, then whatever it scheduled (a read can resolve
|
|
287
|
+
// after we snapshot), so the caller sees a fully-quiesced state.
|
|
288
|
+
while (pending.size > 0) await Promise.allSettled([...pending]);
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|