@blamejs/blamejs-shop 0.4.30 → 0.4.32
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/CHANGELOG.md +4 -0
- package/lib/asset-manifest.json +1 -1
- package/lib/checkout.js +8 -0
- package/lib/order.js +71 -11
- package/lib/vendor/MANIFEST.json +392 -278
- package/lib/vendor/blamejs/.github/workflows/ci.yml +34 -3
- package/lib/vendor/blamejs/.github/workflows/npm-publish.yml +21 -4
- package/lib/vendor/blamejs/.gitignore +6 -0
- package/lib/vendor/blamejs/CHANGELOG.md +26 -0
- package/lib/vendor/blamejs/MIGRATING.md +43 -0
- package/lib/vendor/blamejs/README.md +8 -6
- package/lib/vendor/blamejs/SECURITY.md +19 -3
- package/lib/vendor/blamejs/api-snapshot.json +2190 -664
- package/lib/vendor/blamejs/docker/caddy/localstack.Caddyfile +19 -0
- package/lib/vendor/blamejs/docker/init/generate-certs.sh +1 -1
- package/lib/vendor/blamejs/docker/otel/config.yaml +42 -0
- package/lib/vendor/blamejs/docker/otel/export/.gitkeep +0 -0
- package/lib/vendor/blamejs/docker/postgres/initdb/10-replication.sh +15 -0
- package/lib/vendor/blamejs/docker/postgres/replica-entrypoint.sh +38 -0
- package/lib/vendor/blamejs/docker/toxiproxy/toxiproxy.json +14 -0
- package/lib/vendor/blamejs/docker-compose.test.yml +209 -0
- package/lib/vendor/blamejs/examples/wiki/lib/page-generator.js +132 -0
- package/lib/vendor/blamejs/examples/wiki/lib/source-comment-block-validator.js +221 -61
- package/lib/vendor/blamejs/examples/wiki/lib/source-doc-parser.js +144 -9
- package/lib/vendor/blamejs/examples/wiki/test/e2e.js +99 -0
- package/lib/vendor/blamejs/fuzz/guard-sql.fuzz.js +36 -0
- package/lib/vendor/blamejs/index.js +4 -0
- package/lib/vendor/blamejs/lib/agent-envelope-mac.js +104 -0
- package/lib/vendor/blamejs/lib/agent-event-bus.js +105 -4
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +8 -42
- package/lib/vendor/blamejs/lib/ai-content-detect.js +9 -10
- package/lib/vendor/blamejs/lib/api-key.js +158 -77
- package/lib/vendor/blamejs/lib/atomic-file.js +62 -4
- package/lib/vendor/blamejs/lib/audit-chain.js +47 -11
- package/lib/vendor/blamejs/lib/audit-sign.js +77 -2
- package/lib/vendor/blamejs/lib/audit-tools.js +79 -51
- package/lib/vendor/blamejs/lib/audit.js +259 -123
- package/lib/vendor/blamejs/lib/auth/oauth.js +53 -9
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +108 -47
- package/lib/vendor/blamejs/lib/auth/saml.js +6 -8
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +31 -5
- package/lib/vendor/blamejs/lib/backup/index.js +45 -10
- package/lib/vendor/blamejs/lib/break-glass.js +355 -147
- package/lib/vendor/blamejs/lib/cache.js +174 -105
- package/lib/vendor/blamejs/lib/chain-writer.js +38 -16
- package/lib/vendor/blamejs/lib/cli.js +19 -14
- package/lib/vendor/blamejs/lib/cluster-provider-db.js +130 -104
- package/lib/vendor/blamejs/lib/cluster-storage.js +119 -22
- package/lib/vendor/blamejs/lib/cluster.js +119 -71
- package/lib/vendor/blamejs/lib/codepoint-class.js +23 -0
- package/lib/vendor/blamejs/lib/compliance.js +206 -4
- package/lib/vendor/blamejs/lib/consent.js +82 -29
- package/lib/vendor/blamejs/lib/constants.js +27 -11
- package/lib/vendor/blamejs/lib/crypto-field.js +916 -156
- package/lib/vendor/blamejs/lib/db-declare-row-policy.js +35 -22
- package/lib/vendor/blamejs/lib/db-file-lifecycle.js +3 -2
- package/lib/vendor/blamejs/lib/db-query.js +882 -260
- package/lib/vendor/blamejs/lib/db-schema.js +228 -44
- package/lib/vendor/blamejs/lib/db.js +249 -99
- package/lib/vendor/blamejs/lib/dsr.js +385 -55
- package/lib/vendor/blamejs/lib/error-page.js +14 -1
- package/lib/vendor/blamejs/lib/external-db-migrate.js +239 -137
- package/lib/vendor/blamejs/lib/external-db.js +549 -34
- package/lib/vendor/blamejs/lib/file-upload.js +52 -7
- package/lib/vendor/blamejs/lib/framework-error.js +20 -1
- package/lib/vendor/blamejs/lib/framework-files.js +73 -0
- package/lib/vendor/blamejs/lib/framework-schema.js +695 -394
- package/lib/vendor/blamejs/lib/gate-contract.js +659 -1
- package/lib/vendor/blamejs/lib/guard-agent-registry.js +26 -44
- package/lib/vendor/blamejs/lib/guard-all.js +1 -0
- package/lib/vendor/blamejs/lib/guard-auth.js +42 -112
- package/lib/vendor/blamejs/lib/guard-cidr.js +33 -154
- package/lib/vendor/blamejs/lib/guard-csv.js +46 -113
- package/lib/vendor/blamejs/lib/guard-domain.js +34 -157
- package/lib/vendor/blamejs/lib/guard-dsn.js +27 -43
- package/lib/vendor/blamejs/lib/guard-email.js +47 -69
- package/lib/vendor/blamejs/lib/guard-envelope.js +19 -32
- package/lib/vendor/blamejs/lib/guard-event-bus-payload.js +24 -42
- package/lib/vendor/blamejs/lib/guard-event-bus-topic.js +25 -43
- package/lib/vendor/blamejs/lib/guard-filename.js +42 -106
- package/lib/vendor/blamejs/lib/guard-graphql.js +42 -123
- package/lib/vendor/blamejs/lib/guard-html.js +53 -108
- package/lib/vendor/blamejs/lib/guard-idempotency-key.js +24 -42
- package/lib/vendor/blamejs/lib/guard-image.js +46 -103
- package/lib/vendor/blamejs/lib/guard-imap-command.js +18 -32
- package/lib/vendor/blamejs/lib/guard-jmap.js +16 -30
- package/lib/vendor/blamejs/lib/guard-json.js +38 -108
- package/lib/vendor/blamejs/lib/guard-jsonpath.js +38 -171
- package/lib/vendor/blamejs/lib/guard-jwt.js +49 -179
- package/lib/vendor/blamejs/lib/guard-list-id.js +25 -41
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +27 -43
- package/lib/vendor/blamejs/lib/guard-mail-compose.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-move.js +26 -44
- package/lib/vendor/blamejs/lib/guard-mail-query.js +28 -46
- package/lib/vendor/blamejs/lib/guard-mail-reply.js +24 -42
- package/lib/vendor/blamejs/lib/guard-mail-sieve.js +24 -42
- package/lib/vendor/blamejs/lib/guard-managesieve-command.js +17 -31
- package/lib/vendor/blamejs/lib/guard-markdown.js +37 -104
- package/lib/vendor/blamejs/lib/guard-message-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-mime.js +39 -151
- package/lib/vendor/blamejs/lib/guard-oauth.js +54 -135
- package/lib/vendor/blamejs/lib/guard-pdf.js +45 -101
- package/lib/vendor/blamejs/lib/guard-pop3-command.js +21 -31
- package/lib/vendor/blamejs/lib/guard-posture-chain.js +24 -42
- package/lib/vendor/blamejs/lib/guard-regex.js +33 -107
- package/lib/vendor/blamejs/lib/guard-saga-config.js +24 -42
- package/lib/vendor/blamejs/lib/guard-shell.js +42 -172
- package/lib/vendor/blamejs/lib/guard-smtp-command.js +48 -54
- package/lib/vendor/blamejs/lib/guard-snapshot-envelope.js +24 -42
- package/lib/vendor/blamejs/lib/guard-sql.js +1491 -0
- package/lib/vendor/blamejs/lib/guard-stream-args.js +24 -43
- package/lib/vendor/blamejs/lib/guard-svg.js +47 -65
- package/lib/vendor/blamejs/lib/guard-template.js +35 -172
- package/lib/vendor/blamejs/lib/guard-tenant-id.js +26 -45
- package/lib/vendor/blamejs/lib/guard-time.js +32 -154
- package/lib/vendor/blamejs/lib/guard-trace-context.js +25 -44
- package/lib/vendor/blamejs/lib/guard-uuid.js +32 -153
- package/lib/vendor/blamejs/lib/guard-xml.js +38 -113
- package/lib/vendor/blamejs/lib/guard-yaml.js +51 -163
- package/lib/vendor/blamejs/lib/http-client.js +37 -9
- package/lib/vendor/blamejs/lib/inbox.js +120 -107
- package/lib/vendor/blamejs/lib/legal-hold.js +121 -50
- package/lib/vendor/blamejs/lib/log-stream-cloudwatch.js +47 -31
- package/lib/vendor/blamejs/lib/log-stream-otlp.js +32 -18
- package/lib/vendor/blamejs/lib/mail-auth.js +236 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +2 -6
- package/lib/vendor/blamejs/lib/mail-dkim.js +1 -0
- package/lib/vendor/blamejs/lib/mail-greylist.js +2 -6
- package/lib/vendor/blamejs/lib/mail-helo.js +2 -6
- package/lib/vendor/blamejs/lib/mail-journal.js +85 -64
- package/lib/vendor/blamejs/lib/mail-rbl.js +2 -6
- package/lib/vendor/blamejs/lib/mail-scan.js +2 -6
- package/lib/vendor/blamejs/lib/mail-server-jmap.js +117 -12
- package/lib/vendor/blamejs/lib/mail-server-mx.js +276 -7
- package/lib/vendor/blamejs/lib/mail-spam-score.js +2 -6
- package/lib/vendor/blamejs/lib/mail-store.js +293 -154
- package/lib/vendor/blamejs/lib/mail.js +8 -4
- package/lib/vendor/blamejs/lib/middleware/body-parser.js +71 -25
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +19 -8
- package/lib/vendor/blamejs/lib/middleware/dpop.js +10 -1
- package/lib/vendor/blamejs/lib/middleware/fetch-metadata.js +17 -7
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +75 -51
- package/lib/vendor/blamejs/lib/middleware/rate-limit.js +102 -32
- package/lib/vendor/blamejs/lib/middleware/security-headers.js +21 -5
- package/lib/vendor/blamejs/lib/migrations.js +108 -66
- package/lib/vendor/blamejs/lib/network-heartbeat.js +7 -0
- package/lib/vendor/blamejs/lib/network-proxy.js +24 -1
- package/lib/vendor/blamejs/lib/nonce-store.js +31 -9
- package/lib/vendor/blamejs/lib/object-store/azure-blob-bucket-ops.js +9 -4
- package/lib/vendor/blamejs/lib/object-store/azure-blob.js +57 -3
- package/lib/vendor/blamejs/lib/object-store/gcs.js +4 -1
- package/lib/vendor/blamejs/lib/object-store/sigv4-bucket-ops.js +5 -2
- package/lib/vendor/blamejs/lib/object-store/sigv4.js +38 -6
- package/lib/vendor/blamejs/lib/observability-otlp-exporter.js +9 -1
- package/lib/vendor/blamejs/lib/observability.js +124 -0
- package/lib/vendor/blamejs/lib/otel-export.js +12 -3
- package/lib/vendor/blamejs/lib/outbox.js +184 -83
- package/lib/vendor/blamejs/lib/parsers/safe-xml.js +47 -7
- package/lib/vendor/blamejs/lib/pqc-agent.js +44 -0
- package/lib/vendor/blamejs/lib/pubsub-cluster.js +42 -20
- package/lib/vendor/blamejs/lib/queue-local.js +225 -140
- package/lib/vendor/blamejs/lib/queue-redis.js +9 -1
- package/lib/vendor/blamejs/lib/queue-sqs.js +6 -0
- package/lib/vendor/blamejs/lib/queue.js +7 -0
- package/lib/vendor/blamejs/lib/redact.js +68 -11
- package/lib/vendor/blamejs/lib/redis-client.js +160 -31
- package/lib/vendor/blamejs/lib/request-helpers.js +7 -0
- package/lib/vendor/blamejs/lib/retention.js +101 -40
- package/lib/vendor/blamejs/lib/router.js +212 -5
- package/lib/vendor/blamejs/lib/safe-dns.js +29 -45
- package/lib/vendor/blamejs/lib/safe-ical.js +18 -33
- package/lib/vendor/blamejs/lib/safe-icap.js +27 -43
- package/lib/vendor/blamejs/lib/safe-sieve.js +21 -40
- package/lib/vendor/blamejs/lib/safe-sql.js +212 -3
- package/lib/vendor/blamejs/lib/safe-url.js +170 -3
- package/lib/vendor/blamejs/lib/safe-vcard.js +18 -33
- package/lib/vendor/blamejs/lib/scheduler.js +35 -12
- package/lib/vendor/blamejs/lib/seeders.js +122 -74
- package/lib/vendor/blamejs/lib/session-stores.js +42 -14
- package/lib/vendor/blamejs/lib/session.js +175 -77
- package/lib/vendor/blamejs/lib/sql.js +3842 -0
- package/lib/vendor/blamejs/lib/sse.js +26 -0
- package/lib/vendor/blamejs/lib/ssrf-guard.js +151 -4
- package/lib/vendor/blamejs/lib/static.js +177 -34
- package/lib/vendor/blamejs/lib/subject.js +96 -49
- package/lib/vendor/blamejs/lib/vault/index.js +3 -2
- package/lib/vendor/blamejs/lib/vault/passphrase-ops.js +3 -2
- package/lib/vendor/blamejs/lib/vault/rotate.js +168 -108
- package/lib/vendor/blamejs/lib/vault-aad.js +6 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +2 -0
- package/lib/vendor/blamejs/lib/websocket.js +35 -5
- package/lib/vendor/blamejs/lib/worker-pool.js +11 -0
- package/lib/vendor/blamejs/package.json +2 -2
- package/lib/vendor/blamejs/release-notes/v0.14.x.json +1503 -0
- package/lib/vendor/blamejs/release-notes/v0.15.0.json +77 -0
- package/lib/vendor/blamejs/release-notes/v0.15.1.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.2.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.3.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.4.json +39 -0
- package/lib/vendor/blamejs/release-notes/v0.15.5.json +22 -0
- package/lib/vendor/blamejs/release-notes/v0.15.6.json +59 -0
- package/lib/vendor/blamejs/scripts/check-services.js +21 -0
- package/lib/vendor/blamejs/scripts/gen-migrating.js +51 -0
- package/lib/vendor/blamejs/scripts/release.js +398 -38
- package/lib/vendor/blamejs/test/00-primitives.js +117 -0
- package/lib/vendor/blamejs/test/10-state.js +140 -14
- package/lib/vendor/blamejs/test/20-db.js +65 -2
- package/lib/vendor/blamejs/test/helpers/db.js +9 -0
- package/lib/vendor/blamejs/test/helpers/drivers.js +27 -15
- package/lib/vendor/blamejs/test/helpers/services.js +21 -0
- package/lib/vendor/blamejs/test/integration/audit-actor-binding-pg.test.js +246 -0
- package/lib/vendor/blamejs/test/integration/audit-chain-external-db.test.js +517 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-mysql.test.js +639 -0
- package/lib/vendor/blamejs/test/integration/audit-stack-postgres.test.js +832 -0
- package/lib/vendor/blamejs/test/integration/backup-restore-objectstore.test.js +453 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-mysql.test.js +649 -0
- package/lib/vendor/blamejs/test/integration/data-layer-cluster-pg.test.js +770 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql-privacy.test.js +630 -0
- package/lib/vendor/blamejs/test/integration/data-layer-mysql.test.js +610 -0
- package/lib/vendor/blamejs/test/integration/data-layer-pg.test.js +577 -0
- package/lib/vendor/blamejs/test/integration/data-layer-postgres.test.js +771 -0
- package/lib/vendor/blamejs/test/integration/db-layer-mysql.test.js +549 -0
- package/lib/vendor/blamejs/test/integration/db-layer-postgres.test.js +598 -0
- package/lib/vendor/blamejs/test/integration/distributed-scheduler-fencing-pg.test.js +602 -0
- package/lib/vendor/blamejs/test/integration/external-db-postgres.test.js +576 -0
- package/lib/vendor/blamejs/test/integration/framework-schema-mysql.test.js +353 -0
- package/lib/vendor/blamejs/test/integration/log-stream-cloudwatch.test.js +224 -0
- package/lib/vendor/blamejs/test/integration/mail-crypto-smime.test.js +142 -17
- package/lib/vendor/blamejs/test/integration/network-heartbeat.test.js +25 -10
- package/lib/vendor/blamejs/test/integration/object-store-azure.test.js +101 -0
- package/lib/vendor/blamejs/test/integration/object-store-gcs.test.js +239 -0
- package/lib/vendor/blamejs/test/integration/object-store-sigv4.test.js +35 -16
- package/lib/vendor/blamejs/test/integration/object-store-worm-lock.test.js +291 -0
- package/lib/vendor/blamejs/test/integration/pubsub.test.js +14 -0
- package/lib/vendor/blamejs/test/integration/queue-sqs.test.js +322 -0
- package/lib/vendor/blamejs/test/integration/redis-reconnect-toxiproxy.test.js +300 -0
- package/lib/vendor/blamejs/test/integration/sql-fts5-catalog-sqlite.test.js +154 -0
- package/lib/vendor/blamejs/test/integration/tls-classical-downgrade-audit.test.js +71 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/agent-event-bus.test.js +175 -12
- package/lib/vendor/blamejs/test/layer-0-primitives/atomic-file-exclusive-temp.test.js +216 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-checkpoint-false-rollback.test.js +203 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-query-self-log.test.js +126 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-safeemit-redacts-secrets.test.js +196 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-signing-key-rotation.test.js +197 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-verifybundle-tamper.test.js +209 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/azure-blob-key-encoding.test.js +121 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-residency-posture.test.js +168 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/backup-scheduletest-drill.test.js +318 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/break-glass.test.js +233 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +1120 -14
- package/lib/vendor/blamejs/test/layer-0-primitives/compliance.test.js +229 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +24 -7
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-dual-read-migrate.test.js +165 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-per-row-key.test.js +350 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-unseal-rate-cap.test.js +27 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-upgrade-dialect.test.js +76 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-interop-oracles.test.js +392 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/csrf-protect.test.js +159 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +180 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-cross-schema.test.js +5 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/db-query-sealed-field-in.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-raw-residency-gate.test.js +128 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-drift.test.js +38 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/db-schema-reconcile-emittable.test.js +127 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-stream-and-payload-shape.test.js +267 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-worm.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/defineguard-default-gate-posture-caps.test.js +30 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dpop-middleware-replaystore-required.test.js +46 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/dsr.test.js +218 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/erase-posture-vacuum.test.js +210 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +4 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-migrate.test.js +48 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/federation-vc-suite.test.js +237 -5
- package/lib/vendor/blamejs/test/layer-0-primitives/fetch-metadata.test.js +20 -9
- package/lib/vendor/blamejs/test/layer-0-primitives/file-upload-content-safety-skip-audit.test.js +193 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/guard-csv.test.js +90 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/http-client-stream.test.js +85 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/idempotency-key.test.js +10 -6
- package/lib/vendor/blamejs/test/layer-0-primitives/inbox.test.js +15 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/legal-hold.test.js +146 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-auth.test.js +189 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-journal.test.js +3 -1
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-jmap.test.js +123 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-server-mx.test.js +207 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/mail-store.test.js +74 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/oauth-callback.test.js +43 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otel-export.test.js +133 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/otlp-attr-redaction.test.js +101 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/outbox-inflight-reaper.test.js +136 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/parsers-standalone.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/passkey-real-vectors.test.js +429 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/pqc-agent-curve.test.js +21 -11
- package/lib/vendor/blamejs/test/layer-0-primitives/queue-byo-db.test.js +40 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redact-dlp.test.js +83 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/redis-client.test.js +113 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/retention-dryrun-no-vacuum.test.js +99 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/router-use-path-scope.test.js +255 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-url-canonicalize.test.js +309 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/safe-xml.test.js +143 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/saml-subjectconfirmation-notonorafter.test.js +287 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc-ecdsa-p1363.test.js +79 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sd-jwt-vc.test.js +50 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/security-headers.test.js +31 -4
- package/lib/vendor/blamejs/test/layer-0-primitives/session-extensions.test.js +45 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sigv4-bucket-ops.test.js +49 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sql.test.js +595 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/sse-backpressure.test.js +91 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/ssrf-guard.test.js +69 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/static.test.js +194 -2
- package/lib/vendor/blamejs/test/layer-0-primitives/websocket-extension-header.test.js +88 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/worker-pool-recycle-race.test.js +66 -0
- package/lib/vendor/blamejs/test/layer-1-state/api-key.test.js +84 -0
- package/lib/vendor/blamejs/test/layer-5-integration/external-db-residency.test.js +638 -0
- package/lib/vendor/blamejs/test/layer-5-integration/guard-host-integration.test.js +21 -0
- package/lib/vendor/blamejs/test/smoke.js +79 -21
- package/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.0.json +0 -43
- package/lib/vendor/blamejs/release-notes/v0.14.1.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.10.json +0 -54
- package/lib/vendor/blamejs/release-notes/v0.14.11.json +0 -72
- package/lib/vendor/blamejs/release-notes/v0.14.12.json +0 -95
- package/lib/vendor/blamejs/release-notes/v0.14.13.json +0 -52
- package/lib/vendor/blamejs/release-notes/v0.14.14.json +0 -31
- package/lib/vendor/blamejs/release-notes/v0.14.16.json +0 -45
- package/lib/vendor/blamejs/release-notes/v0.14.17.json +0 -57
- package/lib/vendor/blamejs/release-notes/v0.14.18.json +0 -127
- package/lib/vendor/blamejs/release-notes/v0.14.19.json +0 -61
- package/lib/vendor/blamejs/release-notes/v0.14.2.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.20.json +0 -73
- package/lib/vendor/blamejs/release-notes/v0.14.21.json +0 -98
- package/lib/vendor/blamejs/release-notes/v0.14.22.json +0 -91
- package/lib/vendor/blamejs/release-notes/v0.14.3.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.4.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.5.json +0 -18
- package/lib/vendor/blamejs/release-notes/v0.14.6.json +0 -60
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +0 -77
- package/lib/vendor/blamejs/release-notes/v0.14.8.json +0 -27
- package/lib/vendor/blamejs/release-notes/v0.14.9.json +0 -40
|
@@ -49,6 +49,7 @@ var requestHelpers = require("./request-helpers");
|
|
|
49
49
|
var safeAsync = require("./safe-async");
|
|
50
50
|
var safeJson = require("./safe-json");
|
|
51
51
|
var safeSql = require("./safe-sql");
|
|
52
|
+
var sql = require("./sql");
|
|
52
53
|
var totp = require("./totp");
|
|
53
54
|
var validateOpts = require("./validate-opts");
|
|
54
55
|
var { defineClass } = require("./framework-error");
|
|
@@ -75,6 +76,10 @@ var DEK_BYTES = C.BYTES.bytes(32);
|
|
|
75
76
|
var GRANT_ID_BYTES = C.BYTES.bytes(16);
|
|
76
77
|
|
|
77
78
|
var DEFAULT_GRANT_TTL_MS = C.TIME.minutes(15);
|
|
79
|
+
// Replay-step retention. A TOTP code is only valid inside the verifier's
|
|
80
|
+
// drift window (minutes); retaining the highest-accepted step for an hour
|
|
81
|
+
// guarantees any in-window replay attempt arrives after the floor is set.
|
|
82
|
+
var REPLAY_STEP_TTL_MS = C.TIME.hours(1);
|
|
78
83
|
var DEFAULT_MAX_ROWS = 1; // operator-locked: row-by-row auth
|
|
79
84
|
var DEFAULT_REASON_MIN_LEN = 12;
|
|
80
85
|
var DEFAULT_LOCKED_BEHAVIOR = "throw"; // or "redact"
|
|
@@ -82,6 +87,34 @@ var DEFAULT_AUDIT_REASON = "cleartext";
|
|
|
82
87
|
var ALLOWED_FACTORS = ["totp", "passkey"];
|
|
83
88
|
var ALLOWED_REASON_STORAGE = ["cleartext", "hmac", "both"];
|
|
84
89
|
|
|
90
|
+
// cryptoField REGISTRY KEYS for the two break-glass framework tables. These
|
|
91
|
+
// are the names db.js's FRAMEWORK_SCHEMA registered the tables under, so
|
|
92
|
+
// seal / unseal / computeDerived must key off the byte-identical literal —
|
|
93
|
+
// resolving them through frameworkSchema.tableName would diverge the seal-side
|
|
94
|
+
// key from the registration under a custom prefix and break decryption. (SQL
|
|
95
|
+
// composed via b.sql passes the SAME bare logical name so clusterStorage can
|
|
96
|
+
// rewrite the table reference; these constants cover only the cryptoField
|
|
97
|
+
// keying.) allow:hand-rolled-sql — cryptoField registry keys, not SQL text.
|
|
98
|
+
var POLICIES_TABLE = "_blamejs_break_glass_policies"; // allow:hand-rolled-sql
|
|
99
|
+
var GRANTS_TABLE = "_blamejs_break_glass_grants"; // allow:hand-rolled-sql
|
|
100
|
+
|
|
101
|
+
// b.sql opts for every statement break-glass dispatches through
|
|
102
|
+
// clusterStorage. Thread the ACTIVE backend dialect (clusterStorage.dialect()
|
|
103
|
+
// — "sqlite" single-node, "postgres" | "mysql" in cluster mode) so the
|
|
104
|
+
// emitted identifier quoting + dialect idioms (ON CONFLICT vs ON DUPLICATE
|
|
105
|
+
// KEY) match the backend the SQL dispatches to. Defaulting to "sqlite" works
|
|
106
|
+
// on Postgres only by accident (both double-quote identifiers) and emits the
|
|
107
|
+
// wrong quoting on MySQL. clusterStorage.execute still rewrites framework
|
|
108
|
+
// table names + translates `?` placeholders at dispatch; this controls only
|
|
109
|
+
// the builder-side quoting + idiom selection.
|
|
110
|
+
// _sqlOpts() — framework tables (policies / grants); name resolved bare,
|
|
111
|
+
// clusterStorage rewrites the prefix.
|
|
112
|
+
// _appSqlOpts() — the operator's glass-locked app table; quoteName so b.sql
|
|
113
|
+
// quotes the (validated) identifier, and it is NOT
|
|
114
|
+
// framework-rewritten.
|
|
115
|
+
function _sqlOpts() { return { dialect: clusterStorage.dialect() }; }
|
|
116
|
+
function _appSqlOpts() { return { dialect: clusterStorage.dialect(), quoteName: true }; }
|
|
117
|
+
|
|
85
118
|
// In-memory policy cache. Cluster-shared via the policies table; the
|
|
86
119
|
// cache short-circuits the DB roundtrip on the unsealRow hot path.
|
|
87
120
|
// Populated on first access per-table; invalidated on policy.set/delete.
|
|
@@ -153,10 +186,14 @@ async function _ensureDek(table) {
|
|
|
153
186
|
// DEK is vault-sealed and stored in the policy row's `dekSealed`
|
|
154
187
|
// column. Generated lazily on first use of cryptographic-mode for
|
|
155
188
|
// the table. Cached in-memory after first read.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
)
|
|
189
|
+
// The policy table is external-only; its LOGICAL name IS the
|
|
190
|
+
// `_blamejs_`-prefixed name (self-mapped in LOCAL_TO_EXTERNAL), passed
|
|
191
|
+
// bare to b.sql so clusterStorage rewrites + placeholderizes.
|
|
192
|
+
var dekReadBuilt = sql.select("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
|
|
193
|
+
.columns(["dekSealed"])
|
|
194
|
+
.where("tableName", table)
|
|
195
|
+
.toSql();
|
|
196
|
+
var rows = await clusterStorage.executeAll(dekReadBuilt.sql, dekReadBuilt.params);
|
|
160
197
|
if (!rows || rows.length === 0) {
|
|
161
198
|
throw new BreakGlassError("breakglass/policy-not-set",
|
|
162
199
|
"_ensureDek: no policy for table '" + table + "'", true);
|
|
@@ -168,10 +205,11 @@ async function _ensureDek(table) {
|
|
|
168
205
|
} else {
|
|
169
206
|
dek = generateBytes(DEK_BYTES);
|
|
170
207
|
var sealedDek = vault().seal(dek.toString("base64"));
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
208
|
+
var dekUpdBuilt = sql.update("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
|
|
209
|
+
.set({ dekSealed: sealedDek })
|
|
210
|
+
.where("tableName", table)
|
|
211
|
+
.toSql();
|
|
212
|
+
await clusterStorage.execute(dekUpdBuilt.sql, dekUpdBuilt.params);
|
|
175
213
|
}
|
|
176
214
|
dekCache.set(table, dek);
|
|
177
215
|
return dek;
|
|
@@ -343,14 +381,16 @@ async function migrate(table, opts) {
|
|
|
343
381
|
var lastId = "";
|
|
344
382
|
// Iterate via _id-keyset paging so we don't load the whole table into memory.
|
|
345
383
|
while (true) {
|
|
346
|
-
// table is already validated as a safe
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
|
|
350
|
-
var
|
|
351
|
-
"
|
|
352
|
-
|
|
353
|
-
|
|
384
|
+
// `table` is an operator app table (already validated as a safe
|
|
385
|
+
// identifier via _validatePolicySet). quoteName:true makes b.sql quote
|
|
386
|
+
// the name (reserved-word / case-sensitive safe); it is NOT a framework
|
|
387
|
+
// table, so clusterStorage's resolveTables leaves it untouched.
|
|
388
|
+
var pageBuilt = sql.select(table, _appSqlOpts())
|
|
389
|
+
.whereOp("_id", ">", lastId)
|
|
390
|
+
.orderBy("_id", "asc")
|
|
391
|
+
.limit(batchSize)
|
|
392
|
+
.toSql();
|
|
393
|
+
var rows = await clusterStorage.executeAll(pageBuilt.sql, pageBuilt.params);
|
|
354
394
|
if (!rows || rows.length === 0) break;
|
|
355
395
|
for (var i = 0; i < rows.length; i++) {
|
|
356
396
|
totalRows++;
|
|
@@ -374,16 +414,16 @@ async function migrate(table, opts) {
|
|
|
374
414
|
// the cell ciphertext stays as a literal string, not double-sealed.
|
|
375
415
|
var setCols = Object.keys(update).filter(function (k) { return k !== "_id"; });
|
|
376
416
|
if (setCols.length > 0) {
|
|
377
|
-
// Column names came from the validated policy.columns
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
var
|
|
381
|
-
var
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
"
|
|
385
|
-
|
|
386
|
-
);
|
|
417
|
+
// Column names came from the validated policy.columns. b.sql
|
|
418
|
+
// quotes every SET target + binds every value; the operator app
|
|
419
|
+
// table is quoted (quoteName) and not framework-rewritten.
|
|
420
|
+
var setMap = {};
|
|
421
|
+
for (var sc = 0; sc < setCols.length; sc++) setMap[setCols[sc]] = update[setCols[sc]];
|
|
422
|
+
var updBuilt = sql.update(table, _appSqlOpts())
|
|
423
|
+
.set(setMap)
|
|
424
|
+
.where("_id", row._id)
|
|
425
|
+
.toSql();
|
|
426
|
+
await clusterStorage.execute(updBuilt.sql, updBuilt.params);
|
|
387
427
|
migratedRows++;
|
|
388
428
|
}
|
|
389
429
|
} else {
|
|
@@ -658,17 +698,20 @@ async function policySet(table, opts, callerOpts) {
|
|
|
658
698
|
auditReasonStorage: validated.auditReasonStorage,
|
|
659
699
|
updatedAt: Date.now(),
|
|
660
700
|
};
|
|
661
|
-
var sealed = cryptoField.sealRow(
|
|
662
|
-
// UPSERT
|
|
701
|
+
var sealed = cryptoField.sealRow(POLICIES_TABLE, policyRow);
|
|
702
|
+
// UPSERT via b.sql ON CONFLICT(tableName) DO UPDATE (Postgres + SQLite).
|
|
703
|
+
// BARE logical framework table — clusterStorage rewrites + placeholderizes;
|
|
704
|
+
// b.sql quotes every column + binds every sealed value. The conflict key
|
|
705
|
+
// (tableName) is excluded from the DO UPDATE set.
|
|
663
706
|
var keys = Object.keys(sealed);
|
|
664
|
-
var
|
|
665
|
-
var
|
|
666
|
-
|
|
667
|
-
.
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
await clusterStorage.execute(sql,
|
|
707
|
+
var setCols = keys.filter(function (k) { return k !== "tableName"; });
|
|
708
|
+
var policyBuilt = sql.upsert("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
|
|
709
|
+
.columns(keys)
|
|
710
|
+
.values(sealed)
|
|
711
|
+
.onConflict(["tableName"])
|
|
712
|
+
.doUpdateFromExcluded(setCols)
|
|
713
|
+
.toSql();
|
|
714
|
+
await clusterStorage.execute(policyBuilt.sql, policyBuilt.params);
|
|
672
715
|
policyCache.delete(table);
|
|
673
716
|
|
|
674
717
|
audit.safeEmit({
|
|
@@ -711,15 +754,15 @@ async function policyGet(table) {
|
|
|
711
754
|
_requireInit();
|
|
712
755
|
if (typeof table !== "string" || table.length === 0) return null;
|
|
713
756
|
if (policyCache.has(table)) return policyCache.get(table);
|
|
714
|
-
var
|
|
715
|
-
"
|
|
716
|
-
|
|
717
|
-
);
|
|
757
|
+
var getBuilt = sql.select("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
|
|
758
|
+
.where("tableName", table)
|
|
759
|
+
.toSql();
|
|
760
|
+
var rows = await clusterStorage.executeAll(getBuilt.sql, getBuilt.params);
|
|
718
761
|
if (!rows || rows.length === 0) {
|
|
719
762
|
policyCache.set(table, null);
|
|
720
763
|
return null;
|
|
721
764
|
}
|
|
722
|
-
var unsealed = cryptoField.unsealRow(
|
|
765
|
+
var unsealed = cryptoField.unsealRow(POLICIES_TABLE, rows[0]);
|
|
723
766
|
var policy = {
|
|
724
767
|
table: unsealed.tableName,
|
|
725
768
|
columns: safeJson.parse(unsealed.columnsJson, { maxBytes: C.BYTES.kib(64) }),
|
|
@@ -763,9 +806,11 @@ async function policyGet(table) {
|
|
|
763
806
|
*/
|
|
764
807
|
async function policyList() {
|
|
765
808
|
_requireInit();
|
|
766
|
-
var
|
|
767
|
-
"
|
|
768
|
-
|
|
809
|
+
var listBuilt = sql.select("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
|
|
810
|
+
.columns(["tableName"])
|
|
811
|
+
.orderBy("tableName", "asc")
|
|
812
|
+
.toSql();
|
|
813
|
+
var rows = await clusterStorage.executeAll(listBuilt.sql, listBuilt.params);
|
|
769
814
|
var out = [];
|
|
770
815
|
for (var i = 0; i < (rows || []).length; i++) {
|
|
771
816
|
var p = await policyGet(rows[i].tableName);
|
|
@@ -797,10 +842,10 @@ async function policyDelete(table, callerOpts) {
|
|
|
797
842
|
throw new BreakGlassError("breakglass/bad-policy",
|
|
798
843
|
"policy.delete: table must be a non-empty string");
|
|
799
844
|
}
|
|
800
|
-
|
|
801
|
-
"
|
|
802
|
-
|
|
803
|
-
);
|
|
845
|
+
var delBuilt = sql.delete("_blamejs_break_glass_policies", _sqlOpts()) // allow:hand-rolled-sql
|
|
846
|
+
.where("tableName", table)
|
|
847
|
+
.toSql();
|
|
848
|
+
await clusterStorage.execute(delBuilt.sql, delBuilt.params);
|
|
804
849
|
policyCache.delete(table);
|
|
805
850
|
audit.safeEmit({
|
|
806
851
|
action: "breakglass.policy.delete",
|
|
@@ -817,10 +862,58 @@ function _verifyTotpFactor(factor) {
|
|
|
817
862
|
if (!factor || typeof factor !== "object") return { ok: false };
|
|
818
863
|
if (typeof factor.secret !== "string" || factor.secret.length === 0) return { ok: false };
|
|
819
864
|
if (typeof factor.code !== "string" || factor.code.length === 0) return { ok: false };
|
|
820
|
-
|
|
865
|
+
// factor.now threads a deterministic test clock into totp.verify. The
|
|
866
|
+
// replay floor is NOT applied here: acceptance reserves the matched step
|
|
867
|
+
// atomically in _reserveTotpStep, so two concurrent grants presenting the
|
|
868
|
+
// same in-window code cannot both pass (a read-then-commit floor races —
|
|
869
|
+
// both reads observe the old floor before either commits). totp.verify
|
|
870
|
+
// returns the step the code matches (a fixed value for a given code within
|
|
871
|
+
// the drift window) or false; the reserve then floors replays of that step.
|
|
872
|
+
var vopts = {};
|
|
873
|
+
if (typeof factor.now === "number") vopts.now = factor.now;
|
|
874
|
+
var verified = totp.verify(factor.secret, factor.code, vopts);
|
|
821
875
|
return { ok: verified !== false, step: verified };
|
|
822
876
|
}
|
|
823
877
|
|
|
878
|
+
// Replay-step cache key. Keyed by BOTH the actorId AND a non-reversible
|
|
879
|
+
// fingerprint of the TOTP secret. Keying on actorId alone would falsely
|
|
880
|
+
// reject a legitimate second grant when two distinct credentials accept a
|
|
881
|
+
// code at the same TOTP step (the step number is a wall-clock counter, not
|
|
882
|
+
// per-credential) — the secret fingerprint disambiguates them. The secret
|
|
883
|
+
// never reaches the cache in any reversible form.
|
|
884
|
+
function _replayStepKey(actorId, secret) {
|
|
885
|
+
return "totp-step:" + actorId + ":" + sha3Hash(secret);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// Atomically reserve the accepted TOTP step for (actorId, secret): advance
|
|
889
|
+
// the stored replay floor to `step` only when `step` is strictly above the
|
|
890
|
+
// current floor, and report whether THIS caller won the reservation. The
|
|
891
|
+
// compare-and-advance is one atomic cache update, so two concurrent grant()
|
|
892
|
+
// calls presenting the same in-window code cannot both pass — the first wins
|
|
893
|
+
// and raises the floor to `step`, the second observes step <= floor and is
|
|
894
|
+
// refused. (A separate read-then-commit sequence let both reads see the old
|
|
895
|
+
// floor before either committed, so both verified — the replay this closes.)
|
|
896
|
+
// The TTL outlives the verify drift window many times over so a replayed code
|
|
897
|
+
// stays floored until it expires.
|
|
898
|
+
//
|
|
899
|
+
// Fails CLOSED (returns false) on a cache fault: a grant cannot proceed
|
|
900
|
+
// without a working factor cache regardless — the lockout check at the top of
|
|
901
|
+
// grant() already gates on the same cache — so refusing here can only reject,
|
|
902
|
+
// never loosen replay protection.
|
|
903
|
+
async function _reserveTotpStep(actorId, secret, step) {
|
|
904
|
+
_ensureFactorLockout();
|
|
905
|
+
if (typeof step !== "number") return false;
|
|
906
|
+
var won = false;
|
|
907
|
+
try {
|
|
908
|
+
await _factorLockoutCache.update(_replayStepKey(actorId, secret), function (prior) {
|
|
909
|
+
if (typeof prior === "number" && step <= prior) { won = false; return { value: prior }; }
|
|
910
|
+
won = true;
|
|
911
|
+
return { value: step };
|
|
912
|
+
}, { ttlMs: REPLAY_STEP_TTL_MS });
|
|
913
|
+
} catch (_e) { return false; }
|
|
914
|
+
return won;
|
|
915
|
+
}
|
|
916
|
+
|
|
824
917
|
// Passkey factor — operator presents a WebAuthn assertion plus the
|
|
825
918
|
// challenge/origin/RPID + the previously-enrolled credential record.
|
|
826
919
|
// Phishing-resistant; the private key lives on the YubiKey, not in
|
|
@@ -990,8 +1083,20 @@ async function grant(opts) {
|
|
|
990
1083
|
}
|
|
991
1084
|
|
|
992
1085
|
var factorOk = false;
|
|
1086
|
+
var totpSecret = null;
|
|
993
1087
|
if (factorType === "totp") {
|
|
994
|
-
|
|
1088
|
+
totpSecret = opts.factor && opts.factor.secret;
|
|
1089
|
+
// Verify the code, then atomically reserve the step it matched as the act
|
|
1090
|
+
// of acceptance. The reserve advances the per-(actor,secret) replay floor
|
|
1091
|
+
// in one compare-and-set, so a code already redeemed inside the drift
|
|
1092
|
+
// window — including by a concurrent grant for the same credential — is
|
|
1093
|
+
// refused. (A read-then-commit floor raced: both grants read the old
|
|
1094
|
+
// floor before either committed, so both passed.)
|
|
1095
|
+
var totpResult = _verifyTotpFactor(opts.factor);
|
|
1096
|
+
if (totpResult.ok && typeof totpResult.step === "number" &&
|
|
1097
|
+
typeof totpSecret === "string" && totpSecret.length > 0) {
|
|
1098
|
+
factorOk = await _reserveTotpStep(actorId, totpSecret, totpResult.step);
|
|
1099
|
+
}
|
|
995
1100
|
} else if (factorType === "passkey") {
|
|
996
1101
|
factorOk = (await _verifyPasskeyFactor(opts.factor)).ok;
|
|
997
1102
|
}
|
|
@@ -1036,14 +1141,13 @@ async function grant(opts) {
|
|
|
1036
1141
|
ip: ipFromReq,
|
|
1037
1142
|
kwGrantHalf: null,
|
|
1038
1143
|
};
|
|
1039
|
-
var sealed = cryptoField.sealRow(
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
var
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
);
|
|
1144
|
+
var sealed = cryptoField.sealRow(GRANTS_TABLE, grantRow);
|
|
1145
|
+
// BARE logical framework table — clusterStorage rewrites + placeholderizes;
|
|
1146
|
+
// b.sql quotes every column + binds every sealed value.
|
|
1147
|
+
var grantInsBuilt = sql.insert("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
|
|
1148
|
+
.values(sealed)
|
|
1149
|
+
.toSql();
|
|
1150
|
+
await clusterStorage.execute(grantInsBuilt.sql, grantInsBuilt.params);
|
|
1047
1151
|
|
|
1048
1152
|
// Audit
|
|
1049
1153
|
var reasonForAudit = _reasonForAudit(reason, policy.auditReasonStorage);
|
|
@@ -1086,6 +1190,78 @@ function _reasonForAudit(reason, mode) {
|
|
|
1086
1190
|
return out;
|
|
1087
1191
|
}
|
|
1088
1192
|
|
|
1193
|
+
// Enforce the grant's IP / session bindings at redemption. policy.set
|
|
1194
|
+
// documents pinIp / sessionPin as default-ON, and grant() captures
|
|
1195
|
+
// grantRow.ip / grantRow.sessionId at mint time — but without this gate
|
|
1196
|
+
// the bindings are stored-and-never-enforced (a grant minted from IP-A
|
|
1197
|
+
// would redeem from IP-B). Called BEFORE the SELECT-then-increment so a
|
|
1198
|
+
// mismatch does not consume a grant.
|
|
1199
|
+
//
|
|
1200
|
+
// FAIL-CLOSED: when a pin is requested but the binding was captured null
|
|
1201
|
+
// (e.g. an Express-shaped req whose IP requestHelpers.clientIp couldn't
|
|
1202
|
+
// read at mint time), the redemption is REFUSED rather than silently
|
|
1203
|
+
// skipped — a `grantRow.ip != null` short-circuit would defeat the pin
|
|
1204
|
+
// for exactly the requests whose binding capture failed.
|
|
1205
|
+
function _enforceGrantPins(policy, grantRow, redeemReq, actorFor) {
|
|
1206
|
+
if (!policy) return;
|
|
1207
|
+
if (policy.pinIp) {
|
|
1208
|
+
if (grantRow.ip == null) {
|
|
1209
|
+
audit.safeEmit({
|
|
1210
|
+
action: "breakglass.unsealrow",
|
|
1211
|
+
outcome: "denied",
|
|
1212
|
+
actor: actorFor(grantRow),
|
|
1213
|
+
reason: "grant-ip-binding-missing",
|
|
1214
|
+
metadata: { grantId: grantRow._id, table: grantRow.scopeTable },
|
|
1215
|
+
});
|
|
1216
|
+
throw new BreakGlassError("breakglass/grant-ip-mismatch",
|
|
1217
|
+
"unsealRow: grant " + grantRow._id + " has pinIp on but no IP was " +
|
|
1218
|
+
"captured at mint (fail-closed) — re-mint from a request whose client " +
|
|
1219
|
+
"IP the framework can resolve", true);
|
|
1220
|
+
}
|
|
1221
|
+
var redeemIp = requestHelpers.clientIp(redeemReq, { trustProxy: _trustProxy });
|
|
1222
|
+
if (redeemIp !== grantRow.ip) {
|
|
1223
|
+
audit.safeEmit({
|
|
1224
|
+
action: "breakglass.unsealrow",
|
|
1225
|
+
outcome: "denied",
|
|
1226
|
+
actor: actorFor(grantRow),
|
|
1227
|
+
reason: "grant-ip-mismatch",
|
|
1228
|
+
metadata: { grantId: grantRow._id, table: grantRow.scopeTable },
|
|
1229
|
+
});
|
|
1230
|
+
throw new BreakGlassError("breakglass/grant-ip-mismatch",
|
|
1231
|
+
"unsealRow: grant " + grantRow._id + " is pinned to its issuing IP " +
|
|
1232
|
+
"and this redemption arrived from a different address", true);
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
if (policy.sessionPin) {
|
|
1236
|
+
if (grantRow.sessionId == null) {
|
|
1237
|
+
audit.safeEmit({
|
|
1238
|
+
action: "breakglass.unsealrow",
|
|
1239
|
+
outcome: "denied",
|
|
1240
|
+
actor: actorFor(grantRow),
|
|
1241
|
+
reason: "grant-session-binding-missing",
|
|
1242
|
+
metadata: { grantId: grantRow._id, table: grantRow.scopeTable },
|
|
1243
|
+
});
|
|
1244
|
+
throw new BreakGlassError("breakglass/grant-session-mismatch",
|
|
1245
|
+
"unsealRow: grant " + grantRow._id + " has sessionPin on but no " +
|
|
1246
|
+
"session id was captured at mint (fail-closed) — re-mint from a " +
|
|
1247
|
+
"request carrying req.session.id", true);
|
|
1248
|
+
}
|
|
1249
|
+
var redeemSession = (redeemReq && redeemReq.session && redeemReq.session.id) || null;
|
|
1250
|
+
if (redeemSession !== grantRow.sessionId) {
|
|
1251
|
+
audit.safeEmit({
|
|
1252
|
+
action: "breakglass.unsealrow",
|
|
1253
|
+
outcome: "denied",
|
|
1254
|
+
actor: actorFor(grantRow),
|
|
1255
|
+
reason: "grant-session-mismatch",
|
|
1256
|
+
metadata: { grantId: grantRow._id, table: grantRow.scopeTable },
|
|
1257
|
+
});
|
|
1258
|
+
throw new BreakGlassError("breakglass/grant-session-mismatch",
|
|
1259
|
+
"unsealRow: grant " + grantRow._id + " is pinned to its issuing " +
|
|
1260
|
+
"session and this redemption arrived from a different session", true);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1089
1265
|
// ---- Use a grant ----
|
|
1090
1266
|
|
|
1091
1267
|
/**
|
|
@@ -1137,16 +1313,16 @@ async function unsealRow(grantHandle, table, rowId, opts) {
|
|
|
1137
1313
|
throw new BreakGlassError("breakglass/bad-grant-opts",
|
|
1138
1314
|
"unsealRow: rowId is required");
|
|
1139
1315
|
}
|
|
1140
|
-
var
|
|
1141
|
-
"
|
|
1142
|
-
|
|
1143
|
-
);
|
|
1316
|
+
var grantReadBuilt = sql.select("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
|
|
1317
|
+
.where("_id", grantHandle.id)
|
|
1318
|
+
.toSql();
|
|
1319
|
+
var grantRows = await clusterStorage.executeAll(grantReadBuilt.sql, grantReadBuilt.params);
|
|
1144
1320
|
if (!grantRows || grantRows.length === 0) {
|
|
1145
1321
|
throw new BreakGlassError("breakglass/grant-revoked",
|
|
1146
1322
|
"unsealRow: grant " + grantHandle.id + " not found (deleted or never issued)", true);
|
|
1147
1323
|
}
|
|
1148
1324
|
var sealedGrant = grantRows[0];
|
|
1149
|
-
var grantRow = cryptoField.unsealRow(
|
|
1325
|
+
var grantRow = cryptoField.unsealRow(GRANTS_TABLE, sealedGrant);
|
|
1150
1326
|
|
|
1151
1327
|
// Table mismatch
|
|
1152
1328
|
if (grantRow.scopeTable !== table) {
|
|
@@ -1195,35 +1371,53 @@ async function unsealRow(grantHandle, table, rowId, opts) {
|
|
|
1195
1371
|
grantRow.maxRowsPerGrant + " allowed rows", true);
|
|
1196
1372
|
}
|
|
1197
1373
|
|
|
1374
|
+
// IP / session pin enforcement — BEFORE the SELECT-then-increment so a
|
|
1375
|
+
// pin mismatch does not consume the grant. Fail-closed when a requested
|
|
1376
|
+
// pin's binding was captured null (see _enforceGrantPins). The policy is
|
|
1377
|
+
// fetched once here and reused for the Model-A/B unseal dispatch below.
|
|
1378
|
+
var policy = await policyGet(table);
|
|
1379
|
+
_enforceGrantPins(policy, grantRow, opts.req, _actorFor);
|
|
1380
|
+
|
|
1198
1381
|
// SELECT-before-increment — fetch the target row FIRST. If the row
|
|
1199
1382
|
// doesn't exist (operator typo, race with row-deletion, etc.), the
|
|
1200
1383
|
// grant should not be consumed. Without this ordering, a single
|
|
1201
1384
|
// typo against `maxRowsPerGrant: 1` (the default) exhausts the
|
|
1202
1385
|
// grant and forces the operator to re-do the step-up ceremony.
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1386
|
+
// Operator app table (validated identifier) — quoteName quotes it; it is
|
|
1387
|
+
// not framework-rewritten.
|
|
1388
|
+
var rowReadBuilt = sql.select(table, _appSqlOpts())
|
|
1389
|
+
.where("_id", String(rowId))
|
|
1390
|
+
.toSql();
|
|
1391
|
+
var rows = await clusterStorage.executeAll(rowReadBuilt.sql, rowReadBuilt.params);
|
|
1207
1392
|
if (!rows || rows.length === 0) {
|
|
1208
1393
|
throw new BreakGlassError("breakglass/row-not-found",
|
|
1209
1394
|
"unsealRow: " + table + "[" + rowId + "] not found", true);
|
|
1210
1395
|
}
|
|
1211
1396
|
|
|
1212
|
-
// Increment rowsConsumed (atomic UPDATE with WHERE rowsConsumed < cap
|
|
1213
|
-
//
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
)
|
|
1397
|
+
// Increment rowsConsumed (atomic UPDATE with WHERE rowsConsumed < cap so
|
|
1398
|
+
// concurrent unseals can't both pass the runtime check above). The
|
|
1399
|
+
// rowsConsumed+1 RHS + the rowsConsumed<maxRowsPerGrant column comparison
|
|
1400
|
+
// are guarded raw fragments (b.guardSql + placeholder/literal scan). The
|
|
1401
|
+
// identifier quoting in those raw fragments is dialect-aware (backticks on
|
|
1402
|
+
// MySQL, double-quotes on PG/SQLite) so the column references resolve as
|
|
1403
|
+
// identifiers, not string literals, on the active backend.
|
|
1404
|
+
var incDialect = clusterStorage.dialect();
|
|
1405
|
+
var incBuilt = sql.update("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
|
|
1406
|
+
.setRaw("rowsConsumed", safeSql.quoteIdentifier("rowsConsumed", incDialect) + " + 1", [])
|
|
1407
|
+
.where("_id", grantHandle.id)
|
|
1408
|
+
.whereRaw(safeSql.quoteIdentifier("rowsConsumed", incDialect) + " < " +
|
|
1409
|
+
safeSql.quoteIdentifier("maxRowsPerGrant", incDialect), [])
|
|
1410
|
+
.whereNull("revokedAt")
|
|
1411
|
+
.whereOp("expiresAt", ">", Date.now())
|
|
1412
|
+
.toSql();
|
|
1413
|
+
var updateRes = await clusterStorage.execute(incBuilt.sql, incBuilt.params);
|
|
1221
1414
|
// executeAll-style result; some backends return rowsAffected, others a count.
|
|
1222
1415
|
// Re-query to confirm the increment landed and get the post-increment counter.
|
|
1223
|
-
var
|
|
1224
|
-
"
|
|
1225
|
-
|
|
1226
|
-
|
|
1416
|
+
var postReadBuilt = sql.select("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
|
|
1417
|
+
.columns(["rowsConsumed", "revokedAt", "expiresAt"])
|
|
1418
|
+
.where("_id", grantHandle.id)
|
|
1419
|
+
.toSql();
|
|
1420
|
+
var postRows = await clusterStorage.executeAll(postReadBuilt.sql, postReadBuilt.params);
|
|
1227
1421
|
if (!postRows || postRows.length === 0) {
|
|
1228
1422
|
throw new BreakGlassError("breakglass/grant-revoked",
|
|
1229
1423
|
"unsealRow: grant " + grantHandle.id + " disappeared during unseal", true);
|
|
@@ -1237,7 +1431,8 @@ async function unsealRow(grantHandle, table, rowId, opts) {
|
|
|
1237
1431
|
"unsealRow: grant " + grantHandle.id + " was exhausted by a concurrent read", true);
|
|
1238
1432
|
}
|
|
1239
1433
|
void updateRes;
|
|
1240
|
-
|
|
1434
|
+
// policy was fetched above for the pin enforcement; reuse it for the
|
|
1435
|
+
// Model-A vs Model-B (cryptographic) unseal dispatch.
|
|
1241
1436
|
var unsealedRow;
|
|
1242
1437
|
if (policy && policy.cryptographic) {
|
|
1243
1438
|
// Snapshot the raw glass-locked column ciphertexts BEFORE
|
|
@@ -1325,11 +1520,14 @@ async function revoke(grantId, opts) {
|
|
|
1325
1520
|
}
|
|
1326
1521
|
opts = opts || {};
|
|
1327
1522
|
var nowMs = Date.now();
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1523
|
+
// revokedAt IS NULL keeps the revoke idempotent (already-revoked grants
|
|
1524
|
+
// keep their original timestamp).
|
|
1525
|
+
var revBuilt = sql.update("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
|
|
1526
|
+
.set({ revokedAt: nowMs })
|
|
1527
|
+
.where("_id", grantId)
|
|
1528
|
+
.whereNull("revokedAt")
|
|
1529
|
+
.toSql();
|
|
1530
|
+
await clusterStorage.execute(revBuilt.sql, revBuilt.params);
|
|
1333
1531
|
audit.safeEmit({
|
|
1334
1532
|
action: "breakglass.grant.revoked",
|
|
1335
1533
|
outcome: "success",
|
|
@@ -1372,19 +1570,25 @@ async function listActive(opts) {
|
|
|
1372
1570
|
// Use cryptoField's computeDerived so the hash matches the table's
|
|
1373
1571
|
// hashNamespace prefix — raw sha3Hash would produce a different value.
|
|
1374
1572
|
var derived = cryptoField.computeDerived(
|
|
1375
|
-
|
|
1573
|
+
GRANTS_TABLE, "issuedToActorId", actorId
|
|
1376
1574
|
);
|
|
1377
1575
|
if (!derived) return [];
|
|
1378
1576
|
var nowMs = Date.now();
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1577
|
+
// rowsConsumed < maxRowsPerGrant is a column-to-column comparison (guarded
|
|
1578
|
+
// raw fragment); every other predicate is structured.
|
|
1579
|
+
var laDialect = clusterStorage.dialect();
|
|
1580
|
+
var laBuilt = sql.select("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
|
|
1581
|
+
.where("issuedToActorHash", derived.value)
|
|
1582
|
+
.whereNull("revokedAt")
|
|
1583
|
+
.whereOp("expiresAt", ">", nowMs)
|
|
1584
|
+
.whereRaw(safeSql.quoteIdentifier("rowsConsumed", laDialect) + " < " +
|
|
1585
|
+
safeSql.quoteIdentifier("maxRowsPerGrant", laDialect), [])
|
|
1586
|
+
.orderBy("issuedAt", "desc")
|
|
1587
|
+
.toSql();
|
|
1588
|
+
var rows = await clusterStorage.executeAll(laBuilt.sql, laBuilt.params);
|
|
1385
1589
|
var out = [];
|
|
1386
1590
|
for (var i = 0; i < (rows || []).length; i++) {
|
|
1387
|
-
var u = cryptoField.unsealRow(
|
|
1591
|
+
var u = cryptoField.unsealRow(GRANTS_TABLE, rows[i]);
|
|
1388
1592
|
out.push({
|
|
1389
1593
|
id: u._id,
|
|
1390
1594
|
scopeTable: u.scopeTable,
|
|
@@ -1427,6 +1631,12 @@ async function listActive(opts) {
|
|
|
1427
1631
|
* distinct `breakglass.grant.bypass` audit row so post-incident review
|
|
1428
1632
|
* separates operator-initiated reads from scheduled-job reads.
|
|
1429
1633
|
*
|
|
1634
|
+
* This path is service-to-service: it consumes NO grant row, so the
|
|
1635
|
+
* `pinIp` / `sessionPin` grant bindings enforced by `unsealRow` do not
|
|
1636
|
+
* apply here. A grant that was minted with those pins is not redeemable
|
|
1637
|
+
* through this surface — the bypass is gated solely by the
|
|
1638
|
+
* `serviceAccountBypass` allowlist + required-role check.
|
|
1639
|
+
*
|
|
1430
1640
|
* @opts
|
|
1431
1641
|
* reason: string, // operator-supplied reason recorded into the audit row
|
|
1432
1642
|
*
|
|
@@ -1494,11 +1704,12 @@ async function unsealRowAsService(req, table, rowId, opts) {
|
|
|
1494
1704
|
}
|
|
1495
1705
|
|
|
1496
1706
|
// Fetch + unseal the row (Model A or Model B path, same as
|
|
1497
|
-
// operator-initiated unsealRow).
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1707
|
+
// operator-initiated unsealRow). Operator app table — quoteName quotes it;
|
|
1708
|
+
// it is not framework-rewritten.
|
|
1709
|
+
var svcRowBuilt = sql.select(table, _appSqlOpts())
|
|
1710
|
+
.where("_id", String(rowId))
|
|
1711
|
+
.toSql();
|
|
1712
|
+
var rows = await clusterStorage.executeAll(svcRowBuilt.sql, svcRowBuilt.params);
|
|
1502
1713
|
if (!rows || rows.length === 0) {
|
|
1503
1714
|
throw new BreakGlassError("breakglass/row-not-found",
|
|
1504
1715
|
"unsealRowAsService: " + table + "[" + rowId + "] not found", true);
|
|
@@ -1572,24 +1783,22 @@ async function listActiveAll(opts) {
|
|
|
1572
1783
|
_requireInit();
|
|
1573
1784
|
opts = opts || {};
|
|
1574
1785
|
var nowMs = Date.now();
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
params
|
|
1589
|
-
);
|
|
1786
|
+
// rowsConsumed < maxRowsPerGrant is a column-to-column comparison (guarded
|
|
1787
|
+
// raw fragment); the rest are structured predicates.
|
|
1788
|
+
var laaDialect = clusterStorage.dialect();
|
|
1789
|
+
var laaQb = sql.select("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
|
|
1790
|
+
.whereNull("revokedAt")
|
|
1791
|
+
.whereOp("expiresAt", ">", nowMs)
|
|
1792
|
+
.whereRaw(safeSql.quoteIdentifier("rowsConsumed", laaDialect) + " < " +
|
|
1793
|
+
safeSql.quoteIdentifier("maxRowsPerGrant", laaDialect), []);
|
|
1794
|
+
if (opts.table) laaQb.where("scopeTable", opts.table);
|
|
1795
|
+
if (opts.since) laaQb.whereOp("issuedAt", ">=", opts.since);
|
|
1796
|
+
laaQb.orderBy("issuedAt", "desc");
|
|
1797
|
+
var laaBuilt = laaQb.toSql();
|
|
1798
|
+
var rows = await clusterStorage.executeAll(laaBuilt.sql, laaBuilt.params);
|
|
1590
1799
|
var out = [];
|
|
1591
1800
|
for (var i = 0; i < (rows || []).length; i++) {
|
|
1592
|
-
var u = cryptoField.unsealRow(
|
|
1801
|
+
var u = cryptoField.unsealRow(GRANTS_TABLE, rows[i]);
|
|
1593
1802
|
out.push({
|
|
1594
1803
|
id: u._id,
|
|
1595
1804
|
issuedToActorId: u.issuedToActorId,
|
|
@@ -1646,31 +1855,28 @@ async function revokeAll(criteria, opts) {
|
|
|
1646
1855
|
"revokeAll: at least one of { actorId, table } is required (refusing to mass-revoke without scope)");
|
|
1647
1856
|
}
|
|
1648
1857
|
opts = opts || {};
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
if (criteria.table) {
|
|
1661
|
-
clauses.push("scopeTable = ?");
|
|
1662
|
-
params.push(criteria.table);
|
|
1858
|
+
// The SELECT (snapshot ids) and UPDATE (apply revoke) share one predicate
|
|
1859
|
+
// set; applyRevokeCriteria replays it onto either builder so the WHERE can
|
|
1860
|
+
// never drift between the two.
|
|
1861
|
+
var derived = criteria.actorId
|
|
1862
|
+
? cryptoField.computeDerived(GRANTS_TABLE, "issuedToActorId", criteria.actorId)
|
|
1863
|
+
: null;
|
|
1864
|
+
function applyRevokeCriteria(qb) {
|
|
1865
|
+
qb.whereNull("revokedAt");
|
|
1866
|
+
if (criteria.actorId && derived) qb.where("issuedToActorHash", derived.value);
|
|
1867
|
+
if (criteria.table) qb.where("scopeTable", criteria.table);
|
|
1868
|
+
return qb;
|
|
1663
1869
|
}
|
|
1664
1870
|
// Snapshot the to-be-revoked grant ids first so audit captures specifics.
|
|
1665
|
-
var
|
|
1666
|
-
"
|
|
1667
|
-
|
|
1668
|
-
);
|
|
1871
|
+
var idSelBuilt = applyRevokeCriteria(
|
|
1872
|
+
sql.select("_blamejs_break_glass_grants", _sqlOpts()).columns(["_id"]) // allow:hand-rolled-sql
|
|
1873
|
+
).toSql();
|
|
1874
|
+
var ids = await clusterStorage.executeAll(idSelBuilt.sql, idSelBuilt.params);
|
|
1669
1875
|
var nowMs = Date.now();
|
|
1670
|
-
|
|
1671
|
-
"
|
|
1672
|
-
|
|
1673
|
-
);
|
|
1876
|
+
var revAllBuilt = applyRevokeCriteria(
|
|
1877
|
+
sql.update("_blamejs_break_glass_grants", _sqlOpts()).set({ revokedAt: nowMs }) // allow:hand-rolled-sql
|
|
1878
|
+
).toSql();
|
|
1879
|
+
await clusterStorage.execute(revAllBuilt.sql, revAllBuilt.params);
|
|
1674
1880
|
audit.safeEmit({
|
|
1675
1881
|
action: "breakglass.admin.revokeall",
|
|
1676
1882
|
outcome: "success",
|
|
@@ -1691,11 +1897,12 @@ async function revokeAll(criteria, opts) {
|
|
|
1691
1897
|
async function _sweepExpired(opts) {
|
|
1692
1898
|
opts = opts || {};
|
|
1693
1899
|
var nowMs = Date.now();
|
|
1694
|
-
var
|
|
1695
|
-
"
|
|
1696
|
-
"
|
|
1697
|
-
|
|
1698
|
-
|
|
1900
|
+
var expiredBuilt = sql.select("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
|
|
1901
|
+
.columns(["_id", "issuedToActorId", "scopeTable", "rowsConsumed"])
|
|
1902
|
+
.whereNull("revokedAt")
|
|
1903
|
+
.whereOp("expiresAt", "<=", nowMs)
|
|
1904
|
+
.toSql();
|
|
1905
|
+
var expired = await clusterStorage.executeAll(expiredBuilt.sql, expiredBuilt.params);
|
|
1699
1906
|
for (var i = 0; i < (expired || []).length; i++) {
|
|
1700
1907
|
var row = expired[i];
|
|
1701
1908
|
audit.safeEmit({
|
|
@@ -1705,11 +1912,12 @@ async function _sweepExpired(opts) {
|
|
|
1705
1912
|
metadata: { grantId: row._id, table: row.scopeTable, rowsConsumed: Number(row.rowsConsumed) },
|
|
1706
1913
|
});
|
|
1707
1914
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
"
|
|
1711
|
-
|
|
1712
|
-
|
|
1915
|
+
var sweepUpdBuilt = sql.update("_blamejs_break_glass_grants", _sqlOpts()) // allow:hand-rolled-sql
|
|
1916
|
+
.set({ revokedAt: nowMs })
|
|
1917
|
+
.whereNull("revokedAt")
|
|
1918
|
+
.whereOp("expiresAt", "<=", nowMs)
|
|
1919
|
+
.toSql();
|
|
1920
|
+
await clusterStorage.execute(sweepUpdBuilt.sql, sweepUpdBuilt.params);
|
|
1713
1921
|
return { expired: (expired || []).length };
|
|
1714
1922
|
}
|
|
1715
1923
|
|