@dotdo/postgres 0.1.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/README.md +868 -0
- package/dist/cdc/change-stream.d.ts +44 -0
- package/dist/cdc/change-stream.d.ts.map +1 -0
- package/dist/cdc/change-stream.js +95 -0
- package/dist/cdc/change-stream.js.map +1 -0
- package/dist/cdc/filter.d.ts +58 -0
- package/dist/cdc/filter.d.ts.map +1 -0
- package/dist/cdc/filter.js +520 -0
- package/dist/cdc/filter.js.map +1 -0
- package/dist/cdc/index.d.ts +47 -0
- package/dist/cdc/index.d.ts.map +1 -0
- package/dist/cdc/index.js +50 -0
- package/dist/cdc/index.js.map +1 -0
- package/dist/cdc/resume-token.d.ts +60 -0
- package/dist/cdc/resume-token.d.ts.map +1 -0
- package/dist/cdc/resume-token.js +228 -0
- package/dist/cdc/resume-token.js.map +1 -0
- package/dist/cdc/transport/index.d.ts +7 -0
- package/dist/cdc/transport/index.d.ts.map +1 -0
- package/dist/cdc/transport/index.js +7 -0
- package/dist/cdc/transport/index.js.map +1 -0
- package/dist/cdc/transport/sse.d.ts +120 -0
- package/dist/cdc/transport/sse.d.ts.map +1 -0
- package/dist/cdc/transport/sse.js +590 -0
- package/dist/cdc/transport/sse.js.map +1 -0
- package/dist/cdc/transport/websocket.d.ts +130 -0
- package/dist/cdc/transport/websocket.d.ts.map +1 -0
- package/dist/cdc/transport/websocket.js +688 -0
- package/dist/cdc/transport/websocket.js.map +1 -0
- package/dist/cdc/types.d.ts +306 -0
- package/dist/cdc/types.d.ts.map +1 -0
- package/dist/cdc/types.js +8 -0
- package/dist/cdc/types.js.map +1 -0
- package/dist/config/index.d.ts +25 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +25 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/memory.d.ts +139 -0
- package/dist/config/memory.d.ts.map +1 -0
- package/dist/config/memory.js +157 -0
- package/dist/config/memory.js.map +1 -0
- package/dist/config/storage.d.ts +157 -0
- package/dist/config/storage.d.ts.map +1 -0
- package/dist/config/storage.js +178 -0
- package/dist/config/storage.js.map +1 -0
- package/dist/config/streaming.d.ts +117 -0
- package/dist/config/streaming.d.ts.map +1 -0
- package/dist/config/streaming.js +132 -0
- package/dist/config/streaming.js.map +1 -0
- package/dist/config/timeouts.d.ts +168 -0
- package/dist/config/timeouts.d.ts.map +1 -0
- package/dist/config/timeouts.js +192 -0
- package/dist/config/timeouts.js.map +1 -0
- package/dist/extensions/config.d.ts +89 -0
- package/dist/extensions/config.d.ts.map +1 -0
- package/dist/extensions/config.js +216 -0
- package/dist/extensions/config.js.map +1 -0
- package/dist/extensions/geo.d.ts +452 -0
- package/dist/extensions/geo.d.ts.map +1 -0
- package/dist/extensions/geo.js +583 -0
- package/dist/extensions/geo.js.map +1 -0
- package/dist/extensions/index.d.ts +167 -0
- package/dist/extensions/index.d.ts.map +1 -0
- package/dist/extensions/index.js +99 -0
- package/dist/extensions/index.js.map +1 -0
- package/dist/extensions/loader.d.ts +226 -0
- package/dist/extensions/loader.d.ts.map +1 -0
- package/dist/extensions/loader.js +456 -0
- package/dist/extensions/loader.js.map +1 -0
- package/dist/extensions/pgmq-lite.d.ts +330 -0
- package/dist/extensions/pgmq-lite.d.ts.map +1 -0
- package/dist/extensions/pgmq-lite.js +648 -0
- package/dist/extensions/pgmq-lite.js.map +1 -0
- package/dist/extensions/plugins.d.ts +260 -0
- package/dist/extensions/plugins.d.ts.map +1 -0
- package/dist/extensions/plugins.js +535 -0
- package/dist/extensions/plugins.js.map +1 -0
- package/dist/extensions/registry.d.ts +93 -0
- package/dist/extensions/registry.d.ts.map +1 -0
- package/dist/extensions/registry.js +182 -0
- package/dist/extensions/registry.js.map +1 -0
- package/dist/extensions/vector.d.ts +106 -0
- package/dist/extensions/vector.d.ts.map +1 -0
- package/dist/extensions/vector.js +129 -0
- package/dist/extensions/vector.js.map +1 -0
- package/dist/iceberg/analytics.d.ts +279 -0
- package/dist/iceberg/analytics.d.ts.map +1 -0
- package/dist/iceberg/analytics.js +448 -0
- package/dist/iceberg/analytics.js.map +1 -0
- package/dist/iceberg/catalog-api.d.ts +39 -0
- package/dist/iceberg/catalog-api.d.ts.map +1 -0
- package/dist/iceberg/catalog-api.js +388 -0
- package/dist/iceberg/catalog-api.js.map +1 -0
- package/dist/iceberg/catalog.d.ts +401 -0
- package/dist/iceberg/catalog.d.ts.map +1 -0
- package/dist/iceberg/catalog.js +677 -0
- package/dist/iceberg/catalog.js.map +1 -0
- package/dist/iceberg/duckdb-wasm.d.ts +447 -0
- package/dist/iceberg/duckdb-wasm.d.ts.map +1 -0
- package/dist/iceberg/duckdb-wasm.js +600 -0
- package/dist/iceberg/duckdb-wasm.js.map +1 -0
- package/dist/iceberg/index.d.ts +92 -0
- package/dist/iceberg/index.d.ts.map +1 -0
- package/dist/iceberg/index.js +119 -0
- package/dist/iceberg/index.js.map +1 -0
- package/dist/iceberg/metadata.d.ts +214 -0
- package/dist/iceberg/metadata.d.ts.map +1 -0
- package/dist/iceberg/metadata.js +535 -0
- package/dist/iceberg/metadata.js.map +1 -0
- package/dist/iceberg/optimizer.d.ts +296 -0
- package/dist/iceberg/optimizer.d.ts.map +1 -0
- package/dist/iceberg/optimizer.js +889 -0
- package/dist/iceberg/optimizer.js.map +1 -0
- package/dist/iceberg/parquet.d.ts +447 -0
- package/dist/iceberg/parquet.d.ts.map +1 -0
- package/dist/iceberg/parquet.js +1225 -0
- package/dist/iceberg/parquet.js.map +1 -0
- package/dist/iceberg/r2-organization.d.ts +422 -0
- package/dist/iceberg/r2-organization.d.ts.map +1 -0
- package/dist/iceberg/r2-organization.js +672 -0
- package/dist/iceberg/r2-organization.js.map +1 -0
- package/dist/iceberg/scheduler-do-example.d.ts +158 -0
- package/dist/iceberg/scheduler-do-example.d.ts.map +1 -0
- package/dist/iceberg/scheduler-do-example.js +261 -0
- package/dist/iceberg/scheduler-do-example.js.map +1 -0
- package/dist/iceberg/scheduler.d.ts +434 -0
- package/dist/iceberg/scheduler.d.ts.map +1 -0
- package/dist/iceberg/scheduler.js +818 -0
- package/dist/iceberg/scheduler.js.map +1 -0
- package/dist/iceberg/schema.d.ts +149 -0
- package/dist/iceberg/schema.d.ts.map +1 -0
- package/dist/iceberg/schema.js +525 -0
- package/dist/iceberg/schema.js.map +1 -0
- package/dist/iceberg/snapshot-manager.d.ts +406 -0
- package/dist/iceberg/snapshot-manager.d.ts.map +1 -0
- package/dist/iceberg/snapshot-manager.js +934 -0
- package/dist/iceberg/snapshot-manager.js.map +1 -0
- package/dist/iceberg/sql-router.d.ts +194 -0
- package/dist/iceberg/sql-router.d.ts.map +1 -0
- package/dist/iceberg/sql-router.js +180 -0
- package/dist/iceberg/sql-router.js.map +1 -0
- package/dist/iceberg/test-fixtures.d.ts +151 -0
- package/dist/iceberg/test-fixtures.d.ts.map +1 -0
- package/dist/iceberg/test-fixtures.js +446 -0
- package/dist/iceberg/test-fixtures.js.map +1 -0
- package/dist/iceberg/time-travel-api.d.ts +102 -0
- package/dist/iceberg/time-travel-api.d.ts.map +1 -0
- package/dist/iceberg/time-travel-api.js +437 -0
- package/dist/iceberg/time-travel-api.js.map +1 -0
- package/dist/iceberg/time-travel.d.ts +293 -0
- package/dist/iceberg/time-travel.d.ts.map +1 -0
- package/dist/iceberg/time-travel.js +689 -0
- package/dist/iceberg/time-travel.js.map +1 -0
- package/dist/iceberg/transformer.d.ts +356 -0
- package/dist/iceberg/transformer.d.ts.map +1 -0
- package/dist/iceberg/transformer.js +770 -0
- package/dist/iceberg/transformer.js.map +1 -0
- package/dist/iceberg/types.d.ts +318 -0
- package/dist/iceberg/types.d.ts.map +1 -0
- package/dist/iceberg/types.js +9 -0
- package/dist/iceberg/types.js.map +1 -0
- package/dist/iceberg/writer.d.ts +144 -0
- package/dist/iceberg/writer.d.ts.map +1 -0
- package/dist/iceberg/writer.js +452 -0
- package/dist/iceberg/writer.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/lineage/index.d.ts +11 -0
- package/dist/lineage/index.d.ts.map +1 -0
- package/dist/lineage/index.js +11 -0
- package/dist/lineage/index.js.map +1 -0
- package/dist/lineage/integration.d.ts +134 -0
- package/dist/lineage/integration.d.ts.map +1 -0
- package/dist/lineage/integration.js +258 -0
- package/dist/lineage/integration.js.map +1 -0
- package/dist/lineage/tracker.d.ts +189 -0
- package/dist/lineage/tracker.d.ts.map +1 -0
- package/dist/lineage/tracker.js +1352 -0
- package/dist/lineage/tracker.js.map +1 -0
- package/dist/lineage/types.d.ts +318 -0
- package/dist/lineage/types.d.ts.map +1 -0
- package/dist/lineage/types.js +9 -0
- package/dist/lineage/types.js.map +1 -0
- package/dist/middleware/index.d.ts +11 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +16 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +397 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +507 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/migration-tooling/external-migration.d.ts +601 -0
- package/dist/migration-tooling/external-migration.d.ts.map +1 -0
- package/dist/migration-tooling/external-migration.js +1612 -0
- package/dist/migration-tooling/external-migration.js.map +1 -0
- package/dist/migration-tooling/index.d.ts +19 -0
- package/dist/migration-tooling/index.d.ts.map +1 -0
- package/dist/migration-tooling/index.js +19 -0
- package/dist/migration-tooling/index.js.map +1 -0
- package/dist/migrations/auto-migrator.d.ts +289 -0
- package/dist/migrations/auto-migrator.d.ts.map +1 -0
- package/dist/migrations/auto-migrator.js +396 -0
- package/dist/migrations/auto-migrator.js.map +1 -0
- package/dist/migrations/bulk-orchestrator.d.ts +403 -0
- package/dist/migrations/bulk-orchestrator.d.ts.map +1 -0
- package/dist/migrations/bulk-orchestrator.js +646 -0
- package/dist/migrations/bulk-orchestrator.js.map +1 -0
- package/dist/migrations/compatibility.d.ts +216 -0
- package/dist/migrations/compatibility.d.ts.map +1 -0
- package/dist/migrations/compatibility.js +651 -0
- package/dist/migrations/compatibility.js.map +1 -0
- package/dist/migrations/do-migrations.d.ts +101 -0
- package/dist/migrations/do-migrations.d.ts.map +1 -0
- package/dist/migrations/do-migrations.js +1060 -0
- package/dist/migrations/do-migrations.js.map +1 -0
- package/dist/migrations/do-migrations.types.d.ts +550 -0
- package/dist/migrations/do-migrations.types.d.ts.map +1 -0
- package/dist/migrations/do-migrations.types.js +15 -0
- package/dist/migrations/do-migrations.types.js.map +1 -0
- package/dist/migrations/drizzle-compat.d.ts +163 -0
- package/dist/migrations/drizzle-compat.d.ts.map +1 -0
- package/dist/migrations/drizzle-compat.js +273 -0
- package/dist/migrations/drizzle-compat.js.map +1 -0
- package/dist/migrations/index.d.ts +109 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +127 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/migration-api.d.ts +161 -0
- package/dist/migrations/migration-api.d.ts.map +1 -0
- package/dist/migrations/migration-api.js +499 -0
- package/dist/migrations/migration-api.js.map +1 -0
- package/dist/migrations/progress-tracker-do.d.ts +195 -0
- package/dist/migrations/progress-tracker-do.d.ts.map +1 -0
- package/dist/migrations/progress-tracker-do.js +339 -0
- package/dist/migrations/progress-tracker-do.js.map +1 -0
- package/dist/migrations/progress-tracker-kv.d.ts +103 -0
- package/dist/migrations/progress-tracker-kv.d.ts.map +1 -0
- package/dist/migrations/progress-tracker-kv.js +231 -0
- package/dist/migrations/progress-tracker-kv.js.map +1 -0
- package/dist/migrations/progress-tracker.d.ts +320 -0
- package/dist/migrations/progress-tracker.d.ts.map +1 -0
- package/dist/migrations/progress-tracker.js +443 -0
- package/dist/migrations/progress-tracker.js.map +1 -0
- package/dist/migrations/registry.d.ts +231 -0
- package/dist/migrations/registry.d.ts.map +1 -0
- package/dist/migrations/registry.js +376 -0
- package/dist/migrations/registry.js.map +1 -0
- package/dist/migrations/runner.d.ts +197 -0
- package/dist/migrations/runner.d.ts.map +1 -0
- package/dist/migrations/runner.js +1167 -0
- package/dist/migrations/runner.js.map +1 -0
- package/dist/migrations/schema-generator.d.ts +111 -0
- package/dist/migrations/schema-generator.d.ts.map +1 -0
- package/dist/migrations/schema-generator.js +335 -0
- package/dist/migrations/schema-generator.js.map +1 -0
- package/dist/migrations/testing.d.ts +321 -0
- package/dist/migrations/testing.d.ts.map +1 -0
- package/dist/migrations/testing.js +645 -0
- package/dist/migrations/testing.js.map +1 -0
- package/dist/migrations/types.d.ts +503 -0
- package/dist/migrations/types.d.ts.map +1 -0
- package/dist/migrations/types.js +11 -0
- package/dist/migrations/types.js.map +1 -0
- package/dist/migrations/validator.d.ts +215 -0
- package/dist/migrations/validator.d.ts.map +1 -0
- package/dist/migrations/validator.js +494 -0
- package/dist/migrations/validator.js.map +1 -0
- package/dist/observability/alerting.d.ts +116 -0
- package/dist/observability/alerting.d.ts.map +1 -0
- package/dist/observability/alerting.js +353 -0
- package/dist/observability/alerting.js.map +1 -0
- package/dist/observability/analytics-engine.d.ts +357 -0
- package/dist/observability/analytics-engine.d.ts.map +1 -0
- package/dist/observability/analytics-engine.js +430 -0
- package/dist/observability/analytics-engine.js.map +1 -0
- package/dist/observability/cost-metrics.d.ts +269 -0
- package/dist/observability/cost-metrics.d.ts.map +1 -0
- package/dist/observability/cost-metrics.js +560 -0
- package/dist/observability/cost-metrics.js.map +1 -0
- package/dist/observability/cross-do-tracing.d.ts +305 -0
- package/dist/observability/cross-do-tracing.d.ts.map +1 -0
- package/dist/observability/cross-do-tracing.js +431 -0
- package/dist/observability/cross-do-tracing.js.map +1 -0
- package/dist/observability/error-rate-collector.d.ts +163 -0
- package/dist/observability/error-rate-collector.d.ts.map +1 -0
- package/dist/observability/error-rate-collector.js +306 -0
- package/dist/observability/error-rate-collector.js.map +1 -0
- package/dist/observability/exporters.d.ts +231 -0
- package/dist/observability/exporters.d.ts.map +1 -0
- package/dist/observability/exporters.js +479 -0
- package/dist/observability/exporters.js.map +1 -0
- package/dist/observability/health-check.d.ts +106 -0
- package/dist/observability/health-check.d.ts.map +1 -0
- package/dist/observability/health-check.js +243 -0
- package/dist/observability/health-check.js.map +1 -0
- package/dist/observability/index.d.ts +297 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +455 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/instrumentation.d.ts +222 -0
- package/dist/observability/instrumentation.d.ts.map +1 -0
- package/dist/observability/instrumentation.js +532 -0
- package/dist/observability/instrumentation.js.map +1 -0
- package/dist/observability/memory-metrics.d.ts +227 -0
- package/dist/observability/memory-metrics.d.ts.map +1 -0
- package/dist/observability/memory-metrics.js +688 -0
- package/dist/observability/memory-metrics.js.map +1 -0
- package/dist/observability/metrics-endpoint.d.ts +91 -0
- package/dist/observability/metrics-endpoint.d.ts.map +1 -0
- package/dist/observability/metrics-endpoint.js +246 -0
- package/dist/observability/metrics-endpoint.js.map +1 -0
- package/dist/observability/metrics.d.ts +88 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/metrics.js +253 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/observability/observability-features.d.ts +488 -0
- package/dist/observability/observability-features.d.ts.map +1 -0
- package/dist/observability/observability-features.js +773 -0
- package/dist/observability/observability-features.js.map +1 -0
- package/dist/observability/prometheus.d.ts +39 -0
- package/dist/observability/prometheus.d.ts.map +1 -0
- package/dist/observability/prometheus.js +120 -0
- package/dist/observability/prometheus.js.map +1 -0
- package/dist/observability/propagation.d.ts +126 -0
- package/dist/observability/propagation.d.ts.map +1 -0
- package/dist/observability/propagation.js +234 -0
- package/dist/observability/propagation.js.map +1 -0
- package/dist/observability/query-latency.d.ts +243 -0
- package/dist/observability/query-latency.d.ts.map +1 -0
- package/dist/observability/query-latency.js +292 -0
- package/dist/observability/query-latency.js.map +1 -0
- package/dist/observability/query-performance.d.ts +169 -0
- package/dist/observability/query-performance.d.ts.map +1 -0
- package/dist/observability/query-performance.js +290 -0
- package/dist/observability/query-performance.js.map +1 -0
- package/dist/observability/storage-tier-metrics.d.ts +174 -0
- package/dist/observability/storage-tier-metrics.d.ts.map +1 -0
- package/dist/observability/storage-tier-metrics.js +306 -0
- package/dist/observability/storage-tier-metrics.js.map +1 -0
- package/dist/observability/tier-cost-optimizer.d.ts +155 -0
- package/dist/observability/tier-cost-optimizer.d.ts.map +1 -0
- package/dist/observability/tier-cost-optimizer.js +536 -0
- package/dist/observability/tier-cost-optimizer.js.map +1 -0
- package/dist/observability/tracer.d.ts +149 -0
- package/dist/observability/tracer.d.ts.map +1 -0
- package/dist/observability/tracer.js +435 -0
- package/dist/observability/tracer.js.map +1 -0
- package/dist/observability/types.d.ts +402 -0
- package/dist/observability/types.d.ts.map +1 -0
- package/dist/observability/types.js +103 -0
- package/dist/observability/types.js.map +1 -0
- package/dist/pglite/workers-pglite.d.ts +138 -0
- package/dist/pglite/workers-pglite.d.ts.map +1 -0
- package/dist/pglite/workers-pglite.js +143 -0
- package/dist/pglite/workers-pglite.js.map +1 -0
- package/dist/pglite-assets/pglite.data +0 -0
- package/dist/pglite-assets/pglite.wasm +0 -0
- package/dist/playground/index.d.ts +52 -0
- package/dist/playground/index.d.ts.map +1 -0
- package/dist/playground/index.js +55 -0
- package/dist/playground/index.js.map +1 -0
- package/dist/playground/keyboard-shortcuts.d.ts +116 -0
- package/dist/playground/keyboard-shortcuts.d.ts.map +1 -0
- package/dist/playground/keyboard-shortcuts.js +588 -0
- package/dist/playground/keyboard-shortcuts.js.map +1 -0
- package/dist/playground/playground.d.ts +82 -0
- package/dist/playground/playground.d.ts.map +1 -0
- package/dist/playground/playground.js +271 -0
- package/dist/playground/playground.js.map +1 -0
- package/dist/playground/query-executor.d.ts +115 -0
- package/dist/playground/query-executor.d.ts.map +1 -0
- package/dist/playground/query-executor.js +558 -0
- package/dist/playground/query-executor.js.map +1 -0
- package/dist/playground/query-history.d.ts +92 -0
- package/dist/playground/query-history.d.ts.map +1 -0
- package/dist/playground/query-history.js +259 -0
- package/dist/playground/query-history.js.map +1 -0
- package/dist/playground/result-formatter.d.ts +59 -0
- package/dist/playground/result-formatter.d.ts.map +1 -0
- package/dist/playground/result-formatter.js +341 -0
- package/dist/playground/result-formatter.js.map +1 -0
- package/dist/playground/sample-datasets.d.ts +77 -0
- package/dist/playground/sample-datasets.d.ts.map +1 -0
- package/dist/playground/sample-datasets.js +641 -0
- package/dist/playground/sample-datasets.js.map +1 -0
- package/dist/playground/sample-queries.d.ts +73 -0
- package/dist/playground/sample-queries.d.ts.map +1 -0
- package/dist/playground/sample-queries.js +1095 -0
- package/dist/playground/sample-queries.js.map +1 -0
- package/dist/playground/schema-explorer.d.ts +55 -0
- package/dist/playground/schema-explorer.d.ts.map +1 -0
- package/dist/playground/schema-explorer.js +473 -0
- package/dist/playground/schema-explorer.js.map +1 -0
- package/dist/playground/types.d.ts +430 -0
- package/dist/playground/types.d.ts.map +1 -0
- package/dist/playground/types.js +10 -0
- package/dist/playground/types.js.map +1 -0
- package/dist/readonly/cache-reader.d.ts +145 -0
- package/dist/readonly/cache-reader.d.ts.map +1 -0
- package/dist/readonly/cache-reader.js +198 -0
- package/dist/readonly/cache-reader.js.map +1 -0
- package/dist/readonly/config.d.ts +74 -0
- package/dist/readonly/config.d.ts.map +1 -0
- package/dist/readonly/config.js +67 -0
- package/dist/readonly/config.js.map +1 -0
- package/dist/readonly/index.d.ts +22 -0
- package/dist/readonly/index.d.ts.map +1 -0
- package/dist/readonly/index.js +17 -0
- package/dist/readonly/index.js.map +1 -0
- package/dist/readonly/pglite-wrapper.d.ts +82 -0
- package/dist/readonly/pglite-wrapper.d.ts.map +1 -0
- package/dist/readonly/pglite-wrapper.js +123 -0
- package/dist/readonly/pglite-wrapper.js.map +1 -0
- package/dist/readonly/worker.d.ts +142 -0
- package/dist/readonly/worker.d.ts.map +1 -0
- package/dist/readonly/worker.js +187 -0
- package/dist/readonly/worker.js.map +1 -0
- package/dist/readonly/write-blocker.d.ts +47 -0
- package/dist/readonly/write-blocker.d.ts.map +1 -0
- package/dist/readonly/write-blocker.js +136 -0
- package/dist/readonly/write-blocker.js.map +1 -0
- package/dist/recovery/disaster-recovery.d.ts +326 -0
- package/dist/recovery/disaster-recovery.d.ts.map +1 -0
- package/dist/recovery/disaster-recovery.js +799 -0
- package/dist/recovery/disaster-recovery.js.map +1 -0
- package/dist/recovery/index.d.ts +12 -0
- package/dist/recovery/index.d.ts.map +1 -0
- package/dist/recovery/index.js +12 -0
- package/dist/recovery/index.js.map +1 -0
- package/dist/recovery/parquet-parser.d.ts +321 -0
- package/dist/recovery/parquet-parser.d.ts.map +1 -0
- package/dist/recovery/parquet-parser.js +797 -0
- package/dist/recovery/parquet-parser.js.map +1 -0
- package/dist/retention/index.d.ts +50 -0
- package/dist/retention/index.d.ts.map +1 -0
- package/dist/retention/index.js +50 -0
- package/dist/retention/index.js.map +1 -0
- package/dist/retention/policy.d.ts +344 -0
- package/dist/retention/policy.d.ts.map +1 -0
- package/dist/retention/policy.js +472 -0
- package/dist/retention/policy.js.map +1 -0
- package/dist/retention/purger.d.ts +187 -0
- package/dist/retention/purger.d.ts.map +1 -0
- package/dist/retention/purger.js +411 -0
- package/dist/retention/purger.js.map +1 -0
- package/dist/rls/auth-integration.d.ts +280 -0
- package/dist/rls/auth-integration.d.ts.map +1 -0
- package/dist/rls/auth-integration.js +399 -0
- package/dist/rls/auth-integration.js.map +1 -0
- package/dist/rls/generator.d.ts +249 -0
- package/dist/rls/generator.d.ts.map +1 -0
- package/dist/rls/generator.js +495 -0
- package/dist/rls/generator.js.map +1 -0
- package/dist/rls/index.d.ts +26 -0
- package/dist/rls/index.d.ts.map +1 -0
- package/dist/rls/index.js +58 -0
- package/dist/rls/index.js.map +1 -0
- package/dist/rls/policy.d.ts +116 -0
- package/dist/rls/policy.d.ts.map +1 -0
- package/dist/rls/policy.js +77 -0
- package/dist/rls/policy.js.map +1 -0
- package/dist/rls/validator.d.ts +155 -0
- package/dist/rls/validator.d.ts.map +1 -0
- package/dist/rls/validator.js +792 -0
- package/dist/rls/validator.js.map +1 -0
- package/dist/routing/adaptive-router.d.ts +317 -0
- package/dist/routing/adaptive-router.d.ts.map +1 -0
- package/dist/routing/adaptive-router.js +554 -0
- package/dist/routing/adaptive-router.js.map +1 -0
- package/dist/routing/circuit-breaker.d.ts +339 -0
- package/dist/routing/circuit-breaker.d.ts.map +1 -0
- package/dist/routing/circuit-breaker.js +620 -0
- package/dist/routing/circuit-breaker.js.map +1 -0
- package/dist/routing/cost-metrics.d.ts +133 -0
- package/dist/routing/cost-metrics.d.ts.map +1 -0
- package/dist/routing/cost-metrics.js +259 -0
- package/dist/routing/cost-metrics.js.map +1 -0
- package/dist/routing/do-connection-pool.d.ts +243 -0
- package/dist/routing/do-connection-pool.d.ts.map +1 -0
- package/dist/routing/do-connection-pool.js +572 -0
- package/dist/routing/do-connection-pool.js.map +1 -0
- package/dist/routing/index.d.ts +59 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +59 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/routing/query-complexity-estimator.d.ts +73 -0
- package/dist/routing/query-complexity-estimator.d.ts.map +1 -0
- package/dist/routing/query-complexity-estimator.js +327 -0
- package/dist/routing/query-complexity-estimator.js.map +1 -0
- package/dist/routing/request-coalescing.d.ts +178 -0
- package/dist/routing/request-coalescing.d.ts.map +1 -0
- package/dist/routing/request-coalescing.js +325 -0
- package/dist/routing/request-coalescing.js.map +1 -0
- package/dist/routing/runtime-router.d.ts +107 -0
- package/dist/routing/runtime-router.d.ts.map +1 -0
- package/dist/routing/runtime-router.js +246 -0
- package/dist/routing/runtime-router.js.map +1 -0
- package/dist/routing/tenant-router.d.ts +848 -0
- package/dist/routing/tenant-router.d.ts.map +1 -0
- package/dist/routing/tenant-router.js +1056 -0
- package/dist/routing/tenant-router.js.map +1 -0
- package/dist/routing/websocket-pool.d.ts +119 -0
- package/dist/routing/websocket-pool.d.ts.map +1 -0
- package/dist/routing/websocket-pool.js +436 -0
- package/dist/routing/websocket-pool.js.map +1 -0
- package/dist/storage/cache-layer.d.ts +159 -0
- package/dist/storage/cache-layer.d.ts.map +1 -0
- package/dist/storage/cache-layer.js +245 -0
- package/dist/storage/cache-layer.js.map +1 -0
- package/dist/storage/cost-aware-tiering.d.ts +258 -0
- package/dist/storage/cost-aware-tiering.d.ts.map +1 -0
- package/dist/storage/cost-aware-tiering.js +526 -0
- package/dist/storage/cost-aware-tiering.js.map +1 -0
- package/dist/storage/index.d.ts +87 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +78 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/interfaces.d.ts +856 -0
- package/dist/storage/interfaces.d.ts.map +1 -0
- package/dist/storage/interfaces.js +69 -0
- package/dist/storage/interfaces.js.map +1 -0
- package/dist/storage/r2-layer.d.ts +226 -0
- package/dist/storage/r2-layer.d.ts.map +1 -0
- package/dist/storage/r2-layer.js +307 -0
- package/dist/storage/r2-layer.js.map +1 -0
- package/dist/storage/r2-overflow.d.ts +344 -0
- package/dist/storage/r2-overflow.d.ts.map +1 -0
- package/dist/storage/r2-overflow.js +730 -0
- package/dist/storage/r2-overflow.js.map +1 -0
- package/dist/storage/r2-page-vfs.d.ts +374 -0
- package/dist/storage/r2-page-vfs.d.ts.map +1 -0
- package/dist/storage/r2-page-vfs.js +754 -0
- package/dist/storage/r2-page-vfs.js.map +1 -0
- package/dist/storage/swr-cache.d.ts +181 -0
- package/dist/storage/swr-cache.d.ts.map +1 -0
- package/dist/storage/swr-cache.js +295 -0
- package/dist/storage/swr-cache.js.map +1 -0
- package/dist/storage/tiered-orchestrator.d.ts +951 -0
- package/dist/storage/tiered-orchestrator.d.ts.map +1 -0
- package/dist/storage/tiered-orchestrator.js +1731 -0
- package/dist/storage/tiered-orchestrator.js.map +1 -0
- package/dist/storage/tiered-vfs-swr.d.ts +279 -0
- package/dist/storage/tiered-vfs-swr.d.ts.map +1 -0
- package/dist/storage/tiered-vfs-swr.js +584 -0
- package/dist/storage/tiered-vfs-swr.js.map +1 -0
- package/dist/storage/tiered-vfs.d.ts +405 -0
- package/dist/storage/tiered-vfs.d.ts.map +1 -0
- package/dist/storage/tiered-vfs.js +833 -0
- package/dist/storage/tiered-vfs.js.map +1 -0
- package/dist/streaming/backpressure-controller.d.ts +173 -0
- package/dist/streaming/backpressure-controller.d.ts.map +1 -0
- package/dist/streaming/backpressure-controller.js +344 -0
- package/dist/streaming/backpressure-controller.js.map +1 -0
- package/dist/streaming/buffer-pool.d.ts +241 -0
- package/dist/streaming/buffer-pool.d.ts.map +1 -0
- package/dist/streaming/buffer-pool.js +381 -0
- package/dist/streaming/buffer-pool.js.map +1 -0
- package/dist/streaming/cdc-iceberg-connector.d.ts +272 -0
- package/dist/streaming/cdc-iceberg-connector.d.ts.map +1 -0
- package/dist/streaming/cdc-iceberg-connector.js +408 -0
- package/dist/streaming/cdc-iceberg-connector.js.map +1 -0
- package/dist/streaming/index.d.ts +111 -0
- package/dist/streaming/index.d.ts.map +1 -0
- package/dist/streaming/index.js +128 -0
- package/dist/streaming/index.js.map +1 -0
- package/dist/streaming/live-cdc-stream.d.ts +400 -0
- package/dist/streaming/live-cdc-stream.d.ts.map +1 -0
- package/dist/streaming/live-cdc-stream.js +703 -0
- package/dist/streaming/live-cdc-stream.js.map +1 -0
- package/dist/streaming/memory-bounded-stream.d.ts +207 -0
- package/dist/streaming/memory-bounded-stream.d.ts.map +1 -0
- package/dist/streaming/memory-bounded-stream.js +340 -0
- package/dist/streaming/memory-bounded-stream.js.map +1 -0
- package/dist/streaming/query-streamer.d.ts +379 -0
- package/dist/streaming/query-streamer.d.ts.map +1 -0
- package/dist/streaming/query-streamer.js +495 -0
- package/dist/streaming/query-streamer.js.map +1 -0
- package/dist/streaming/response-streaming.d.ts +203 -0
- package/dist/streaming/response-streaming.d.ts.map +1 -0
- package/dist/streaming/response-streaming.js +449 -0
- package/dist/streaming/response-streaming.js.map +1 -0
- package/dist/types/branded.d.ts +859 -0
- package/dist/types/branded.d.ts.map +1 -0
- package/dist/types/branded.js +891 -0
- package/dist/types/branded.js.map +1 -0
- package/dist/types/utilities.d.ts +757 -0
- package/dist/types/utilities.d.ts.map +1 -0
- package/dist/types/utilities.js +447 -0
- package/dist/types/utilities.js.map +1 -0
- package/dist/wal/replay-engine.d.ts +344 -0
- package/dist/wal/replay-engine.d.ts.map +1 -0
- package/dist/wal/replay-engine.js +975 -0
- package/dist/wal/replay-engine.js.map +1 -0
- package/dist/worker/__mocks__/capnweb.d.ts +13 -0
- package/dist/worker/__mocks__/capnweb.d.ts.map +1 -0
- package/dist/worker/__mocks__/capnweb.js +15 -0
- package/dist/worker/__mocks__/capnweb.js.map +1 -0
- package/dist/worker/__mocks__/cloudflare-workers.d.ts +31 -0
- package/dist/worker/__mocks__/cloudflare-workers.d.ts.map +1 -0
- package/dist/worker/__mocks__/cloudflare-workers.js +33 -0
- package/dist/worker/__mocks__/cloudflare-workers.js.map +1 -0
- package/dist/worker/__mocks__/pglite.data.d.ts +3 -0
- package/dist/worker/__mocks__/pglite.data.d.ts.map +1 -0
- package/dist/worker/__mocks__/pglite.data.js +20 -0
- package/dist/worker/__mocks__/pglite.data.js.map +1 -0
- package/dist/worker/__mocks__/pglite.wasm.d.ts +3 -0
- package/dist/worker/__mocks__/pglite.wasm.d.ts.map +1 -0
- package/dist/worker/__mocks__/pglite.wasm.js +30 -0
- package/dist/worker/__mocks__/pglite.wasm.js.map +1 -0
- package/dist/worker/auth-rate-limiter.d.ts +270 -0
- package/dist/worker/auth-rate-limiter.d.ts.map +1 -0
- package/dist/worker/auth-rate-limiter.js +332 -0
- package/dist/worker/auth-rate-limiter.js.map +1 -0
- package/dist/worker/auth.d.ts +345 -0
- package/dist/worker/auth.d.ts.map +1 -0
- package/dist/worker/auth.js +837 -0
- package/dist/worker/auth.js.map +1 -0
- package/dist/worker/cdc-backpressure.d.ts +338 -0
- package/dist/worker/cdc-backpressure.d.ts.map +1 -0
- package/dist/worker/cdc-backpressure.js +619 -0
- package/dist/worker/cdc-backpressure.js.map +1 -0
- package/dist/worker/cdc-sse.d.ts +277 -0
- package/dist/worker/cdc-sse.d.ts.map +1 -0
- package/dist/worker/cdc-sse.js +528 -0
- package/dist/worker/cdc-sse.js.map +1 -0
- package/dist/worker/cdc-websocket.d.ts +252 -0
- package/dist/worker/cdc-websocket.d.ts.map +1 -0
- package/dist/worker/cdc-websocket.js +940 -0
- package/dist/worker/cdc-websocket.js.map +1 -0
- package/dist/worker/cdc.d.ts +95 -0
- package/dist/worker/cdc.d.ts.map +1 -0
- package/dist/worker/cdc.js +211 -0
- package/dist/worker/cdc.js.map +1 -0
- package/dist/worker/concerns/auth-concern.d.ts +50 -0
- package/dist/worker/concerns/auth-concern.d.ts.map +1 -0
- package/dist/worker/concerns/auth-concern.js +131 -0
- package/dist/worker/concerns/auth-concern.js.map +1 -0
- package/dist/worker/concerns/cdc-concern.d.ts +99 -0
- package/dist/worker/concerns/cdc-concern.d.ts.map +1 -0
- package/dist/worker/concerns/cdc-concern.js +137 -0
- package/dist/worker/concerns/cdc-concern.js.map +1 -0
- package/dist/worker/concerns/index.d.ts +22 -0
- package/dist/worker/concerns/index.d.ts.map +1 -0
- package/dist/worker/concerns/index.js +13 -0
- package/dist/worker/concerns/index.js.map +1 -0
- package/dist/worker/concerns/query-execution-concern.d.ts +104 -0
- package/dist/worker/concerns/query-execution-concern.d.ts.map +1 -0
- package/dist/worker/concerns/query-execution-concern.js +95 -0
- package/dist/worker/concerns/query-execution-concern.js.map +1 -0
- package/dist/worker/concerns/storage-orchestration-concern.d.ts +78 -0
- package/dist/worker/concerns/storage-orchestration-concern.d.ts.map +1 -0
- package/dist/worker/concerns/storage-orchestration-concern.js +240 -0
- package/dist/worker/concerns/storage-orchestration-concern.js.map +1 -0
- package/dist/worker/do-auth-manager.d.ts +108 -0
- package/dist/worker/do-auth-manager.d.ts.map +1 -0
- package/dist/worker/do-auth-manager.js +212 -0
- package/dist/worker/do-auth-manager.js.map +1 -0
- package/dist/worker/do-pglite-manager.d.ts +137 -0
- package/dist/worker/do-pglite-manager.d.ts.map +1 -0
- package/dist/worker/do-pglite-manager.js +228 -0
- package/dist/worker/do-pglite-manager.js.map +1 -0
- package/dist/worker/do.d.ts +556 -0
- package/dist/worker/do.d.ts.map +1 -0
- package/dist/worker/do.js +1441 -0
- package/dist/worker/do.js.map +1 -0
- package/dist/worker/entry.d.ts +23 -0
- package/dist/worker/entry.d.ts.map +1 -0
- package/dist/worker/entry.js +362 -0
- package/dist/worker/entry.js.map +1 -0
- package/dist/worker/errors.d.ts +106 -0
- package/dist/worker/errors.d.ts.map +1 -0
- package/dist/worker/errors.js +178 -0
- package/dist/worker/errors.js.map +1 -0
- package/dist/worker/health-check-manager.d.ts +141 -0
- package/dist/worker/health-check-manager.d.ts.map +1 -0
- package/dist/worker/health-check-manager.js +145 -0
- package/dist/worker/health-check-manager.js.map +1 -0
- package/dist/worker/index.d.ts +60 -0
- package/dist/worker/index.d.ts.map +1 -0
- package/dist/worker/index.js +67 -0
- package/dist/worker/index.js.map +1 -0
- package/dist/worker/memory-pressure.d.ts +892 -0
- package/dist/worker/memory-pressure.d.ts.map +1 -0
- package/dist/worker/memory-pressure.js +1990 -0
- package/dist/worker/memory-pressure.js.map +1 -0
- package/dist/worker/migration-manager.d.ts +153 -0
- package/dist/worker/migration-manager.d.ts.map +1 -0
- package/dist/worker/migration-manager.js +461 -0
- package/dist/worker/migration-manager.js.map +1 -0
- package/dist/worker/plugin-manager.d.ts +147 -0
- package/dist/worker/plugin-manager.d.ts.map +1 -0
- package/dist/worker/plugin-manager.js +408 -0
- package/dist/worker/plugin-manager.js.map +1 -0
- package/dist/worker/proxy.d.ts +330 -0
- package/dist/worker/proxy.d.ts.map +1 -0
- package/dist/worker/proxy.js +504 -0
- package/dist/worker/proxy.js.map +1 -0
- package/dist/worker/query-execution-manager.d.ts +107 -0
- package/dist/worker/query-execution-manager.d.ts.map +1 -0
- package/dist/worker/query-execution-manager.js +155 -0
- package/dist/worker/query-execution-manager.js.map +1 -0
- package/dist/worker/query-executor.d.ts +163 -0
- package/dist/worker/query-executor.d.ts.map +1 -0
- package/dist/worker/query-executor.js +413 -0
- package/dist/worker/query-executor.js.map +1 -0
- package/dist/worker/query-stats-manager.d.ts +117 -0
- package/dist/worker/query-stats-manager.d.ts.map +1 -0
- package/dist/worker/query-stats-manager.js +162 -0
- package/dist/worker/query-stats-manager.js.map +1 -0
- package/dist/worker/result-handler.d.ts +192 -0
- package/dist/worker/result-handler.d.ts.map +1 -0
- package/dist/worker/result-handler.js +346 -0
- package/dist/worker/result-handler.js.map +1 -0
- package/dist/worker/routes.d.ts +135 -0
- package/dist/worker/routes.d.ts.map +1 -0
- package/dist/worker/routes.js +460 -0
- package/dist/worker/routes.js.map +1 -0
- package/dist/worker/rpc-methods-manager.d.ts +142 -0
- package/dist/worker/rpc-methods-manager.d.ts.map +1 -0
- package/dist/worker/rpc-methods-manager.js +195 -0
- package/dist/worker/rpc-methods-manager.js.map +1 -0
- package/dist/worker/rpc.d.ts +259 -0
- package/dist/worker/rpc.d.ts.map +1 -0
- package/dist/worker/rpc.js +398 -0
- package/dist/worker/rpc.js.map +1 -0
- package/dist/worker/schema-version.d.ts +209 -0
- package/dist/worker/schema-version.d.ts.map +1 -0
- package/dist/worker/schema-version.js +450 -0
- package/dist/worker/schema-version.js.map +1 -0
- package/dist/worker/session-manager.d.ts +282 -0
- package/dist/worker/session-manager.d.ts.map +1 -0
- package/dist/worker/session-manager.js +523 -0
- package/dist/worker/session-manager.js.map +1 -0
- package/dist/worker/shutdown-manager.d.ts +188 -0
- package/dist/worker/shutdown-manager.d.ts.map +1 -0
- package/dist/worker/shutdown-manager.js +347 -0
- package/dist/worker/shutdown-manager.js.map +1 -0
- package/dist/worker/sql-transform.d.ts +61 -0
- package/dist/worker/sql-transform.d.ts.map +1 -0
- package/dist/worker/sql-transform.js +312 -0
- package/dist/worker/sql-transform.js.map +1 -0
- package/dist/worker/types.d.ts +738 -0
- package/dist/worker/types.d.ts.map +1 -0
- package/dist/worker/types.js +6 -0
- package/dist/worker/types.js.map +1 -0
- package/dist/worker/user-routes.d.ts +76 -0
- package/dist/worker/user-routes.d.ts.map +1 -0
- package/dist/worker/user-routes.js +188 -0
- package/dist/worker/user-routes.js.map +1 -0
- package/dist/worker/wal-facade.d.ts +138 -0
- package/dist/worker/wal-facade.d.ts.map +1 -0
- package/dist/worker/wal-facade.js +184 -0
- package/dist/worker/wal-facade.js.map +1 -0
- package/dist/worker/wal-r2.d.ts +271 -0
- package/dist/worker/wal-r2.d.ts.map +1 -0
- package/dist/worker/wal-r2.js +689 -0
- package/dist/worker/wal-r2.js.map +1 -0
- package/dist/worker/wal-replay.d.ts +361 -0
- package/dist/worker/wal-replay.d.ts.map +1 -0
- package/dist/worker/wal-replay.js +628 -0
- package/dist/worker/wal-replay.js.map +1 -0
- package/dist/worker/wal-retention.d.ts +389 -0
- package/dist/worker/wal-retention.d.ts.map +1 -0
- package/dist/worker/wal-retention.js +763 -0
- package/dist/worker/wal-retention.js.map +1 -0
- package/dist/worker/wal.d.ts +278 -0
- package/dist/worker/wal.d.ts.map +1 -0
- package/dist/worker/wal.js +467 -0
- package/dist/worker/wal.js.map +1 -0
- package/dist/worker/websocket.d.ts +85 -0
- package/dist/worker/websocket.d.ts.map +1 -0
- package/dist/worker/websocket.js +227 -0
- package/dist/worker/websocket.js.map +1 -0
- package/package.json +108 -0
- package/src/cdc/change-stream.ts +137 -0
- package/src/cdc/filter.ts +646 -0
- package/src/cdc/index.ts +112 -0
- package/src/cdc/resume-token.ts +280 -0
- package/src/cdc/transport/index.ts +7 -0
- package/src/cdc/transport/sse.ts +723 -0
- package/src/cdc/transport/websocket.ts +873 -0
- package/src/cdc/types.ts +346 -0
- package/src/config/index.ts +25 -0
- package/src/config/memory.ts +177 -0
- package/src/config/storage.ts +204 -0
- package/src/config/streaming.ts +147 -0
- package/src/config/timeouts.ts +221 -0
- package/src/extensions/config.test.ts +187 -0
- package/src/extensions/config.ts +278 -0
- package/src/extensions/geo.test.ts +455 -0
- package/src/extensions/geo.ts +858 -0
- package/src/extensions/index.test.ts +259 -0
- package/src/extensions/index.ts +227 -0
- package/src/extensions/loader.test.ts +555 -0
- package/src/extensions/loader.ts +588 -0
- package/src/extensions/pgmq-lite.test.ts +727 -0
- package/src/extensions/pgmq-lite.ts +770 -0
- package/src/extensions/plugins.test.ts +528 -0
- package/src/extensions/plugins.ts +718 -0
- package/src/extensions/registry.test.ts +202 -0
- package/src/extensions/registry.ts +267 -0
- package/src/extensions/vector.test.ts +195 -0
- package/src/extensions/vector.ts +217 -0
- package/src/iceberg/SCHEDULER.md +580 -0
- package/src/iceberg/analytics.test.ts +703 -0
- package/src/iceberg/analytics.ts +727 -0
- package/src/iceberg/catalog-api.test.ts +838 -0
- package/src/iceberg/catalog-api.ts +520 -0
- package/src/iceberg/catalog.test.ts +680 -0
- package/src/iceberg/catalog.ts +1007 -0
- package/src/iceberg/iceberg.test.ts +705 -0
- package/src/iceberg/index.ts +406 -0
- package/src/iceberg/metadata.test.ts +632 -0
- package/src/iceberg/metadata.ts +649 -0
- package/src/iceberg/optimizer.test.ts +868 -0
- package/src/iceberg/optimizer.ts +1287 -0
- package/src/iceberg/parquet.test.ts +899 -0
- package/src/iceberg/parquet.ts +1640 -0
- package/src/iceberg/r2-organization.test.ts +615 -0
- package/src/iceberg/r2-organization.ts +951 -0
- package/src/iceberg/scheduler-do-example.ts +364 -0
- package/src/iceberg/scheduler.test.ts +861 -0
- package/src/iceberg/scheduler.ts +1201 -0
- package/src/iceberg/schema.test.ts +547 -0
- package/src/iceberg/schema.ts +616 -0
- package/src/iceberg/snapshot-manager.test.ts +919 -0
- package/src/iceberg/snapshot-manager.ts +1369 -0
- package/src/iceberg/sql-router.test.ts +334 -0
- package/src/iceberg/sql-router.ts +337 -0
- package/src/iceberg/test-fixtures.ts +605 -0
- package/src/iceberg/time-travel-api.test.ts +1029 -0
- package/src/iceberg/time-travel-api.ts +731 -0
- package/src/iceberg/time-travel.test.ts +1218 -0
- package/src/iceberg/time-travel.ts +1052 -0
- package/src/iceberg/transformer.test.ts +689 -0
- package/src/iceberg/transformer.ts +1029 -0
- package/src/iceberg/types.ts +373 -0
- package/src/iceberg/writer.test.ts +716 -0
- package/src/iceberg/writer.ts +590 -0
- package/src/index.ts +212 -0
- package/src/lineage/index.ts +42 -0
- package/src/lineage/integration.ts +334 -0
- package/src/lineage/tracker.ts +1618 -0
- package/src/lineage/types.ts +354 -0
- package/src/middleware/index.ts +36 -0
- package/src/middleware/rate-limit-concurrent.test.ts +794 -0
- package/src/middleware/rate-limit.test.ts +1568 -0
- package/src/middleware/rate-limit.ts +840 -0
- package/src/migration-tooling/external-migration.test.ts +1864 -0
- package/src/migration-tooling/external-migration.ts +2355 -0
- package/src/migration-tooling/index.ts +19 -0
- package/src/migrations/ARCHITECTURE.md +474 -0
- package/src/migrations/PROGRESS_TRACKING.md +485 -0
- package/src/migrations/auto-migrator.test.ts +732 -0
- package/src/migrations/auto-migrator.ts +531 -0
- package/src/migrations/bulk-orchestrator.test.ts +801 -0
- package/src/migrations/bulk-orchestrator.ts +1039 -0
- package/src/migrations/compatibility.test.ts +958 -0
- package/src/migrations/compatibility.ts +902 -0
- package/src/migrations/do-migrations.test.ts +2620 -0
- package/src/migrations/do-migrations.ts +1289 -0
- package/src/migrations/do-migrations.types.ts +715 -0
- package/src/migrations/drizzle-compat.test.ts +210 -0
- package/src/migrations/drizzle-compat.ts +337 -0
- package/src/migrations/index.ts +334 -0
- package/src/migrations/migration-api.test.ts +438 -0
- package/src/migrations/migration-api.ts +704 -0
- package/src/migrations/progress-tracker-do.ts +518 -0
- package/src/migrations/progress-tracker-kv.ts +305 -0
- package/src/migrations/progress-tracker.test.ts +937 -0
- package/src/migrations/progress-tracker.ts +665 -0
- package/src/migrations/registry.test.ts +331 -0
- package/src/migrations/registry.ts +468 -0
- package/src/migrations/rollback.test.ts +644 -0
- package/src/migrations/runner.test.ts +807 -0
- package/src/migrations/runner.test.ts.backup +759 -0
- package/src/migrations/runner.ts +1459 -0
- package/src/migrations/schema-generator.test.ts +649 -0
- package/src/migrations/schema-generator.ts +513 -0
- package/src/migrations/testing.ts +1037 -0
- package/src/migrations/types.ts +573 -0
- package/src/migrations/validator.test.ts +660 -0
- package/src/migrations/validator.ts +741 -0
- package/src/observability/alerting.test.ts +1133 -0
- package/src/observability/alerting.ts +455 -0
- package/src/observability/analytics-engine.ts +733 -0
- package/src/observability/cost-metrics.ts +804 -0
- package/src/observability/cross-do-tracing.test.ts +516 -0
- package/src/observability/cross-do-tracing.ts +588 -0
- package/src/observability/dashboards/postgres-do-overview.json +1656 -0
- package/src/observability/error-rate-collector.test.ts +977 -0
- package/src/observability/error-rate-collector.ts +518 -0
- package/src/observability/exporters.test.ts +365 -0
- package/src/observability/exporters.ts +650 -0
- package/src/observability/health-check.test.ts +353 -0
- package/src/observability/health-check.ts +341 -0
- package/src/observability/index.test.ts +298 -0
- package/src/observability/index.ts +885 -0
- package/src/observability/instrumentation.test.ts +428 -0
- package/src/observability/instrumentation.ts +788 -0
- package/src/observability/memory-metrics.test.ts +355 -0
- package/src/observability/memory-metrics.ts +990 -0
- package/src/observability/metrics-endpoint.test.ts +402 -0
- package/src/observability/metrics-endpoint.ts +374 -0
- package/src/observability/metrics.test.ts +291 -0
- package/src/observability/metrics.ts +315 -0
- package/src/observability/observability-features.ts +1296 -0
- package/src/observability/prometheus.test.ts +292 -0
- package/src/observability/prometheus.ts +170 -0
- package/src/observability/propagation.test.ts +417 -0
- package/src/observability/propagation.ts +294 -0
- package/src/observability/query-latency.ts +586 -0
- package/src/observability/query-performance.test.ts +406 -0
- package/src/observability/query-performance.ts +491 -0
- package/src/observability/storage-tier-metrics.test.ts +633 -0
- package/src/observability/storage-tier-metrics.ts +570 -0
- package/src/observability/tier-cost-optimizer.ts +740 -0
- package/src/observability/tracer.test.ts +346 -0
- package/src/observability/tracer.ts +585 -0
- package/src/observability/types.test.ts +726 -0
- package/src/observability/types.ts +434 -0
- package/src/pglite/auto-demotion.test.ts +477 -0
- package/src/pglite/auto-demotion.ts +385 -0
- package/src/pglite/auto-promotion.test.ts +824 -0
- package/src/pglite/auto-promotion.ts +547 -0
- package/src/pglite/cache-layer.test.ts +469 -0
- package/src/pglite/cache-layer.ts +271 -0
- package/src/pglite/cold-start-manager.ts +1260 -0
- package/src/pglite/cold-start-optimizer.test.ts +937 -0
- package/src/pglite/cold-start-optimizer.ts +1895 -0
- package/src/pglite/dovfs-adapter.ts +1122 -0
- package/src/pglite/dovfs.ts +1258 -0
- package/src/pglite/etag-cache.test.ts +844 -0
- package/src/pglite/etag-cache.ts +526 -0
- package/src/pglite/index.ts +442 -0
- package/src/pglite/init.test.ts +455 -0
- package/src/pglite/init.ts +574 -0
- package/src/pglite/lifecycle.test.ts +599 -0
- package/src/pglite/lifecycle.ts +704 -0
- package/src/pglite/parallel-loader.test.ts +586 -0
- package/src/pglite/parallel-loader.ts +481 -0
- package/src/pglite/production-pglite.test.ts +666 -0
- package/src/pglite/production-pglite.ts +537 -0
- package/src/pglite/query-executor.ts +614 -0
- package/src/pglite/r2-layer.test.ts +501 -0
- package/src/pglite/r2-layer.ts +322 -0
- package/src/pglite/tiered-init.test.ts +725 -0
- package/src/pglite/tiered-init.ts +556 -0
- package/src/pglite/tiered-vfs.test.ts +726 -0
- package/src/pglite/tiered-vfs.ts +33 -0
- package/src/pglite/tiering-stats.test.ts +531 -0
- package/src/pglite/tiering-stats.ts +407 -0
- package/src/pglite/transaction-hooks.ts +343 -0
- package/src/pglite/warm-loader.test.ts +1701 -0
- package/src/pglite/warm-loader.ts +528 -0
- package/src/pglite/workers-pglite.ts +224 -0
- package/src/pglite-assets/pglite.data +0 -0
- package/src/pglite-assets/pglite.wasm +0 -0
- package/src/pglite.d.ts +47 -0
- package/src/playground/index.ts +137 -0
- package/src/playground/keyboard-shortcuts.ts +677 -0
- package/src/playground/playground.ts +323 -0
- package/src/playground/query-executor.ts +669 -0
- package/src/playground/query-history.ts +328 -0
- package/src/playground/result-formatter.ts +420 -0
- package/src/playground/sample-datasets.ts +674 -0
- package/src/playground/sample-queries.ts +1168 -0
- package/src/playground/schema-explorer.ts +558 -0
- package/src/playground/types.ts +518 -0
- package/src/readonly/cache-reader.test.ts +460 -0
- package/src/readonly/cache-reader.ts +313 -0
- package/src/readonly/config.test.ts +187 -0
- package/src/readonly/config.ts +128 -0
- package/src/readonly/index.ts +50 -0
- package/src/readonly/pglite-wrapper.test.ts +278 -0
- package/src/readonly/pglite-wrapper.ts +184 -0
- package/src/readonly/worker.test.ts +533 -0
- package/src/readonly/worker.ts +341 -0
- package/src/readonly/write-blocker.test.ts +459 -0
- package/src/readonly/write-blocker.ts +175 -0
- package/src/recovery/disaster-recovery.test.ts +618 -0
- package/src/recovery/disaster-recovery.ts +1181 -0
- package/src/recovery/index.ts +43 -0
- package/src/recovery/parquet-parser.ts +974 -0
- package/src/retention/index.ts +74 -0
- package/src/retention/policy.test.ts +571 -0
- package/src/retention/policy.ts +774 -0
- package/src/retention/purger.test.ts +465 -0
- package/src/retention/purger.ts +558 -0
- package/src/rls/auth-integration.test.ts +752 -0
- package/src/rls/auth-integration.ts +533 -0
- package/src/rls/generator.test.ts +829 -0
- package/src/rls/generator.ts +573 -0
- package/src/rls/index.ts +128 -0
- package/src/rls/policy.ts +208 -0
- package/src/rls/rls.test.ts +1071 -0
- package/src/rls/validator.test.ts +930 -0
- package/src/rls/validator.ts +895 -0
- package/src/routing/adaptive-router.test.ts +884 -0
- package/src/routing/adaptive-router.ts +845 -0
- package/src/routing/circuit-breaker.test.ts +1505 -0
- package/src/routing/circuit-breaker.ts +852 -0
- package/src/routing/cost-metrics.test.ts +565 -0
- package/src/routing/cost-metrics.ts +408 -0
- package/src/routing/do-connection-pool.test.ts +1109 -0
- package/src/routing/do-connection-pool.ts +828 -0
- package/src/routing/index.ts +158 -0
- package/src/routing/query-complexity-estimator.test.ts +356 -0
- package/src/routing/query-complexity-estimator.ts +444 -0
- package/src/routing/request-coalescing.test.ts +738 -0
- package/src/routing/request-coalescing.ts +475 -0
- package/src/routing/runtime-router.test.ts +436 -0
- package/src/routing/runtime-router.ts +357 -0
- package/src/routing/tenant-router.test.ts +2493 -0
- package/src/routing/tenant-router.ts +1908 -0
- package/src/routing/websocket-pool.test.ts +551 -0
- package/src/routing/websocket-pool.ts +577 -0
- package/src/storage/access-pattern-tracker.test.ts +874 -0
- package/src/storage/cache-layer.test.ts +560 -0
- package/src/storage/cache-layer.ts +328 -0
- package/src/storage/cost-aware-tiering.test.ts +652 -0
- package/src/storage/cost-aware-tiering.ts +794 -0
- package/src/storage/do-sqlite-blobs.test.ts +937 -0
- package/src/storage/index.ts +272 -0
- package/src/storage/interfaces.ts +974 -0
- package/src/storage/r2-layer.test.ts +653 -0
- package/src/storage/r2-layer.ts +434 -0
- package/src/storage/r2-overflow.ts +920 -0
- package/src/storage/r2-page-vfs.test.ts +2348 -0
- package/src/storage/r2-page-vfs.ts +1054 -0
- package/src/storage/swr-cache.test.ts +832 -0
- package/src/storage/swr-cache.ts +398 -0
- package/src/storage/swr-tiered-integration.test.ts +617 -0
- package/src/storage/tiered-orchestrator.test.ts +2441 -0
- package/src/storage/tiered-orchestrator.ts +2081 -0
- package/src/storage/tiered-vfs-swr.test.ts +736 -0
- package/src/storage/tiered-vfs-swr.ts +735 -0
- package/src/storage/tiered-vfs.test.ts +793 -0
- package/src/storage/tiered-vfs.ts +1082 -0
- package/src/streaming/backpressure-controller.ts +452 -0
- package/src/streaming/buffer-pool.ts +484 -0
- package/src/streaming/cdc-iceberg-connector.ts +605 -0
- package/src/streaming/index.ts +225 -0
- package/src/streaming/live-cdc-stream.ts +985 -0
- package/src/streaming/memory-bounded-stream.ts +443 -0
- package/src/streaming/query-streamer.ts +662 -0
- package/src/streaming/response-streaming.ts +557 -0
- package/src/types/branded.ts +1075 -0
- package/src/types/branded.ts.backup +273 -0
- package/src/types/utilities.ts +1023 -0
- package/src/types/wasm.d.ts +30 -0
- package/src/validation/typed-errors.test.ts +420 -0
- package/src/wal/replay-engine.ts +1264 -0
- package/src/worker/__mocks__/capnweb.ts +15 -0
- package/src/worker/__mocks__/pglite.data.ts +22 -0
- package/src/worker/__mocks__/pglite.wasm.ts +33 -0
- package/src/worker/auth-rate-limiter.test.ts +272 -0
- package/src/worker/auth-rate-limiter.ts +448 -0
- package/src/worker/auth.security-red.test.ts +1236 -0
- package/src/worker/auth.security.test.ts +822 -0
- package/src/worker/auth.test.ts +469 -0
- package/src/worker/auth.ts +1104 -0
- package/src/worker/cdc-backpressure.test.ts +726 -0
- package/src/worker/cdc-backpressure.ts +866 -0
- package/src/worker/cdc-sse.test.ts +780 -0
- package/src/worker/cdc-sse.ts +728 -0
- package/src/worker/cdc-websocket.ts +1229 -0
- package/src/worker/cdc-ws.test.ts +1009 -0
- package/src/worker/cdc.test.ts +327 -0
- package/src/worker/cdc.ts +289 -0
- package/src/worker/concerns/auth-concern.ts +179 -0
- package/src/worker/concerns/cdc-concern.ts +247 -0
- package/src/worker/concerns/index.ts +58 -0
- package/src/worker/concerns/query-execution-concern.ts +194 -0
- package/src/worker/concerns/storage-orchestration-concern.ts +373 -0
- package/src/worker/discriminated-types.test.ts +280 -0
- package/src/worker/do-auth-manager.ts +257 -0
- package/src/worker/do-decomposition.test.ts +1236 -0
- package/src/worker/do-pglite-manager.ts +302 -0
- package/src/worker/do.test.ts +2254 -0
- package/src/worker/do.ts +1878 -0
- package/src/worker/entry.ts +417 -0
- package/src/worker/errors.ts +285 -0
- package/src/worker/health-check-manager.test.ts +261 -0
- package/src/worker/health-check-manager.ts +231 -0
- package/src/worker/index.ts +389 -0
- package/src/worker/memory-pressure.test.ts +1460 -0
- package/src/worker/memory-pressure.ts +2650 -0
- package/src/worker/migration-manager.ts +582 -0
- package/src/worker/neon-compat.test.ts +332 -0
- package/src/worker/plugin-manager.ts +485 -0
- package/src/worker/postgres.do-rpc.d.ts +76 -0
- package/src/worker/proxy.ts +694 -0
- package/src/worker/query-execution-manager.test.ts +303 -0
- package/src/worker/query-execution-manager.ts +219 -0
- package/src/worker/query-executor.test.ts +282 -0
- package/src/worker/query-executor.ts +560 -0
- package/src/worker/query-stats-manager.ts +229 -0
- package/src/worker/result-handler.test.ts +364 -0
- package/src/worker/result-handler.ts +510 -0
- package/src/worker/routes.test.ts +795 -0
- package/src/worker/routes.ts +650 -0
- package/src/worker/rpc-methods-manager.test.ts +326 -0
- package/src/worker/rpc-methods-manager.ts +276 -0
- package/src/worker/rpc.ts +524 -0
- package/src/worker/schema-version.ts +605 -0
- package/src/worker/session-manager.test.ts +506 -0
- package/src/worker/session-manager.ts +732 -0
- package/src/worker/shutdown-manager.ts +469 -0
- package/src/worker/sql-transform.test.ts +286 -0
- package/src/worker/sql-transform.ts +368 -0
- package/src/worker/supabase-compat.test.ts +621 -0
- package/src/worker/types.test.ts +292 -0
- package/src/worker/types.ts +873 -0
- package/src/worker/user-routes.test.ts +703 -0
- package/src/worker/user-routes.ts +303 -0
- package/src/worker/wal-facade.ts +235 -0
- package/src/worker/wal-r2.test.ts +570 -0
- package/src/worker/wal-r2.ts +930 -0
- package/src/worker/wal-replay.test.ts +845 -0
- package/src/worker/wal-replay.ts +897 -0
- package/src/worker/wal-retention.test.ts +758 -0
- package/src/worker/wal-retention.ts +1075 -0
- package/src/worker/wal.test.ts +618 -0
- package/src/worker/wal.ts +697 -0
- package/src/worker/websocket.test.ts +296 -0
- package/src/worker/websocket.ts +284 -0
|
@@ -0,0 +1,1236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RED PHASE: Worker Authentication Security Tests
|
|
3
|
+
*
|
|
4
|
+
* SECURITY CRITICAL: These tests protect against unauthorized access.
|
|
5
|
+
*
|
|
6
|
+
* Issue: postgres-8y64 - [P0-RED] Worker auth missing tests - auth bypass risk
|
|
7
|
+
*
|
|
8
|
+
* These tests are intentionally written to FAIL because the security features
|
|
9
|
+
* they verify are not yet implemented or have gaps. Each failing test represents
|
|
10
|
+
* a security vulnerability that needs to be addressed.
|
|
11
|
+
*
|
|
12
|
+
* Tests verify:
|
|
13
|
+
* 1. Token validation works correctly (cryptographic verification)
|
|
14
|
+
* 2. Expired tokens are rejected (with edge cases)
|
|
15
|
+
* 3. Invalid signatures are rejected (cryptographic validation)
|
|
16
|
+
* 4. Rate limiting prevents brute force (comprehensive scenarios)
|
|
17
|
+
* 5. SQL injection in claims is blocked (all injection vectors)
|
|
18
|
+
* 6. Clock skew handling (boundary conditions)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
22
|
+
import { Hono } from 'hono'
|
|
23
|
+
import {
|
|
24
|
+
createAuthMiddleware,
|
|
25
|
+
requireAuth,
|
|
26
|
+
getAuth,
|
|
27
|
+
generateDatabaseId,
|
|
28
|
+
clearTokenCache,
|
|
29
|
+
clearRateLimitState,
|
|
30
|
+
type AuthConfig,
|
|
31
|
+
type AuthContext,
|
|
32
|
+
type AuthenticatedUser,
|
|
33
|
+
} from './auth'
|
|
34
|
+
|
|
35
|
+
// Mock fetch for testing
|
|
36
|
+
const mockFetch = vi.fn()
|
|
37
|
+
let originalFetch: typeof fetch
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
mockFetch.mockReset()
|
|
41
|
+
originalFetch = globalThis.fetch
|
|
42
|
+
globalThis.fetch = mockFetch
|
|
43
|
+
clearTokenCache()
|
|
44
|
+
clearRateLimitState()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
globalThis.fetch = originalFetch
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Helper to create a test app with auth middleware
|
|
53
|
+
*/
|
|
54
|
+
function createTestApp(config: AuthConfig = {}) {
|
|
55
|
+
const app = new Hono<{ Variables: { auth: AuthContext } }>()
|
|
56
|
+
app.use('*', createAuthMiddleware(config))
|
|
57
|
+
|
|
58
|
+
app.get('/api/protected', (c) => {
|
|
59
|
+
const auth = getAuth(c)
|
|
60
|
+
return c.json({
|
|
61
|
+
user: auth.user,
|
|
62
|
+
databaseId: auth.databaseId,
|
|
63
|
+
isAuthenticated: auth.isAuthenticated,
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
app.get('/api/admin', requireAuth(), (c) => {
|
|
68
|
+
const auth = getAuth(c)
|
|
69
|
+
return c.json({ admin: true, user: auth.user })
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
return app
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Helper to create a JWT-like token for testing
|
|
77
|
+
*/
|
|
78
|
+
function createTestJWT(
|
|
79
|
+
payload: Record<string, unknown>,
|
|
80
|
+
options: { header?: Record<string, unknown>; signature?: string } = {}
|
|
81
|
+
): string {
|
|
82
|
+
const header = options.header ?? { alg: 'HS256', typ: 'JWT' }
|
|
83
|
+
const signature = options.signature ?? 'test-signature'
|
|
84
|
+
|
|
85
|
+
const headerB64 = btoa(JSON.stringify(header))
|
|
86
|
+
const payloadB64 = btoa(JSON.stringify(payload))
|
|
87
|
+
|
|
88
|
+
return `${headerB64}.${payloadB64}.${signature}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// =============================================================================
|
|
92
|
+
// 1. TOKEN VALIDATION - CRYPTOGRAPHIC VERIFICATION
|
|
93
|
+
// =============================================================================
|
|
94
|
+
|
|
95
|
+
describe('[RED] Token Cryptographic Validation', () => {
|
|
96
|
+
describe('Signature verification', () => {
|
|
97
|
+
it.fails('should reject tokens with forged signatures when using default validator', async () => {
|
|
98
|
+
// RED: Default oauth.do validator should verify signatures cryptographically
|
|
99
|
+
// Currently, if oauth.do returns success, the forged token is accepted
|
|
100
|
+
// This test FAILS because we need proper signature verification
|
|
101
|
+
mockFetch.mockResolvedValueOnce({
|
|
102
|
+
ok: true,
|
|
103
|
+
json: async () => ({
|
|
104
|
+
user: { id: 'attacker', email: 'attacker@evil.com' },
|
|
105
|
+
}),
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const app = createTestApp()
|
|
109
|
+
|
|
110
|
+
// Attacker creates a token with forged payload but mock oauth.do accepts it
|
|
111
|
+
const forgedToken = createTestJWT({
|
|
112
|
+
sub: 'admin',
|
|
113
|
+
role: 'super_admin',
|
|
114
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
const res = await app.request('/api/protected', {
|
|
118
|
+
headers: { Authorization: `Bearer ${forgedToken}` },
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
// Should be 401 but currently returns 200 if oauth.do is mocked to accept
|
|
122
|
+
expect(res.status).toBe(401)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it.fails('should verify signature using the correct secret/public key', async () => {
|
|
126
|
+
// RED: Middleware should validate signatures locally before hitting oauth.do
|
|
127
|
+
// Currently relies entirely on external validation
|
|
128
|
+
const app = createTestApp({
|
|
129
|
+
validateToken: async (token: string) => {
|
|
130
|
+
// Simulates accepting any token without cryptographic verification
|
|
131
|
+
const parts = token.split('.')
|
|
132
|
+
if (parts.length !== 3) {
|
|
133
|
+
return { valid: false, error: 'Invalid format' }
|
|
134
|
+
}
|
|
135
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
136
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Token signed with wrong key should be rejected
|
|
141
|
+
const wrongKeyToken = createTestJWT({
|
|
142
|
+
sub: 'user123',
|
|
143
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
144
|
+
}, { signature: 'signed-with-wrong-key' })
|
|
145
|
+
|
|
146
|
+
const res = await app.request('/api/protected', {
|
|
147
|
+
headers: { Authorization: `Bearer ${wrongKeyToken}` },
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// This should fail because signature is not verified
|
|
151
|
+
expect(res.status).toBe(401)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it.fails('should reject tokens with truncated signatures', async () => {
|
|
155
|
+
// RED: Truncated signatures should be rejected
|
|
156
|
+
const app = createTestApp({
|
|
157
|
+
validateToken: async (token: string) => {
|
|
158
|
+
const parts = token.split('.')
|
|
159
|
+
if (parts.length !== 3) return { valid: false, error: 'Invalid' }
|
|
160
|
+
// Does not check signature length
|
|
161
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
162
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
// Very short signature (truncated attack)
|
|
167
|
+
const truncatedSigToken = createTestJWT(
|
|
168
|
+
{ sub: 'user123', exp: Math.floor(Date.now() / 1000) + 3600 },
|
|
169
|
+
{ signature: 'a' } // Truncated to 1 character
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
const res = await app.request('/api/protected', {
|
|
173
|
+
headers: { Authorization: `Bearer ${truncatedSigToken}` },
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// Should reject truncated signatures
|
|
177
|
+
expect(res.status).toBe(401)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it.fails('should reject tokens with null signature (different from empty)', async () => {
|
|
181
|
+
// RED: null signature attack vector
|
|
182
|
+
const app = createTestApp({
|
|
183
|
+
validateToken: async (token: string) => {
|
|
184
|
+
const parts = token.split('.')
|
|
185
|
+
if (parts.length !== 3) return { valid: false, error: 'Invalid' }
|
|
186
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
187
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
// Signature that decodes to null
|
|
192
|
+
const nullSigToken = createTestJWT(
|
|
193
|
+
{ sub: 'user123', exp: Math.floor(Date.now() / 1000) + 3600 },
|
|
194
|
+
{ signature: btoa('null') }
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
const res = await app.request('/api/protected', {
|
|
198
|
+
headers: { Authorization: `Bearer ${nullSigToken}` },
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
expect(res.status).toBe(401)
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
describe('Algorithm enforcement', () => {
|
|
206
|
+
it.fails('should only accept explicitly configured algorithms', async () => {
|
|
207
|
+
// RED: Should have allowlist of algorithms, not just blocklist of 'none'
|
|
208
|
+
const app = createTestApp({
|
|
209
|
+
validateToken: async (token: string) => {
|
|
210
|
+
const parts = token.split('.')
|
|
211
|
+
if (parts.length !== 3) return { valid: false, error: 'Invalid' }
|
|
212
|
+
const header = JSON.parse(atob(parts[0]))
|
|
213
|
+
// Only blocks 'none', but accepts any other algorithm
|
|
214
|
+
if (header.alg === 'none') return { valid: false, error: 'Invalid algorithm' }
|
|
215
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
216
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
// Weak algorithms that should be rejected
|
|
221
|
+
const weakAlgorithms = ['HS1', 'MD5', 'SHA1', 'PS256-WEAK', 'CUSTOM']
|
|
222
|
+
|
|
223
|
+
for (const alg of weakAlgorithms) {
|
|
224
|
+
const token = createTestJWT(
|
|
225
|
+
{ sub: 'user123', exp: Math.floor(Date.now() / 1000) + 3600 },
|
|
226
|
+
{ header: { alg, typ: 'JWT' } }
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
const res = await app.request('/api/protected', {
|
|
230
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
// Should reject weak/unknown algorithms
|
|
234
|
+
expect(res.status).toBe(401)
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it.fails('should reject jwk header injection attack', async () => {
|
|
239
|
+
// RED: JWK header injection allows attacker to specify their own key
|
|
240
|
+
const app = createTestApp({
|
|
241
|
+
validateToken: async (token: string) => {
|
|
242
|
+
const parts = token.split('.')
|
|
243
|
+
if (parts.length !== 3) return { valid: false, error: 'Invalid' }
|
|
244
|
+
const header = JSON.parse(atob(parts[0]))
|
|
245
|
+
// Does not check for jwk header injection
|
|
246
|
+
if (header.alg === 'none') return { valid: false, error: 'Invalid algorithm' }
|
|
247
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
248
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
249
|
+
},
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// Token with embedded JWK (attacker's key)
|
|
253
|
+
const jwkToken = createTestJWT(
|
|
254
|
+
{ sub: 'admin', role: 'admin', exp: Math.floor(Date.now() / 1000) + 3600 },
|
|
255
|
+
{
|
|
256
|
+
header: {
|
|
257
|
+
alg: 'RS256',
|
|
258
|
+
typ: 'JWT',
|
|
259
|
+
jwk: {
|
|
260
|
+
kty: 'RSA',
|
|
261
|
+
n: 'attacker-public-key-n',
|
|
262
|
+
e: 'AQAB',
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
const res = await app.request('/api/protected', {
|
|
269
|
+
headers: { Authorization: `Bearer ${jwkToken}` },
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
// Should reject tokens with embedded JWK
|
|
273
|
+
expect(res.status).toBe(401)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it.fails('should reject jku header injection attack', async () => {
|
|
277
|
+
// RED: JKU header injection allows attacker to specify key URL
|
|
278
|
+
const app = createTestApp({
|
|
279
|
+
validateToken: async (token: string) => {
|
|
280
|
+
const parts = token.split('.')
|
|
281
|
+
if (parts.length !== 3) return { valid: false, error: 'Invalid' }
|
|
282
|
+
const header = JSON.parse(atob(parts[0]))
|
|
283
|
+
if (header.alg === 'none') return { valid: false, error: 'Invalid algorithm' }
|
|
284
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
285
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
286
|
+
},
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
// Token with JKU pointing to attacker's server
|
|
290
|
+
const jkuToken = createTestJWT(
|
|
291
|
+
{ sub: 'admin', role: 'admin', exp: Math.floor(Date.now() / 1000) + 3600 },
|
|
292
|
+
{
|
|
293
|
+
header: {
|
|
294
|
+
alg: 'RS256',
|
|
295
|
+
typ: 'JWT',
|
|
296
|
+
jku: 'https://attacker.com/jwks.json',
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
const res = await app.request('/api/protected', {
|
|
302
|
+
headers: { Authorization: `Bearer ${jkuToken}` },
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// Should reject tokens with jku header
|
|
306
|
+
expect(res.status).toBe(401)
|
|
307
|
+
})
|
|
308
|
+
})
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
// =============================================================================
|
|
312
|
+
// 2. EXPIRED TOKENS - EDGE CASES
|
|
313
|
+
// =============================================================================
|
|
314
|
+
|
|
315
|
+
describe('[RED] Expired Token Edge Cases', () => {
|
|
316
|
+
describe('Expiration boundary conditions', () => {
|
|
317
|
+
it.fails('should reject tokens that expire during request processing', async () => {
|
|
318
|
+
// RED: Race condition - token valid at start but expires during processing
|
|
319
|
+
const nowSeconds = Math.floor(Date.now() / 1000)
|
|
320
|
+
|
|
321
|
+
const app = createTestApp({
|
|
322
|
+
validateToken: async (token: string) => {
|
|
323
|
+
// Simulate slow validation (100ms)
|
|
324
|
+
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
325
|
+
|
|
326
|
+
const parts = token.split('.')
|
|
327
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
328
|
+
|
|
329
|
+
// Check expiration AFTER delay - token may have expired
|
|
330
|
+
const currentTime = Math.floor(Date.now() / 1000)
|
|
331
|
+
if (payload.exp && payload.exp < currentTime) {
|
|
332
|
+
return { valid: false, error: 'Token expired' }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
336
|
+
},
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
// Token that expires in 50ms (before validation completes)
|
|
340
|
+
const expiringToken = createTestJWT({
|
|
341
|
+
sub: 'user123',
|
|
342
|
+
exp: nowSeconds + 0.05, // Expires in 50ms
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
const res = await app.request('/api/protected', {
|
|
346
|
+
headers: { Authorization: `Bearer ${expiringToken}` },
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
// Should be rejected because token expired during processing
|
|
350
|
+
expect(res.status).toBe(401)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it.fails('should reject tokens with exp exactly at current time (boundary)', async () => {
|
|
354
|
+
// RED: Boundary condition - exp === now should be treated as expired
|
|
355
|
+
const nowSeconds = Math.floor(Date.now() / 1000)
|
|
356
|
+
|
|
357
|
+
const app = createTestApp({
|
|
358
|
+
validateToken: async (token: string) => {
|
|
359
|
+
const parts = token.split('.')
|
|
360
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
361
|
+
// Bug: Uses >= instead of > allowing exp === now
|
|
362
|
+
if (payload.exp && payload.exp >= Math.floor(Date.now() / 1000)) {
|
|
363
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
364
|
+
}
|
|
365
|
+
return { valid: false, error: 'Token expired' }
|
|
366
|
+
},
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
// Token that expires exactly now
|
|
370
|
+
const boundaryToken = createTestJWT({
|
|
371
|
+
sub: 'user123',
|
|
372
|
+
exp: nowSeconds,
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
const res = await app.request('/api/protected', {
|
|
376
|
+
headers: { Authorization: `Bearer ${boundaryToken}` },
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
// Should be rejected - exp === now means expired
|
|
380
|
+
expect(res.status).toBe(401)
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
it('should reject tokens with exp as string instead of number', async () => {
|
|
384
|
+
// RED: Type coercion attack - exp as string
|
|
385
|
+
const app = createTestApp({
|
|
386
|
+
validateToken: async (token: string) => {
|
|
387
|
+
const parts = token.split('.')
|
|
388
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
389
|
+
// Bug: Doesn't validate exp is a number
|
|
390
|
+
if (payload.exp && payload.exp > Math.floor(Date.now() / 1000)) {
|
|
391
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
392
|
+
}
|
|
393
|
+
return { valid: false, error: 'Token expired' }
|
|
394
|
+
},
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
// exp as string that coerces to large number
|
|
398
|
+
const stringExpToken = createTestJWT({
|
|
399
|
+
sub: 'user123',
|
|
400
|
+
exp: '9999999999', // String, not number
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
const res = await app.request('/api/protected', {
|
|
404
|
+
headers: { Authorization: `Bearer ${stringExpToken}` },
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
// Should reject - exp must be a number
|
|
408
|
+
expect(res.status).toBe(401)
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
it('should reject tokens with exp as array', async () => {
|
|
412
|
+
// RED: Type confusion - exp as array
|
|
413
|
+
const app = createTestApp({
|
|
414
|
+
validateToken: async (token: string) => {
|
|
415
|
+
const parts = token.split('.')
|
|
416
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
417
|
+
// Bug: Doesn't check if exp is actually a number
|
|
418
|
+
if (payload.exp > Math.floor(Date.now() / 1000)) {
|
|
419
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
420
|
+
}
|
|
421
|
+
return { valid: false, error: 'Token expired' }
|
|
422
|
+
},
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
// exp as array
|
|
426
|
+
const arrayExpToken = createTestJWT({
|
|
427
|
+
sub: 'user123',
|
|
428
|
+
exp: [9999999999],
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
const res = await app.request('/api/protected', {
|
|
432
|
+
headers: { Authorization: `Bearer ${arrayExpToken}` },
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
// Should reject - exp must be a number
|
|
436
|
+
expect(res.status).toBe(401)
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
it('should reject tokens with exp as object', async () => {
|
|
440
|
+
// RED: Type confusion - exp as object
|
|
441
|
+
const app = createTestApp({
|
|
442
|
+
validateToken: async (token: string) => {
|
|
443
|
+
const parts = token.split('.')
|
|
444
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
445
|
+
if (payload.exp > Math.floor(Date.now() / 1000)) {
|
|
446
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
447
|
+
}
|
|
448
|
+
return { valid: false, error: 'Token expired' }
|
|
449
|
+
},
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
// exp as object with valueOf
|
|
453
|
+
const objectExpToken = createTestJWT({
|
|
454
|
+
sub: 'user123',
|
|
455
|
+
exp: { valueOf: 9999999999 },
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
const res = await app.request('/api/protected', {
|
|
459
|
+
headers: { Authorization: `Bearer ${objectExpToken}` },
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
// Should reject - exp must be a number
|
|
463
|
+
expect(res.status).toBe(401)
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
it('should reject tokens with exp as Infinity', async () => {
|
|
467
|
+
// RED: Never-expiring token attack
|
|
468
|
+
const app = createTestApp({
|
|
469
|
+
validateToken: async (token: string) => {
|
|
470
|
+
const parts = token.split('.')
|
|
471
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
472
|
+
if (!isFinite(payload.exp)) {
|
|
473
|
+
return { valid: false, error: 'Invalid expiration' }
|
|
474
|
+
}
|
|
475
|
+
if (payload.exp > Math.floor(Date.now() / 1000)) {
|
|
476
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
477
|
+
}
|
|
478
|
+
return { valid: false, error: 'Token expired' }
|
|
479
|
+
},
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
// This test would need special handling since JSON doesn't support Infinity
|
|
483
|
+
// We test with a very large number instead
|
|
484
|
+
const infinityLikeToken = createTestJWT({
|
|
485
|
+
sub: 'user123',
|
|
486
|
+
exp: Number.MAX_SAFE_INTEGER,
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
const res = await app.request('/api/protected', {
|
|
490
|
+
headers: { Authorization: `Bearer ${infinityLikeToken}` },
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
// Should reject tokens with unreasonably far future exp
|
|
494
|
+
expect(res.status).toBe(401)
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
it('should enforce maximum token lifetime', async () => {
|
|
498
|
+
// RED: Tokens with exp too far in the future should be rejected
|
|
499
|
+
const app = createTestApp({
|
|
500
|
+
validateToken: async (token: string) => {
|
|
501
|
+
const parts = token.split('.')
|
|
502
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
503
|
+
const now = Math.floor(Date.now() / 1000)
|
|
504
|
+
const maxLifetime = 24 * 60 * 60 // 24 hours max
|
|
505
|
+
|
|
506
|
+
// Bug: No maximum lifetime check
|
|
507
|
+
if (payload.exp && payload.exp > now) {
|
|
508
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
509
|
+
}
|
|
510
|
+
return { valid: false, error: 'Token expired' }
|
|
511
|
+
},
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
// Token valid for 1 year (should be rejected)
|
|
515
|
+
const longLivedToken = createTestJWT({
|
|
516
|
+
sub: 'user123',
|
|
517
|
+
exp: Math.floor(Date.now() / 1000) + 365 * 24 * 60 * 60,
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
const res = await app.request('/api/protected', {
|
|
521
|
+
headers: { Authorization: `Bearer ${longLivedToken}` },
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
// Should reject - token lifetime exceeds maximum
|
|
525
|
+
expect(res.status).toBe(401)
|
|
526
|
+
})
|
|
527
|
+
})
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
// =============================================================================
|
|
531
|
+
// 3. INVALID SIGNATURES - COMPREHENSIVE
|
|
532
|
+
// =============================================================================
|
|
533
|
+
|
|
534
|
+
describe('[RED] Invalid Signature Handling', () => {
|
|
535
|
+
describe('Signature manipulation attacks', () => {
|
|
536
|
+
it.fails('should reject signature with appended data', async () => {
|
|
537
|
+
// RED: Appending data to valid signature
|
|
538
|
+
const app = createTestApp({
|
|
539
|
+
validateToken: async (token: string) => {
|
|
540
|
+
const parts = token.split('.')
|
|
541
|
+
if (parts.length !== 3) return { valid: false, error: 'Invalid' }
|
|
542
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
543
|
+
// Bug: Doesn't verify signature integrity
|
|
544
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
545
|
+
},
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
const tokenWithAppendedSig = createTestJWT(
|
|
549
|
+
{ sub: 'user123', exp: Math.floor(Date.now() / 1000) + 3600 },
|
|
550
|
+
{ signature: 'valid-signature-APPENDED_DATA' }
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
const res = await app.request('/api/protected', {
|
|
554
|
+
headers: { Authorization: `Bearer ${tokenWithAppendedSig}` },
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
expect(res.status).toBe(401)
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
it.fails('should reject signature with prepended data', async () => {
|
|
561
|
+
// RED: Prepending data to valid signature
|
|
562
|
+
const app = createTestApp({
|
|
563
|
+
validateToken: async (token: string) => {
|
|
564
|
+
const parts = token.split('.')
|
|
565
|
+
if (parts.length !== 3) return { valid: false, error: 'Invalid' }
|
|
566
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
567
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
568
|
+
},
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
const tokenWithPrependedSig = createTestJWT(
|
|
572
|
+
{ sub: 'user123', exp: Math.floor(Date.now() / 1000) + 3600 },
|
|
573
|
+
{ signature: 'PREPENDED_DATA-valid-signature' }
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
const res = await app.request('/api/protected', {
|
|
577
|
+
headers: { Authorization: `Bearer ${tokenWithPrependedSig}` },
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
expect(res.status).toBe(401)
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
it.fails('should reject signature with modified byte', async () => {
|
|
584
|
+
// RED: Single byte modification in signature
|
|
585
|
+
const app = createTestApp({
|
|
586
|
+
validateToken: async (token: string) => {
|
|
587
|
+
const parts = token.split('.')
|
|
588
|
+
if (parts.length !== 3) return { valid: false, error: 'Invalid' }
|
|
589
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
590
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
591
|
+
},
|
|
592
|
+
})
|
|
593
|
+
|
|
594
|
+
// Signature with one character changed
|
|
595
|
+
const modifiedSigToken = createTestJWT(
|
|
596
|
+
{ sub: 'user123', exp: Math.floor(Date.now() / 1000) + 3600 },
|
|
597
|
+
{ signature: 'valid-signaturX' } // X instead of e
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
const res = await app.request('/api/protected', {
|
|
601
|
+
headers: { Authorization: `Bearer ${modifiedSigToken}` },
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
expect(res.status).toBe(401)
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
it.fails('should reject base64url vs base64 confusion in signature', async () => {
|
|
608
|
+
// RED: Base64 encoding confusion attack
|
|
609
|
+
const app = createTestApp({
|
|
610
|
+
validateToken: async (token: string) => {
|
|
611
|
+
const parts = token.split('.')
|
|
612
|
+
if (parts.length !== 3) return { valid: false, error: 'Invalid' }
|
|
613
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
614
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
615
|
+
},
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
// Use standard base64 characters instead of base64url
|
|
619
|
+
const base64ConfusionToken = createTestJWT(
|
|
620
|
+
{ sub: 'user123', exp: Math.floor(Date.now() / 1000) + 3600 },
|
|
621
|
+
{ signature: 'sig+with/base64+chars==' } // + and / instead of - and _
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
const res = await app.request('/api/protected', {
|
|
625
|
+
headers: { Authorization: `Bearer ${base64ConfusionToken}` },
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
// Should normalize and reject if doesn't match
|
|
629
|
+
expect(res.status).toBe(401)
|
|
630
|
+
})
|
|
631
|
+
})
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
// =============================================================================
|
|
635
|
+
// 4. RATE LIMITING - BRUTE FORCE PREVENTION
|
|
636
|
+
// =============================================================================
|
|
637
|
+
|
|
638
|
+
describe('[RED] Rate Limiting Brute Force Prevention', () => {
|
|
639
|
+
describe('Rate limit bypass attempts', () => {
|
|
640
|
+
it('should rate limit by IP address, not just by token', async () => {
|
|
641
|
+
// RED: Current implementation rate limits by token prefix
|
|
642
|
+
// Attacker can bypass by using different tokens from same IP
|
|
643
|
+
const requestsFromIP = new Map<string, number>()
|
|
644
|
+
const ipRateLimit = 100
|
|
645
|
+
|
|
646
|
+
const app = new Hono<{ Variables: { auth: AuthContext } }>()
|
|
647
|
+
app.use('*', async (c, next) => {
|
|
648
|
+
const ip = c.req.header('CF-Connecting-IP') || '127.0.0.1'
|
|
649
|
+
const count = (requestsFromIP.get(ip) || 0) + 1
|
|
650
|
+
requestsFromIP.set(ip, count)
|
|
651
|
+
|
|
652
|
+
if (count > ipRateLimit) {
|
|
653
|
+
return c.json({ error: true, code: 'RATE_LIMITED' }, 429)
|
|
654
|
+
}
|
|
655
|
+
return next()
|
|
656
|
+
})
|
|
657
|
+
app.use('*', createAuthMiddleware({
|
|
658
|
+
validateToken: async () => ({ valid: false, error: 'Invalid' }),
|
|
659
|
+
}))
|
|
660
|
+
app.get('/api/protected', (c) => c.json({ success: true }))
|
|
661
|
+
|
|
662
|
+
// Attacker tries many different tokens from same IP
|
|
663
|
+
for (let i = 0; i < ipRateLimit + 10; i++) {
|
|
664
|
+
clearTokenCache()
|
|
665
|
+
const uniqueToken = createTestJWT({ sub: `attempt-${i}` })
|
|
666
|
+
const res = await app.request('/api/protected', {
|
|
667
|
+
headers: {
|
|
668
|
+
Authorization: `Bearer ${uniqueToken}`,
|
|
669
|
+
'CF-Connecting-IP': '192.168.1.100',
|
|
670
|
+
},
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
if (i >= ipRateLimit) {
|
|
674
|
+
// Should be rate limited by IP
|
|
675
|
+
expect(res.status).toBe(429)
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
it.fails('should implement exponential backoff for repeated failures', async () => {
|
|
681
|
+
// RED: Should increase delay between allowed attempts after failures
|
|
682
|
+
const failureCount = new Map<string, { count: number; lastAttempt: number }>()
|
|
683
|
+
|
|
684
|
+
const app = createTestApp({
|
|
685
|
+
validateToken: async (token: string) => {
|
|
686
|
+
const key = token.slice(0, 20)
|
|
687
|
+
const entry = failureCount.get(key) || { count: 0, lastAttempt: 0 }
|
|
688
|
+
const now = Date.now()
|
|
689
|
+
|
|
690
|
+
// Exponential backoff: 2^failures seconds
|
|
691
|
+
const requiredDelay = Math.pow(2, entry.count) * 1000
|
|
692
|
+
if (now - entry.lastAttempt < requiredDelay) {
|
|
693
|
+
return { valid: false, error: 'Rate limited - exponential backoff' }
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
entry.count++
|
|
697
|
+
entry.lastAttempt = now
|
|
698
|
+
failureCount.set(key, entry)
|
|
699
|
+
|
|
700
|
+
return { valid: false, error: 'Invalid token' }
|
|
701
|
+
},
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
const token = createTestJWT({ sub: 'brute-force-attempt' })
|
|
705
|
+
|
|
706
|
+
// Rapid sequential requests should be blocked with increasing delays
|
|
707
|
+
const results: number[] = []
|
|
708
|
+
for (let i = 0; i < 5; i++) {
|
|
709
|
+
clearTokenCache()
|
|
710
|
+
const res = await app.request('/api/protected', {
|
|
711
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
712
|
+
})
|
|
713
|
+
results.push(res.status)
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// After several attempts, should see rate limiting
|
|
717
|
+
expect(results.filter((s) => s === 429).length).toBeGreaterThan(0)
|
|
718
|
+
})
|
|
719
|
+
|
|
720
|
+
it('should prevent distributed brute force from multiple IPs', async () => {
|
|
721
|
+
// RED: Should detect patterns across multiple IPs targeting same account
|
|
722
|
+
const targetUser = 'victim@example.com'
|
|
723
|
+
const attemptsByTarget = new Map<string, number>()
|
|
724
|
+
const distributedLimit = 50
|
|
725
|
+
|
|
726
|
+
const app = createTestApp({
|
|
727
|
+
validateToken: async (token: string) => {
|
|
728
|
+
const parts = token.split('.')
|
|
729
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
730
|
+
const targetEmail = payload.email
|
|
731
|
+
|
|
732
|
+
if (targetEmail) {
|
|
733
|
+
const count = (attemptsByTarget.get(targetEmail) || 0) + 1
|
|
734
|
+
attemptsByTarget.set(targetEmail, count)
|
|
735
|
+
|
|
736
|
+
if (count > distributedLimit) {
|
|
737
|
+
return { valid: false, error: 'Account locked due to suspicious activity' }
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return { valid: false, error: 'Invalid credentials' }
|
|
742
|
+
},
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
// Distributed attack from different IPs
|
|
746
|
+
for (let i = 0; i < distributedLimit + 10; i++) {
|
|
747
|
+
clearTokenCache()
|
|
748
|
+
const token = createTestJWT({
|
|
749
|
+
sub: 'unknown',
|
|
750
|
+
email: targetUser,
|
|
751
|
+
password_attempt: `password${i}`,
|
|
752
|
+
})
|
|
753
|
+
|
|
754
|
+
const res = await app.request('/api/protected', {
|
|
755
|
+
headers: {
|
|
756
|
+
Authorization: `Bearer ${token}`,
|
|
757
|
+
'CF-Connecting-IP': `192.168.${Math.floor(i / 255)}.${i % 255}`,
|
|
758
|
+
},
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
if (i >= distributedLimit) {
|
|
762
|
+
// Should detect distributed attack and lock account
|
|
763
|
+
expect(res.status).toBe(429)
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
})
|
|
767
|
+
|
|
768
|
+
it('should apply stricter rate limits for sensitive operations', async () => {
|
|
769
|
+
// RED: Different rate limits for different endpoints
|
|
770
|
+
const requestCounts = new Map<string, number>()
|
|
771
|
+
|
|
772
|
+
const app = new Hono<{ Variables: { auth: AuthContext } }>()
|
|
773
|
+
app.use('*', createAuthMiddleware({
|
|
774
|
+
validateToken: async () => ({
|
|
775
|
+
valid: true,
|
|
776
|
+
user: { id: 'user123', email: 'test@test.com' },
|
|
777
|
+
}),
|
|
778
|
+
}))
|
|
779
|
+
|
|
780
|
+
// Stricter rate limit for sensitive endpoint
|
|
781
|
+
app.get('/api/admin', async (c, next) => {
|
|
782
|
+
const key = 'admin'
|
|
783
|
+
const count = (requestCounts.get(key) || 0) + 1
|
|
784
|
+
requestCounts.set(key, count)
|
|
785
|
+
|
|
786
|
+
if (count > 5) { // Only 5 requests allowed
|
|
787
|
+
return c.json({ error: true, code: 'RATE_LIMITED' }, 429)
|
|
788
|
+
}
|
|
789
|
+
return c.json({ admin: true })
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
const token = createTestJWT({ sub: 'user123' })
|
|
793
|
+
|
|
794
|
+
// Should hit rate limit quickly for admin endpoint
|
|
795
|
+
for (let i = 0; i < 10; i++) {
|
|
796
|
+
clearTokenCache()
|
|
797
|
+
const res = await app.request('/api/admin', {
|
|
798
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
799
|
+
})
|
|
800
|
+
|
|
801
|
+
if (i >= 5) {
|
|
802
|
+
expect(res.status).toBe(429)
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
})
|
|
806
|
+
})
|
|
807
|
+
})
|
|
808
|
+
|
|
809
|
+
// =============================================================================
|
|
810
|
+
// 5. SQL INJECTION IN CLAIMS
|
|
811
|
+
// =============================================================================
|
|
812
|
+
|
|
813
|
+
describe('[RED] SQL Injection in Claims', () => {
|
|
814
|
+
describe('SQL injection vectors in user ID', () => {
|
|
815
|
+
it('should block union-based SQL injection in sub claim', async () => {
|
|
816
|
+
// RED: UNION injection in user ID
|
|
817
|
+
const app = createTestApp({
|
|
818
|
+
validateToken: async (token: string) => {
|
|
819
|
+
const parts = token.split('.')
|
|
820
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
821
|
+
// Returns user with potentially malicious ID
|
|
822
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
823
|
+
},
|
|
824
|
+
})
|
|
825
|
+
|
|
826
|
+
const unionInjectionToken = createTestJWT({
|
|
827
|
+
sub: "1' UNION SELECT * FROM users--",
|
|
828
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
829
|
+
})
|
|
830
|
+
|
|
831
|
+
const res = await app.request('/api/protected', {
|
|
832
|
+
headers: { Authorization: `Bearer ${unionInjectionToken}` },
|
|
833
|
+
})
|
|
834
|
+
|
|
835
|
+
// Should reject SQL injection patterns
|
|
836
|
+
expect(res.status).toBe(401)
|
|
837
|
+
})
|
|
838
|
+
|
|
839
|
+
it('should block time-based blind SQL injection', async () => {
|
|
840
|
+
// RED: Time-based blind injection
|
|
841
|
+
const app = createTestApp({
|
|
842
|
+
validateToken: async (token: string) => {
|
|
843
|
+
const parts = token.split('.')
|
|
844
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
845
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
846
|
+
},
|
|
847
|
+
})
|
|
848
|
+
|
|
849
|
+
const blindInjectionToken = createTestJWT({
|
|
850
|
+
sub: "1'; WAITFOR DELAY '00:00:05'--",
|
|
851
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
852
|
+
})
|
|
853
|
+
|
|
854
|
+
const res = await app.request('/api/protected', {
|
|
855
|
+
headers: { Authorization: `Bearer ${blindInjectionToken}` },
|
|
856
|
+
})
|
|
857
|
+
|
|
858
|
+
expect(res.status).toBe(401)
|
|
859
|
+
})
|
|
860
|
+
|
|
861
|
+
it('should block stacked queries injection', async () => {
|
|
862
|
+
// RED: Stacked queries
|
|
863
|
+
const app = createTestApp({
|
|
864
|
+
validateToken: async (token: string) => {
|
|
865
|
+
const parts = token.split('.')
|
|
866
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
867
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
868
|
+
},
|
|
869
|
+
})
|
|
870
|
+
|
|
871
|
+
const stackedToken = createTestJWT({
|
|
872
|
+
sub: "1'; DROP TABLE users; SELECT '1",
|
|
873
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
874
|
+
})
|
|
875
|
+
|
|
876
|
+
const res = await app.request('/api/protected', {
|
|
877
|
+
headers: { Authorization: `Bearer ${stackedToken}` },
|
|
878
|
+
})
|
|
879
|
+
|
|
880
|
+
expect(res.status).toBe(401)
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
it('should block SQL injection in email claim', async () => {
|
|
884
|
+
// RED: Injection via email claim
|
|
885
|
+
const app = createTestApp({
|
|
886
|
+
validateToken: async (token: string) => {
|
|
887
|
+
const parts = token.split('.')
|
|
888
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
889
|
+
return {
|
|
890
|
+
valid: true,
|
|
891
|
+
user: { id: payload.sub, email: payload.email },
|
|
892
|
+
}
|
|
893
|
+
},
|
|
894
|
+
})
|
|
895
|
+
|
|
896
|
+
const emailInjectionToken = createTestJWT({
|
|
897
|
+
sub: 'user123',
|
|
898
|
+
email: "test@test.com'; DELETE FROM logs--",
|
|
899
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
900
|
+
})
|
|
901
|
+
|
|
902
|
+
const res = await app.request('/api/protected', {
|
|
903
|
+
headers: { Authorization: `Bearer ${emailInjectionToken}` },
|
|
904
|
+
})
|
|
905
|
+
|
|
906
|
+
expect(res.status).toBe(401)
|
|
907
|
+
})
|
|
908
|
+
|
|
909
|
+
it.fails('should block SQL injection in custom metadata claims', async () => {
|
|
910
|
+
// RED: Injection via custom claims
|
|
911
|
+
const app = createTestApp({
|
|
912
|
+
validateToken: async (token: string) => {
|
|
913
|
+
const parts = token.split('.')
|
|
914
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
915
|
+
return {
|
|
916
|
+
valid: true,
|
|
917
|
+
user: {
|
|
918
|
+
id: payload.sub,
|
|
919
|
+
email: 'test@test.com',
|
|
920
|
+
metadata: { tenantId: payload.tenant_id },
|
|
921
|
+
},
|
|
922
|
+
}
|
|
923
|
+
},
|
|
924
|
+
})
|
|
925
|
+
|
|
926
|
+
const metadataInjectionToken = createTestJWT({
|
|
927
|
+
sub: 'user123',
|
|
928
|
+
tenant_id: "tenant-a' OR '1'='1",
|
|
929
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
930
|
+
})
|
|
931
|
+
|
|
932
|
+
const res = await app.request('/api/protected', {
|
|
933
|
+
headers: { Authorization: `Bearer ${metadataInjectionToken}` },
|
|
934
|
+
})
|
|
935
|
+
|
|
936
|
+
expect(res.status).toBe(401)
|
|
937
|
+
})
|
|
938
|
+
|
|
939
|
+
it('should block NoSQL injection in claims', async () => {
|
|
940
|
+
// RED: NoSQL/MongoDB-style injection
|
|
941
|
+
const app = createTestApp({
|
|
942
|
+
validateToken: async (token: string) => {
|
|
943
|
+
const parts = token.split('.')
|
|
944
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
945
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
946
|
+
},
|
|
947
|
+
})
|
|
948
|
+
|
|
949
|
+
const nosqlInjectionToken = createTestJWT({
|
|
950
|
+
sub: { $gt: '' }, // MongoDB injection
|
|
951
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
952
|
+
})
|
|
953
|
+
|
|
954
|
+
const res = await app.request('/api/protected', {
|
|
955
|
+
headers: { Authorization: `Bearer ${nosqlInjectionToken}` },
|
|
956
|
+
})
|
|
957
|
+
|
|
958
|
+
expect(res.status).toBe(401)
|
|
959
|
+
})
|
|
960
|
+
|
|
961
|
+
it.fails('should block LDAP injection in claims', async () => {
|
|
962
|
+
// RED: LDAP injection if claims are used in LDAP queries
|
|
963
|
+
const app = createTestApp({
|
|
964
|
+
validateToken: async (token: string) => {
|
|
965
|
+
const parts = token.split('.')
|
|
966
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
967
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
968
|
+
},
|
|
969
|
+
})
|
|
970
|
+
|
|
971
|
+
const ldapInjectionToken = createTestJWT({
|
|
972
|
+
sub: '*)(uid=*))(|(uid=*',
|
|
973
|
+
exp: Math.floor(Date.now() / 1000) + 3600,
|
|
974
|
+
})
|
|
975
|
+
|
|
976
|
+
const res = await app.request('/api/protected', {
|
|
977
|
+
headers: { Authorization: `Bearer ${ldapInjectionToken}` },
|
|
978
|
+
})
|
|
979
|
+
|
|
980
|
+
expect(res.status).toBe(401)
|
|
981
|
+
})
|
|
982
|
+
})
|
|
983
|
+
|
|
984
|
+
describe('Database ID generation security', () => {
|
|
985
|
+
it('should sanitize all SQL metacharacters in generateDatabaseId', async () => {
|
|
986
|
+
// RED: generateDatabaseId may not sanitize all dangerous patterns
|
|
987
|
+
const dangerousInputs = [
|
|
988
|
+
"user'; DROP TABLE--",
|
|
989
|
+
'user/**/UNION/**/SELECT',
|
|
990
|
+
'user%00admin',
|
|
991
|
+
'user\x00admin',
|
|
992
|
+
'user\nadmin',
|
|
993
|
+
'user\radmin',
|
|
994
|
+
"user\\'admin",
|
|
995
|
+
'user\\admin',
|
|
996
|
+
'user`admin`',
|
|
997
|
+
'user$(whoami)',
|
|
998
|
+
'user|cat /etc/passwd',
|
|
999
|
+
'user&& ls',
|
|
1000
|
+
'user; ls',
|
|
1001
|
+
]
|
|
1002
|
+
|
|
1003
|
+
for (const input of dangerousInputs) {
|
|
1004
|
+
const dbId = generateDatabaseId(input)
|
|
1005
|
+
|
|
1006
|
+
// Should not contain any dangerous characters
|
|
1007
|
+
expect(dbId).not.toMatch(/[';"\-\-\/\*\x00\n\r\\`$|&;]/)
|
|
1008
|
+
// Should be alphanumeric with underscores only
|
|
1009
|
+
expect(dbId).toMatch(/^[a-zA-Z0-9_]+$/)
|
|
1010
|
+
}
|
|
1011
|
+
})
|
|
1012
|
+
})
|
|
1013
|
+
})
|
|
1014
|
+
|
|
1015
|
+
// =============================================================================
|
|
1016
|
+
// 6. CLOCK SKEW HANDLING
|
|
1017
|
+
// =============================================================================
|
|
1018
|
+
|
|
1019
|
+
describe('[RED] Clock Skew Handling', () => {
|
|
1020
|
+
describe('Clock skew edge cases', () => {
|
|
1021
|
+
it('should reject tokens with exp exactly at clock skew boundary', async () => {
|
|
1022
|
+
// RED: Boundary condition at exactly MAX_CLOCK_SKEW_SECONDS
|
|
1023
|
+
const MAX_CLOCK_SKEW = 60 // From auth.ts
|
|
1024
|
+
const now = Math.floor(Date.now() / 1000)
|
|
1025
|
+
|
|
1026
|
+
const app = createTestApp({
|
|
1027
|
+
validateToken: async (token: string) => {
|
|
1028
|
+
const parts = token.split('.')
|
|
1029
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
1030
|
+
// Uses <= which allows tokens at exactly the boundary
|
|
1031
|
+
if (payload.exp + MAX_CLOCK_SKEW >= now) {
|
|
1032
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
1033
|
+
}
|
|
1034
|
+
return { valid: false, error: 'Token expired' }
|
|
1035
|
+
},
|
|
1036
|
+
})
|
|
1037
|
+
|
|
1038
|
+
// Token expired exactly MAX_CLOCK_SKEW seconds ago (boundary)
|
|
1039
|
+
const boundaryToken = createTestJWT({
|
|
1040
|
+
sub: 'user123',
|
|
1041
|
+
exp: now - MAX_CLOCK_SKEW,
|
|
1042
|
+
})
|
|
1043
|
+
|
|
1044
|
+
const res = await app.request('/api/protected', {
|
|
1045
|
+
headers: { Authorization: `Bearer ${boundaryToken}` },
|
|
1046
|
+
})
|
|
1047
|
+
|
|
1048
|
+
// Should be rejected at exactly the boundary
|
|
1049
|
+
expect(res.status).toBe(401)
|
|
1050
|
+
})
|
|
1051
|
+
|
|
1052
|
+
it('should reject tokens with nbf exactly at clock skew boundary', async () => {
|
|
1053
|
+
// RED: Boundary condition for not-before claim
|
|
1054
|
+
const MAX_CLOCK_SKEW = 60
|
|
1055
|
+
const now = Math.floor(Date.now() / 1000)
|
|
1056
|
+
|
|
1057
|
+
const app = createTestApp({
|
|
1058
|
+
validateToken: async (token: string) => {
|
|
1059
|
+
const parts = token.split('.')
|
|
1060
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
1061
|
+
// Uses >= which allows tokens at exactly the boundary
|
|
1062
|
+
if (payload.nbf - MAX_CLOCK_SKEW <= now) {
|
|
1063
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
1064
|
+
}
|
|
1065
|
+
return { valid: false, error: 'Token not yet valid' }
|
|
1066
|
+
},
|
|
1067
|
+
})
|
|
1068
|
+
|
|
1069
|
+
// Token becomes valid exactly MAX_CLOCK_SKEW seconds in the future (boundary)
|
|
1070
|
+
const boundaryToken = createTestJWT({
|
|
1071
|
+
sub: 'user123',
|
|
1072
|
+
nbf: now + MAX_CLOCK_SKEW,
|
|
1073
|
+
exp: now + 3600,
|
|
1074
|
+
})
|
|
1075
|
+
|
|
1076
|
+
const res = await app.request('/api/protected', {
|
|
1077
|
+
headers: { Authorization: `Bearer ${boundaryToken}` },
|
|
1078
|
+
})
|
|
1079
|
+
|
|
1080
|
+
// Should be rejected at exactly the boundary
|
|
1081
|
+
expect(res.status).toBe(401)
|
|
1082
|
+
})
|
|
1083
|
+
|
|
1084
|
+
it('should handle clock skew when iat is in the future', async () => {
|
|
1085
|
+
// RED: iat (issued at) in the future indicates clock issues or forgery
|
|
1086
|
+
const now = Math.floor(Date.now() / 1000)
|
|
1087
|
+
const MAX_CLOCK_SKEW = 60
|
|
1088
|
+
|
|
1089
|
+
const app = createTestApp({
|
|
1090
|
+
validateToken: async (token: string) => {
|
|
1091
|
+
const parts = token.split('.')
|
|
1092
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
1093
|
+
|
|
1094
|
+
// Check if iat is unreasonably in the future
|
|
1095
|
+
if (payload.iat && payload.iat > now + MAX_CLOCK_SKEW) {
|
|
1096
|
+
return { valid: false, error: 'Token issued in the future' }
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
1100
|
+
},
|
|
1101
|
+
})
|
|
1102
|
+
|
|
1103
|
+
// Token with iat 5 minutes in the future
|
|
1104
|
+
const futureIatToken = createTestJWT({
|
|
1105
|
+
sub: 'user123',
|
|
1106
|
+
iat: now + 300,
|
|
1107
|
+
exp: now + 3600,
|
|
1108
|
+
})
|
|
1109
|
+
|
|
1110
|
+
const res = await app.request('/api/protected', {
|
|
1111
|
+
headers: { Authorization: `Bearer ${futureIatToken}` },
|
|
1112
|
+
})
|
|
1113
|
+
|
|
1114
|
+
// Should reject - iat too far in future
|
|
1115
|
+
expect(res.status).toBe(401)
|
|
1116
|
+
})
|
|
1117
|
+
|
|
1118
|
+
it('should apply clock skew consistently across all time claims', async () => {
|
|
1119
|
+
// RED: Clock skew should be applied to exp, nbf, and iat consistently
|
|
1120
|
+
const now = Math.floor(Date.now() / 1000)
|
|
1121
|
+
const MAX_CLOCK_SKEW = 60
|
|
1122
|
+
|
|
1123
|
+
const app = createTestApp({
|
|
1124
|
+
validateToken: async (token: string) => {
|
|
1125
|
+
const parts = token.split('.')
|
|
1126
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
1127
|
+
|
|
1128
|
+
// Check all time claims with consistent skew
|
|
1129
|
+
const effectiveNow = now
|
|
1130
|
+
|
|
1131
|
+
// exp check (with skew)
|
|
1132
|
+
if (payload.exp && payload.exp + MAX_CLOCK_SKEW < effectiveNow) {
|
|
1133
|
+
return { valid: false, error: 'Token expired' }
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// nbf check (with skew)
|
|
1137
|
+
if (payload.nbf && payload.nbf - MAX_CLOCK_SKEW > effectiveNow) {
|
|
1138
|
+
return { valid: false, error: 'Token not yet valid' }
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// iat check (with skew)
|
|
1142
|
+
if (payload.iat && payload.iat - MAX_CLOCK_SKEW > effectiveNow) {
|
|
1143
|
+
return { valid: false, error: 'Token from future' }
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
1147
|
+
},
|
|
1148
|
+
})
|
|
1149
|
+
|
|
1150
|
+
// Token with slightly future iat but within skew
|
|
1151
|
+
const slightlyFutureToken = createTestJWT({
|
|
1152
|
+
sub: 'user123',
|
|
1153
|
+
iat: now + 30, // 30 seconds in future (within 60s skew)
|
|
1154
|
+
nbf: now - 10,
|
|
1155
|
+
exp: now + 3600,
|
|
1156
|
+
})
|
|
1157
|
+
|
|
1158
|
+
const res = await app.request('/api/protected', {
|
|
1159
|
+
headers: { Authorization: `Bearer ${slightlyFutureToken}` },
|
|
1160
|
+
})
|
|
1161
|
+
|
|
1162
|
+
// Should accept - within clock skew tolerance
|
|
1163
|
+
expect(res.status).toBe(200)
|
|
1164
|
+
})
|
|
1165
|
+
|
|
1166
|
+
it('should not allow clock skew to extend token lifetime', async () => {
|
|
1167
|
+
// RED: Clock skew should not effectively extend token lifetime
|
|
1168
|
+
const now = Math.floor(Date.now() / 1000)
|
|
1169
|
+
const MAX_CLOCK_SKEW = 60
|
|
1170
|
+
|
|
1171
|
+
const app = createTestApp({
|
|
1172
|
+
validateToken: async (token: string) => {
|
|
1173
|
+
const parts = token.split('.')
|
|
1174
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
1175
|
+
|
|
1176
|
+
// Token lifetime should be measured from iat to exp
|
|
1177
|
+
const lifetime = payload.exp - payload.iat
|
|
1178
|
+
const maxLifetime = 24 * 60 * 60 // 24 hours
|
|
1179
|
+
|
|
1180
|
+
// Bug: Attacker can set iat in the past to extend effective lifetime
|
|
1181
|
+
if (lifetime > maxLifetime) {
|
|
1182
|
+
return { valid: false, error: 'Token lifetime too long' }
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
1186
|
+
},
|
|
1187
|
+
})
|
|
1188
|
+
|
|
1189
|
+
// Attacker sets iat far in past to get longer effective lifetime
|
|
1190
|
+
const extendedToken = createTestJWT({
|
|
1191
|
+
sub: 'user123',
|
|
1192
|
+
iat: now - 365 * 24 * 60 * 60, // 1 year ago
|
|
1193
|
+
exp: now + 3600,
|
|
1194
|
+
})
|
|
1195
|
+
|
|
1196
|
+
const res = await app.request('/api/protected', {
|
|
1197
|
+
headers: { Authorization: `Bearer ${extendedToken}` },
|
|
1198
|
+
})
|
|
1199
|
+
|
|
1200
|
+
// Should reject - effective lifetime exceeds maximum
|
|
1201
|
+
expect(res.status).toBe(401)
|
|
1202
|
+
})
|
|
1203
|
+
|
|
1204
|
+
it('should validate that exp > nbf', async () => {
|
|
1205
|
+
// RED: Invalid token where exp is before nbf
|
|
1206
|
+
const now = Math.floor(Date.now() / 1000)
|
|
1207
|
+
|
|
1208
|
+
const app = createTestApp({
|
|
1209
|
+
validateToken: async (token: string) => {
|
|
1210
|
+
const parts = token.split('.')
|
|
1211
|
+
const payload = JSON.parse(atob(parts[1]))
|
|
1212
|
+
|
|
1213
|
+
// Bug: Doesn't check that exp > nbf
|
|
1214
|
+
if (payload.exp > now) {
|
|
1215
|
+
return { valid: true, user: { id: payload.sub, email: 'test@test.com' } }
|
|
1216
|
+
}
|
|
1217
|
+
return { valid: false, error: 'Token expired' }
|
|
1218
|
+
},
|
|
1219
|
+
})
|
|
1220
|
+
|
|
1221
|
+
// Invalid: exp before nbf
|
|
1222
|
+
const invalidToken = createTestJWT({
|
|
1223
|
+
sub: 'user123',
|
|
1224
|
+
nbf: now + 7200,
|
|
1225
|
+
exp: now + 3600, // exp before nbf
|
|
1226
|
+
})
|
|
1227
|
+
|
|
1228
|
+
const res = await app.request('/api/protected', {
|
|
1229
|
+
headers: { Authorization: `Bearer ${invalidToken}` },
|
|
1230
|
+
})
|
|
1231
|
+
|
|
1232
|
+
// Should reject - logically invalid time claims
|
|
1233
|
+
expect(res.status).toBe(401)
|
|
1234
|
+
})
|
|
1235
|
+
})
|
|
1236
|
+
})
|