@kindgi/api 0.1.4-rc.5 → 0.1.5-rc.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/dist/agent-binding.d.ts +13 -0
- package/dist/agent-binding.d.ts.map +1 -1
- package/dist/agent-pins.d.ts.map +1 -1
- package/dist/agent-pins.js +18 -2
- package/dist/agent-pins.js.map +1 -1
- package/dist/app.d.ts +110 -17
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +163 -35
- package/dist/app.js.map +1 -1
- package/dist/block-publish.d.ts +56 -0
- package/dist/block-publish.d.ts.map +1 -0
- package/dist/block-publish.js +132 -0
- package/dist/block-publish.js.map +1 -0
- package/dist/caller.d.ts +24 -0
- package/dist/caller.d.ts.map +1 -0
- package/dist/caller.js +47 -0
- package/dist/caller.js.map +1 -0
- package/dist/capability-binding.d.ts +11 -0
- package/dist/capability-binding.d.ts.map +1 -1
- package/dist/derive-agent-version.d.ts +7 -0
- package/dist/derive-agent-version.d.ts.map +1 -1
- package/dist/derive-agent-version.js +4 -0
- package/dist/derive-agent-version.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +73 -7
- package/dist/errors.js.map +1 -1
- package/dist/eval-case-binding.d.ts +16 -4
- package/dist/eval-case-binding.d.ts.map +1 -1
- package/dist/eval-run-binding.d.ts +31 -0
- package/dist/eval-run-binding.d.ts.map +1 -1
- package/dist/eval-run-dispatcher.d.ts +7 -0
- package/dist/eval-run-dispatcher.d.ts.map +1 -1
- package/dist/eval-run-dispatcher.js.map +1 -1
- package/dist/eval-sample.d.ts +11 -0
- package/dist/eval-sample.d.ts.map +1 -0
- package/dist/eval-sample.js +34 -0
- package/dist/eval-sample.js.map +1 -0
- package/dist/eval-suite-binding.d.ts +13 -0
- package/dist/eval-suite-binding.d.ts.map +1 -1
- package/dist/eval-suite-binding.js.map +1 -1
- package/dist/flow-binding.d.ts +18 -0
- package/dist/flow-binding.d.ts.map +1 -1
- package/dist/flow-pins.d.ts.map +1 -1
- package/dist/flow-pins.js +18 -2
- package/dist/flow-pins.js.map +1 -1
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +25 -0
- package/dist/gate.js.map +1 -1
- package/dist/handler-binding.d.ts +32 -0
- package/dist/handler-binding.d.ts.map +1 -1
- package/dist/hitl-binding.d.ts +23 -0
- package/dist/hitl-binding.d.ts.map +1 -1
- package/dist/identity-directory-binding.d.ts +82 -4
- package/dist/identity-directory-binding.d.ts.map +1 -1
- package/dist/identity-provider-binding.d.ts +197 -17
- package/dist/identity-provider-binding.d.ts.map +1 -1
- package/dist/improvement-pass-binding.d.ts +196 -0
- package/dist/improvement-pass-binding.d.ts.map +1 -0
- package/dist/improvement-pass-binding.js +9 -0
- package/dist/improvement-pass-binding.js.map +1 -0
- package/dist/index.d.ts +35 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/judged-dispatcher.d.ts +25 -4
- package/dist/judged-dispatcher.d.ts.map +1 -1
- package/dist/judged-dispatcher.js +43 -6
- package/dist/judged-dispatcher.js.map +1 -1
- package/dist/judgment-binding.d.ts +25 -1
- package/dist/judgment-binding.d.ts.map +1 -1
- package/dist/mcp-endpoint-binding.d.ts +7 -0
- package/dist/mcp-endpoint-binding.d.ts.map +1 -1
- package/dist/memory-binding.d.ts +124 -17
- package/dist/memory-binding.d.ts.map +1 -1
- package/dist/memory-erasure-binding.d.ts +145 -0
- package/dist/memory-erasure-binding.d.ts.map +1 -0
- package/dist/memory-erasure-binding.js +4 -0
- package/dist/memory-erasure-binding.js.map +1 -0
- package/dist/middleware/auth.d.ts +66 -13
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +150 -50
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/authorize.d.ts +6 -0
- package/dist/middleware/authorize.d.ts.map +1 -1
- package/dist/middleware/authorize.js +113 -4
- package/dist/middleware/authorize.js.map +1 -1
- package/dist/middleware/error-mapper.d.ts +5 -0
- package/dist/middleware/error-mapper.d.ts.map +1 -1
- package/dist/middleware/error-mapper.js +14 -1
- package/dist/middleware/error-mapper.js.map +1 -1
- package/dist/middleware/idempotency.d.ts +70 -5
- package/dist/middleware/idempotency.d.ts.map +1 -1
- package/dist/middleware/idempotency.js +174 -41
- package/dist/middleware/idempotency.js.map +1 -1
- package/dist/middleware/key-project.d.ts +17 -0
- package/dist/middleware/key-project.d.ts.map +1 -0
- package/dist/middleware/key-project.js +75 -0
- package/dist/middleware/key-project.js.map +1 -0
- package/dist/middleware/principal.d.ts.map +1 -1
- package/dist/middleware/principal.js +9 -5
- package/dist/middleware/principal.js.map +1 -1
- package/dist/middleware/project-ref.d.ts.map +1 -1
- package/dist/middleware/project-ref.js +3 -0
- package/dist/middleware/project-ref.js.map +1 -1
- package/dist/openapi/generate.d.ts.map +1 -1
- package/dist/openapi/generate.js +5 -2
- package/dist/openapi/generate.js.map +1 -1
- package/dist/openapi/operations.d.ts.map +1 -1
- package/dist/openapi/operations.js +918 -139
- package/dist/openapi/operations.js.map +1 -1
- package/dist/openapi/schemas.d.ts +92 -13
- package/dist/openapi/schemas.d.ts.map +1 -1
- package/dist/openapi/schemas.js +2122 -370
- package/dist/openapi/schemas.js.map +1 -1
- package/dist/person-grants-binding.d.ts +77 -0
- package/dist/person-grants-binding.d.ts.map +1 -0
- package/dist/person-grants-binding.js +4 -0
- package/dist/person-grants-binding.js.map +1 -0
- package/dist/proposal-service.d.ts +121 -0
- package/dist/proposal-service.d.ts.map +1 -0
- package/dist/proposal-service.js +447 -0
- package/dist/proposal-service.js.map +1 -0
- package/dist/proposal-status.d.ts +60 -0
- package/dist/proposal-status.d.ts.map +1 -0
- package/dist/proposal-status.js +109 -0
- package/dist/proposal-status.js.map +1 -0
- package/dist/provider-binding.d.ts +10 -0
- package/dist/provider-binding.d.ts.map +1 -1
- package/dist/publish-refused.d.ts +18 -3
- package/dist/publish-refused.d.ts.map +1 -1
- package/dist/publish-refused.js +18 -3
- package/dist/publish-refused.js.map +1 -1
- package/dist/routes/adapters.d.ts +2 -1
- package/dist/routes/adapters.d.ts.map +1 -1
- package/dist/routes/adapters.js +3 -1
- package/dist/routes/adapters.js.map +1 -1
- package/dist/routes/agent-releases.d.ts +55 -3
- package/dist/routes/agent-releases.d.ts.map +1 -1
- package/dist/routes/agent-releases.js +85 -55
- package/dist/routes/agent-releases.js.map +1 -1
- package/dist/routes/agents.d.ts +12 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +100 -2
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/approvals.d.ts +15 -3
- package/dist/routes/approvals.d.ts.map +1 -1
- package/dist/routes/approvals.js +133 -122
- package/dist/routes/approvals.js.map +1 -1
- package/dist/routes/artifacts.d.ts +25 -3
- package/dist/routes/artifacts.d.ts.map +1 -1
- package/dist/routes/artifacts.js +116 -7
- package/dist/routes/artifacts.js.map +1 -1
- package/dist/routes/auth.d.ts +25 -1
- package/dist/routes/auth.d.ts.map +1 -1
- package/dist/routes/auth.js +655 -281
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/blocks.d.ts.map +1 -1
- package/dist/routes/blocks.js +2 -42
- package/dist/routes/blocks.js.map +1 -1
- package/dist/routes/capabilities.d.ts +2 -1
- package/dist/routes/capabilities.d.ts.map +1 -1
- package/dist/routes/capabilities.js +6 -1
- package/dist/routes/capabilities.js.map +1 -1
- package/dist/routes/compliance.d.ts +9 -10
- package/dist/routes/compliance.d.ts.map +1 -1
- package/dist/routes/compliance.js +42 -61
- package/dist/routes/compliance.js.map +1 -1
- package/dist/routes/conversations.d.ts +8 -1
- package/dist/routes/conversations.d.ts.map +1 -1
- package/dist/routes/conversations.js +87 -8
- package/dist/routes/conversations.js.map +1 -1
- package/dist/routes/cost.d.ts +7 -1
- package/dist/routes/cost.d.ts.map +1 -1
- package/dist/routes/cost.js +22 -3
- package/dist/routes/cost.js.map +1 -1
- package/dist/routes/denied.d.ts +11 -0
- package/dist/routes/denied.d.ts.map +1 -0
- package/dist/routes/denied.js +17 -0
- package/dist/routes/denied.js.map +1 -0
- package/dist/routes/deployments.d.ts +2 -1
- package/dist/routes/deployments.d.ts.map +1 -1
- package/dist/routes/deployments.js +44 -12
- package/dist/routes/deployments.js.map +1 -1
- package/dist/routes/eval-comparison.d.ts.map +1 -1
- package/dist/routes/eval-comparison.js +69 -1
- package/dist/routes/eval-comparison.js.map +1 -1
- package/dist/routes/eval-overrides.d.ts +18 -0
- package/dist/routes/eval-overrides.d.ts.map +1 -0
- package/dist/routes/eval-overrides.js +81 -0
- package/dist/routes/eval-overrides.js.map +1 -0
- package/dist/routes/eval-runs.d.ts +10 -1
- package/dist/routes/eval-runs.d.ts.map +1 -1
- package/dist/routes/eval-runs.js +63 -7
- package/dist/routes/eval-runs.js.map +1 -1
- package/dist/routes/eval-suites.d.ts.map +1 -1
- package/dist/routes/eval-suites.js +20 -1
- package/dist/routes/eval-suites.js.map +1 -1
- package/dist/routes/event-triggers.d.ts +5 -1
- package/dist/routes/event-triggers.d.ts.map +1 -1
- package/dist/routes/event-triggers.js +26 -3
- package/dist/routes/event-triggers.js.map +1 -1
- package/dist/routes/export-signing-keys.d.ts +12 -0
- package/dist/routes/export-signing-keys.d.ts.map +1 -0
- package/dist/routes/export-signing-keys.js +24 -0
- package/dist/routes/export-signing-keys.js.map +1 -0
- package/dist/routes/flows.d.ts.map +1 -1
- package/dist/routes/flows.js +9 -1
- package/dist/routes/flows.js.map +1 -1
- package/dist/routes/gate-policies.d.ts +4 -1
- package/dist/routes/gate-policies.d.ts.map +1 -1
- package/dist/routes/gate-policies.js +29 -5
- package/dist/routes/gate-policies.js.map +1 -1
- package/dist/routes/guardrails.d.ts +16 -14
- package/dist/routes/guardrails.d.ts.map +1 -1
- package/dist/routes/guardrails.js +39 -3
- package/dist/routes/guardrails.js.map +1 -1
- package/dist/routes/identity.d.ts +20 -4
- package/dist/routes/identity.d.ts.map +1 -1
- package/dist/routes/identity.js +246 -6
- package/dist/routes/identity.js.map +1 -1
- package/dist/routes/improvement-passes.d.ts +52 -0
- package/dist/routes/improvement-passes.d.ts.map +1 -0
- package/dist/routes/improvement-passes.js +449 -0
- package/dist/routes/improvement-passes.js.map +1 -0
- package/dist/routes/judged-suites.d.ts +50 -0
- package/dist/routes/judged-suites.d.ts.map +1 -1
- package/dist/routes/judged-suites.js +66 -27
- package/dist/routes/judged-suites.js.map +1 -1
- package/dist/routes/judgment-context.d.ts +18 -2
- package/dist/routes/judgment-context.d.ts.map +1 -1
- package/dist/routes/judgment-context.js +63 -5
- package/dist/routes/judgment-context.js.map +1 -1
- package/dist/routes/judgment-flow-context.d.ts.map +1 -1
- package/dist/routes/judgment-flow-context.js +14 -2
- package/dist/routes/judgment-flow-context.js.map +1 -1
- package/dist/routes/judgments.d.ts.map +1 -1
- package/dist/routes/judgments.js +8 -1
- package/dist/routes/judgments.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +26 -1
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/memory-access.d.ts +43 -0
- package/dist/routes/memory-access.d.ts.map +1 -0
- package/dist/routes/memory-access.js +129 -0
- package/dist/routes/memory-access.js.map +1 -0
- package/dist/routes/memory-erasures.d.ts +25 -0
- package/dist/routes/memory-erasures.d.ts.map +1 -0
- package/dist/routes/memory-erasures.js +214 -0
- package/dist/routes/memory-erasures.js.map +1 -0
- package/dist/routes/memory-parse.d.ts +38 -0
- package/dist/routes/memory-parse.d.ts.map +1 -0
- package/dist/routes/memory-parse.js +199 -0
- package/dist/routes/memory-parse.js.map +1 -0
- package/dist/routes/memory.d.ts +9 -4
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/memory.js +226 -234
- package/dist/routes/memory.js.map +1 -1
- package/dist/routes/observations.d.ts +4 -1
- package/dist/routes/observations.d.ts.map +1 -1
- package/dist/routes/observations.js +8 -2
- package/dist/routes/observations.js.map +1 -1
- package/dist/routes/pagination.d.ts +7 -0
- package/dist/routes/pagination.d.ts.map +1 -1
- package/dist/routes/pagination.js +10 -0
- package/dist/routes/pagination.js.map +1 -1
- package/dist/routes/policies.d.ts +2 -1
- package/dist/routes/policies.d.ts.map +1 -1
- package/dist/routes/policies.js +3 -1
- package/dist/routes/policies.js.map +1 -1
- package/dist/routes/project-mismatch.d.ts +22 -0
- package/dist/routes/project-mismatch.d.ts.map +1 -0
- package/dist/routes/project-mismatch.js +34 -0
- package/dist/routes/project-mismatch.js.map +1 -0
- package/dist/routes/projects.d.ts +15 -4
- package/dist/routes/projects.d.ts.map +1 -1
- package/dist/routes/projects.js +100 -9
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/proposals.d.ts +25 -17
- package/dist/routes/proposals.d.ts.map +1 -1
- package/dist/routes/proposals.js +340 -507
- package/dist/routes/proposals.js.map +1 -1
- package/dist/routes/provenance.d.ts +17 -3
- package/dist/routes/provenance.d.ts.map +1 -1
- package/dist/routes/provenance.js +79 -103
- package/dist/routes/provenance.js.map +1 -1
- package/dist/routes/providers.d.ts +17 -1
- package/dist/routes/providers.d.ts.map +1 -1
- package/dist/routes/providers.js +188 -8
- package/dist/routes/providers.js.map +1 -1
- package/dist/routes/public-run-tokens.d.ts.map +1 -1
- package/dist/routes/public-run-tokens.js +3 -0
- package/dist/routes/public-run-tokens.js.map +1 -1
- package/dist/routes/runs.d.ts +10 -0
- package/dist/routes/runs.d.ts.map +1 -1
- package/dist/routes/runs.js +92 -42
- package/dist/routes/runs.js.map +1 -1
- package/dist/routes/schedules.d.ts +21 -13
- package/dist/routes/schedules.d.ts.map +1 -1
- package/dist/routes/schedules.js +416 -55
- package/dist/routes/schedules.js.map +1 -1
- package/dist/routes/segments.d.ts +2 -0
- package/dist/routes/segments.d.ts.map +1 -1
- package/dist/routes/segments.js +5 -0
- package/dist/routes/segments.js.map +1 -1
- package/dist/routes/service-accounts.d.ts +17 -0
- package/dist/routes/service-accounts.d.ts.map +1 -0
- package/dist/routes/service-accounts.js +212 -0
- package/dist/routes/service-accounts.js.map +1 -0
- package/dist/routes/sign-in-options.d.ts +50 -0
- package/dist/routes/sign-in-options.d.ts.map +1 -0
- package/dist/routes/sign-in-options.js +98 -0
- package/dist/routes/sign-in-options.js.map +1 -0
- package/dist/routes/signing-keys.d.ts +2 -1
- package/dist/routes/signing-keys.d.ts.map +1 -1
- package/dist/routes/signing-keys.js +3 -1
- package/dist/routes/signing-keys.js.map +1 -1
- package/dist/routes/tenant-access.d.ts +13 -0
- package/dist/routes/tenant-access.d.ts.map +1 -0
- package/dist/routes/tenant-access.js +26 -0
- package/dist/routes/tenant-access.js.map +1 -0
- package/dist/routes/tenant.d.ts.map +1 -1
- package/dist/routes/tenant.js +4 -0
- package/dist/routes/tenant.js.map +1 -1
- package/dist/routes/token-sign-in.d.ts +36 -0
- package/dist/routes/token-sign-in.d.ts.map +1 -0
- package/dist/routes/token-sign-in.js +101 -0
- package/dist/routes/token-sign-in.js.map +1 -0
- package/dist/routes/tokens.d.ts +15 -5
- package/dist/routes/tokens.d.ts.map +1 -1
- package/dist/routes/tokens.js +162 -43
- package/dist/routes/tokens.js.map +1 -1
- package/dist/routes/tools.d.ts.map +1 -1
- package/dist/routes/tools.js +9 -1
- package/dist/routes/tools.js.map +1 -1
- package/dist/routes/trigger-access.d.ts +29 -0
- package/dist/routes/trigger-access.d.ts.map +1 -0
- package/dist/routes/trigger-access.js +33 -0
- package/dist/routes/trigger-access.js.map +1 -0
- package/dist/routes/webhook-endpoints.d.ts +2 -1
- package/dist/routes/webhook-endpoints.d.ts.map +1 -1
- package/dist/routes/webhook-endpoints.js +11 -3
- package/dist/routes/webhook-endpoints.js.map +1 -1
- package/dist/routes/webhooks.d.ts +5 -1
- package/dist/routes/webhooks.d.ts.map +1 -1
- package/dist/routes/webhooks.js +25 -2
- package/dist/routes/webhooks.js.map +1 -1
- package/dist/run-failure.d.ts +21 -0
- package/dist/run-failure.d.ts.map +1 -0
- package/dist/run-failure.js +29 -0
- package/dist/run-failure.js.map +1 -0
- package/dist/service-account-binding.d.ts +108 -0
- package/dist/service-account-binding.d.ts.map +1 -0
- package/dist/service-account-binding.js +4 -0
- package/dist/service-account-binding.js.map +1 -0
- package/dist/session-store-binding.d.ts +61 -13
- package/dist/session-store-binding.d.ts.map +1 -1
- package/dist/signed-export.d.ts +89 -0
- package/dist/signed-export.d.ts.map +1 -0
- package/dist/signed-export.js +149 -0
- package/dist/signed-export.js.map +1 -0
- package/dist/supervisor-binding.d.ts +186 -413
- package/dist/supervisor-binding.d.ts.map +1 -1
- package/dist/testing/app-bindings.d.ts +33 -0
- package/dist/testing/app-bindings.d.ts.map +1 -0
- package/dist/testing/app-bindings.js +112 -0
- package/dist/testing/app-bindings.js.map +1 -0
- package/dist/testing/index.d.ts +11 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +13 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/stub-binding.d.ts +34 -0
- package/dist/testing/stub-binding.d.ts.map +1 -0
- package/dist/testing/stub-binding.js +42 -0
- package/dist/testing/stub-binding.js.map +1 -0
- package/dist/testing/trigger-registry.d.ts +21 -0
- package/dist/testing/trigger-registry.d.ts.map +1 -0
- package/dist/testing/trigger-registry.js +206 -0
- package/dist/testing/trigger-registry.js.map +1 -0
- package/dist/token-admin.d.ts +46 -8
- package/dist/token-admin.d.ts.map +1 -1
- package/dist/token-admin.js.map +1 -1
- package/dist/tool-binding.d.ts +13 -0
- package/dist/tool-binding.d.ts.map +1 -1
- package/dist/trigger-binding.d.ts +2 -2
- package/dist/trigger-binding.d.ts.map +1 -1
- package/dist/trigger-binding.js +1 -1
- package/dist/trigger-binding.js.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/webhook-endpoint-binding.d.ts +19 -3
- package/dist/webhook-endpoint-binding.d.ts.map +1 -1
- package/dist/webhook-endpoint-binding.js +1 -1
- package/dist/webhook-endpoint-binding.js.map +1 -1
- package/openapi.json +15286 -8653
- package/package.json +26 -22
- package/src/agent-binding.ts +14 -0
- package/src/agent-pins.ts +18 -2
- package/src/app.ts +366 -80
- package/src/block-publish.ts +181 -0
- package/src/caller.ts +62 -0
- package/src/capability-binding.ts +12 -0
- package/src/derive-agent-version.ts +9 -1
- package/src/errors.ts +73 -7
- package/src/eval-case-binding.ts +17 -1
- package/src/eval-run-binding.ts +31 -0
- package/src/eval-run-dispatcher.ts +7 -0
- package/src/eval-sample.ts +42 -0
- package/src/eval-suite-binding.ts +14 -0
- package/src/flow-binding.ts +19 -0
- package/src/flow-pins.ts +18 -2
- package/src/gate.ts +26 -0
- package/src/handler-binding.ts +36 -1
- package/src/hitl-binding.ts +24 -0
- package/src/identity-directory-binding.ts +85 -4
- package/src/identity-provider-binding.ts +216 -21
- package/src/improvement-pass-binding.ts +193 -0
- package/src/index.ts +153 -31
- package/src/judged-dispatcher.ts +78 -11
- package/src/judgment-binding.ts +25 -1
- package/src/mcp-endpoint-binding.ts +7 -0
- package/src/memory-binding.ts +121 -17
- package/src/memory-erasure-binding.ts +156 -0
- package/src/middleware/auth.ts +228 -62
- package/src/middleware/authorize.ts +139 -3
- package/src/middleware/error-mapper.ts +15 -1
- package/src/middleware/idempotency.ts +230 -43
- package/src/middleware/key-project.ts +86 -0
- package/src/middleware/principal.ts +9 -5
- package/src/middleware/project-ref.ts +3 -0
- package/src/openapi/generate.ts +11 -2
- package/src/openapi/operations.ts +1059 -142
- package/src/openapi/schemas.ts +2438 -485
- package/src/person-grants-binding.ts +76 -0
- package/src/proposal-service.ts +640 -0
- package/src/proposal-status.ts +176 -0
- package/src/provider-binding.ts +10 -0
- package/src/publish-refused.ts +38 -9
- package/src/routes/adapters.ts +4 -0
- package/src/routes/agent-releases.ts +121 -69
- package/src/routes/agents.ts +128 -1
- package/src/routes/approvals.ts +164 -154
- package/src/routes/artifacts.ts +181 -11
- package/src/routes/auth.ts +804 -336
- package/src/routes/blocks.ts +2 -53
- package/src/routes/capabilities.ts +10 -1
- package/src/routes/compliance.ts +60 -99
- package/src/routes/conversations.ts +101 -7
- package/src/routes/cost.ts +32 -3
- package/src/routes/denied.ts +28 -0
- package/src/routes/deployments.ts +62 -16
- package/src/routes/eval-comparison.ts +81 -2
- package/src/routes/eval-overrides.ts +108 -0
- package/src/routes/eval-runs.ts +86 -6
- package/src/routes/eval-suites.ts +25 -1
- package/src/routes/event-triggers.ts +27 -3
- package/src/routes/export-signing-keys.ts +31 -0
- package/src/routes/flows.ts +12 -1
- package/src/routes/gate-policies.ts +37 -6
- package/src/routes/guardrails.ts +61 -2
- package/src/routes/identity.ts +328 -10
- package/src/routes/improvement-passes.ts +541 -0
- package/src/routes/judged-suites.ts +113 -43
- package/src/routes/judgment-context.ts +76 -5
- package/src/routes/judgment-flow-context.ts +16 -2
- package/src/routes/judgments.ts +17 -3
- package/src/routes/mcp.ts +29 -1
- package/src/routes/memory-access.ts +179 -0
- package/src/routes/memory-erasures.ts +240 -0
- package/src/routes/memory-parse.ts +250 -0
- package/src/routes/memory.ts +244 -286
- package/src/routes/observations.ts +15 -2
- package/src/routes/pagination.ts +12 -0
- package/src/routes/policies.ts +7 -1
- package/src/routes/project-mismatch.ts +56 -0
- package/src/routes/projects.ts +107 -13
- package/src/routes/proposals.ts +370 -611
- package/src/routes/provenance.ts +99 -133
- package/src/routes/providers.ts +223 -8
- package/src/routes/public-run-tokens.ts +3 -0
- package/src/routes/runs.ts +119 -39
- package/src/routes/schedules.ts +472 -60
- package/src/routes/segments.ts +11 -0
- package/src/routes/service-accounts.ts +262 -0
- package/src/routes/sign-in-options.ts +168 -0
- package/src/routes/signing-keys.ts +7 -1
- package/src/routes/tenant-access.ts +37 -0
- package/src/routes/tenant.ts +6 -0
- package/src/routes/token-sign-in.ts +161 -0
- package/src/routes/tokens.ts +172 -46
- package/src/routes/tools.ts +12 -1
- package/src/routes/trigger-access.ts +72 -0
- package/src/routes/webhook-endpoints.ts +15 -3
- package/src/routes/webhooks.ts +26 -2
- package/src/run-failure.ts +42 -0
- package/src/service-account-binding.ts +113 -0
- package/src/session-store-binding.ts +65 -13
- package/src/signed-export.ts +250 -0
- package/src/supervisor-binding.ts +169 -409
- package/src/testing/app-bindings.ts +142 -0
- package/src/testing/index.ts +21 -0
- package/src/testing/stub-binding.ts +53 -0
- package/src/testing/trigger-registry.ts +259 -0
- package/src/token-admin.ts +40 -8
- package/src/tool-binding.ts +14 -0
- package/src/trigger-binding.ts +8 -1
- package/src/types.ts +16 -0
- package/src/webhook-endpoint-binding.ts +18 -3
package/src/middleware/auth.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright (C) 2026 Kindgi Inc.
|
|
3
3
|
|
|
4
4
|
import type { Context, MiddlewareHandler, Next } from 'hono';
|
|
5
|
+
import { getCookie } from 'hono/cookie';
|
|
5
6
|
|
|
6
7
|
import type { ReviewerRole } from '@kindgi/authz';
|
|
7
8
|
import type { ApiTokenId, SessionId, TenantId, UserId } from '@kindgi/types';
|
|
@@ -49,11 +50,27 @@ export interface TokenResolution {
|
|
|
49
50
|
*/
|
|
50
51
|
readonly userId?: UserId;
|
|
51
52
|
/**
|
|
52
|
-
* The id of a durable API key (`POST /v1/tokens`)
|
|
53
|
-
* the key's
|
|
54
|
-
*
|
|
53
|
+
* The id of a durable API key (`POST /v1/tokens`), surfaced as
|
|
54
|
+
* `c.get('tokenId')`. The caller is the key's principal: `userId` for a
|
|
55
|
+
* person's key, `serviceAccountId` for a service account's. A key with
|
|
56
|
+
* neither (a store built before principals) is its own service account,
|
|
57
|
+
* `service_account:<tokenId>`.
|
|
55
58
|
*/
|
|
56
59
|
readonly tokenId?: ApiTokenId;
|
|
60
|
+
/** The service account an API key acts for (`service_account:<id>`). */
|
|
61
|
+
readonly serviceAccountId?: string;
|
|
62
|
+
/**
|
|
63
|
+
* An API key's role: the most it may do, under its principal's grants.
|
|
64
|
+
* A `member` key is refused tenant-admin actions even when its principal
|
|
65
|
+
* is an admin.
|
|
66
|
+
*/
|
|
67
|
+
readonly tokenRole?: 'admin' | 'member';
|
|
68
|
+
/**
|
|
69
|
+
* The project an API key is narrowed to. A request naming another
|
|
70
|
+
* project is refused (`key-project-mismatch`), and so is an `admin`
|
|
71
|
+
* action on the tenant.
|
|
72
|
+
*/
|
|
73
|
+
readonly tokenProjectId?: string;
|
|
57
74
|
/**
|
|
58
75
|
* Optional session id. Populated by the session-token verifier path
|
|
59
76
|
* (`kgi_sk_*` tokens) so `/v1/identity/whoami` and `/v1/auth/logout`
|
|
@@ -115,10 +132,45 @@ export interface TokenResolution {
|
|
|
115
132
|
export const SESSION_TOKEN_PREFIX = 'kgi_sk_' as const;
|
|
116
133
|
|
|
117
134
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
|
|
135
|
+
* The cookie a browser carries its session token in, when the deployment
|
|
136
|
+
* turns cookie sessions on (`SessionConfig.cookie`). `__Host-`: Secure,
|
|
137
|
+
* Path=/, no Domain, so it's sent only to the origin that set it.
|
|
138
|
+
*/
|
|
139
|
+
export const SESSION_COOKIE_NAME = '__Host-kindgi_session' as const;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Cookie sessions: where the middleware reads a browser's session token
|
|
143
|
+
* when the request has no `Authorization` header.
|
|
144
|
+
*/
|
|
145
|
+
export interface SessionCookieOptions {
|
|
146
|
+
/** The cookie's name. Default `SESSION_COOKIE_NAME`. */
|
|
147
|
+
readonly name?: string;
|
|
148
|
+
/**
|
|
149
|
+
* The origins a cookie-authenticated request may come from (the
|
|
150
|
+
* console's origin, e.g. `https://kindgi.example.com`). An unsafe method
|
|
151
|
+
* (anything but GET, HEAD and OPTIONS) authenticated by the cookie must
|
|
152
|
+
* carry one of them in `Origin`, else 403 `csrf-origin-mismatch`; so
|
|
153
|
+
* must a request with no `Origin` at all. Bearer requests are unaffected.
|
|
154
|
+
*/
|
|
155
|
+
readonly allowedOrigins: readonly string[];
|
|
156
|
+
/**
|
|
157
|
+
* Also accept an `Origin` naming the host the request was sent to (its
|
|
158
|
+
* `Host`, or `X-Forwarded-Host` behind a proxy that sets it): the
|
|
159
|
+
* console served from the runtime itself, wherever it's reached. For a
|
|
160
|
+
* deployment that doesn't know its public URL. A cross-site page can't
|
|
161
|
+
* forge `Origin`, nor add `X-Forwarded-Host` without a CORS preflight.
|
|
162
|
+
*/
|
|
163
|
+
readonly sameOrigin?: boolean;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The older session token, `kgi_sk_<sessionId>`, for a session store
|
|
170
|
+
* without `resolveToken`. A store with it mints its own token
|
|
171
|
+
* (`SessionCreateOutput.token`), and the routes hand that out instead.
|
|
172
|
+
* Session tokens are opaque to clients: never parse one outside the
|
|
173
|
+
* middleware or the store that minted it.
|
|
122
174
|
*/
|
|
123
175
|
export function encodeSessionToken(sessionId: SessionId): string {
|
|
124
176
|
return `${SESSION_TOKEN_PREFIX}${sessionId as unknown as string}`;
|
|
@@ -150,6 +202,12 @@ export interface BearerAuthMiddlewareOptions {
|
|
|
150
202
|
* fewer writes but coarser inactivity enforcement. Defaults to 60s.
|
|
151
203
|
*/
|
|
152
204
|
readonly touchThrottleMs?: number;
|
|
205
|
+
/**
|
|
206
|
+
* Read a session token from a cookie when the request carries no
|
|
207
|
+
* `Authorization` header (browser sessions). Only `kgi_sk_` tokens are
|
|
208
|
+
* taken from it, and only with a `sessionStore`.
|
|
209
|
+
*/
|
|
210
|
+
readonly sessionCookie?: SessionCookieOptions;
|
|
153
211
|
/**
|
|
154
212
|
* Accept public run tokens (`kgi_pt_…`), verified against
|
|
155
213
|
* `signingKey`, on the routes `isAllowed` admits (every other route
|
|
@@ -169,9 +227,9 @@ const DEFAULT_TOUCH_THROTTLE_MS = 60_000;
|
|
|
169
227
|
* handlers.
|
|
170
228
|
*
|
|
171
229
|
* Two token flavors are accepted:
|
|
172
|
-
* - `kgi_sk_
|
|
173
|
-
*
|
|
174
|
-
* from the session row.
|
|
230
|
+
* - `kgi_sk_…` — session tokens. Verified by the session store
|
|
231
|
+
* (`resolveToken`, or `get` for an older store); expiration +
|
|
232
|
+
* revocation are pulled from the session row.
|
|
175
233
|
* - anything else — caller-plugged static bearer tokens. Verified via
|
|
176
234
|
* `resolveToken`. Byte-shape-identical with the session-less contract.
|
|
177
235
|
*/
|
|
@@ -186,36 +244,13 @@ export function bearerAuthMiddleware(
|
|
|
186
244
|
// bucket is bounded by the number of active sessions the process
|
|
187
245
|
// has recently observed.
|
|
188
246
|
const touchBucket = new Map<string, number>();
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
197
|
-
return c.json(body, statusFor('auth-missing') as never);
|
|
198
|
-
}
|
|
199
|
-
const match = /^Bearer\s+(.+)$/i.exec(header);
|
|
200
|
-
if (match === null) {
|
|
201
|
-
const body = toWireError(
|
|
202
|
-
{
|
|
203
|
-
code: 'auth-missing',
|
|
204
|
-
message: 'Authorization header must be "Bearer <token>"',
|
|
205
|
-
},
|
|
206
|
-
requestId,
|
|
207
|
-
);
|
|
208
|
-
return c.json(body, statusFor('auth-missing') as never);
|
|
209
|
-
}
|
|
210
|
-
const token = match[1]?.trim() ?? '';
|
|
211
|
-
if (token.length === 0) {
|
|
212
|
-
const body = toWireError(
|
|
213
|
-
{ code: 'auth-missing', message: 'Bearer token is empty' },
|
|
214
|
-
requestId,
|
|
215
|
-
);
|
|
216
|
-
return c.json(body, statusFor('auth-missing') as never);
|
|
217
|
-
}
|
|
218
|
-
|
|
247
|
+
// Everything after the token is found: the header's or the cookie's.
|
|
248
|
+
const authenticate = async (
|
|
249
|
+
c: Context,
|
|
250
|
+
next: Next,
|
|
251
|
+
token: string,
|
|
252
|
+
requestId: string,
|
|
253
|
+
): Promise<Response | undefined> => {
|
|
219
254
|
if (token.startsWith(PUBLIC_RUN_TOKEN_PREFIX)) {
|
|
220
255
|
return authenticatePublicRunToken(c, next, token, requestId, options.publicRunTokens);
|
|
221
256
|
}
|
|
@@ -297,6 +332,18 @@ export function bearerAuthMiddleware(
|
|
|
297
332
|
if (resolution.tokenId !== undefined) {
|
|
298
333
|
c.set('tokenId', resolution.tokenId);
|
|
299
334
|
}
|
|
335
|
+
if (resolution.serviceAccountId !== undefined) {
|
|
336
|
+
c.set('serviceAccountId', resolution.serviceAccountId);
|
|
337
|
+
}
|
|
338
|
+
if (resolution.tokenRole !== undefined) {
|
|
339
|
+
c.set('tokenRole', resolution.tokenRole);
|
|
340
|
+
}
|
|
341
|
+
if (resolution.tokenProjectId !== undefined) {
|
|
342
|
+
c.set('tokenProjectId', resolution.tokenProjectId);
|
|
343
|
+
}
|
|
344
|
+
if (resolution.expiresAt !== undefined && !isSessionToken) {
|
|
345
|
+
c.set('tokenExpiresAt', resolution.expiresAt);
|
|
346
|
+
}
|
|
300
347
|
if (resolution.sessionId !== undefined) {
|
|
301
348
|
c.set('sessionId', resolution.sessionId);
|
|
302
349
|
}
|
|
@@ -337,6 +384,45 @@ export function bearerAuthMiddleware(
|
|
|
337
384
|
await next();
|
|
338
385
|
return;
|
|
339
386
|
};
|
|
387
|
+
return async (c, next) => {
|
|
388
|
+
const requestId = c.get('requestId') as string;
|
|
389
|
+
const header = c.req.header('authorization');
|
|
390
|
+
if (header === undefined || header.length === 0) {
|
|
391
|
+
const fromCookie = sessionCookieToken(c, sessionStore, options.sessionCookie);
|
|
392
|
+
if (fromCookie !== undefined) {
|
|
393
|
+
const refusal = csrfRefusal(c, options.sessionCookie as SessionCookieOptions, requestId);
|
|
394
|
+
if (refusal !== undefined) return refusal;
|
|
395
|
+
c.set('sessionCookieName', options.sessionCookie?.name ?? SESSION_COOKIE_NAME);
|
|
396
|
+
return authenticate(c, next, fromCookie, requestId);
|
|
397
|
+
}
|
|
398
|
+
const body = toWireError(
|
|
399
|
+
{ code: 'auth-missing', message: 'Authorization header is required' },
|
|
400
|
+
requestId,
|
|
401
|
+
);
|
|
402
|
+
return c.json(body, statusFor('auth-missing') as never);
|
|
403
|
+
}
|
|
404
|
+
const match = /^Bearer\s+(.+)$/i.exec(header);
|
|
405
|
+
if (match === null) {
|
|
406
|
+
const body = toWireError(
|
|
407
|
+
{
|
|
408
|
+
code: 'auth-missing',
|
|
409
|
+
message: 'Authorization header must be "Bearer <token>"',
|
|
410
|
+
},
|
|
411
|
+
requestId,
|
|
412
|
+
);
|
|
413
|
+
return c.json(body, statusFor('auth-missing') as never);
|
|
414
|
+
}
|
|
415
|
+
const token = match[1]?.trim() ?? '';
|
|
416
|
+
if (token.length === 0) {
|
|
417
|
+
const body = toWireError(
|
|
418
|
+
{ code: 'auth-missing', message: 'Bearer token is empty' },
|
|
419
|
+
requestId,
|
|
420
|
+
);
|
|
421
|
+
return c.json(body, statusFor('auth-missing') as never);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return authenticate(c, next, token, requestId);
|
|
425
|
+
};
|
|
340
426
|
}
|
|
341
427
|
|
|
342
428
|
/**
|
|
@@ -345,31 +431,20 @@ export function bearerAuthMiddleware(
|
|
|
345
431
|
* scopes + expiration + revocation) that the store returns natively —
|
|
346
432
|
* we don't want to squeeze that shape through the resolver contract.
|
|
347
433
|
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
* can't cross tenants because the session it points to belongs to
|
|
353
|
-
* exactly one tenant.
|
|
434
|
+
* A store with `resolveToken` gets the whole token: it reads the tenant
|
|
435
|
+
* from it, looks only in that tenant and compares a hash, so nothing is
|
|
436
|
+
* read across tenants before the token is authenticated. The session it
|
|
437
|
+
* returns is authoritative for `tenantId`.
|
|
354
438
|
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
* Session-store implementations MUST recognize the `MULTI_TENANT_LOOKUP`
|
|
359
|
-
* sentinel and skip the tenant filter; the `tenantId` on the stored
|
|
360
|
-
* session is authoritative.
|
|
439
|
+
* A store without it gets the older lookup: the token's remainder is the
|
|
440
|
+
* session id, read with `get({ tenantId: MULTI_TENANT_LOOKUP, sessionId })`,
|
|
441
|
+
* which such stores must honor by skipping the tenant filter.
|
|
361
442
|
*/
|
|
362
443
|
async function resolveSessionToken(
|
|
363
444
|
sessionStore: SessionStoreBinding,
|
|
364
445
|
token: string,
|
|
365
446
|
): Promise<TokenResolution | null> {
|
|
366
|
-
const
|
|
367
|
-
if (sessionIdStr.length === 0) return null;
|
|
368
|
-
const sessionId = sessionIdStr as unknown as SessionId;
|
|
369
|
-
const session = await sessionStore.get({
|
|
370
|
-
tenantId: MULTI_TENANT_LOOKUP,
|
|
371
|
-
sessionId,
|
|
372
|
-
});
|
|
447
|
+
const session = await findSession(sessionStore, token);
|
|
373
448
|
if (session === null) return null;
|
|
374
449
|
return {
|
|
375
450
|
tenantId: session.tenantId,
|
|
@@ -386,12 +461,93 @@ async function resolveSessionToken(
|
|
|
386
461
|
};
|
|
387
462
|
}
|
|
388
463
|
|
|
464
|
+
/**
|
|
465
|
+
* The session token in the request's cookie, when cookie sessions are on
|
|
466
|
+
* and the cookie holds a session token; `undefined` otherwise (an API
|
|
467
|
+
* key or any other token is never taken from a cookie).
|
|
468
|
+
*/
|
|
469
|
+
function sessionCookieToken(
|
|
470
|
+
c: Context,
|
|
471
|
+
sessionStore: SessionStoreBinding | undefined,
|
|
472
|
+
cookie: SessionCookieOptions | undefined,
|
|
473
|
+
): string | undefined {
|
|
474
|
+
if (cookie === undefined || sessionStore === undefined) return undefined;
|
|
475
|
+
const value = getCookie(c, cookie.name ?? SESSION_COOKIE_NAME)?.trim();
|
|
476
|
+
if (value === undefined || !value.startsWith(SESSION_TOKEN_PREFIX)) return undefined;
|
|
477
|
+
return value;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* A cookie-authenticated unsafe request must come from an allowed origin:
|
|
482
|
+
* a browser always sends `Origin` on such requests, and a cross-site page
|
|
483
|
+
* can't forge it. A missing `Origin` is refused too.
|
|
484
|
+
*/
|
|
485
|
+
function csrfRefusal(
|
|
486
|
+
c: Context,
|
|
487
|
+
cookie: SessionCookieOptions,
|
|
488
|
+
requestId: string,
|
|
489
|
+
): Response | undefined {
|
|
490
|
+
if (SAFE_METHODS.has(c.req.method)) return undefined;
|
|
491
|
+
const origin = c.req.header('origin');
|
|
492
|
+
if (origin !== undefined && cookie.allowedOrigins.includes(origin)) return undefined;
|
|
493
|
+
if (origin !== undefined && cookie.sameOrigin === true && isSameHost(c, origin)) return undefined;
|
|
494
|
+
const body = toWireError(
|
|
495
|
+
{
|
|
496
|
+
code: 'csrf-origin-mismatch',
|
|
497
|
+
message:
|
|
498
|
+
origin === undefined
|
|
499
|
+
? 'A request signed in by the session cookie must say where it comes from (Origin)'
|
|
500
|
+
: `A request signed in by the session cookie can't come from ${origin}`,
|
|
501
|
+
},
|
|
502
|
+
requestId,
|
|
503
|
+
);
|
|
504
|
+
return c.json(body, statusFor('csrf-origin-mismatch') as never);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Whether `origin` names the host this request was sent to, over https (or
|
|
509
|
+
* plain http on loopback, for local development). The session cookie is
|
|
510
|
+
* `__Host-` and Secure, so a browser holding it is on https: a plain-http
|
|
511
|
+
* page on the same host (an on-path attacker's) is not the same origin.
|
|
512
|
+
*/
|
|
513
|
+
function isSameHost(c: Context, origin: string): boolean {
|
|
514
|
+
let host: string;
|
|
515
|
+
try {
|
|
516
|
+
const url = new URL(origin);
|
|
517
|
+
if (url.protocol !== 'https:' && !(url.protocol === 'http:' && isLoopback(url.hostname))) {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
host = url.host;
|
|
521
|
+
} catch {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
// The request's own URL carries its Host (the Node server builds it from it).
|
|
525
|
+
const sentTo = [
|
|
526
|
+
new URL(c.req.url).host,
|
|
527
|
+
c.req.header('host'),
|
|
528
|
+
c.req.header('x-forwarded-host')?.split(',')[0]?.trim(),
|
|
529
|
+
];
|
|
530
|
+
return sentTo.some((h) => h !== undefined && h !== '' && h.toLowerCase() === host.toLowerCase());
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function findSession(sessionStore: SessionStoreBinding, token: string) {
|
|
534
|
+
if (sessionStore.resolveToken !== undefined) {
|
|
535
|
+
return sessionStore.resolveToken({ token });
|
|
536
|
+
}
|
|
537
|
+
const sessionIdStr = token.slice(SESSION_TOKEN_PREFIX.length);
|
|
538
|
+
if (sessionIdStr.length === 0) return null;
|
|
539
|
+
return sessionStore.get({
|
|
540
|
+
tenantId: MULTI_TENANT_LOOKUP,
|
|
541
|
+
sessionId: sessionIdStr as unknown as SessionId,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
|
|
389
545
|
/**
|
|
390
546
|
* Sentinel `tenantId` value the middleware passes to
|
|
391
547
|
* `SessionStoreBinding.get` when it needs to look up a session by its
|
|
392
|
-
* id without knowing the tenant in advance
|
|
393
|
-
* MUST honor
|
|
394
|
-
* known until the session row has been read.
|
|
548
|
+
* id without knowing the tenant in advance: only for a store without
|
|
549
|
+
* `resolveToken`. Such stores MUST honor it by skipping the tenant
|
|
550
|
+
* filter (the tenant is not known until the session row has been read).
|
|
395
551
|
*/
|
|
396
552
|
export const MULTI_TENANT_LOOKUP = '__kindgi_session_multi_tenant_lookup__' as unknown as TenantId;
|
|
397
553
|
|
|
@@ -436,3 +592,13 @@ async function authenticatePublicRunToken(
|
|
|
436
592
|
await next();
|
|
437
593
|
return undefined;
|
|
438
594
|
}
|
|
595
|
+
|
|
596
|
+
/** `localhost`, `127.0.0.0/8` or `::1`. */
|
|
597
|
+
function isLoopback(hostname: string): boolean {
|
|
598
|
+
return (
|
|
599
|
+
hostname === 'localhost' ||
|
|
600
|
+
hostname === '[::1]' ||
|
|
601
|
+
hostname === '::1' ||
|
|
602
|
+
/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)
|
|
603
|
+
);
|
|
604
|
+
}
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
type Action,
|
|
24
24
|
type AuthzCheckBinding,
|
|
25
25
|
type Decision,
|
|
26
|
+
OBJECT_ACTIONS,
|
|
27
|
+
type ObjectType,
|
|
26
28
|
type Principal,
|
|
27
29
|
type ResourceRef,
|
|
28
30
|
denyPayload,
|
|
@@ -57,9 +59,37 @@ export interface Authorizer {
|
|
|
57
59
|
items: readonly T[],
|
|
58
60
|
refFn: (item: T) => ResourceRef,
|
|
59
61
|
) => Promise<T[]>;
|
|
62
|
+
/**
|
|
63
|
+
* The ids of every object of `type` the caller may `action`
|
|
64
|
+
* (`AuthzCheckBinding.listObjects`); `undefined` when the binding
|
|
65
|
+
* can't list, so the caller falls back to `filterByCan`.
|
|
66
|
+
*/
|
|
67
|
+
readonly listObjects?: (
|
|
68
|
+
c: Context<AppEnv>,
|
|
69
|
+
action: Action,
|
|
70
|
+
type: ResourceRef['type'],
|
|
71
|
+
) => Promise<readonly string[] | undefined>;
|
|
60
72
|
}
|
|
61
73
|
|
|
62
74
|
export function createAuthorizer(binding: AuthzCheckBinding): Authorizer {
|
|
75
|
+
/**
|
|
76
|
+
* Whether a resource is inside the project the caller's API key is
|
|
77
|
+
* limited to (always, for a caller with no such key). The tenant, orgs,
|
|
78
|
+
* teams and projects are held by `keyCeilingDeny`; anything else must
|
|
79
|
+
* belong to the key's project, as the store says (`inProject`). Without
|
|
80
|
+
* that answer, the key reaches no other resource.
|
|
81
|
+
*/
|
|
82
|
+
async function inKeyProject(
|
|
83
|
+
c: Context<AppEnv>,
|
|
84
|
+
principal: Principal,
|
|
85
|
+
resource: ResourceRef,
|
|
86
|
+
): Promise<boolean> {
|
|
87
|
+
const keyProject = c.get('tokenProjectId');
|
|
88
|
+
if (keyProject === undefined || STRUCTURAL.has(resource.type)) return true;
|
|
89
|
+
if (binding.inProject === undefined) return false;
|
|
90
|
+
return binding.inProject(principal, resource, keyProject);
|
|
91
|
+
}
|
|
92
|
+
|
|
63
93
|
async function checkInternal(
|
|
64
94
|
c: Context<AppEnv>,
|
|
65
95
|
action: Action,
|
|
@@ -79,6 +109,17 @@ export function createAuthorizer(binding: AuthzCheckBinding): Authorizer {
|
|
|
79
109
|
},
|
|
80
110
|
};
|
|
81
111
|
}
|
|
112
|
+
const undefinedPair = undefinedRelation(action, resource);
|
|
113
|
+
if (undefinedPair !== undefined) return undefinedPair;
|
|
114
|
+
const ceiling = keyCeilingDeny(c, action, resource);
|
|
115
|
+
if (ceiling !== undefined) return ceiling;
|
|
116
|
+
if (!(await inKeyProject(c, principal, resource))) {
|
|
117
|
+
return keyDecision(
|
|
118
|
+
action,
|
|
119
|
+
resource,
|
|
120
|
+
`the API key is limited to project ${c.get('tokenProjectId')}`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
82
123
|
const requestId = c.get('requestId');
|
|
83
124
|
const ctx =
|
|
84
125
|
typeof requestId === 'string' && requestId.length > 0
|
|
@@ -124,7 +165,19 @@ export function createAuthorizer(binding: AuthzCheckBinding): Authorizer {
|
|
|
124
165
|
if (items.length === 0) return [];
|
|
125
166
|
const principal = c.get('principal') as Principal | undefined;
|
|
126
167
|
if (principal === undefined) return [];
|
|
127
|
-
|
|
168
|
+
// What the model can't answer, or the key itself rules out, never
|
|
169
|
+
// reaches the store.
|
|
170
|
+
const allowedByKey = await Promise.all(
|
|
171
|
+
items.map(
|
|
172
|
+
async (item) =>
|
|
173
|
+
undefinedRelation(action, refFn(item)) === undefined &&
|
|
174
|
+
keyCeilingDeny(c, action, refFn(item)) === undefined &&
|
|
175
|
+
(await inKeyProject(c, principal, refFn(item))),
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
const open = items.filter((_, i) => allowedByKey[i]);
|
|
179
|
+
if (open.length === 0) return [];
|
|
180
|
+
const refs = open.map(refFn);
|
|
128
181
|
const requestId = c.get('requestId');
|
|
129
182
|
const ctx =
|
|
130
183
|
typeof requestId === 'string' && requestId.length > 0
|
|
@@ -132,10 +185,93 @@ export function createAuthorizer(binding: AuthzCheckBinding): Authorizer {
|
|
|
132
185
|
: undefined;
|
|
133
186
|
const decisions = await binding.checkBatch(principal, action, refs, ctx);
|
|
134
187
|
const out: (typeof items)[number][] = [];
|
|
135
|
-
for (let i = 0; i <
|
|
136
|
-
if (decisions[i]?.allowed) out.push(
|
|
188
|
+
for (let i = 0; i < open.length; i++) {
|
|
189
|
+
if (decisions[i]?.allowed) out.push(open[i] as (typeof items)[number]);
|
|
137
190
|
}
|
|
138
191
|
return out;
|
|
139
192
|
},
|
|
193
|
+
async listObjects(c, action, type) {
|
|
194
|
+
if (binding.listObjects === undefined) return undefined;
|
|
195
|
+
const principal = c.get('principal') as Principal | undefined;
|
|
196
|
+
if (principal === undefined) return [];
|
|
197
|
+
const requestId = c.get('requestId');
|
|
198
|
+
const ctx =
|
|
199
|
+
typeof requestId === 'string' && requestId.length > 0
|
|
200
|
+
? { correlationId: requestId }
|
|
201
|
+
: undefined;
|
|
202
|
+
return binding.listObjects(principal, action, type, ctx);
|
|
203
|
+
},
|
|
140
204
|
};
|
|
141
205
|
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* A check must name a relation the authorization model defines for the
|
|
209
|
+
* resource's type (`OBJECT_ACTIONS`, kept equal to the runtime's model by
|
|
210
|
+
* its tests). Any other pair is a bug in the route: the PDP rejects it
|
|
211
|
+
* for everyone, the seed admin included, and only with authorization on,
|
|
212
|
+
* so it's refused here, naming the pair, and logged.
|
|
213
|
+
*/
|
|
214
|
+
function undefinedRelation(action: Action, resource: ResourceRef): Decision | undefined {
|
|
215
|
+
const defined = OBJECT_ACTIONS[resource.type as ObjectType] as readonly Action[] | undefined;
|
|
216
|
+
if (defined?.includes(action) === true) return undefined;
|
|
217
|
+
const reason = `the authorization model has no \`${action}\` on \`${resource.type}\` (a check that can never pass: a bug in the route)`;
|
|
218
|
+
console.error(`[authz] ${reason}`);
|
|
219
|
+
return {
|
|
220
|
+
allowed: false,
|
|
221
|
+
failing: 'invalid-action',
|
|
222
|
+
reason,
|
|
223
|
+
evidence: {
|
|
224
|
+
action,
|
|
225
|
+
relation: '',
|
|
226
|
+
resource: `${resource.type}:${resource.id}`,
|
|
227
|
+
actorSubject: '',
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Types a key's project limit is checked on directly, not through `inProject`. */
|
|
233
|
+
const STRUCTURAL: ReadonlySet<string> = new Set(['tenant', 'org', 'team', 'project']);
|
|
234
|
+
|
|
235
|
+
function keyDecision(action: Action, resource: ResourceRef, reason: string): Decision {
|
|
236
|
+
return {
|
|
237
|
+
allowed: false,
|
|
238
|
+
failing: 'scope',
|
|
239
|
+
reason,
|
|
240
|
+
evidence: {
|
|
241
|
+
action,
|
|
242
|
+
relation: '',
|
|
243
|
+
resource: `${resource.type}:${resource.id}`,
|
|
244
|
+
actorSubject: '',
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* What an API key itself rules out, before the principal's grants are
|
|
251
|
+
* asked: a key can do less than its principal, never more.
|
|
252
|
+
*
|
|
253
|
+
* - A `member` key takes no `admin` action on the tenant, even for a
|
|
254
|
+
* principal who is a tenant admin: a day-to-day key can't change keys,
|
|
255
|
+
* grants or policies. Below the tenant, its principal's roles hold: a
|
|
256
|
+
* project admin's member key administers that project.
|
|
257
|
+
* - A key limited to a project acts on no other project, and takes no
|
|
258
|
+
* `admin` action on the tenant, an org or a team. Other resources must
|
|
259
|
+
* be in its project (`inKeyProject`).
|
|
260
|
+
*/
|
|
261
|
+
function keyCeilingDeny(
|
|
262
|
+
c: Context<AppEnv>,
|
|
263
|
+
action: Action,
|
|
264
|
+
resource: ResourceRef,
|
|
265
|
+
): Decision | undefined {
|
|
266
|
+
if (action === 'admin' && resource.type === 'tenant' && c.get('tokenRole') === 'member') {
|
|
267
|
+
return keyDecision(action, resource, 'a member API key takes no admin action on the tenant');
|
|
268
|
+
}
|
|
269
|
+
const keyProject = c.get('tokenProjectId');
|
|
270
|
+
if (keyProject === undefined) return undefined;
|
|
271
|
+
const otherProject = resource.type === 'project' && resource.id !== keyProject;
|
|
272
|
+
const aboveIt =
|
|
273
|
+
resource.type !== 'project' && STRUCTURAL.has(resource.type) && action === 'admin';
|
|
274
|
+
return otherProject || aboveIt
|
|
275
|
+
? keyDecision(action, resource, `the API key is limited to project ${keyProject}`)
|
|
276
|
+
: undefined;
|
|
277
|
+
}
|
|
@@ -22,6 +22,11 @@ import { statusFor, toWireError } from '../errors.js';
|
|
|
22
22
|
*
|
|
23
23
|
* The requestId embedded in the response body matches
|
|
24
24
|
* `middleware/request-id.ts`, so a caller can correlate.
|
|
25
|
+
*
|
|
26
|
+
* A failed database query's message carries its SQL and its parameters
|
|
27
|
+
* (Drizzle's `Failed query: … params: …`), which can be a person's email or
|
|
28
|
+
* address: the body says only that a query failed, and the log keeps the
|
|
29
|
+
* whole error.
|
|
25
30
|
*/
|
|
26
31
|
export function mapThrownError(cause: Error, c: Context): Response {
|
|
27
32
|
if ('getResponse' in cause && typeof cause.getResponse === 'function') {
|
|
@@ -34,10 +39,19 @@ export function mapThrownError(cause: Error, c: Context): Response {
|
|
|
34
39
|
const body = toWireError(
|
|
35
40
|
{
|
|
36
41
|
code: 'internal-server-error',
|
|
37
|
-
message: cause
|
|
42
|
+
message: isQueryError(cause)
|
|
43
|
+
? 'A database query failed.'
|
|
44
|
+
: cause.message.length > 0
|
|
45
|
+
? cause.message
|
|
46
|
+
: 'Unhandled server error',
|
|
38
47
|
},
|
|
39
48
|
requestId,
|
|
40
49
|
);
|
|
41
50
|
c.status(statusFor('internal-server-error') as never);
|
|
42
51
|
return c.json(body);
|
|
43
52
|
}
|
|
53
|
+
|
|
54
|
+
/** Drizzle's wrapper around a failed query, whose message is the SQL and its values. */
|
|
55
|
+
function isQueryError(cause: Error): boolean {
|
|
56
|
+
return cause.name === 'DrizzleQueryError' || cause.message.startsWith('Failed query: ');
|
|
57
|
+
}
|