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,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The off-thread KDF (05 §B6) — a long-lived worker that owns every byte of key
|
|
3
|
+
* material it produces.
|
|
4
|
+
*
|
|
5
|
+
* ## The problem
|
|
6
|
+
*
|
|
7
|
+
* argon2id at the platform parameters is ~150–250 ms of *pure CPU*. The KDF seam
|
|
8
|
+
* is synchronous by design (see `Argon2idDeriver` — the async bindings return
|
|
9
|
+
* un-zeroizable PHC strings), so that time is spent on the event loop with
|
|
10
|
+
* nothing else able to run. There is no race and no correctness bug; there is a
|
|
11
|
+
* hard throughput ceiling. A saas node handling a burst of logins serializes
|
|
12
|
+
* them AND stalls every unrelated request for the duration, so one tenant's
|
|
13
|
+
* login storm is every other tenant's latency spike.
|
|
14
|
+
*
|
|
15
|
+
* ## The design, and the one thing that makes it worth building
|
|
16
|
+
*
|
|
17
|
+
* The naive off-thread KDF derives in a worker and posts the KEY BACK. That
|
|
18
|
+
* moves the CPU but weakens custody: the key crosses a structured-clone boundary
|
|
19
|
+
* (a copy the sender cannot zeroize, because clone semantics leave the original
|
|
20
|
+
* behind and the receiver adopts a second allocation), and it lands in a host
|
|
21
|
+
* that must remember to destroy it.
|
|
22
|
+
*
|
|
23
|
+
* **Nothing here posts a key.** The worker runs the SHIPPED
|
|
24
|
+
* `sealPasswordVerifier` / `verifyPasswordEnvelope` — the same functions, the
|
|
25
|
+
* same envelope format, the same one KDF — so the derived key is born, used and
|
|
26
|
+
* zeroized entirely inside the worker thread. What crosses back is a boolean or
|
|
27
|
+
* the sealed *ciphertext*. The password crosses inward, because it arrives from
|
|
28
|
+
* the request and must; the worker zeroizes its copy on every path, and the host
|
|
29
|
+
* zeroizes its own.
|
|
30
|
+
*
|
|
31
|
+
* That is why this is a worker for the whole VERIFY rather than a worker for the
|
|
32
|
+
* derivation: relocating the derivation alone would have traded a throughput
|
|
33
|
+
* problem for a custody problem.
|
|
34
|
+
*
|
|
35
|
+
* ## Long-lived, never per-call
|
|
36
|
+
*
|
|
37
|
+
* Thread startup plus module load (a native argon2 binding!) is tens of
|
|
38
|
+
* milliseconds, and a spawn-per-login design lets a login storm fork unbounded
|
|
39
|
+
* OS threads — which is the same denial of service the ceiling above, only
|
|
40
|
+
* worse. One thread, multiplexed by request id. This is the mistake
|
|
41
|
+
* `createMarshalWorkerClient` documents in `core/wire/marshalWorker.ts`; it is
|
|
42
|
+
* repeated here deliberately in the same shape so both read alike.
|
|
43
|
+
*/
|
|
44
|
+
import { AppError } from '../errors';
|
|
45
|
+
import { createInProcessPasswordVerifier, sealPasswordVerifier, verifyPasswordEnvelope, } from './passwordVerifier';
|
|
46
|
+
import { registerNodeRsArgon2id } from './argon2Binding';
|
|
47
|
+
/** Raised when the worker thread dies, fails to start, or is disposed mid-flight. */
|
|
48
|
+
export const GUARD_KDF_WORKER_FAILED = 'GUARD_KDF_WORKER_FAILED';
|
|
49
|
+
/** Raised when the worker does not answer within the configured budget. */
|
|
50
|
+
export const GUARD_KDF_WORKER_TIMEOUT = 'GUARD_KDF_WORKER_TIMEOUT';
|
|
51
|
+
/**
|
|
52
|
+
* Default per-request ceiling. Generous relative to a ~250 ms derivation on
|
|
53
|
+
* purpose: this is a liveness backstop for a wedged thread, not a latency SLO,
|
|
54
|
+
* and a tight budget here would turn a loaded machine into a login outage.
|
|
55
|
+
*/
|
|
56
|
+
export const KDF_WORKER_DEFAULT_TIMEOUT_MS = 15_000;
|
|
57
|
+
const nodeWorkerScope = async () => {
|
|
58
|
+
const { parentPort } = await import('node:worker_threads');
|
|
59
|
+
if (parentPort === null)
|
|
60
|
+
throw new Error('serveKdfWorker() was called outside a worker thread.');
|
|
61
|
+
return {
|
|
62
|
+
postMessage: (data) => parentPort.postMessage(data),
|
|
63
|
+
onMessage: (handler) => parentPort.on('message', handler),
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Run the KDF worker's message loop. A worker entry module is this call and
|
|
68
|
+
* nothing else (see `kdfWorkerEntry.ts`).
|
|
69
|
+
*
|
|
70
|
+
* `register` is injectable so the spec can drive the loop with a cheap fake
|
|
71
|
+
* binding — registering the real native argon2 in a test that only checks
|
|
72
|
+
* message plumbing would pay 250 ms per case to prove nothing about the
|
|
73
|
+
* plumbing. The DEFAULT is the real binding, because a worker that silently ran
|
|
74
|
+
* a fake KDF would be a catastrophe rather than a bug.
|
|
75
|
+
*/
|
|
76
|
+
export async function serveKdfWorker(options = {}) {
|
|
77
|
+
const scope = options.scope ?? (await nodeWorkerScope());
|
|
78
|
+
// Registration happens ONCE, at worker start, before any request is served —
|
|
79
|
+
// not lazily per request, or the first login of every thread pays a native
|
|
80
|
+
// module load inside its own timeout budget.
|
|
81
|
+
await (options.register ?? registerNodeRsArgon2id)();
|
|
82
|
+
scope.onMessage((data) => {
|
|
83
|
+
const request = data;
|
|
84
|
+
if (typeof request?.id !== 'number')
|
|
85
|
+
return;
|
|
86
|
+
// The password arrives as a structured-clone copy owned by THIS thread.
|
|
87
|
+
// Re-wrap anything that is not already a Uint8Array rather than trusting the
|
|
88
|
+
// wire type — a plain `{0:..,1:..}` would silently derive from the wrong
|
|
89
|
+
// bytes instead of failing — but keep the inbound view too, because it is a
|
|
90
|
+
// second plaintext copy this thread is responsible for.
|
|
91
|
+
const inbound = request.password;
|
|
92
|
+
const password = inbound instanceof Uint8Array ? inbound : new Uint8Array(inbound);
|
|
93
|
+
try {
|
|
94
|
+
const value = request.kind === 'seal'
|
|
95
|
+
? sealPasswordVerifier(request.userId, password)
|
|
96
|
+
: verifyPasswordEnvelope(request.userId, request.envelopeJson, password);
|
|
97
|
+
scope.postMessage({ id: request.id, ok: true, value });
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
// Only the CODE and message cross back. An error `context` in this module
|
|
101
|
+
// can hold envelope fields, so the envelope itself never leaves.
|
|
102
|
+
scope.postMessage({
|
|
103
|
+
id: request.id,
|
|
104
|
+
ok: false,
|
|
105
|
+
code: (error instanceof AppError ? error.code : undefined) ?? GUARD_KDF_WORKER_FAILED,
|
|
106
|
+
message: error instanceof Error ? error.message : String(error),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
// Both `sealPasswordVerifier` and `verifyPasswordEnvelope` consume (and so
|
|
111
|
+
// zeroize) the buffer they are handed — but they are the far side of a
|
|
112
|
+
// contract, and a throw BEFORE they are reached (a malformed envelope, an
|
|
113
|
+
// unregistered deriver) leaves a plaintext password in worker RAM. Clearing
|
|
114
|
+
// again is idempotent and costs a memset.
|
|
115
|
+
//
|
|
116
|
+
// EVERY view, not just the one that was used: when the wire type is wrong
|
|
117
|
+
// the re-wrap above makes a second allocation, and zeroizing only the copy
|
|
118
|
+
// we derived from would leave the structured clone this thread was handed
|
|
119
|
+
// sitting in memory for the life of the worker.
|
|
120
|
+
password.fill(0);
|
|
121
|
+
if (inbound !== password && inbound instanceof Uint8Array)
|
|
122
|
+
inbound.fill(0);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function spawnHostWorker(entry) {
|
|
127
|
+
// Callers await `settled` rather than seeing the construction race. It is
|
|
128
|
+
// RESOLVED, never rejected, on every exit path including a failed spawn: a
|
|
129
|
+
// rejection here would surface as an unhandled rejection on a request that the
|
|
130
|
+
// error handler below is already failing properly.
|
|
131
|
+
let realPost = null;
|
|
132
|
+
let markSettled = () => undefined;
|
|
133
|
+
const settled = new Promise((resolve) => {
|
|
134
|
+
markSettled = resolve;
|
|
135
|
+
});
|
|
136
|
+
let terminated = false;
|
|
137
|
+
const messageHandlers = [];
|
|
138
|
+
const errorHandlers = [];
|
|
139
|
+
let terminateReal = null;
|
|
140
|
+
void (async () => {
|
|
141
|
+
try {
|
|
142
|
+
const { Worker } = await import('node:worker_threads');
|
|
143
|
+
if (terminated)
|
|
144
|
+
return;
|
|
145
|
+
const worker = new Worker(entry);
|
|
146
|
+
worker.on('message', (data) => {
|
|
147
|
+
for (const handler of messageHandlers)
|
|
148
|
+
handler(data);
|
|
149
|
+
});
|
|
150
|
+
worker.on('error', (error) => {
|
|
151
|
+
for (const handler of errorHandlers)
|
|
152
|
+
handler(error);
|
|
153
|
+
});
|
|
154
|
+
// ANY exit is a failure: this worker is long-lived by design, so it should
|
|
155
|
+
// outlive every request. A cleanly-exited thread that went unnoticed would
|
|
156
|
+
// leave each in-flight login waiting out its full timeout, and every later
|
|
157
|
+
// one posting into a dead thread to do the same.
|
|
158
|
+
worker.on('exit', (code) => {
|
|
159
|
+
const how = code === 0 ? 'exited cleanly while still in use' : `exited with code ${code}`;
|
|
160
|
+
for (const handler of errorHandlers)
|
|
161
|
+
handler(new Error(`KDF worker ${how}`));
|
|
162
|
+
});
|
|
163
|
+
realPost = (data) => worker.postMessage(data);
|
|
164
|
+
terminateReal = () => void worker.terminate();
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
for (const handler of errorHandlers)
|
|
168
|
+
handler(error);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
markSettled();
|
|
172
|
+
}
|
|
173
|
+
})();
|
|
174
|
+
return {
|
|
175
|
+
async post(data) {
|
|
176
|
+
if (realPost === null)
|
|
177
|
+
await settled;
|
|
178
|
+
// Still null ⇒ the spawn failed or we were disposed mid-construction. The
|
|
179
|
+
// error handler has already failed (or will fail) the request; dropping
|
|
180
|
+
// the message is correct, and the caller may now zeroize.
|
|
181
|
+
realPost?.(data);
|
|
182
|
+
},
|
|
183
|
+
onMessage: (handler) => messageHandlers.push(handler),
|
|
184
|
+
onError: (handler) => errorHandlers.push(handler),
|
|
185
|
+
terminate: () => {
|
|
186
|
+
terminated = true;
|
|
187
|
+
markSettled();
|
|
188
|
+
terminateReal?.();
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* A {@link PasswordVerifierPort} backed by one long-lived worker thread.
|
|
194
|
+
*
|
|
195
|
+
* Hand it to `createAccountService({ passwords })` on a host that expects login
|
|
196
|
+
* concurrency. Local single-user hosts should NOT — they would pay a thread and
|
|
197
|
+
* a native module load to serialize a workload that was never contended.
|
|
198
|
+
*/
|
|
199
|
+
export function createKdfWorkerPasswordVerifier(options) {
|
|
200
|
+
const timeoutMs = options.timeoutMs ?? KDF_WORKER_DEFAULT_TIMEOUT_MS;
|
|
201
|
+
const worker = (options.spawn ?? spawnHostWorker)(options.entry);
|
|
202
|
+
const pending = new Map();
|
|
203
|
+
let nextId = 1;
|
|
204
|
+
let disposed = false;
|
|
205
|
+
const settle = (id, apply) => {
|
|
206
|
+
const entry = pending.get(id);
|
|
207
|
+
if (!entry)
|
|
208
|
+
return; // already timed out — a late reply is discarded
|
|
209
|
+
pending.delete(id);
|
|
210
|
+
clearTimeout(entry.timer);
|
|
211
|
+
apply(entry);
|
|
212
|
+
};
|
|
213
|
+
const failure = (message, code, status) => new AppError(message, { code, status, category: 'security', context: { worker: String(options.entry) } });
|
|
214
|
+
worker.onMessage((data) => {
|
|
215
|
+
const response = data;
|
|
216
|
+
if (typeof response?.id !== 'number')
|
|
217
|
+
return;
|
|
218
|
+
settle(response.id, (p) => {
|
|
219
|
+
if (response.ok) {
|
|
220
|
+
p.resolve(response.value);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// Preserve the worker's own code: `SECRET_ENVELOPE_*` failures are a
|
|
224
|
+
// corrupt row, not a dead thread, and a caller distinguishing them must
|
|
225
|
+
// still be able to.
|
|
226
|
+
p.reject(failure(response.message, response.code, 500));
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
// A dead worker takes every request in flight with it — failing them now beats
|
|
230
|
+
// each one waiting out its own timeout.
|
|
231
|
+
worker.onError((error) => {
|
|
232
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
233
|
+
for (const id of [...pending.keys()])
|
|
234
|
+
settle(id, (p) => p.reject(failure(`KDF worker failed: ${message}`, GUARD_KDF_WORKER_FAILED, 500)));
|
|
235
|
+
});
|
|
236
|
+
/**
|
|
237
|
+
* Post the request, zeroize the host's copy of the password as soon as the
|
|
238
|
+
* thread has it, then await the verdict.
|
|
239
|
+
*
|
|
240
|
+
* The zeroize placement is the point. The worker holds a structured-clone
|
|
241
|
+
* copy, so the host's buffer is dead weight the instant the post lands —
|
|
242
|
+
* and the await that follows lasts the whole ~250 ms derivation, which is
|
|
243
|
+
* exactly the window in which a heap snapshot, a core dump or a crash reporter
|
|
244
|
+
* would capture a plaintext password. Clearing it before the await instead of
|
|
245
|
+
* in a `finally` after it removes that window entirely.
|
|
246
|
+
*/
|
|
247
|
+
const send = async (build, password) => {
|
|
248
|
+
if (disposed)
|
|
249
|
+
throw failure('The KDF worker has been disposed.', GUARD_KDF_WORKER_FAILED, 500);
|
|
250
|
+
const id = nextId++;
|
|
251
|
+
const answer = new Promise((resolve, reject) => {
|
|
252
|
+
const timer = setTimeout(() => {
|
|
253
|
+
pending.delete(id);
|
|
254
|
+
reject(failure(`The KDF worker did not answer within ${timeoutMs}ms.`, GUARD_KDF_WORKER_TIMEOUT, 504));
|
|
255
|
+
}, timeoutMs);
|
|
256
|
+
pending.set(id, { resolve, reject, timer });
|
|
257
|
+
});
|
|
258
|
+
try {
|
|
259
|
+
await worker.post(build(id));
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
password.fill(0);
|
|
263
|
+
}
|
|
264
|
+
return await answer;
|
|
265
|
+
};
|
|
266
|
+
return {
|
|
267
|
+
async seal(userId, password) {
|
|
268
|
+
const value = await send((id) => ({ id, kind: 'seal', userId, password }), password);
|
|
269
|
+
if (typeof value !== 'string')
|
|
270
|
+
throw failure('The KDF worker returned a non-string envelope for seal().', GUARD_KDF_WORKER_FAILED, 500);
|
|
271
|
+
return value;
|
|
272
|
+
},
|
|
273
|
+
async verify(userId, envelopeJson, password) {
|
|
274
|
+
const value = await send((id) => ({ id, kind: 'verify', userId, envelopeJson, password }), password);
|
|
275
|
+
if (typeof value !== 'boolean')
|
|
276
|
+
throw failure('The KDF worker returned a non-boolean verdict for verify().', GUARD_KDF_WORKER_FAILED, 500);
|
|
277
|
+
return value;
|
|
278
|
+
},
|
|
279
|
+
async dispose() {
|
|
280
|
+
disposed = true;
|
|
281
|
+
for (const id of [...pending.keys()])
|
|
282
|
+
settle(id, (p) => p.reject(failure('The KDF worker was disposed while a request was in flight.', GUARD_KDF_WORKER_FAILED, 503)));
|
|
283
|
+
worker.terminate();
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* The URL of the entry module {@link createKdfWorkerPasswordVerifier} spawns.
|
|
289
|
+
*
|
|
290
|
+
* Resolved relative to THIS module rather than hard-coded by the host, so an app
|
|
291
|
+
* never has to know where inside the package the entry lives:
|
|
292
|
+
*
|
|
293
|
+
* createKdfWorkerPasswordVerifier({ entry: kdfWorkerEntryUrl() })
|
|
294
|
+
*
|
|
295
|
+
* Under the `bun` export condition this resolves to the TypeScript source, which
|
|
296
|
+
* is what every first-party consumer runs (see the monorepo's zero-build-lag
|
|
297
|
+
* contract). A non-Bun host consuming built `dist/` names its own built entry.
|
|
298
|
+
*/
|
|
299
|
+
export const kdfWorkerEntryUrl = () => new URL('./kdfWorkerEntry.ts', import.meta.url);
|
|
300
|
+
/** Re-exported so a host wiring the guard tier needs one import. */
|
|
301
|
+
export { createInProcessPasswordVerifier };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SHIPPED KDF worker entry (05 §B6).
|
|
3
|
+
*
|
|
4
|
+
* Unlike a marshalling worker — where every marshaller declares its own entry
|
|
5
|
+
* because every marshaller has its own schema — there is exactly ONE password
|
|
6
|
+
* KDF in the platform (Law #7), so there is exactly one entry and the framework
|
|
7
|
+
* ships it. A host does not write this file; it points
|
|
8
|
+
* `createKdfWorkerPasswordVerifier({ entry: kdfWorkerEntryUrl() })` at it.
|
|
9
|
+
*
|
|
10
|
+
* The whole module is one call on purpose: everything a worker entry does that
|
|
11
|
+
* is not "start the loop" is code running on a thread with a live argon2id
|
|
12
|
+
* binding and a plaintext password in scope.
|
|
13
|
+
*/
|
|
14
|
+
import { serveKdfWorker } from './kdfWorker';
|
|
15
|
+
await serveKdfWorker();
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type Argon2idDeriver } from 'cwip/guard';
|
|
2
|
+
/**
|
|
3
|
+
* Seal a password verifier for `userId`, returning the serialized `SecretEnvelope`
|
|
4
|
+
* to store in `users.pw_envelope`. **Consumes `password`** (zeroized before return).
|
|
5
|
+
*
|
|
6
|
+
* A fresh per-user KDF salt is minted for each call, so two users with the same
|
|
7
|
+
* password produce different envelopes and re-deriving one never helps the other.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sealPasswordVerifier(userId: string, password: Uint8Array, deriver?: Argon2idDeriver): string;
|
|
10
|
+
/**
|
|
11
|
+
* Check `password` against the stored `envelopeJson` for `userId`. **Consumes
|
|
12
|
+
* `password`** (zeroized before return). Returns `true` iff the password is
|
|
13
|
+
* correct; `false` for a wrong password (GCM authentication failure). A malformed
|
|
14
|
+
* stored envelope is corruption, not a wrong password, and re-throws.
|
|
15
|
+
*
|
|
16
|
+
* The comparison is constant-time in the sensitive part: argon2id derivation
|
|
17
|
+
* dominates the timing, and GCM tag verification plus the final `timingSafeEqual`
|
|
18
|
+
* are constant-time, so a right and a wrong password of equal length are
|
|
19
|
+
* indistinguishable by timing.
|
|
20
|
+
*/
|
|
21
|
+
export declare function verifyPasswordEnvelope(userId: string, envelopeJson: string, password: Uint8Array, deriver?: Argon2idDeriver): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Where password derivation RUNS (05 §B6).
|
|
24
|
+
*
|
|
25
|
+
* The two functions above are synchronous on purpose — the KDF seam is, and for
|
|
26
|
+
* the reason documented on `Argon2idDeriver`: every async argon2id binding hands
|
|
27
|
+
* back a promise AND a base64/PHC *string*, which is key material that cannot be
|
|
28
|
+
* zeroized. The cost is that one derivation is ~150–250 ms of pure CPU on the
|
|
29
|
+
* event loop, so concurrent logins SERIALIZE and stall every unrelated request
|
|
30
|
+
* on the process for that window. A single-user local app never notices; a saas
|
|
31
|
+
* node taking a burst of logins does, and so does every tenant sharing it.
|
|
32
|
+
*
|
|
33
|
+
* This port is the seam that lets a host move that burn off the request thread
|
|
34
|
+
* WITHOUT introducing a second KDF, a second envelope format, or an async
|
|
35
|
+
* deriver. Both implementations call the exact same `sealPasswordVerifier` /
|
|
36
|
+
* `verifyPasswordEnvelope` above; they differ only in which thread runs them.
|
|
37
|
+
*
|
|
38
|
+
* local / default {@link createInProcessPasswordVerifier} — direct calls,
|
|
39
|
+
* byte-for-byte the behavior that shipped before this port
|
|
40
|
+
* existed.
|
|
41
|
+
* saas / opt-in `createKdfWorkerPasswordVerifier` (`./kdfWorker`) — a
|
|
42
|
+
* long-lived worker thread. **No key material crosses the
|
|
43
|
+
* thread boundary in either direction**: the password goes
|
|
44
|
+
* in, the derived key is born, used and zeroized inside the
|
|
45
|
+
* worker, and only a boolean (or the sealed ciphertext)
|
|
46
|
+
* comes back.
|
|
47
|
+
*
|
|
48
|
+
* Both entry points CONSUME `password` — ownership transfers on the call, and
|
|
49
|
+
* the buffer is zeroized on every path including a throw.
|
|
50
|
+
*/
|
|
51
|
+
export interface PasswordVerifierPort {
|
|
52
|
+
/** Seal a verifier for `userId`, returning the serialized envelope. Consumes `password`. */
|
|
53
|
+
seal(userId: string, password: Uint8Array): Promise<string>;
|
|
54
|
+
/** `true` iff `password` matches `envelopeJson`. Consumes `password`. */
|
|
55
|
+
verify(userId: string, envelopeJson: string, password: Uint8Array): Promise<boolean>;
|
|
56
|
+
/** Release any thread/handle the implementation owns. In-process: a no-op. */
|
|
57
|
+
dispose?(): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The default port: derive on the calling thread, exactly as every caller did
|
|
61
|
+
* before the port existed. Nothing about the crypto changes — this is the same
|
|
62
|
+
* two functions behind a promise, so a host that never opts into the worker
|
|
63
|
+
* path pays no async penalty beyond one already-resolved microtask.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createInProcessPasswordVerifier(deriver?: Argon2idDeriver): PasswordVerifierPort;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Password verifiers over the ONE KDF (02 §2, Law #7).
|
|
3
|
+
*
|
|
4
|
+
* A login password check needs a *verifier* — something stored per user that a
|
|
5
|
+
* presented password can be checked against — WITHOUT introducing a second hash
|
|
6
|
+
* function or a second at-rest secret format. The platform already ships exactly
|
|
7
|
+
* one of each: the argon2id seam (`cwip/.../guard/kdf.ts`) and the
|
|
8
|
+
* `SecretEnvelope` (`cwip/.../guard/secretEnvelope.ts`). This module builds the
|
|
9
|
+
* verifier out of those, adding nothing.
|
|
10
|
+
*
|
|
11
|
+
* The trick: a verifier is a `SecretEnvelope` that seals a FIXED known plaintext
|
|
12
|
+
* under a key argon2id-derived from the user's password. To check a password,
|
|
13
|
+
* re-derive the one-shot key from the presented password and try to open the
|
|
14
|
+
* envelope — GCM's authentication tag IS the password check. A wrong password
|
|
15
|
+
* derives a wrong key and GCM refuses (`SECRET_ENVELOPE_AUTH_FAILED`), which we
|
|
16
|
+
* translate to `false`; a right password opens and the recovered plaintext
|
|
17
|
+
* matches the known constant. No password hash is ever stored, compared, or even
|
|
18
|
+
* computed outside the KDF, and the envelope is the same `pw_envelope` string the
|
|
19
|
+
* registry `users.pw_envelope` column already types.
|
|
20
|
+
*
|
|
21
|
+
* Two properties that are not incidental:
|
|
22
|
+
*
|
|
23
|
+
* - **The verifier is bound to its user.** The envelope AAD is `cc-pw:<userId>`,
|
|
24
|
+
* so a `pw_envelope` lifted out of user A's row into user B's fails GCM
|
|
25
|
+
* authentication rather than letting B log in with A's password (the same
|
|
26
|
+
* swap defense `SecretEnvelope` gives every secret).
|
|
27
|
+
* - **Byte custody (01 §3.4).** Both entry points CONSUME the password buffer:
|
|
28
|
+
* the underlying manager zeroizes it the moment the KDF is done with it, on
|
|
29
|
+
* every path including a thrown KDF. Callers hand over ownership and must not
|
|
30
|
+
* touch the buffer afterwards.
|
|
31
|
+
*/
|
|
32
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
33
|
+
import { createSecretEnvelopeManager, mintEnvelopeSalt, parseSecretEnvelope, serializeSecretEnvelope, zeroize, } from 'cwip/guard';
|
|
34
|
+
import { isAppError } from '../errors';
|
|
35
|
+
/**
|
|
36
|
+
* The reserved AAD app namespace for password verifiers. `SecretEnvelope` forbids
|
|
37
|
+
* a ':' in the appId (it separates the AAD fields), so this is a plain token; the
|
|
38
|
+
* userId rides the AAD `key` half.
|
|
39
|
+
*/
|
|
40
|
+
const PW_APP = 'cc-pw';
|
|
41
|
+
/** The fixed plaintext every verifier seals — its recovery on open is the success signal. */
|
|
42
|
+
const VERIFIER_PLAINTEXT = new TextEncoder().encode('cc-guard-pw-verifier-v1');
|
|
43
|
+
/** Bind an envelope to one user: AAD becomes `cc-pw:<userId>`. */
|
|
44
|
+
const refFor = (userId) => ({ appId: PW_APP, key: userId });
|
|
45
|
+
/**
|
|
46
|
+
* Seal a password verifier for `userId`, returning the serialized `SecretEnvelope`
|
|
47
|
+
* to store in `users.pw_envelope`. **Consumes `password`** (zeroized before return).
|
|
48
|
+
*
|
|
49
|
+
* A fresh per-user KDF salt is minted for each call, so two users with the same
|
|
50
|
+
* password produce different envelopes and re-deriving one never helps the other.
|
|
51
|
+
*/
|
|
52
|
+
export function sealPasswordVerifier(userId, password, deriver) {
|
|
53
|
+
const manager = createSecretEnvelopeManager({ salt: mintEnvelopeSalt(), deriver });
|
|
54
|
+
try {
|
|
55
|
+
// unlock derives the master key from (password, salt) and zeroizes `password`
|
|
56
|
+
// in its own finally — including if the KDF throws.
|
|
57
|
+
manager.unlock(password);
|
|
58
|
+
return serializeSecretEnvelope(manager.seal(refFor(userId), VERIFIER_PLAINTEXT));
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
manager.lock();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Check `password` against the stored `envelopeJson` for `userId`. **Consumes
|
|
66
|
+
* `password`** (zeroized before return). Returns `true` iff the password is
|
|
67
|
+
* correct; `false` for a wrong password (GCM authentication failure). A malformed
|
|
68
|
+
* stored envelope is corruption, not a wrong password, and re-throws.
|
|
69
|
+
*
|
|
70
|
+
* The comparison is constant-time in the sensitive part: argon2id derivation
|
|
71
|
+
* dominates the timing, and GCM tag verification plus the final `timingSafeEqual`
|
|
72
|
+
* are constant-time, so a right and a wrong password of equal length are
|
|
73
|
+
* indistinguishable by timing.
|
|
74
|
+
*/
|
|
75
|
+
export function verifyPasswordEnvelope(userId, envelopeJson, password, deriver) {
|
|
76
|
+
let envelope;
|
|
77
|
+
try {
|
|
78
|
+
envelope = parseSecretEnvelope(envelopeJson);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// A stored envelope that will not parse is a tampered/corrupt row — never
|
|
82
|
+
// silently treat it as "wrong password". Zeroize the buffer we were handed
|
|
83
|
+
// before propagating (the manager never got a chance to).
|
|
84
|
+
zeroize(password);
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
const manager = createSecretEnvelopeManager({ salt: mintEnvelopeSalt(), deriver });
|
|
88
|
+
let opened = null;
|
|
89
|
+
try {
|
|
90
|
+
// openWithPassphrase re-derives a one-shot key from the envelope's own salt +
|
|
91
|
+
// the presented password, opens, and zeroizes `password` on every path.
|
|
92
|
+
opened = manager.openWithPassphrase(envelope, refFor(userId), password);
|
|
93
|
+
return opened.byteLength === VERIFIER_PLAINTEXT.byteLength && timingSafeEqual(opened, VERIFIER_PLAINTEXT);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
// GCM's rejection is the wrong-password verdict — the ONLY error we swallow.
|
|
97
|
+
if (isAppError(error) && error.code === 'SECRET_ENVELOPE_AUTH_FAILED')
|
|
98
|
+
return false;
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
if (opened)
|
|
103
|
+
zeroize(opened);
|
|
104
|
+
manager.lock();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The default port: derive on the calling thread, exactly as every caller did
|
|
109
|
+
* before the port existed. Nothing about the crypto changes — this is the same
|
|
110
|
+
* two functions behind a promise, so a host that never opts into the worker
|
|
111
|
+
* path pays no async penalty beyond one already-resolved microtask.
|
|
112
|
+
*/
|
|
113
|
+
export function createInProcessPasswordVerifier(deriver) {
|
|
114
|
+
return {
|
|
115
|
+
async seal(userId, password) {
|
|
116
|
+
return sealPasswordVerifier(userId, password, deriver);
|
|
117
|
+
},
|
|
118
|
+
async verify(userId, envelopeJson, password) {
|
|
119
|
+
return verifyPasswordEnvelope(userId, envelopeJson, password, deriver);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Somewhere to record the session/token ids that must no longer be accepted,
|
|
3
|
+
* answered synchronously.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately tiny, and deliberately not a session store: the token still
|
|
6
|
+
* carries its own expiry and signature, so this only ever answers "was this one
|
|
7
|
+
* withdrawn early". `sweep` exists so the table cannot grow forever — every
|
|
8
|
+
* entry becomes meaningless the moment its session would have expired anyway.
|
|
9
|
+
*
|
|
10
|
+
* Epoch **milliseconds** throughout, matching `Date.now()` and the satellites'
|
|
11
|
+
* cookie expiry. (The cross-node {@link AsyncRevocationStore} works in seconds,
|
|
12
|
+
* matching the signer's `exp`; {@link createAsyncBackedRevocationStore} bridges
|
|
13
|
+
* the two.)
|
|
14
|
+
*/
|
|
15
|
+
export interface RevocationStore {
|
|
16
|
+
/** Has this id been revoked? Called on every authenticated request. */
|
|
17
|
+
isRevoked(id: string): boolean;
|
|
18
|
+
/** Record a revocation until `expiresAtMs`, after which it is moot. */
|
|
19
|
+
revoke(id: string, expiresAtMs: number): void;
|
|
20
|
+
/** Drop entries whose sessions have expired. Called opportunistically. */
|
|
21
|
+
sweep(nowMs: number): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* An in-memory sync {@link RevocationStore}.
|
|
25
|
+
*
|
|
26
|
+
* For tests, and for a single-process caller that does not need durability.
|
|
27
|
+
* NOT for a deployed satellite: an in-memory table would be worse than none —
|
|
28
|
+
* sign out, redeploy, and the session works again, which reads as the feature
|
|
29
|
+
* being broken rather than absent. Give a deployed door
|
|
30
|
+
* {@link createSqliteRevocationStore} (durable) or nothing.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createMemoryRevocationStore(): RevocationStore & {
|
|
33
|
+
size(): number;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* A durable sync {@link RevocationStore} backed by a sqlite file.
|
|
37
|
+
*
|
|
38
|
+
* Because an in-memory table would be worse than none (see above), and the
|
|
39
|
+
* deploy engine persists each release's data directory, so durability costs one
|
|
40
|
+
* file. The table is self-limiting: an entry is meaningless once the session it
|
|
41
|
+
* names would have expired anyway, and `sweep` drops those on logout — rare,
|
|
42
|
+
* cheap, and exactly when the table has just grown, so there is no background
|
|
43
|
+
* timer to own.
|
|
44
|
+
*
|
|
45
|
+
* Its OWN file, not a table in an app's database: keeping a credential surface
|
|
46
|
+
* out of an app's data file bounds the blast radius of a corrupt or deleted
|
|
47
|
+
* file to "everyone signs in again".
|
|
48
|
+
*/
|
|
49
|
+
export declare function createSqliteRevocationStore(dbPath: string): RevocationStore & {
|
|
50
|
+
close(): void;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Where revoked jtis live so every node sees them (03 §4a). Local profile:
|
|
54
|
+
* unused (a per-process `Map` is enough for one process). SaaS: a Redis hash
|
|
55
|
+
* with per-entry TTL, or a row in the shared Postgres — the contract is
|
|
56
|
+
* identical, which is the point.
|
|
57
|
+
*
|
|
58
|
+
* Only `set`/`get`/`prune` and no bulk `list`, because the consumer's hot path
|
|
59
|
+
* is a SYNCHRONOUS `verify` (see {@link createSessionSigner}) that consults the
|
|
60
|
+
* store read-through by id off the hot path — never a full scan on every verify.
|
|
61
|
+
*
|
|
62
|
+
* Epoch **seconds** throughout, matching a token's own `exp`.
|
|
63
|
+
*/
|
|
64
|
+
export interface AsyncRevocationStore {
|
|
65
|
+
/**
|
|
66
|
+
* Record `jti` as revoked until `expEpochSeconds`. Idempotent; a re-revoke of
|
|
67
|
+
* the same jti is a no-op (or extends the exp, which is harmless — a denylist
|
|
68
|
+
* entry that outlives its token only wastes a little space until `prune`).
|
|
69
|
+
*/
|
|
70
|
+
set(jti: string, expEpochSeconds: number): Promise<void>;
|
|
71
|
+
/** The `exp` of a revoked jti (epoch seconds), or `null` when it is not revoked. */
|
|
72
|
+
get(jti: string): Promise<number | null>;
|
|
73
|
+
/** Drop every entry whose `exp <= nowEpochSeconds`. Returns the count removed. */
|
|
74
|
+
prune(nowEpochSeconds: number): Promise<number>;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* An in-memory {@link AsyncRevocationStore}.
|
|
78
|
+
*
|
|
79
|
+
* Two uses: a single-process saas test that wants the seam exercised without a
|
|
80
|
+
* real Redis, and — crucially — the **two-signer replay probe**, where one
|
|
81
|
+
* shared instance is handed to signer A and signer B so "revoke on A, verify on
|
|
82
|
+
* B" becomes provable in one process (V7.3). It is NOT a cross-process store:
|
|
83
|
+
* two OS processes do not share this Map, which is the whole reason a real
|
|
84
|
+
* deployment injects Redis/Postgres instead.
|
|
85
|
+
*
|
|
86
|
+
* (Named `…Async…` to stand apart from the sync {@link createMemoryRevocationStore};
|
|
87
|
+
* before DEDUP-7 both shapes shared the bare name across two packages and that
|
|
88
|
+
* collision was the footgun this file exists to remove.)
|
|
89
|
+
*/
|
|
90
|
+
export declare function createMemoryAsyncRevocationStore(): AsyncRevocationStore & {
|
|
91
|
+
size(): number;
|
|
92
|
+
};
|
|
93
|
+
export interface AsyncBackedRevocationStoreOptions {
|
|
94
|
+
/** Injected clock in **milliseconds** (default `Date.now`), for tests. */
|
|
95
|
+
now?: () => number;
|
|
96
|
+
/**
|
|
97
|
+
* How long a store verdict is trusted before `isRevoked` re-reads it (ms).
|
|
98
|
+
* This is the cross-node revocation LATENCY BOUND: a revocation written by
|
|
99
|
+
* another node is seen here within this window (plus the store's own read
|
|
100
|
+
* latency), because `isRevoked` stays synchronous — it answers from the cache
|
|
101
|
+
* and refreshes it off the hot path rather than awaiting a round trip. Default
|
|
102
|
+
* 5000 (03 §4a's ≤5s).
|
|
103
|
+
*/
|
|
104
|
+
cacheTtlMs?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Reports a backing read/write that failed. A failed write-through means a
|
|
107
|
+
* revocation did not reach other nodes — the local cache still refuses the id
|
|
108
|
+
* HERE, but the host may want to retry or alarm. Default: swallow, because a
|
|
109
|
+
* sync store must never throw out of `isRevoked`/`revoke` on the request path.
|
|
110
|
+
*/
|
|
111
|
+
onError?: (op: 'get' | 'set' | 'prune', error: unknown) => void;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A SYNC {@link RevocationStore} whose durable backing is an async cross-node
|
|
115
|
+
* {@link AsyncRevocationStore} — the bridge that gives a satellite the cross-node
|
|
116
|
+
* seam without its `isAuthenticated` becoming a promise.
|
|
117
|
+
*
|
|
118
|
+
* It is the same read-through-cache trick {@link createSessionSigner} keeps
|
|
119
|
+
* internally, packaged so a plain sync consumer can use it: `revoke` writes
|
|
120
|
+
* through fire-and-forget and caches locally so the id is refused HERE the
|
|
121
|
+
* instant it returns; `isRevoked` answers from the cache and schedules a refresh
|
|
122
|
+
* when the entry is missing or older than `cacheTtlMs`, never blocking. Ids and
|
|
123
|
+
* expiries cross the ms↔seconds boundary here (the sync contract is ms, the
|
|
124
|
+
* async one seconds).
|
|
125
|
+
*
|
|
126
|
+
* {@link flush} settles the fire-and-forget work — for shutdown/handover (a node
|
|
127
|
+
* draining must not lose an unwritten revocation) and for tests that need the
|
|
128
|
+
* cross-node state to have settled.
|
|
129
|
+
*/
|
|
130
|
+
export declare function createAsyncBackedRevocationStore(backing: AsyncRevocationStore, options?: AsyncBackedRevocationStoreOptions): RevocationStore & {
|
|
131
|
+
flush(): Promise<void>;
|
|
132
|
+
};
|