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,367 @@
|
|
|
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 {
|
|
46
|
+
createInProcessPasswordVerifier,
|
|
47
|
+
type PasswordVerifierPort,
|
|
48
|
+
sealPasswordVerifier,
|
|
49
|
+
verifyPasswordEnvelope,
|
|
50
|
+
} from './passwordVerifier';
|
|
51
|
+
import { registerNodeRsArgon2id } from './argon2Binding';
|
|
52
|
+
|
|
53
|
+
/** Raised when the worker thread dies, fails to start, or is disposed mid-flight. */
|
|
54
|
+
export const GUARD_KDF_WORKER_FAILED = 'GUARD_KDF_WORKER_FAILED';
|
|
55
|
+
/** Raised when the worker does not answer within the configured budget. */
|
|
56
|
+
export const GUARD_KDF_WORKER_TIMEOUT = 'GUARD_KDF_WORKER_TIMEOUT';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Default per-request ceiling. Generous relative to a ~250 ms derivation on
|
|
60
|
+
* purpose: this is a liveness backstop for a wedged thread, not a latency SLO,
|
|
61
|
+
* and a tight budget here would turn a loaded machine into a login outage.
|
|
62
|
+
*/
|
|
63
|
+
export const KDF_WORKER_DEFAULT_TIMEOUT_MS = 15_000;
|
|
64
|
+
|
|
65
|
+
/* ------------------------------------------------------------- the protocol */
|
|
66
|
+
|
|
67
|
+
export type KdfWorkerRequest =
|
|
68
|
+
| { id: number; kind: 'seal'; userId: string; password: Uint8Array }
|
|
69
|
+
| { id: number; kind: 'verify'; userId: string; envelopeJson: string; password: Uint8Array };
|
|
70
|
+
|
|
71
|
+
export type KdfWorkerResponse =
|
|
72
|
+
| { id: number; ok: true; value: string | boolean }
|
|
73
|
+
| { id: number; ok: false; code: string; message: string };
|
|
74
|
+
|
|
75
|
+
/* ---------------------------------------------------------- the worker side */
|
|
76
|
+
|
|
77
|
+
/** The subset of a worker-thread global this module needs, in either runtime. */
|
|
78
|
+
interface WorkerScope {
|
|
79
|
+
postMessage(data: unknown): void;
|
|
80
|
+
onMessage(handler: (data: unknown) => void): void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const nodeWorkerScope = async (): Promise<WorkerScope> => {
|
|
84
|
+
const { parentPort } = await import('node:worker_threads');
|
|
85
|
+
if (parentPort === null) throw new Error('serveKdfWorker() was called outside a worker thread.');
|
|
86
|
+
return {
|
|
87
|
+
postMessage: (data) => parentPort.postMessage(data),
|
|
88
|
+
onMessage: (handler) => parentPort.on('message', handler),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Run the KDF worker's message loop. A worker entry module is this call and
|
|
94
|
+
* nothing else (see `kdfWorkerEntry.ts`).
|
|
95
|
+
*
|
|
96
|
+
* `register` is injectable so the spec can drive the loop with a cheap fake
|
|
97
|
+
* binding — registering the real native argon2 in a test that only checks
|
|
98
|
+
* message plumbing would pay 250 ms per case to prove nothing about the
|
|
99
|
+
* plumbing. The DEFAULT is the real binding, because a worker that silently ran
|
|
100
|
+
* a fake KDF would be a catastrophe rather than a bug.
|
|
101
|
+
*/
|
|
102
|
+
export async function serveKdfWorker(options: { scope?: WorkerScope; register?: () => Promise<void> } = {}): Promise<void> {
|
|
103
|
+
const scope = options.scope ?? (await nodeWorkerScope());
|
|
104
|
+
// Registration happens ONCE, at worker start, before any request is served —
|
|
105
|
+
// not lazily per request, or the first login of every thread pays a native
|
|
106
|
+
// module load inside its own timeout budget.
|
|
107
|
+
await (options.register ?? registerNodeRsArgon2id)();
|
|
108
|
+
|
|
109
|
+
scope.onMessage((data: unknown) => {
|
|
110
|
+
const request = data as KdfWorkerRequest;
|
|
111
|
+
if (typeof request?.id !== 'number') return;
|
|
112
|
+
// The password arrives as a structured-clone copy owned by THIS thread.
|
|
113
|
+
// Re-wrap anything that is not already a Uint8Array rather than trusting the
|
|
114
|
+
// wire type — a plain `{0:..,1:..}` would silently derive from the wrong
|
|
115
|
+
// bytes instead of failing — but keep the inbound view too, because it is a
|
|
116
|
+
// second plaintext copy this thread is responsible for.
|
|
117
|
+
const inbound = request.password;
|
|
118
|
+
const password = inbound instanceof Uint8Array ? inbound : new Uint8Array(inbound);
|
|
119
|
+
try {
|
|
120
|
+
const value =
|
|
121
|
+
request.kind === 'seal'
|
|
122
|
+
? sealPasswordVerifier(request.userId, password)
|
|
123
|
+
: verifyPasswordEnvelope(request.userId, request.envelopeJson, password);
|
|
124
|
+
scope.postMessage({ id: request.id, ok: true, value } satisfies KdfWorkerResponse);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
// Only the CODE and message cross back. An error `context` in this module
|
|
127
|
+
// can hold envelope fields, so the envelope itself never leaves.
|
|
128
|
+
scope.postMessage({
|
|
129
|
+
id: request.id,
|
|
130
|
+
ok: false,
|
|
131
|
+
code: (error instanceof AppError ? error.code : undefined) ?? GUARD_KDF_WORKER_FAILED,
|
|
132
|
+
message: error instanceof Error ? error.message : String(error),
|
|
133
|
+
} satisfies KdfWorkerResponse);
|
|
134
|
+
} finally {
|
|
135
|
+
// Both `sealPasswordVerifier` and `verifyPasswordEnvelope` consume (and so
|
|
136
|
+
// zeroize) the buffer they are handed — but they are the far side of a
|
|
137
|
+
// contract, and a throw BEFORE they are reached (a malformed envelope, an
|
|
138
|
+
// unregistered deriver) leaves a plaintext password in worker RAM. Clearing
|
|
139
|
+
// again is idempotent and costs a memset.
|
|
140
|
+
//
|
|
141
|
+
// EVERY view, not just the one that was used: when the wire type is wrong
|
|
142
|
+
// the re-wrap above makes a second allocation, and zeroizing only the copy
|
|
143
|
+
// we derived from would leave the structured clone this thread was handed
|
|
144
|
+
// sitting in memory for the life of the worker.
|
|
145
|
+
password.fill(0);
|
|
146
|
+
if (inbound !== password && inbound instanceof Uint8Array) inbound.fill(0);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* ------------------------------------------------------------- the host side */
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* The subset of both runtimes' Worker the client needs.
|
|
155
|
+
*
|
|
156
|
+
* `post` RESOLVES ONCE THE DATA HAS BEEN HANDED TO THE THREAD, and that is not a
|
|
157
|
+
* stylistic choice. A worker is constructed asynchronously, so early requests
|
|
158
|
+
* are queued; the queue holds the caller's `Uint8Array` by reference. A client
|
|
159
|
+
* that zeroized the password right after a fire-and-forget `post()` would blank
|
|
160
|
+
* the buffer the queue is still holding, and the worker would then derive a key
|
|
161
|
+
* from 32 zero bytes — accepting every wrong password that happened to be
|
|
162
|
+
* presented during startup. Awaiting the post is what makes "zeroize as early as
|
|
163
|
+
* possible" and "send the real bytes" compatible.
|
|
164
|
+
*/
|
|
165
|
+
interface HostWorker {
|
|
166
|
+
post(data: unknown): Promise<void>;
|
|
167
|
+
onMessage(handler: (data: unknown) => void): void;
|
|
168
|
+
onError(handler: (error: unknown) => void): void;
|
|
169
|
+
terminate(): void;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface KdfWorkerOptions {
|
|
173
|
+
/** Module specifier / URL of an entry that calls {@link serveKdfWorker}. */
|
|
174
|
+
entry: string | URL;
|
|
175
|
+
/** Per-request ceiling. Defaults to {@link KDF_WORKER_DEFAULT_TIMEOUT_MS}. */
|
|
176
|
+
timeoutMs?: number;
|
|
177
|
+
/** Spawn override — the spec uses it to drive the protocol without a real thread. */
|
|
178
|
+
spawn?: (entry: string | URL) => HostWorker;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function spawnHostWorker(entry: string | URL): HostWorker {
|
|
182
|
+
// Callers await `settled` rather than seeing the construction race. It is
|
|
183
|
+
// RESOLVED, never rejected, on every exit path including a failed spawn: a
|
|
184
|
+
// rejection here would surface as an unhandled rejection on a request that the
|
|
185
|
+
// error handler below is already failing properly.
|
|
186
|
+
let realPost: ((data: unknown) => void) | null = null;
|
|
187
|
+
let markSettled: () => void = () => undefined;
|
|
188
|
+
const settled = new Promise<void>((resolve) => {
|
|
189
|
+
markSettled = resolve;
|
|
190
|
+
});
|
|
191
|
+
let terminated = false;
|
|
192
|
+
const messageHandlers: ((data: unknown) => void)[] = [];
|
|
193
|
+
const errorHandlers: ((error: unknown) => void)[] = [];
|
|
194
|
+
let terminateReal: (() => void) | null = null;
|
|
195
|
+
|
|
196
|
+
void (async () => {
|
|
197
|
+
try {
|
|
198
|
+
const { Worker } = await import('node:worker_threads');
|
|
199
|
+
if (terminated) return;
|
|
200
|
+
const worker = new Worker(entry as string | URL);
|
|
201
|
+
worker.on('message', (data: unknown) => {
|
|
202
|
+
for (const handler of messageHandlers) handler(data);
|
|
203
|
+
});
|
|
204
|
+
worker.on('error', (error: unknown) => {
|
|
205
|
+
for (const handler of errorHandlers) handler(error);
|
|
206
|
+
});
|
|
207
|
+
// ANY exit is a failure: this worker is long-lived by design, so it should
|
|
208
|
+
// outlive every request. A cleanly-exited thread that went unnoticed would
|
|
209
|
+
// leave each in-flight login waiting out its full timeout, and every later
|
|
210
|
+
// one posting into a dead thread to do the same.
|
|
211
|
+
worker.on('exit', (code: number) => {
|
|
212
|
+
const how = code === 0 ? 'exited cleanly while still in use' : `exited with code ${code}`;
|
|
213
|
+
for (const handler of errorHandlers) handler(new Error(`KDF worker ${how}`));
|
|
214
|
+
});
|
|
215
|
+
realPost = (data) => worker.postMessage(data);
|
|
216
|
+
terminateReal = () => void worker.terminate();
|
|
217
|
+
} catch (error) {
|
|
218
|
+
for (const handler of errorHandlers) handler(error);
|
|
219
|
+
} finally {
|
|
220
|
+
markSettled();
|
|
221
|
+
}
|
|
222
|
+
})();
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
async post(data) {
|
|
226
|
+
if (realPost === null) await settled;
|
|
227
|
+
// Still null ⇒ the spawn failed or we were disposed mid-construction. The
|
|
228
|
+
// error handler has already failed (or will fail) the request; dropping
|
|
229
|
+
// the message is correct, and the caller may now zeroize.
|
|
230
|
+
realPost?.(data);
|
|
231
|
+
},
|
|
232
|
+
onMessage: (handler) => messageHandlers.push(handler),
|
|
233
|
+
onError: (handler) => errorHandlers.push(handler),
|
|
234
|
+
terminate: () => {
|
|
235
|
+
terminated = true;
|
|
236
|
+
markSettled();
|
|
237
|
+
terminateReal?.();
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* A {@link PasswordVerifierPort} backed by one long-lived worker thread.
|
|
244
|
+
*
|
|
245
|
+
* Hand it to `createAccountService({ passwords })` on a host that expects login
|
|
246
|
+
* concurrency. Local single-user hosts should NOT — they would pay a thread and
|
|
247
|
+
* a native module load to serialize a workload that was never contended.
|
|
248
|
+
*/
|
|
249
|
+
export function createKdfWorkerPasswordVerifier(options: KdfWorkerOptions): PasswordVerifierPort {
|
|
250
|
+
const timeoutMs = options.timeoutMs ?? KDF_WORKER_DEFAULT_TIMEOUT_MS;
|
|
251
|
+
const worker = (options.spawn ?? spawnHostWorker)(options.entry);
|
|
252
|
+
|
|
253
|
+
interface Pending {
|
|
254
|
+
resolve(value: string | boolean): void;
|
|
255
|
+
reject(error: unknown): void;
|
|
256
|
+
timer: ReturnType<typeof setTimeout>;
|
|
257
|
+
}
|
|
258
|
+
const pending = new Map<number, Pending>();
|
|
259
|
+
let nextId = 1;
|
|
260
|
+
let disposed = false;
|
|
261
|
+
|
|
262
|
+
const settle = (id: number, apply: (p: Pending) => void): void => {
|
|
263
|
+
const entry = pending.get(id);
|
|
264
|
+
if (!entry) return; // already timed out — a late reply is discarded
|
|
265
|
+
pending.delete(id);
|
|
266
|
+
clearTimeout(entry.timer);
|
|
267
|
+
apply(entry);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const failure = (message: string, code: string, status: number): AppError =>
|
|
271
|
+
new AppError(message, { code, status, category: 'security', context: { worker: String(options.entry) } });
|
|
272
|
+
|
|
273
|
+
worker.onMessage((data) => {
|
|
274
|
+
const response = data as KdfWorkerResponse;
|
|
275
|
+
if (typeof response?.id !== 'number') return;
|
|
276
|
+
settle(response.id, (p) => {
|
|
277
|
+
if (response.ok) {
|
|
278
|
+
p.resolve(response.value);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
// Preserve the worker's own code: `SECRET_ENVELOPE_*` failures are a
|
|
282
|
+
// corrupt row, not a dead thread, and a caller distinguishing them must
|
|
283
|
+
// still be able to.
|
|
284
|
+
p.reject(failure(response.message, response.code, 500));
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// A dead worker takes every request in flight with it — failing them now beats
|
|
289
|
+
// each one waiting out its own timeout.
|
|
290
|
+
worker.onError((error) => {
|
|
291
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
292
|
+
for (const id of [...pending.keys()])
|
|
293
|
+
settle(id, (p) => p.reject(failure(`KDF worker failed: ${message}`, GUARD_KDF_WORKER_FAILED, 500)));
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Post the request, zeroize the host's copy of the password as soon as the
|
|
298
|
+
* thread has it, then await the verdict.
|
|
299
|
+
*
|
|
300
|
+
* The zeroize placement is the point. The worker holds a structured-clone
|
|
301
|
+
* copy, so the host's buffer is dead weight the instant the post lands —
|
|
302
|
+
* and the await that follows lasts the whole ~250 ms derivation, which is
|
|
303
|
+
* exactly the window in which a heap snapshot, a core dump or a crash reporter
|
|
304
|
+
* would capture a plaintext password. Clearing it before the await instead of
|
|
305
|
+
* in a `finally` after it removes that window entirely.
|
|
306
|
+
*/
|
|
307
|
+
const send = async (build: (id: number) => KdfWorkerRequest, password: Uint8Array): Promise<string | boolean> => {
|
|
308
|
+
if (disposed) throw failure('The KDF worker has been disposed.', GUARD_KDF_WORKER_FAILED, 500);
|
|
309
|
+
const id = nextId++;
|
|
310
|
+
const answer = new Promise<string | boolean>((resolve, reject) => {
|
|
311
|
+
const timer = setTimeout(() => {
|
|
312
|
+
pending.delete(id);
|
|
313
|
+
reject(failure(`The KDF worker did not answer within ${timeoutMs}ms.`, GUARD_KDF_WORKER_TIMEOUT, 504));
|
|
314
|
+
}, timeoutMs);
|
|
315
|
+
pending.set(id, { resolve, reject, timer });
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
try {
|
|
319
|
+
await worker.post(build(id));
|
|
320
|
+
} finally {
|
|
321
|
+
password.fill(0);
|
|
322
|
+
}
|
|
323
|
+
return await answer;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
async seal(userId, password) {
|
|
328
|
+
const value = await send((id) => ({ id, kind: 'seal', userId, password }), password);
|
|
329
|
+
if (typeof value !== 'string')
|
|
330
|
+
throw failure('The KDF worker returned a non-string envelope for seal().', GUARD_KDF_WORKER_FAILED, 500);
|
|
331
|
+
return value;
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
async verify(userId, envelopeJson, password) {
|
|
335
|
+
const value = await send((id) => ({ id, kind: 'verify', userId, envelopeJson, password }), password);
|
|
336
|
+
if (typeof value !== 'boolean')
|
|
337
|
+
throw failure('The KDF worker returned a non-boolean verdict for verify().', GUARD_KDF_WORKER_FAILED, 500);
|
|
338
|
+
return value;
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
async dispose() {
|
|
342
|
+
disposed = true;
|
|
343
|
+
for (const id of [...pending.keys()])
|
|
344
|
+
settle(id, (p) =>
|
|
345
|
+
p.reject(failure('The KDF worker was disposed while a request was in flight.', GUARD_KDF_WORKER_FAILED, 503)),
|
|
346
|
+
);
|
|
347
|
+
worker.terminate();
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* The URL of the entry module {@link createKdfWorkerPasswordVerifier} spawns.
|
|
354
|
+
*
|
|
355
|
+
* Resolved relative to THIS module rather than hard-coded by the host, so an app
|
|
356
|
+
* never has to know where inside the package the entry lives:
|
|
357
|
+
*
|
|
358
|
+
* createKdfWorkerPasswordVerifier({ entry: kdfWorkerEntryUrl() })
|
|
359
|
+
*
|
|
360
|
+
* Under the `bun` export condition this resolves to the TypeScript source, which
|
|
361
|
+
* is what every first-party consumer runs (see the monorepo's zero-build-lag
|
|
362
|
+
* contract). A non-Bun host consuming built `dist/` names its own built entry.
|
|
363
|
+
*/
|
|
364
|
+
export const kdfWorkerEntryUrl = (): URL => new URL('./kdfWorkerEntry.ts', import.meta.url);
|
|
365
|
+
|
|
366
|
+
/** Re-exported so a host wiring the guard tier needs one import. */
|
|
367
|
+
export { createInProcessPasswordVerifier, type PasswordVerifierPort };
|
|
@@ -0,0 +1,16 @@
|
|
|
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
|
+
|
|
16
|
+
await serveKdfWorker();
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Password verifiers over the ONE KDF (02 §2).
|
|
3
|
+
*
|
|
4
|
+
* Proves the verifier IS a `SecretEnvelope`: a right password opens it, a wrong
|
|
5
|
+
* one fails GCM authentication (→ `false`, not a throw), the envelope is bound to
|
|
6
|
+
* its user (a lifted envelope is refused), a malformed row is corruption (throws),
|
|
7
|
+
* and every entry point CONSUMES the password buffer (byte custody, 01 §3.4).
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, it } from 'bun:test';
|
|
10
|
+
import type { Argon2idDeriver } from 'cwip/guard';
|
|
11
|
+
import { sealPasswordVerifier, verifyPasswordEnvelope } from './passwordVerifier';
|
|
12
|
+
|
|
13
|
+
// A deterministic, PASSWORD-SENSITIVE stand-in for argon2id (the real seam mixes
|
|
14
|
+
// far more): the output must depend on the password, or a "wrong password" would
|
|
15
|
+
// derive the same key and the verifier would be meaningless.
|
|
16
|
+
const stubDeriver: Argon2idDeriver = ({ password, salt, keyLength }) => {
|
|
17
|
+
const out = new Uint8Array(keyLength);
|
|
18
|
+
for (let i = 0; i < keyLength; i += 1)
|
|
19
|
+
out[i] = ((password[i % password.length] ?? 0) ^ (salt[i % salt.length] ?? 0) ^ (i & 0xff)) & 0xff;
|
|
20
|
+
return out;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const bytes = (value: string): Uint8Array => new TextEncoder().encode(value);
|
|
24
|
+
const allZero = (buffer: Uint8Array): boolean => buffer.every((b) => b === 0);
|
|
25
|
+
|
|
26
|
+
describe('passwordVerifier', () => {
|
|
27
|
+
it('round-trips: the sealed password verifies', () => {
|
|
28
|
+
const envelope = sealPasswordVerifier('u_ann', bytes('correct horse battery'), stubDeriver);
|
|
29
|
+
expect(verifyPasswordEnvelope('u_ann', envelope, bytes('correct horse battery'), stubDeriver)).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('rejects a wrong password with false (not a throw)', () => {
|
|
33
|
+
const envelope = sealPasswordVerifier('u_ann', bytes('correct horse battery'), stubDeriver);
|
|
34
|
+
expect(verifyPasswordEnvelope('u_ann', envelope, bytes('wrong password here!!'), stubDeriver)).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('is bound to its user — a lifted envelope is refused, not silently accepted', () => {
|
|
38
|
+
const envelope = sealPasswordVerifier('u_ann', bytes('shared-secret-value'), stubDeriver);
|
|
39
|
+
// Same password, wrong user id: the AAD (`cc-pw:u_ann`) no longer matches, so
|
|
40
|
+
// the envelope refuses rather than authenticating u_eve with u_ann's verifier.
|
|
41
|
+
expect(() => verifyPasswordEnvelope('u_eve', envelope, bytes('shared-secret-value'), stubDeriver)).toThrow(
|
|
42
|
+
/bound to|relocated|SecretEnvelope/i,
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('two users with the same password get different envelopes (fresh per-user salt)', () => {
|
|
47
|
+
const a = sealPasswordVerifier('u_a', bytes('identical-pw'), stubDeriver);
|
|
48
|
+
const b = sealPasswordVerifier('u_b', bytes('identical-pw'), stubDeriver);
|
|
49
|
+
expect(a).not.toEqual(b);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('throws on a malformed stored envelope (corruption, never "wrong password")', () => {
|
|
53
|
+
expect(() => verifyPasswordEnvelope('u_ann', '{"not":"an envelope"}', bytes('pw'), stubDeriver)).toThrow();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('CONSUMES the password buffer on seal, on a correct verify, and on a wrong verify', () => {
|
|
57
|
+
const p1 = bytes('to-be-sealed-pw');
|
|
58
|
+
sealPasswordVerifier('u_ann', p1, stubDeriver);
|
|
59
|
+
expect(allZero(p1)).toBe(true);
|
|
60
|
+
|
|
61
|
+
const envelope = sealPasswordVerifier('u_ann', bytes('to-be-sealed-pw'), stubDeriver);
|
|
62
|
+
|
|
63
|
+
const p2 = bytes('to-be-sealed-pw');
|
|
64
|
+
verifyPasswordEnvelope('u_ann', envelope, p2, stubDeriver);
|
|
65
|
+
expect(allZero(p2)).toBe(true);
|
|
66
|
+
|
|
67
|
+
const p3 = bytes('the-wrong-password');
|
|
68
|
+
verifyPasswordEnvelope('u_ann', envelope, p3, stubDeriver);
|
|
69
|
+
expect(allZero(p3)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
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 {
|
|
34
|
+
type Argon2idDeriver,
|
|
35
|
+
createSecretEnvelopeManager,
|
|
36
|
+
mintEnvelopeSalt,
|
|
37
|
+
parseSecretEnvelope,
|
|
38
|
+
serializeSecretEnvelope,
|
|
39
|
+
zeroize,
|
|
40
|
+
} from 'cwip/guard';
|
|
41
|
+
import { isAppError } from '../errors';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The reserved AAD app namespace for password verifiers. `SecretEnvelope` forbids
|
|
45
|
+
* a ':' in the appId (it separates the AAD fields), so this is a plain token; the
|
|
46
|
+
* userId rides the AAD `key` half.
|
|
47
|
+
*/
|
|
48
|
+
const PW_APP = 'cc-pw';
|
|
49
|
+
|
|
50
|
+
/** The fixed plaintext every verifier seals — its recovery on open is the success signal. */
|
|
51
|
+
const VERIFIER_PLAINTEXT = new TextEncoder().encode('cc-guard-pw-verifier-v1');
|
|
52
|
+
|
|
53
|
+
/** Bind an envelope to one user: AAD becomes `cc-pw:<userId>`. */
|
|
54
|
+
const refFor = (userId: string): { appId: string; key: string } => ({ appId: PW_APP, key: userId });
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Seal a password verifier for `userId`, returning the serialized `SecretEnvelope`
|
|
58
|
+
* to store in `users.pw_envelope`. **Consumes `password`** (zeroized before return).
|
|
59
|
+
*
|
|
60
|
+
* A fresh per-user KDF salt is minted for each call, so two users with the same
|
|
61
|
+
* password produce different envelopes and re-deriving one never helps the other.
|
|
62
|
+
*/
|
|
63
|
+
export function sealPasswordVerifier(userId: string, password: Uint8Array, deriver?: Argon2idDeriver): string {
|
|
64
|
+
const manager = createSecretEnvelopeManager({ salt: mintEnvelopeSalt(), deriver });
|
|
65
|
+
try {
|
|
66
|
+
// unlock derives the master key from (password, salt) and zeroizes `password`
|
|
67
|
+
// in its own finally — including if the KDF throws.
|
|
68
|
+
manager.unlock(password);
|
|
69
|
+
return serializeSecretEnvelope(manager.seal(refFor(userId), VERIFIER_PLAINTEXT));
|
|
70
|
+
} finally {
|
|
71
|
+
manager.lock();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check `password` against the stored `envelopeJson` for `userId`. **Consumes
|
|
77
|
+
* `password`** (zeroized before return). Returns `true` iff the password is
|
|
78
|
+
* correct; `false` for a wrong password (GCM authentication failure). A malformed
|
|
79
|
+
* stored envelope is corruption, not a wrong password, and re-throws.
|
|
80
|
+
*
|
|
81
|
+
* The comparison is constant-time in the sensitive part: argon2id derivation
|
|
82
|
+
* dominates the timing, and GCM tag verification plus the final `timingSafeEqual`
|
|
83
|
+
* are constant-time, so a right and a wrong password of equal length are
|
|
84
|
+
* indistinguishable by timing.
|
|
85
|
+
*/
|
|
86
|
+
export function verifyPasswordEnvelope(
|
|
87
|
+
userId: string,
|
|
88
|
+
envelopeJson: string,
|
|
89
|
+
password: Uint8Array,
|
|
90
|
+
deriver?: Argon2idDeriver,
|
|
91
|
+
): boolean {
|
|
92
|
+
let envelope: ReturnType<typeof parseSecretEnvelope>;
|
|
93
|
+
try {
|
|
94
|
+
envelope = parseSecretEnvelope(envelopeJson);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
// A stored envelope that will not parse is a tampered/corrupt row — never
|
|
97
|
+
// silently treat it as "wrong password". Zeroize the buffer we were handed
|
|
98
|
+
// before propagating (the manager never got a chance to).
|
|
99
|
+
zeroize(password);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const manager = createSecretEnvelopeManager({ salt: mintEnvelopeSalt(), deriver });
|
|
104
|
+
let opened: Uint8Array | null = null;
|
|
105
|
+
try {
|
|
106
|
+
// openWithPassphrase re-derives a one-shot key from the envelope's own salt +
|
|
107
|
+
// the presented password, opens, and zeroizes `password` on every path.
|
|
108
|
+
opened = manager.openWithPassphrase(envelope, refFor(userId), password);
|
|
109
|
+
return opened.byteLength === VERIFIER_PLAINTEXT.byteLength && timingSafeEqual(opened, VERIFIER_PLAINTEXT);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// GCM's rejection is the wrong-password verdict — the ONLY error we swallow.
|
|
112
|
+
if (isAppError(error) && error.code === 'SECRET_ENVELOPE_AUTH_FAILED') return false;
|
|
113
|
+
throw error;
|
|
114
|
+
} finally {
|
|
115
|
+
if (opened) zeroize(opened);
|
|
116
|
+
manager.lock();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* ------------------------------------------------------------------ the port */
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Where password derivation RUNS (05 §B6).
|
|
124
|
+
*
|
|
125
|
+
* The two functions above are synchronous on purpose — the KDF seam is, and for
|
|
126
|
+
* the reason documented on `Argon2idDeriver`: every async argon2id binding hands
|
|
127
|
+
* back a promise AND a base64/PHC *string*, which is key material that cannot be
|
|
128
|
+
* zeroized. The cost is that one derivation is ~150–250 ms of pure CPU on the
|
|
129
|
+
* event loop, so concurrent logins SERIALIZE and stall every unrelated request
|
|
130
|
+
* on the process for that window. A single-user local app never notices; a saas
|
|
131
|
+
* node taking a burst of logins does, and so does every tenant sharing it.
|
|
132
|
+
*
|
|
133
|
+
* This port is the seam that lets a host move that burn off the request thread
|
|
134
|
+
* WITHOUT introducing a second KDF, a second envelope format, or an async
|
|
135
|
+
* deriver. Both implementations call the exact same `sealPasswordVerifier` /
|
|
136
|
+
* `verifyPasswordEnvelope` above; they differ only in which thread runs them.
|
|
137
|
+
*
|
|
138
|
+
* local / default {@link createInProcessPasswordVerifier} — direct calls,
|
|
139
|
+
* byte-for-byte the behavior that shipped before this port
|
|
140
|
+
* existed.
|
|
141
|
+
* saas / opt-in `createKdfWorkerPasswordVerifier` (`./kdfWorker`) — a
|
|
142
|
+
* long-lived worker thread. **No key material crosses the
|
|
143
|
+
* thread boundary in either direction**: the password goes
|
|
144
|
+
* in, the derived key is born, used and zeroized inside the
|
|
145
|
+
* worker, and only a boolean (or the sealed ciphertext)
|
|
146
|
+
* comes back.
|
|
147
|
+
*
|
|
148
|
+
* Both entry points CONSUME `password` — ownership transfers on the call, and
|
|
149
|
+
* the buffer is zeroized on every path including a throw.
|
|
150
|
+
*/
|
|
151
|
+
export interface PasswordVerifierPort {
|
|
152
|
+
/** Seal a verifier for `userId`, returning the serialized envelope. Consumes `password`. */
|
|
153
|
+
seal(userId: string, password: Uint8Array): Promise<string>;
|
|
154
|
+
/** `true` iff `password` matches `envelopeJson`. Consumes `password`. */
|
|
155
|
+
verify(userId: string, envelopeJson: string, password: Uint8Array): Promise<boolean>;
|
|
156
|
+
/** Release any thread/handle the implementation owns. In-process: a no-op. */
|
|
157
|
+
dispose?(): Promise<void>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The default port: derive on the calling thread, exactly as every caller did
|
|
162
|
+
* before the port existed. Nothing about the crypto changes — this is the same
|
|
163
|
+
* two functions behind a promise, so a host that never opts into the worker
|
|
164
|
+
* path pays no async penalty beyond one already-resolved microtask.
|
|
165
|
+
*/
|
|
166
|
+
export function createInProcessPasswordVerifier(deriver?: Argon2idDeriver): PasswordVerifierPort {
|
|
167
|
+
return {
|
|
168
|
+
async seal(userId, password) {
|
|
169
|
+
return sealPasswordVerifier(userId, password, deriver);
|
|
170
|
+
},
|
|
171
|
+
async verify(userId, envelopeJson, password) {
|
|
172
|
+
return verifyPasswordEnvelope(userId, envelopeJson, password, deriver);
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|