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,76 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { ApiError, isApiError, toErrorEnvelope } from './errors';
|
|
3
|
+
|
|
4
|
+
/** ISO-8601 timestamp check + strip, so the dynamic field doesn't break equality. */
|
|
5
|
+
const withoutTimestamp = (env: ReturnType<typeof toErrorEnvelope>) => {
|
|
6
|
+
expect(env.error.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T[\d:.]+Z$/);
|
|
7
|
+
const { timestamp: _timestamp, ...error } = env.error;
|
|
8
|
+
return { ...env, error };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe('ApiError + toErrorEnvelope', () => {
|
|
12
|
+
test('carries status, message, code, context', () => {
|
|
13
|
+
const err = new ApiError(422, 'bad input', {
|
|
14
|
+
code: 'validation_failed',
|
|
15
|
+
context: { issues: [{ path: 'name', message: 'required' }] },
|
|
16
|
+
});
|
|
17
|
+
expect(err.status).toBe(422);
|
|
18
|
+
expect(err.message).toBe('bad input');
|
|
19
|
+
expect(err.name).toBe('ApiError');
|
|
20
|
+
expect(isApiError(err)).toBe(true);
|
|
21
|
+
expect(err instanceof Error).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('minimal ApiError → rich envelope (name/status/isOperational/timestamp), no undefined keys', () => {
|
|
25
|
+
expect(withoutTimestamp(toErrorEnvelope(new ApiError(404, 'not found')))).toEqual({
|
|
26
|
+
success: false,
|
|
27
|
+
error: { name: 'ApiError', message: 'not found', status: 404, isOperational: true },
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('full ApiError → message + code + context + rich metadata', () => {
|
|
32
|
+
const env = toErrorEnvelope(
|
|
33
|
+
new ApiError(422, 'invalid', { code: 'validation_failed', context: { issues: ['x'] } }),
|
|
34
|
+
);
|
|
35
|
+
expect(withoutTimestamp(env)).toEqual({
|
|
36
|
+
success: false,
|
|
37
|
+
error: {
|
|
38
|
+
name: 'ApiError',
|
|
39
|
+
message: 'invalid',
|
|
40
|
+
code: 'validation_failed',
|
|
41
|
+
status: 422,
|
|
42
|
+
context: { issues: ['x'] },
|
|
43
|
+
isOperational: true,
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('correlationId, when supplied, rides alongside the error', () => {
|
|
49
|
+
const env = toErrorEnvelope(new ApiError(404, 'nope'), { correlationId: 'req-1' });
|
|
50
|
+
expect(env.correlationId).toBe('req-1');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('plain Error / non-error → sanitized generic 500 (internals never leak)', () => {
|
|
54
|
+
expect(withoutTimestamp(toErrorEnvelope(new Error('boom')))).toEqual({
|
|
55
|
+
success: false,
|
|
56
|
+
error: { name: 'Error', message: 'Internal Server Error', status: 500, isOperational: false },
|
|
57
|
+
});
|
|
58
|
+
expect(withoutTimestamp(toErrorEnvelope('nope'))).toEqual({
|
|
59
|
+
success: false,
|
|
60
|
+
error: { name: 'Error', message: 'Internal Server Error', status: 500, isOperational: false },
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('exposeMessage surfaces a non-AppError message (opt-in)', () => {
|
|
65
|
+
expect(toErrorEnvelope(new Error('boom'), { exposeMessage: true }).error.message).toBe('boom');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('preserves cause for server-side logging without serializing it', () => {
|
|
69
|
+
const cause = new Error('root');
|
|
70
|
+
const err = new ApiError(500, 'wrapped', { cause });
|
|
71
|
+
expect(err.cause).toBe(cause);
|
|
72
|
+
const env = toErrorEnvelope(err);
|
|
73
|
+
expect(env.error.message).toBe('wrapped');
|
|
74
|
+
expect(JSON.stringify(env)).not.toContain('root');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side re-export of the canonical error primitives, which now live in the
|
|
3
|
+
* framework-free {@link ../shared/errors} module so cursedshell's plugin-pure layer
|
|
4
|
+
* can import the SAME `ApiError` + envelope without pulling any server code
|
|
5
|
+
* (`cursedbelt-core/errors` subpath). Server modules keep importing from `./errors`.
|
|
6
|
+
*/
|
|
7
|
+
export {
|
|
8
|
+
AppError,
|
|
9
|
+
type AppErrorSummary,
|
|
10
|
+
ApiError,
|
|
11
|
+
type ApiErrorOptions,
|
|
12
|
+
type ErrorEnvelope,
|
|
13
|
+
isAppError,
|
|
14
|
+
isApiError,
|
|
15
|
+
type ToErrorEnvelopeOptions,
|
|
16
|
+
toErrorEnvelope,
|
|
17
|
+
type ValidationIssue,
|
|
18
|
+
VALIDATION_ERROR_CODE,
|
|
19
|
+
VALIDATION_STATUS,
|
|
20
|
+
toValidationIssues,
|
|
21
|
+
validationEnvelope,
|
|
22
|
+
} from 'cursedbelt-core/errors';
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The argon2id binding adapter, against the REAL native module (02 §2-tail).
|
|
3
|
+
*
|
|
4
|
+
* `registerNodeRsArgon2id` existed for a whole arc with no caller and no test —
|
|
5
|
+
* and it was broken: `@node-rs/argon2`'s `version` option is a NAPI enum
|
|
6
|
+
* (`V0x10 = 0`, `V0x13 = 1`), not the argon2 version number `0x13` the envelope
|
|
7
|
+
* records, so every derivation threw `InvalidArg` from the native layer. Nothing
|
|
8
|
+
* caught it because nothing had ever registered the binding for real. Now the
|
|
9
|
+
* local host does at boot, so this suite drives the actual module: register,
|
|
10
|
+
* derive, and check the bytes.
|
|
11
|
+
*
|
|
12
|
+
* `@node-rs/argon2` is an OPTIONAL peer of this package (a generated app without
|
|
13
|
+
* a GuardEngine surface should not carry a native module) and a devDependency
|
|
14
|
+
* here, which is what lets the shipping path be proven rather than asserted.
|
|
15
|
+
*/
|
|
16
|
+
import { afterEach, describe, expect, it } from 'bun:test';
|
|
17
|
+
import {
|
|
18
|
+
CC_ARGON2ID_VERSION,
|
|
19
|
+
deriveArgon2idKey,
|
|
20
|
+
hasArgon2idDeriver,
|
|
21
|
+
resolveArgon2idDeriver,
|
|
22
|
+
setArgon2idDeriver,
|
|
23
|
+
} from 'cwip/guard';
|
|
24
|
+
import { argon2idDeriverFrom, nodeRsArgon2idBinding, type RawArgon2idBinding, registerNodeRsArgon2id } from './argon2Binding';
|
|
25
|
+
|
|
26
|
+
/** Cheap parameters — this suite proves the WIRING, not the cost calibration. */
|
|
27
|
+
const FAST = { m: 8_192, t: 1, p: 1 };
|
|
28
|
+
const SALT = new Uint8Array(16).fill(7);
|
|
29
|
+
const password = (value: string): Uint8Array => new TextEncoder().encode(value);
|
|
30
|
+
|
|
31
|
+
// The seam is process-wide; never leak a registration into another suite.
|
|
32
|
+
afterEach(() => setArgon2idDeriver(null));
|
|
33
|
+
|
|
34
|
+
describe('nodeRsArgon2idBinding — the version-vocabulary translation', () => {
|
|
35
|
+
const calls: Array<Record<string, unknown>> = [];
|
|
36
|
+
const spy: RawArgon2idBinding = {
|
|
37
|
+
hashRawSync(_password, options) {
|
|
38
|
+
calls.push({ ...options });
|
|
39
|
+
return new Uint8Array(options.outputLen);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
it('maps the argon2 version number the envelope records onto node-rs’s enum', () => {
|
|
44
|
+
calls.length = 0;
|
|
45
|
+
const binding = nodeRsArgon2idBinding(spy);
|
|
46
|
+
binding.hashRawSync(password('x'), { salt: SALT, memoryCost: 8, timeCost: 1, parallelism: 1, outputLen: 32, version: 0x13 });
|
|
47
|
+
binding.hashRawSync(password('x'), { salt: SALT, memoryCost: 8, timeCost: 1, parallelism: 1, outputLen: 32, version: 0x10 });
|
|
48
|
+
// 0x13 → 1, 0x10 → 0. Passing 0x13 straight through is the `InvalidArg` bug.
|
|
49
|
+
expect(calls.map((call) => call.version)).toEqual([1, 0]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('defaults to the platform version when the caller names none', () => {
|
|
53
|
+
calls.length = 0;
|
|
54
|
+
nodeRsArgon2idBinding(spy).hashRawSync(password('x'), {
|
|
55
|
+
salt: SALT,
|
|
56
|
+
memoryCost: 8,
|
|
57
|
+
timeCost: 1,
|
|
58
|
+
parallelism: 1,
|
|
59
|
+
outputLen: 32,
|
|
60
|
+
});
|
|
61
|
+
expect(CC_ARGON2ID_VERSION).toBe(0x13);
|
|
62
|
+
expect(calls[0]?.version).toBe(1);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('refuses a version the binding cannot implement instead of silently deriving different bytes', () => {
|
|
66
|
+
expect(() =>
|
|
67
|
+
nodeRsArgon2idBinding(spy).hashRawSync(password('x'), {
|
|
68
|
+
salt: SALT,
|
|
69
|
+
memoryCost: 8,
|
|
70
|
+
timeCost: 1,
|
|
71
|
+
parallelism: 1,
|
|
72
|
+
outputLen: 32,
|
|
73
|
+
version: 0x11,
|
|
74
|
+
}),
|
|
75
|
+
).toThrow(/not one @node-rs\/argon2 implements/);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('registerNodeRsArgon2id — the real native binding, end to end', () => {
|
|
80
|
+
it('arms the process-wide seam and derives raw bytes of the requested length', async () => {
|
|
81
|
+
expect(hasArgon2idDeriver()).toBe(false);
|
|
82
|
+
await registerNodeRsArgon2id();
|
|
83
|
+
expect(hasArgon2idDeriver()).toBe(true);
|
|
84
|
+
|
|
85
|
+
// Straight through the SAME entry point every envelope/verifier uses. Before
|
|
86
|
+
// the version fix this threw `InvalidArg` here, on the first derivation.
|
|
87
|
+
const key = deriveArgon2idKey({ password: password('correct horse'), salt: SALT, params: FAST, keyLength: 32, label: 'test' });
|
|
88
|
+
const bytes = key.use((raw) => new Uint8Array(raw));
|
|
89
|
+
expect(bytes.byteLength).toBe(32);
|
|
90
|
+
expect(bytes.some((byte) => byte !== 0)).toBe(true);
|
|
91
|
+
key.destroy();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('is deterministic for the same inputs and diverges on the password, the salt and the version', async () => {
|
|
95
|
+
await registerNodeRsArgon2id();
|
|
96
|
+
const deriver = resolveArgon2idDeriver();
|
|
97
|
+
const hex = (bytes: Uint8Array): string => Buffer.from(bytes).toString('hex');
|
|
98
|
+
|
|
99
|
+
const base = hex(deriver({ password: password('pw-one'), salt: SALT, params: FAST, keyLength: 32, version: 0x13 }));
|
|
100
|
+
// Re-deriving must reproduce the key, or no sealed envelope could ever be opened.
|
|
101
|
+
expect(hex(deriver({ password: password('pw-one'), salt: SALT, params: FAST, keyLength: 32, version: 0x13 }))).toBe(base);
|
|
102
|
+
expect(hex(deriver({ password: password('pw-two'), salt: SALT, params: FAST, keyLength: 32, version: 0x13 }))).not.toBe(base);
|
|
103
|
+
expect(
|
|
104
|
+
hex(deriver({ password: password('pw-one'), salt: new Uint8Array(16).fill(9), params: FAST, keyLength: 32, version: 0x13 })),
|
|
105
|
+
).not.toBe(base);
|
|
106
|
+
// The reason the envelope records `ver` at all: 0x10 and 0x13 are different
|
|
107
|
+
// functions of the same inputs, and this is the proof the binding honors it.
|
|
108
|
+
expect(hex(deriver({ password: password('pw-one'), salt: SALT, params: FAST, keyLength: 32, version: 0x10 }))).not.toBe(base);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('refuses a binding that hands back a string — key material that cannot be zeroized', () => {
|
|
112
|
+
const stringy = { hashRawSync: () => 'deadbeef' } as unknown as RawArgon2idBinding;
|
|
113
|
+
expect(() =>
|
|
114
|
+
argon2idDeriverFrom(stringy)({ password: password('x'), salt: SALT, params: FAST, keyLength: 32 }),
|
|
115
|
+
).toThrow(/must be raw bytes/);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wiring a REAL argon2id into the KDF seam (01 §3.3/§3.4, finding 1.2).
|
|
3
|
+
*
|
|
4
|
+
* cwip ships no KDF — it is zero-dependency by contract — so
|
|
5
|
+
* `setArgon2idDeriver` sat inert: nothing registered a binding, and every
|
|
6
|
+
* `unlock()` would have thrown `GUARD_KDF_UNAVAILABLE` the first time a real
|
|
7
|
+
* user tried it in production. `assertArgon2idReady()` converts that into a boot
|
|
8
|
+
* failure; this module is the other half — the adapter that makes registering a
|
|
9
|
+
* binding a one-liner, and the reason a host has no excuse to hand-roll one.
|
|
10
|
+
*
|
|
11
|
+
* The requirements on a binding are not negotiable, and the adapter enforces
|
|
12
|
+
* them rather than documenting them:
|
|
13
|
+
*
|
|
14
|
+
* * **Raw bytes out.** A binding that returns a PHC/base64 STRING is a
|
|
15
|
+
* `key-material-as-string` violation regardless of how strong the KDF is —
|
|
16
|
+
* a JS string is immutable and GC-managed, so the key lingers in
|
|
17
|
+
* unallocated RAM until the collector happens to overwrite it.
|
|
18
|
+
* * **Caller-supplied salt.** Anything that generates its own salt (e.g.
|
|
19
|
+
* `Bun.password`) cannot re-derive the key for an existing envelope, so it
|
|
20
|
+
* cannot open a secret it sealed yesterday.
|
|
21
|
+
* * **Synchronous.** The seam is sync on purpose and its throughput ceiling is
|
|
22
|
+
* documented on `Argon2idDeriver`; an async binding would hand back a
|
|
23
|
+
* promise AND (usually) a string.
|
|
24
|
+
*/
|
|
25
|
+
import {
|
|
26
|
+
type Argon2idDeriver,
|
|
27
|
+
type Argon2idInput,
|
|
28
|
+
assertArgon2idReady,
|
|
29
|
+
CC_ARGON2ID_VERSION,
|
|
30
|
+
setArgon2idDeriver,
|
|
31
|
+
} from 'cwip/guard';
|
|
32
|
+
import { AppError } from '../errors';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The shape of a raw, synchronous argon2id binding, in the REFERENCE
|
|
36
|
+
* implementation's vocabulary: `algorithm` is the argon2 variant number
|
|
37
|
+
* (2 = argon2id) and `version` is the argon2 algorithm version as the spec
|
|
38
|
+
* writes it (`0x10` / `0x13`) — the same number the envelope's `kdf.ver`
|
|
39
|
+
* records.
|
|
40
|
+
*
|
|
41
|
+
* `@node-rs/argon2`'s `hashRawSync` is shape-compatible but NOT
|
|
42
|
+
* vocabulary-compatible: its `version` is a NAPI enum (`Version.V0x10 = 0`,
|
|
43
|
+
* `Version.V0x13 = 1`), so handing it `0x13` is an `InvalidArg` throw on every
|
|
44
|
+
* single derivation. {@link nodeRsArgon2idBinding} is the (tiny) translation,
|
|
45
|
+
* and {@link registerNodeRsArgon2id} applies it — libsodium's `crypto_pwhash`
|
|
46
|
+
* needs a comparable shim.
|
|
47
|
+
*/
|
|
48
|
+
export interface RawArgon2idBinding {
|
|
49
|
+
hashRawSync(
|
|
50
|
+
password: Uint8Array,
|
|
51
|
+
options: {
|
|
52
|
+
salt: Uint8Array;
|
|
53
|
+
/** Memory cost in KiB. */
|
|
54
|
+
memoryCost: number;
|
|
55
|
+
/** Time cost (iterations). */
|
|
56
|
+
timeCost: number;
|
|
57
|
+
/** Parallelism (lanes). */
|
|
58
|
+
parallelism: number;
|
|
59
|
+
/** Output length in bytes. */
|
|
60
|
+
outputLen: number;
|
|
61
|
+
/** argon2 variant: 2 = argon2id. */
|
|
62
|
+
algorithm?: number;
|
|
63
|
+
/** argon2 algorithm version (0x10 | 0x13). */
|
|
64
|
+
version?: number;
|
|
65
|
+
},
|
|
66
|
+
): Uint8Array;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** argon2id's variant number in the `@node-rs/argon2` / reference-implementation vocabulary. */
|
|
70
|
+
export const ARGON2ID_ALGORITHM = 2;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Build a deriver from a raw binding, checking the two properties that make it
|
|
74
|
+
* usable as key material. The length check is not paranoia: a binding that
|
|
75
|
+
* silently truncates would weaken every secret sealed under it, and the failure
|
|
76
|
+
* would look like nothing at all.
|
|
77
|
+
*/
|
|
78
|
+
export const argon2idDeriverFrom = (binding: RawArgon2idBinding): Argon2idDeriver => {
|
|
79
|
+
return (input: Argon2idInput): Uint8Array => {
|
|
80
|
+
const output = binding.hashRawSync(input.password, {
|
|
81
|
+
salt: input.salt,
|
|
82
|
+
memoryCost: input.params.m,
|
|
83
|
+
timeCost: input.params.t,
|
|
84
|
+
parallelism: input.params.p,
|
|
85
|
+
outputLen: input.keyLength,
|
|
86
|
+
algorithm: ARGON2ID_ALGORITHM,
|
|
87
|
+
// Honor the version the ENVELOPE was sealed with, not the binding's
|
|
88
|
+
// compile-time default: 0x10 and 0x13 produce different bytes for
|
|
89
|
+
// identical m/t/p/salt/password, which is why the envelope records it.
|
|
90
|
+
version: input.version ?? CC_ARGON2ID_VERSION,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (typeof output === 'string')
|
|
94
|
+
throw new AppError(
|
|
95
|
+
'The registered argon2id binding returned a string. Key material must be raw bytes — a string cannot be zeroized (01 §3.4).',
|
|
96
|
+
{ code: 'GUARD_KDF_BINDING_INVALID', status: 500, category: 'security', isOperational: false },
|
|
97
|
+
);
|
|
98
|
+
if (!(output instanceof Uint8Array))
|
|
99
|
+
throw new AppError('The registered argon2id binding did not return a Uint8Array.', {
|
|
100
|
+
code: 'GUARD_KDF_BINDING_INVALID',
|
|
101
|
+
status: 500,
|
|
102
|
+
category: 'security',
|
|
103
|
+
isOperational: false,
|
|
104
|
+
});
|
|
105
|
+
return output;
|
|
106
|
+
};
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* `@node-rs/argon2`'s `Version` enum, keyed by the argon2 version number the
|
|
111
|
+
* ENVELOPE records. Held here rather than imported so the adapter never has to
|
|
112
|
+
* load the optional native module to know how to talk to it.
|
|
113
|
+
*/
|
|
114
|
+
// 0x10 and 0x13 ARE the Argon2 version identifiers: the spec, the reference
|
|
115
|
+
// implementation and every envelope on disk write them in hex. Biome's autofix
|
|
116
|
+
// rewrites them as 16 and 19 — the same map and a worse one, because nobody
|
|
117
|
+
// reading an `$argon2id$v=19$` record would recognise the decimal.
|
|
118
|
+
// biome-ignore lint/complexity/useSimpleNumberKeys: see above — hex IS the spec's spelling
|
|
119
|
+
const NODE_RS_VERSION: Readonly<Record<number, number>> = { 0x10: 0, 0x13: 1 };
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Translate a `@node-rs/argon2` module into a {@link RawArgon2idBinding}.
|
|
123
|
+
*
|
|
124
|
+
* The whole translation is the `version` field, and skipping it is not a
|
|
125
|
+
* cosmetic bug: node-rs validates the enum, so an unmapped `0x13` throws
|
|
126
|
+
* `InvalidArg` from the native layer on EVERY derivation — the first unlock, the
|
|
127
|
+
* first login, every password verify. That failure is invisible until a host
|
|
128
|
+
* actually registers this binding, which is exactly what the local host now does
|
|
129
|
+
* at boot.
|
|
130
|
+
*/
|
|
131
|
+
export const nodeRsArgon2idBinding = (module: RawArgon2idBinding): RawArgon2idBinding => ({
|
|
132
|
+
hashRawSync(password, options) {
|
|
133
|
+
const declared = options.version ?? CC_ARGON2ID_VERSION;
|
|
134
|
+
const mapped = NODE_RS_VERSION[declared];
|
|
135
|
+
if (mapped === undefined)
|
|
136
|
+
throw new AppError(
|
|
137
|
+
`argon2 version 0x${declared.toString(16)} is not one @node-rs/argon2 implements (0x10 or 0x13). The envelope asking for it cannot be opened by this binding — reseal it, or register a binding that supports the version.`,
|
|
138
|
+
{
|
|
139
|
+
code: 'GUARD_KDF_BINDING_INVALID',
|
|
140
|
+
status: 500,
|
|
141
|
+
category: 'security',
|
|
142
|
+
isOperational: false,
|
|
143
|
+
context: { version: declared },
|
|
144
|
+
},
|
|
145
|
+
);
|
|
146
|
+
return module.hashRawSync(password, { ...options, version: mapped });
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
/** Register a raw binding as the process-wide deriver, then prove the seam is armed. */
|
|
151
|
+
export const registerArgon2idBinding = (binding: RawArgon2idBinding): void => {
|
|
152
|
+
setArgon2idDeriver(argon2idDeriverFrom(binding));
|
|
153
|
+
// Fail here rather than at the first unlock: this call is the whole point.
|
|
154
|
+
assertArgon2idReady();
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Register `@node-rs/argon2` if the host installed it.
|
|
159
|
+
*
|
|
160
|
+
* It is an OPTIONAL peer dependency: a generated app with no GuardEngine surface
|
|
161
|
+
* should not carry a native module, and an app that has one must make the choice
|
|
162
|
+
* explicitly. The specifier is held in a variable so bundlers and `tsc` do not
|
|
163
|
+
* try to resolve a package that may not be there.
|
|
164
|
+
*
|
|
165
|
+
* @throws {AppError} `GUARD_KDF_BINDING_MISSING` naming the install command —
|
|
166
|
+
* an unhelpful `ERR_MODULE_NOT_FOUND` at boot is exactly the failure this
|
|
167
|
+
* module exists to prevent.
|
|
168
|
+
*
|
|
169
|
+
* The same indirection that hides the specifier from a bundler hides it from
|
|
170
|
+
* every static import walker, so the guard's peer map would report
|
|
171
|
+
* this edge as absent. The pragma is what puts it back.
|
|
172
|
+
*
|
|
173
|
+
* @peer @node-rs/argon2
|
|
174
|
+
*/
|
|
175
|
+
export const registerNodeRsArgon2id = async (): Promise<void> => {
|
|
176
|
+
const specifier = '@node-rs/argon2';
|
|
177
|
+
let loaded: { hashRawSync?: unknown };
|
|
178
|
+
try {
|
|
179
|
+
loaded = (await import(specifier)) as { hashRawSync?: unknown };
|
|
180
|
+
} catch (cause) {
|
|
181
|
+
throw new AppError(
|
|
182
|
+
'GuardEngine needs an argon2id binding and `@node-rs/argon2` is not installed. Run `bun add @node-rs/argon2` in the app, or call registerArgon2idBinding(...) with your own raw, synchronous binding.',
|
|
183
|
+
{ code: 'GUARD_KDF_BINDING_MISSING', status: 500, category: 'security', isOperational: false, cause },
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
if (typeof loaded.hashRawSync !== 'function')
|
|
187
|
+
throw new AppError('`@node-rs/argon2` is installed but exposes no `hashRawSync` — it is too old for the raw-bytes seam.', {
|
|
188
|
+
code: 'GUARD_KDF_BINDING_INVALID',
|
|
189
|
+
status: 500,
|
|
190
|
+
category: 'security',
|
|
191
|
+
isOperational: false,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
registerArgon2idBinding(nodeRsArgon2idBinding(loaded as unknown as RawArgon2idBinding));
|
|
195
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* argon2id KNOWN-ANSWER TESTS (05 §B6).
|
|
3
|
+
*
|
|
4
|
+
* Every other guard suite in this repo injects a FAKE deriver — `guard.spec.ts`
|
|
5
|
+
* says so in its header — and `argon2Binding.spec.ts` drives the real native
|
|
6
|
+
* module but only asserts SHAPE properties: right length, non-zero, deterministic,
|
|
7
|
+
* diverges on password/salt/version. All of that is equally true of a wrong
|
|
8
|
+
* KDF. Until this file existed, "does the KDF produce a CORRECT key" had zero
|
|
9
|
+
* evidence anywhere in the monorepo — a binding that computed argon2i, or the
|
|
10
|
+
* wrong argon2 version, or silently ignored the parallelism lane count would
|
|
11
|
+
* have passed every gate we had while sealing every envelope in the product with
|
|
12
|
+
* keys no other argon2 implementation can reproduce.
|
|
13
|
+
*
|
|
14
|
+
* That failure mode is not hypothetical and it is not recoverable. Key material
|
|
15
|
+
* is written into envelopes at rest; by the time an interoperability problem
|
|
16
|
+
* surfaces, the secrets sealed under the wrong function are the customer's, and
|
|
17
|
+
* "reseal everything" needs the plaintext we no longer have.
|
|
18
|
+
*
|
|
19
|
+
* Two independent kinds of evidence, because each covers the other's blind spot:
|
|
20
|
+
*
|
|
21
|
+
* 1. **Reference vectors** (`REFERENCE_VECTORS`) — fixed inputs and fixed
|
|
22
|
+
* outputs from the argon2 REFERENCE implementation's own test suite
|
|
23
|
+
* (`phc-winner-argon2`, `src/test.c`, the `ARGON2_VERSION_NUMBER = 0x13`
|
|
24
|
+
* block). These pin the absolute function: no implementation of ours can
|
|
25
|
+
* satisfy them by being self-consistent.
|
|
26
|
+
* 2. **Cross-implementation agreement** (the last describe) — Bun's built-in
|
|
27
|
+
* argon2id against the registered `@node-rs/argon2` binding on the SAME salt
|
|
28
|
+
* and parameters. Two unrelated codebases (Zig/C vs Rust) agreeing bit for
|
|
29
|
+
* bit is what catches a vector transcription error, which a fixed table
|
|
30
|
+
* cannot catch about itself.
|
|
31
|
+
*
|
|
32
|
+
* The negative controls matter as much as the positives: argon2**i** and version
|
|
33
|
+
* **0x10** are wrong answers that are otherwise indistinguishable from right
|
|
34
|
+
* ones — same length, same determinism, same divergence behavior — so the
|
|
35
|
+
* vectors assert they produce DIFFERENT bytes. Without those two rows a binding
|
|
36
|
+
* defaulting to the wrong variant or the wrong version passes this file.
|
|
37
|
+
*
|
|
38
|
+
* Everything runs through `deriveArgon2idKey`, the entry point every envelope,
|
|
39
|
+
* row signature and password verify uses — not against the module directly.
|
|
40
|
+
* A KAT that bypasses the shipped seam proves the library and not the product.
|
|
41
|
+
*/
|
|
42
|
+
import { afterEach, describe, expect, it } from 'bun:test';
|
|
43
|
+
import { deriveArgon2idKey, resolveArgon2idDeriver, setArgon2idDeriver } from 'cwip/guard';
|
|
44
|
+
import { type Algorithm, hashRawSync, type Version } from '@node-rs/argon2';
|
|
45
|
+
|
|
46
|
+
import { argon2idDeriverFrom, nodeRsArgon2idBinding, registerNodeRsArgon2id } from './argon2Binding';
|
|
47
|
+
/*
|
|
48
|
+
* `Algorithm` and `Version` are declared as AMBIENT CONST ENUMS in the binding's
|
|
49
|
+
* `.d.ts`, and `verbatimModuleSyntax` refuses to read a member off one — the
|
|
50
|
+
* import that would carry it is erased, so TypeScript cannot prove the object
|
|
51
|
+
* survives to runtime. It does: napi-rs emits both onto `module.exports`, which
|
|
52
|
+
* is why these lines have always RUN correctly and only ever failed to compile.
|
|
53
|
+
*
|
|
54
|
+
* So the enums come in as TYPES and the ordinals are named here, checked against
|
|
55
|
+
* the binding below. **The ordinals are NOT the argon2 wire constants** —
|
|
56
|
+
* `Version.V0x13` is `1`, not `19` — which is exactly the mistake a reader
|
|
57
|
+
* inlining "0x13" from the member's name would make, and it would quietly
|
|
58
|
+
* compute a different KDF rather than fail.
|
|
59
|
+
*/
|
|
60
|
+
const ARGON2ID = 2 as Algorithm;
|
|
61
|
+
const ARGON2I = 1 as Algorithm;
|
|
62
|
+
const V0X13 = 1 as Version;
|
|
63
|
+
const V0X10 = 0 as Version;
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
const enc = new TextEncoder();
|
|
67
|
+
const hex = (bytes: Uint8Array): string => Buffer.from(bytes).toString('hex');
|
|
68
|
+
|
|
69
|
+
// The seam is process-wide; never leak a registration into another suite.
|
|
70
|
+
afterEach(() => setArgon2idDeriver(null));
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* argon2id, version 0x13, password `"password"`, salt `"somesalt"`, 32-byte tag —
|
|
74
|
+
* the reference implementation's own `hashtest(version, t, log2(m), p, …)` rows.
|
|
75
|
+
*
|
|
76
|
+
* The `m` column is written as the KiB value the envelope records, which is
|
|
77
|
+
* `2^log2m` in the reference suite's vocabulary: 2^8 = 256, 2^16 = 65536,
|
|
78
|
+
* 2^18 = 262144. Getting that exponent↔value translation wrong is precisely the
|
|
79
|
+
* kind of mistake this table exists to catch, so it is spelled out rather than
|
|
80
|
+
* computed.
|
|
81
|
+
*/
|
|
82
|
+
const REFERENCE_VECTORS: readonly { m: number; t: number; p: number; tag: string }[] = [
|
|
83
|
+
{ m: 65_536, t: 2, p: 1, tag: '09316115d5cf24ed5a15a31a3ba326e5cf32edc24702987c02b6566f61913cf7' },
|
|
84
|
+
{ m: 262_144, t: 2, p: 1, tag: '78fe1ec91fb3aa5657d72e710854e4c3d9b9198c742f9616c2f085bed95b2e8c' },
|
|
85
|
+
{ m: 256, t: 2, p: 1, tag: '9dfeb910e80bad0311fee20f9c0e2b12c17987b4cac90c2ef54d5b3021c68bfe' },
|
|
86
|
+
{ m: 256, t: 2, p: 2, tag: '6d093c501fd5999645e0ea3bf620d7b8be7fd2db59c20d9fff9539da2bf57037' },
|
|
87
|
+
{ m: 65_536, t: 1, p: 1, tag: 'f6a5adc1ba723dddef9b5ac1d464e180fcd9dffc9d1cbf76cca2fed795d9ca98' },
|
|
88
|
+
{ m: 65_536, t: 4, p: 1, tag: '9025d48e68ef7395cca9079da4c4ec3affb3c8911fe4f86d1a2520856f63172c' },
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const PASSWORD = enc.encode('password');
|
|
92
|
+
const SALT = enc.encode('somesalt');
|
|
93
|
+
|
|
94
|
+
describe('argon2id known-answer vectors — the shipped seam computes argon2id 0x13', () => {
|
|
95
|
+
for (const { m, t, p, tag } of REFERENCE_VECTORS) {
|
|
96
|
+
it(`m=${m} t=${t} p=${p} reproduces the reference tag`, async () => {
|
|
97
|
+
await registerNodeRsArgon2id();
|
|
98
|
+
|
|
99
|
+
// Through `deriveArgon2idKey`, so the KAT covers the parameter validation,
|
|
100
|
+
// the version default and the length check as well as the primitive.
|
|
101
|
+
const key = deriveArgon2idKey({
|
|
102
|
+
password: PASSWORD,
|
|
103
|
+
salt: SALT,
|
|
104
|
+
params: { m, t, p },
|
|
105
|
+
keyLength: 32,
|
|
106
|
+
label: 'kat',
|
|
107
|
+
});
|
|
108
|
+
const derived = key.use((raw) => hex(raw));
|
|
109
|
+
key.destroy();
|
|
110
|
+
|
|
111
|
+
expect(derived).toBe(tag);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
it('is NOT argon2i — the variant selector reaches the binding', () => {
|
|
116
|
+
// Same inputs as the first reference row. argon2i is a real, strong KDF that
|
|
117
|
+
// fails no shape assertion in `argon2Binding.spec.ts`; only a vector tells
|
|
118
|
+
// the two apart, and `algorithm: 2` is the single field that decides which
|
|
119
|
+
// one every envelope in the product is sealed with.
|
|
120
|
+
const argon2i = hashRawSync(PASSWORD, {
|
|
121
|
+
salt: SALT,
|
|
122
|
+
memoryCost: 65_536,
|
|
123
|
+
timeCost: 2,
|
|
124
|
+
parallelism: 1,
|
|
125
|
+
outputLen: 32,
|
|
126
|
+
algorithm: ARGON2I,
|
|
127
|
+
version: V0X13,
|
|
128
|
+
});
|
|
129
|
+
expect(hex(argon2i)).not.toBe(REFERENCE_VECTORS[0]?.tag);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('is NOT version 0x10 — the envelope’s recorded version reaches the binding', () => {
|
|
133
|
+
// The whole reason `kdf.ver` is recorded per envelope (see CC_ARGON2ID_VERSION).
|
|
134
|
+
// 0x10 and 0x13 are different functions of identical inputs, and a binding
|
|
135
|
+
// that ignored the field would seal secrets no 0x13 reader can open.
|
|
136
|
+
const v0x10 = hashRawSync(PASSWORD, {
|
|
137
|
+
salt: SALT,
|
|
138
|
+
memoryCost: 65_536,
|
|
139
|
+
timeCost: 2,
|
|
140
|
+
parallelism: 1,
|
|
141
|
+
outputLen: 32,
|
|
142
|
+
algorithm: ARGON2ID,
|
|
143
|
+
version: V0X10,
|
|
144
|
+
});
|
|
145
|
+
expect(hex(v0x10)).not.toBe(REFERENCE_VECTORS[0]?.tag);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('cross-implementation agreement — Bun’s argon2id vs the registered binding', () => {
|
|
150
|
+
/**
|
|
151
|
+
* The vectors above are transcribed by a human; this test is not. Bun ships
|
|
152
|
+
* its own argon2id (a different language, a different codebase, no shared
|
|
153
|
+
* lineage with the Rust `argon2` crate behind `@node-rs/argon2`), and its PHC
|
|
154
|
+
* output RECORDS the salt it chose — which is what makes the comparison
|
|
155
|
+
* possible at all, since `Bun.password` generates its own salt and cannot be
|
|
156
|
+
* asked to re-derive.
|
|
157
|
+
*
|
|
158
|
+
* So: let Bun hash, read back the salt and parameters it used, and drive the
|
|
159
|
+
* registered binding with them. Two implementations landing on the same 32
|
|
160
|
+
* bytes is independent of anything either this file or the adapter believes.
|
|
161
|
+
*/
|
|
162
|
+
const parsePhc = (
|
|
163
|
+
phc: string,
|
|
164
|
+
): { version: number; m: number; t: number; p: number; salt: Uint8Array; tag: Uint8Array } => {
|
|
165
|
+
const match = /^\$argon2id\$v=(\d+)\$m=(\d+),t=(\d+),p=(\d+)\$([^$]+)\$([^$]+)$/.exec(phc);
|
|
166
|
+
if (match === null) throw new Error(`Bun produced a PHC string this test cannot parse: ${phc}`);
|
|
167
|
+
// PHC uses unpadded base64url.
|
|
168
|
+
const b64 = (value: string): Uint8Array =>
|
|
169
|
+
new Uint8Array(
|
|
170
|
+
Buffer.from(value.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (value.length % 4)) % 4), 'base64'),
|
|
171
|
+
);
|
|
172
|
+
return {
|
|
173
|
+
version: Number(match[1]),
|
|
174
|
+
m: Number(match[2]),
|
|
175
|
+
t: Number(match[3]),
|
|
176
|
+
p: Number(match[4]),
|
|
177
|
+
salt: b64(match[5] as string),
|
|
178
|
+
tag: b64(match[6] as string),
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
it('derives byte-identical keys from the same password, salt and parameters', async () => {
|
|
183
|
+
const phc = await Bun.password.hash('correct horse battery staple', {
|
|
184
|
+
algorithm: 'argon2id',
|
|
185
|
+
memoryCost: 2_048,
|
|
186
|
+
timeCost: 2,
|
|
187
|
+
});
|
|
188
|
+
const { version, m, t, p, salt, tag } = parsePhc(phc);
|
|
189
|
+
// 19 === 0x13. If Bun ever defaults to something else, the comparison below
|
|
190
|
+
// would be against a different function and must not silently pass.
|
|
191
|
+
expect(version).toBe(0x13);
|
|
192
|
+
|
|
193
|
+
await registerNodeRsArgon2id();
|
|
194
|
+
const ours = resolveArgon2idDeriver()({
|
|
195
|
+
password: enc.encode('correct horse battery staple'),
|
|
196
|
+
salt,
|
|
197
|
+
params: { m, t, p },
|
|
198
|
+
keyLength: tag.byteLength,
|
|
199
|
+
version,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
expect(hex(ours)).toBe(hex(tag));
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('disagrees when the salt differs — proving the agreement above is not vacuous', async () => {
|
|
206
|
+
// A comparison that passes no matter what is worse than no comparison. If
|
|
207
|
+
// the binding ignored the salt (or the parser handed it the wrong bytes),
|
|
208
|
+
// the previous test would still be green; this one would not.
|
|
209
|
+
const phc = await Bun.password.hash('correct horse battery staple', {
|
|
210
|
+
algorithm: 'argon2id',
|
|
211
|
+
memoryCost: 2_048,
|
|
212
|
+
timeCost: 2,
|
|
213
|
+
});
|
|
214
|
+
const { m, t, p, salt, tag } = parsePhc(phc);
|
|
215
|
+
|
|
216
|
+
const tampered = new Uint8Array(salt);
|
|
217
|
+
tampered[0] = (tampered[0] ?? 0) ^ 0xff;
|
|
218
|
+
|
|
219
|
+
// Through the same translation the shipping path uses — node-rs's `version`
|
|
220
|
+
// is a NAPI enum, so a raw `{ hashRawSync }` would throw `InvalidArg`.
|
|
221
|
+
const deriver = argon2idDeriverFrom(nodeRsArgon2idBinding({ hashRawSync }));
|
|
222
|
+
const ours = deriver({
|
|
223
|
+
password: enc.encode('correct horse battery staple'),
|
|
224
|
+
salt: tampered,
|
|
225
|
+
params: { m, t, p },
|
|
226
|
+
keyLength: tag.byteLength,
|
|
227
|
+
version: 0x13,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(hex(ours)).not.toBe(hex(tag));
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
/*
|
|
234
|
+
* The ordinals above are inlined because `verbatimModuleSyntax` cannot read a
|
|
235
|
+
* member off an ambient const enum. That is a copy, and a copy needs a gate: if
|
|
236
|
+
* a napi-rs regeneration ever renumbers `Algorithm` or `Version`, every KAT
|
|
237
|
+
* above would keep passing while silently exercising a different variant.
|
|
238
|
+
* Reading the RUNTIME objects here is what makes the copy safe.
|
|
239
|
+
*/
|
|
240
|
+
it('the inlined enum ordinals still match the binding', async () => {
|
|
241
|
+
const binding = (await import('@node-rs/argon2')) as unknown as {
|
|
242
|
+
Algorithm: Record<string, number>;
|
|
243
|
+
Version: Record<string, number>;
|
|
244
|
+
};
|
|
245
|
+
expect(binding.Algorithm.Argon2id).toBe(ARGON2ID as unknown as number);
|
|
246
|
+
expect(binding.Algorithm.Argon2i).toBe(ARGON2I as unknown as number);
|
|
247
|
+
expect(binding.Version.V0x13).toBe(V0X13 as unknown as number);
|
|
248
|
+
expect(binding.Version.V0x10).toBe(V0X10 as unknown as number);
|
|
249
|
+
});
|
|
250
|
+
});
|