@kindgi/api 0.0.0-bootstrap.0 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +216 -1
- package/dist/adapter-binding.d.ts +177 -0
- package/dist/adapter-binding.d.ts.map +1 -0
- package/dist/adapter-binding.js +12 -0
- package/dist/adapter-binding.js.map +1 -0
- package/dist/agent-binding.d.ts +207 -0
- package/dist/agent-binding.d.ts.map +1 -0
- package/dist/agent-binding.js +4 -0
- package/dist/agent-binding.js.map +1 -0
- package/dist/app.d.ts +770 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +396 -0
- package/dist/app.js.map +1 -0
- package/dist/capability-binding.d.ts +76 -0
- package/dist/capability-binding.d.ts.map +1 -0
- package/dist/capability-binding.js +4 -0
- package/dist/capability-binding.js.map +1 -0
- package/dist/cost-binding.d.ts +175 -0
- package/dist/cost-binding.d.ts.map +1 -0
- package/dist/cost-binding.js +13 -0
- package/dist/cost-binding.js.map +1 -0
- package/dist/deployment-binding.d.ts +201 -0
- package/dist/deployment-binding.d.ts.map +1 -0
- package/dist/deployment-binding.js +4 -0
- package/dist/deployment-binding.js.map +1 -0
- package/dist/env-binding.d.ts +132 -0
- package/dist/env-binding.d.ts.map +1 -0
- package/dist/env-binding.js +4 -0
- package/dist/env-binding.js.map +1 -0
- package/dist/errors.d.ts +37 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +264 -0
- package/dist/errors.js.map +1 -0
- package/dist/eval-run-binding.d.ts +196 -0
- package/dist/eval-run-binding.d.ts.map +1 -0
- package/dist/eval-run-binding.js +16 -0
- package/dist/eval-run-binding.js.map +1 -0
- package/dist/eval-run-dispatcher.d.ts +131 -0
- package/dist/eval-run-dispatcher.d.ts.map +1 -0
- package/dist/eval-run-dispatcher.js +368 -0
- package/dist/eval-run-dispatcher.js.map +1 -0
- package/dist/eval-suite-binding.d.ts +227 -0
- package/dist/eval-suite-binding.d.ts.map +1 -0
- package/dist/eval-suite-binding.js +24 -0
- package/dist/eval-suite-binding.js.map +1 -0
- package/dist/event-bus-binding.d.ts +107 -0
- package/dist/event-bus-binding.d.ts.map +1 -0
- package/dist/event-bus-binding.js +5 -0
- package/dist/event-bus-binding.js.map +1 -0
- package/dist/flow-binding.d.ts +194 -0
- package/dist/flow-binding.d.ts.map +1 -0
- package/dist/flow-binding.js +4 -0
- package/dist/flow-binding.js.map +1 -0
- package/dist/guardrail-binding.d.ts +138 -0
- package/dist/guardrail-binding.d.ts.map +1 -0
- package/dist/guardrail-binding.js +4 -0
- package/dist/guardrail-binding.js.map +1 -0
- package/dist/handler-binding.d.ts +126 -0
- package/dist/handler-binding.d.ts.map +1 -0
- package/dist/handler-binding.js +4 -0
- package/dist/handler-binding.js.map +1 -0
- package/dist/hitl-binding.d.ts +115 -0
- package/dist/hitl-binding.d.ts.map +1 -0
- package/dist/hitl-binding.js +4 -0
- package/dist/hitl-binding.js.map +1 -0
- package/dist/identity-directory-binding.d.ts +116 -0
- package/dist/identity-directory-binding.d.ts.map +1 -0
- package/dist/identity-directory-binding.js +4 -0
- package/dist/identity-directory-binding.js.map +1 -0
- package/dist/identity-provider-binding.d.ts +184 -0
- package/dist/identity-provider-binding.d.ts.map +1 -0
- package/dist/identity-provider-binding.js +4 -0
- package/dist/identity-provider-binding.js.map +1 -0
- package/dist/image-registry-binding.d.ts +165 -0
- package/dist/image-registry-binding.d.ts.map +1 -0
- package/dist/image-registry-binding.js +4 -0
- package/dist/image-registry-binding.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/key-management-binding.d.ts +101 -0
- package/dist/key-management-binding.d.ts.map +1 -0
- package/dist/key-management-binding.js +4 -0
- package/dist/key-management-binding.js.map +1 -0
- package/dist/mcp-endpoint-binding.d.ts +312 -0
- package/dist/mcp-endpoint-binding.d.ts.map +1 -0
- package/dist/mcp-endpoint-binding.js +12 -0
- package/dist/mcp-endpoint-binding.js.map +1 -0
- package/dist/memory-binding.d.ts +209 -0
- package/dist/memory-binding.d.ts.map +1 -0
- package/dist/memory-binding.js +4 -0
- package/dist/memory-binding.js.map +1 -0
- package/dist/middleware/auth.d.ts +168 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +247 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/authorize.d.ts +26 -0
- package/dist/middleware/authorize.d.ts.map +1 -0
- package/dist/middleware/authorize.js +65 -0
- package/dist/middleware/authorize.js.map +1 -0
- package/dist/middleware/error-mapper.d.ts +13 -0
- package/dist/middleware/error-mapper.d.ts.map +1 -0
- package/dist/middleware/error-mapper.js +32 -0
- package/dist/middleware/error-mapper.js.map +1 -0
- package/dist/middleware/idempotency.d.ts +43 -0
- package/dist/middleware/idempotency.d.ts.map +1 -0
- package/dist/middleware/idempotency.js +118 -0
- package/dist/middleware/idempotency.js.map +1 -0
- package/dist/middleware/principal.d.ts +4 -0
- package/dist/middleware/principal.d.ts.map +1 -0
- package/dist/middleware/principal.js +42 -0
- package/dist/middleware/principal.js.map +1 -0
- package/dist/middleware/public-run-routes.d.ts +16 -0
- package/dist/middleware/public-run-routes.d.ts.map +1 -0
- package/dist/middleware/public-run-routes.js +43 -0
- package/dist/middleware/public-run-routes.js.map +1 -0
- package/dist/middleware/request-id.d.ts +14 -0
- package/dist/middleware/request-id.d.ts.map +1 -0
- package/dist/middleware/request-id.js +24 -0
- package/dist/middleware/request-id.js.map +1 -0
- package/dist/middleware/sigv4-verify.d.ts +64 -0
- package/dist/middleware/sigv4-verify.d.ts.map +1 -0
- package/dist/middleware/sigv4-verify.js +286 -0
- package/dist/middleware/sigv4-verify.js.map +1 -0
- package/dist/middleware/sigv4.d.ts +25 -0
- package/dist/middleware/sigv4.d.ts.map +1 -0
- package/dist/middleware/sigv4.js +120 -0
- package/dist/middleware/sigv4.js.map +1 -0
- package/dist/openapi/generate.d.ts +15 -0
- package/dist/openapi/generate.d.ts.map +1 -0
- package/dist/openapi/generate.js +241 -0
- package/dist/openapi/generate.js.map +1 -0
- package/dist/openapi/operations.d.ts +70 -0
- package/dist/openapi/operations.d.ts.map +1 -0
- package/dist/openapi/operations.js +5051 -0
- package/dist/openapi/operations.js.map +1 -0
- package/dist/openapi/schemas.d.ts +590 -0
- package/dist/openapi/schemas.d.ts.map +1 -0
- package/dist/openapi/schemas.js +5820 -0
- package/dist/openapi/schemas.js.map +1 -0
- package/dist/provenance-binding.d.ts +116 -0
- package/dist/provenance-binding.d.ts.map +1 -0
- package/dist/provenance-binding.js +31 -0
- package/dist/provenance-binding.js.map +1 -0
- package/dist/provider-binding.d.ts +174 -0
- package/dist/provider-binding.d.ts.map +1 -0
- package/dist/provider-binding.js +4 -0
- package/dist/provider-binding.js.map +1 -0
- package/dist/public-run-token.d.ts +97 -0
- package/dist/public-run-token.d.ts.map +1 -0
- package/dist/public-run-token.js +167 -0
- package/dist/public-run-token.js.map +1 -0
- package/dist/retention-binding.d.ts +69 -0
- package/dist/retention-binding.d.ts.map +1 -0
- package/dist/retention-binding.js +4 -0
- package/dist/retention-binding.js.map +1 -0
- package/dist/reviewer-binding.d.ts +84 -0
- package/dist/reviewer-binding.d.ts.map +1 -0
- package/dist/reviewer-binding.js +4 -0
- package/dist/reviewer-binding.js.map +1 -0
- package/dist/rotation-status-store.d.ts +96 -0
- package/dist/rotation-status-store.d.ts.map +1 -0
- package/dist/rotation-status-store.js +106 -0
- package/dist/rotation-status-store.js.map +1 -0
- package/dist/routes/adapters.d.ts +29 -0
- package/dist/routes/adapters.d.ts.map +1 -0
- package/dist/routes/adapters.js +230 -0
- package/dist/routes/adapters.js.map +1 -0
- package/dist/routes/agents.d.ts +17 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +342 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/approvals.d.ts +37 -0
- package/dist/routes/approvals.d.ts.map +1 -0
- package/dist/routes/approvals.js +551 -0
- package/dist/routes/approvals.js.map +1 -0
- package/dist/routes/artifacts.d.ts +25 -0
- package/dist/routes/artifacts.d.ts.map +1 -0
- package/dist/routes/artifacts.js +265 -0
- package/dist/routes/artifacts.js.map +1 -0
- package/dist/routes/audit.d.ts +6 -0
- package/dist/routes/audit.d.ts.map +1 -0
- package/dist/routes/audit.js +162 -0
- package/dist/routes/audit.js.map +1 -0
- package/dist/routes/auth.d.ts +47 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +630 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/capabilities.d.ts +14 -0
- package/dist/routes/capabilities.d.ts.map +1 -0
- package/dist/routes/capabilities.js +62 -0
- package/dist/routes/capabilities.js.map +1 -0
- package/dist/routes/compliance.d.ts +37 -0
- package/dist/routes/compliance.d.ts.map +1 -0
- package/dist/routes/compliance.js +321 -0
- package/dist/routes/compliance.js.map +1 -0
- package/dist/routes/conversations.d.ts +13 -0
- package/dist/routes/conversations.d.ts.map +1 -0
- package/dist/routes/conversations.js +295 -0
- package/dist/routes/conversations.js.map +1 -0
- package/dist/routes/cost.d.ts +24 -0
- package/dist/routes/cost.d.ts.map +1 -0
- package/dist/routes/cost.js +243 -0
- package/dist/routes/cost.js.map +1 -0
- package/dist/routes/deployments.d.ts +122 -0
- package/dist/routes/deployments.d.ts.map +1 -0
- package/dist/routes/deployments.js +1036 -0
- package/dist/routes/deployments.js.map +1 -0
- package/dist/routes/env.d.ts +62 -0
- package/dist/routes/env.d.ts.map +1 -0
- package/dist/routes/env.js +311 -0
- package/dist/routes/env.js.map +1 -0
- package/dist/routes/eval-runs.d.ts +28 -0
- package/dist/routes/eval-runs.d.ts.map +1 -0
- package/dist/routes/eval-runs.js +479 -0
- package/dist/routes/eval-runs.js.map +1 -0
- package/dist/routes/eval-suites.d.ts +24 -0
- package/dist/routes/eval-suites.d.ts.map +1 -0
- package/dist/routes/eval-suites.js +381 -0
- package/dist/routes/eval-suites.js.map +1 -0
- package/dist/routes/event-triggers.d.ts +12 -0
- package/dist/routes/event-triggers.d.ts.map +1 -0
- package/dist/routes/event-triggers.js +218 -0
- package/dist/routes/event-triggers.js.map +1 -0
- package/dist/routes/flows.d.ts +23 -0
- package/dist/routes/flows.d.ts.map +1 -0
- package/dist/routes/flows.js +344 -0
- package/dist/routes/flows.js.map +1 -0
- package/dist/routes/guardrails.d.ts +33 -0
- package/dist/routes/guardrails.d.ts.map +1 -0
- package/dist/routes/guardrails.js +236 -0
- package/dist/routes/guardrails.js.map +1 -0
- package/dist/routes/identity.d.ts +39 -0
- package/dist/routes/identity.d.ts.map +1 -0
- package/dist/routes/identity.js +138 -0
- package/dist/routes/identity.js.map +1 -0
- package/dist/routes/mcp.d.ts +23 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +727 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/memory.d.ts +16 -0
- package/dist/routes/memory.d.ts.map +1 -0
- package/dist/routes/memory.js +353 -0
- package/dist/routes/memory.js.map +1 -0
- package/dist/routes/observations.d.ts +19 -0
- package/dist/routes/observations.d.ts.map +1 -0
- package/dist/routes/observations.js +135 -0
- package/dist/routes/observations.js.map +1 -0
- package/dist/routes/orgs.d.ts +24 -0
- package/dist/routes/orgs.d.ts.map +1 -0
- package/dist/routes/orgs.js +208 -0
- package/dist/routes/orgs.js.map +1 -0
- package/dist/routes/pagination.d.ts +18 -0
- package/dist/routes/pagination.d.ts.map +1 -0
- package/dist/routes/pagination.js +35 -0
- package/dist/routes/pagination.js.map +1 -0
- package/dist/routes/policies.d.ts +23 -0
- package/dist/routes/policies.d.ts.map +1 -0
- package/dist/routes/policies.js +314 -0
- package/dist/routes/policies.js.map +1 -0
- package/dist/routes/projects.d.ts +45 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +485 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/proposals.d.ts +24 -0
- package/dist/routes/proposals.d.ts.map +1 -0
- package/dist/routes/proposals.js +581 -0
- package/dist/routes/proposals.js.map +1 -0
- package/dist/routes/provenance.d.ts +34 -0
- package/dist/routes/provenance.d.ts.map +1 -0
- package/dist/routes/provenance.js +274 -0
- package/dist/routes/provenance.js.map +1 -0
- package/dist/routes/providers.d.ts +32 -0
- package/dist/routes/providers.d.ts.map +1 -0
- package/dist/routes/providers.js +490 -0
- package/dist/routes/providers.js.map +1 -0
- package/dist/routes/public-run-tokens.d.ts +17 -0
- package/dist/routes/public-run-tokens.d.ts.map +1 -0
- package/dist/routes/public-run-tokens.js +69 -0
- package/dist/routes/public-run-tokens.js.map +1 -0
- package/dist/routes/retention.d.ts +19 -0
- package/dist/routes/retention.d.ts.map +1 -0
- package/dist/routes/retention.js +152 -0
- package/dist/routes/retention.js.map +1 -0
- package/dist/routes/reviewers.d.ts +5 -0
- package/dist/routes/reviewers.d.ts.map +1 -0
- package/dist/routes/reviewers.js +161 -0
- package/dist/routes/reviewers.js.map +1 -0
- package/dist/routes/runs.d.ts +34 -0
- package/dist/routes/runs.d.ts.map +1 -0
- package/dist/routes/runs.js +773 -0
- package/dist/routes/runs.js.map +1 -0
- package/dist/routes/s3.d.ts +31 -0
- package/dist/routes/s3.d.ts.map +1 -0
- package/dist/routes/s3.js +433 -0
- package/dist/routes/s3.js.map +1 -0
- package/dist/routes/schedules.d.ts +20 -0
- package/dist/routes/schedules.d.ts.map +1 -0
- package/dist/routes/schedules.js +234 -0
- package/dist/routes/schedules.js.map +1 -0
- package/dist/routes/scope-params.d.ts +57 -0
- package/dist/routes/scope-params.d.ts.map +1 -0
- package/dist/routes/scope-params.js +105 -0
- package/dist/routes/scope-params.js.map +1 -0
- package/dist/routes/secret-ref.d.ts +20 -0
- package/dist/routes/secret-ref.d.ts.map +1 -0
- package/dist/routes/secret-ref.js +42 -0
- package/dist/routes/secret-ref.js.map +1 -0
- package/dist/routes/secrets.d.ts +53 -0
- package/dist/routes/secrets.d.ts.map +1 -0
- package/dist/routes/secrets.js +715 -0
- package/dist/routes/secrets.js.map +1 -0
- package/dist/routes/signing-keys.d.ts +23 -0
- package/dist/routes/signing-keys.d.ts.map +1 -0
- package/dist/routes/signing-keys.js +212 -0
- package/dist/routes/signing-keys.js.map +1 -0
- package/dist/routes/sse.d.ts +58 -0
- package/dist/routes/sse.d.ts.map +1 -0
- package/dist/routes/sse.js +92 -0
- package/dist/routes/sse.js.map +1 -0
- package/dist/routes/teams.d.ts +30 -0
- package/dist/routes/teams.d.ts.map +1 -0
- package/dist/routes/teams.js +443 -0
- package/dist/routes/teams.js.map +1 -0
- package/dist/routes/tenant.d.ts +57 -0
- package/dist/routes/tenant.d.ts.map +1 -0
- package/dist/routes/tenant.js +352 -0
- package/dist/routes/tenant.js.map +1 -0
- package/dist/routes/tokens.d.ts +14 -0
- package/dist/routes/tokens.d.ts.map +1 -0
- package/dist/routes/tokens.js +231 -0
- package/dist/routes/tokens.js.map +1 -0
- package/dist/routes/tools.d.ts +33 -0
- package/dist/routes/tools.d.ts.map +1 -0
- package/dist/routes/tools.js +380 -0
- package/dist/routes/tools.js.map +1 -0
- package/dist/routes/webhook-endpoints.d.ts +10 -0
- package/dist/routes/webhook-endpoints.d.ts.map +1 -0
- package/dist/routes/webhook-endpoints.js +407 -0
- package/dist/routes/webhook-endpoints.js.map +1 -0
- package/dist/routes/webhooks.d.ts +21 -0
- package/dist/routes/webhooks.d.ts.map +1 -0
- package/dist/routes/webhooks.js +228 -0
- package/dist/routes/webhooks.js.map +1 -0
- package/dist/s3-credential-binding.d.ts +54 -0
- package/dist/s3-credential-binding.d.ts.map +1 -0
- package/dist/s3-credential-binding.js +4 -0
- package/dist/s3-credential-binding.js.map +1 -0
- package/dist/secrets-binding.d.ts +281 -0
- package/dist/secrets-binding.d.ts.map +1 -0
- package/dist/secrets-binding.js +10 -0
- package/dist/secrets-binding.js.map +1 -0
- package/dist/secrets-provider-binding.d.ts +164 -0
- package/dist/secrets-provider-binding.d.ts.map +1 -0
- package/dist/secrets-provider-binding.js +4 -0
- package/dist/secrets-provider-binding.js.map +1 -0
- package/dist/session-store-binding.d.ts +148 -0
- package/dist/session-store-binding.d.ts.map +1 -0
- package/dist/session-store-binding.js +4 -0
- package/dist/session-store-binding.js.map +1 -0
- package/dist/signing-key-binding.d.ts +210 -0
- package/dist/signing-key-binding.d.ts.map +1 -0
- package/dist/signing-key-binding.js +4 -0
- package/dist/signing-key-binding.js.map +1 -0
- package/dist/state-store-binding.d.ts +53 -0
- package/dist/state-store-binding.d.ts.map +1 -0
- package/dist/state-store-binding.js +38 -0
- package/dist/state-store-binding.js.map +1 -0
- package/dist/supervisor-binding.d.ts +526 -0
- package/dist/supervisor-binding.d.ts.map +1 -0
- package/dist/supervisor-binding.js +4 -0
- package/dist/supervisor-binding.js.map +1 -0
- package/dist/tenant-host-access.d.ts +41 -0
- package/dist/tenant-host-access.d.ts.map +1 -0
- package/dist/tenant-host-access.js +42 -0
- package/dist/tenant-host-access.js.map +1 -0
- package/dist/token-admin.d.ts +99 -0
- package/dist/token-admin.d.ts.map +1 -0
- package/dist/token-admin.js +4 -0
- package/dist/token-admin.js.map +1 -0
- package/dist/tool-binding.d.ts +247 -0
- package/dist/tool-binding.d.ts.map +1 -0
- package/dist/tool-binding.js +4 -0
- package/dist/tool-binding.js.map +1 -0
- package/dist/trigger-binding.d.ts +13 -0
- package/dist/trigger-binding.d.ts.map +1 -0
- package/dist/trigger-binding.js +4 -0
- package/dist/trigger-binding.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook-endpoint-binding.d.ts +230 -0
- package/dist/webhook-endpoint-binding.d.ts.map +1 -0
- package/dist/webhook-endpoint-binding.js +12 -0
- package/dist/webhook-endpoint-binding.js.map +1 -0
- package/dist/xml.d.ts +79 -0
- package/dist/xml.d.ts.map +1 -0
- package/dist/xml.js +115 -0
- package/dist/xml.js.map +1 -0
- package/openapi.json +29659 -0
- package/package.json +76 -4
- package/scripts/emit-openapi.mjs +44 -0
- package/src/adapter-binding.ts +204 -0
- package/src/agent-binding.ts +231 -0
- package/src/app.ts +1285 -0
- package/src/capability-binding.ts +83 -0
- package/src/cost-binding.ts +213 -0
- package/src/deployment-binding.ts +225 -0
- package/src/env-binding.ts +140 -0
- package/src/errors.ts +291 -0
- package/src/eval-run-binding.ts +209 -0
- package/src/eval-run-dispatcher.ts +497 -0
- package/src/eval-suite-binding.ts +257 -0
- package/src/event-bus-binding.ts +127 -0
- package/src/flow-binding.ts +213 -0
- package/src/guardrail-binding.ts +151 -0
- package/src/handler-binding.ts +132 -0
- package/src/hitl-binding.ts +172 -0
- package/src/identity-directory-binding.ts +128 -0
- package/src/identity-provider-binding.ts +203 -0
- package/src/image-registry-binding.ts +193 -0
- package/src/index.ts +606 -0
- package/src/key-management-binding.ts +91 -0
- package/src/mcp-endpoint-binding.ts +311 -0
- package/src/memory-binding.ts +239 -0
- package/src/middleware/auth.ts +437 -0
- package/src/middleware/authorize.ts +125 -0
- package/src/middleware/error-mapper.ts +37 -0
- package/src/middleware/idempotency.ts +160 -0
- package/src/middleware/principal.ts +64 -0
- package/src/middleware/public-run-routes.ts +50 -0
- package/src/middleware/request-id.ts +28 -0
- package/src/middleware/sigv4-verify.ts +374 -0
- package/src/middleware/sigv4.ts +165 -0
- package/src/openapi/generate.ts +307 -0
- package/src/openapi/operations.ts +5445 -0
- package/src/openapi/schemas.ts +6363 -0
- package/src/provenance-binding.ts +169 -0
- package/src/provider-binding.ts +196 -0
- package/src/public-run-token.ts +263 -0
- package/src/retention-binding.ts +78 -0
- package/src/reviewer-binding.ts +99 -0
- package/src/rotation-status-store.ts +209 -0
- package/src/routes/adapters.ts +319 -0
- package/src/routes/agents.ts +424 -0
- package/src/routes/approvals.ts +759 -0
- package/src/routes/artifacts.ts +317 -0
- package/src/routes/audit.ts +219 -0
- package/src/routes/auth.ts +806 -0
- package/src/routes/capabilities.ts +79 -0
- package/src/routes/compliance.ts +482 -0
- package/src/routes/conversations.ts +369 -0
- package/src/routes/cost.ts +327 -0
- package/src/routes/deployments.ts +1493 -0
- package/src/routes/env.ts +429 -0
- package/src/routes/eval-runs.ts +631 -0
- package/src/routes/eval-suites.ts +490 -0
- package/src/routes/event-triggers.ts +283 -0
- package/src/routes/flows.ts +423 -0
- package/src/routes/guardrails.ts +320 -0
- package/src/routes/identity.ts +203 -0
- package/src/routes/mcp.ts +940 -0
- package/src/routes/memory.ts +430 -0
- package/src/routes/observations.ts +175 -0
- package/src/routes/orgs.ts +273 -0
- package/src/routes/pagination.ts +51 -0
- package/src/routes/policies.ts +413 -0
- package/src/routes/projects.ts +634 -0
- package/src/routes/proposals.ts +719 -0
- package/src/routes/provenance.ts +406 -0
- package/src/routes/providers.ts +624 -0
- package/src/routes/public-run-tokens.ts +104 -0
- package/src/routes/retention.ts +193 -0
- package/src/routes/reviewers.ts +209 -0
- package/src/routes/runs.ts +981 -0
- package/src/routes/s3.ts +519 -0
- package/src/routes/schedules.ts +300 -0
- package/src/routes/scope-params.ts +133 -0
- package/src/routes/secret-ref.ts +52 -0
- package/src/routes/secrets.ts +975 -0
- package/src/routes/signing-keys.ts +259 -0
- package/src/routes/sse.ts +148 -0
- package/src/routes/teams.ts +584 -0
- package/src/routes/tenant.ts +530 -0
- package/src/routes/tokens.ts +285 -0
- package/src/routes/tools.ts +491 -0
- package/src/routes/webhook-endpoints.ts +534 -0
- package/src/routes/webhooks.ts +296 -0
- package/src/s3-credential-binding.ts +58 -0
- package/src/secrets-binding.ts +308 -0
- package/src/secrets-provider-binding.ts +167 -0
- package/src/session-store-binding.ts +163 -0
- package/src/signing-key-binding.ts +235 -0
- package/src/state-store-binding.ts +84 -0
- package/src/supervisor-binding.ts +554 -0
- package/src/tenant-host-access.ts +62 -0
- package/src/token-admin.ts +104 -0
- package/src/tool-binding.ts +274 -0
- package/src/trigger-binding.ts +38 -0
- package/src/types.ts +74 -0
- package/src/webhook-endpoint-binding.ts +246 -0
- package/src/xml.ts +196 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
import type { MiddlewareHandler } from 'hono';
|
|
7
|
+
|
|
8
|
+
import type { TenantId } from '@kindgi/types';
|
|
9
|
+
|
|
10
|
+
import { statusFor, toWireError } from '../errors.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Idempotency-key store: `(tenantId, route, key) → cached response`,
|
|
14
|
+
* cached for `ttlMs` (default 24h per API-ROUTE-CONVENTIONS.md §3.1).
|
|
15
|
+
*
|
|
16
|
+
* Injected into the middleware so deployments can plug in a durable
|
|
17
|
+
* store (e.g. Postgres, Redis). The bundled `createInMemoryIdempotencyStore()`
|
|
18
|
+
* is fine for dev + tests; production installs are expected to plug
|
|
19
|
+
* in a persistent one so retries survive restarts.
|
|
20
|
+
*/
|
|
21
|
+
export interface IdempotencyStore {
|
|
22
|
+
get(key: string): Promise<StoredIdempotencyEntry | null>;
|
|
23
|
+
set(key: string, entry: StoredIdempotencyEntry): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StoredIdempotencyEntry {
|
|
27
|
+
readonly bodyHash: string;
|
|
28
|
+
readonly status: number;
|
|
29
|
+
readonly contentType: string;
|
|
30
|
+
readonly bodyText: string;
|
|
31
|
+
readonly expiresAt: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Simple in-memory store with lazy expiry — swap for a DB store in prod. */
|
|
35
|
+
export function createInMemoryIdempotencyStore(): IdempotencyStore {
|
|
36
|
+
const table = new Map<string, StoredIdempotencyEntry>();
|
|
37
|
+
return {
|
|
38
|
+
async get(key) {
|
|
39
|
+
const hit = table.get(key);
|
|
40
|
+
if (hit === undefined) return null;
|
|
41
|
+
if (hit.expiresAt <= Date.now()) {
|
|
42
|
+
table.delete(key);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return hit;
|
|
46
|
+
},
|
|
47
|
+
async set(key, entry) {
|
|
48
|
+
table.set(key, entry);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const MUTATING_METHODS: ReadonlySet<string> = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
54
|
+
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Idempotency-Key middleware per API-ROUTE-CONVENTIONS.md §3.1.
|
|
58
|
+
*
|
|
59
|
+
* Behaviour:
|
|
60
|
+
* - Only applies to mutating methods (POST/PUT/PATCH/DELETE).
|
|
61
|
+
* - No `Idempotency-Key` header → pass through.
|
|
62
|
+
* - Cached hit with matching body hash → replay stored response
|
|
63
|
+
* byte-identical (status, content-type, body).
|
|
64
|
+
* - Cached hit with different body hash → 409
|
|
65
|
+
* `idempotency-key-body-mismatch`.
|
|
66
|
+
* - Cache miss → run the handler, then store the response.
|
|
67
|
+
*
|
|
68
|
+
* The middleware sits AFTER auth so `tenantId` is available; the
|
|
69
|
+
* cache key is namespaced by tenant so callers can't collide across
|
|
70
|
+
* tenants.
|
|
71
|
+
*/
|
|
72
|
+
export function idempotencyMiddleware(
|
|
73
|
+
store: IdempotencyStore,
|
|
74
|
+
opts: { ttlMs?: number } = {},
|
|
75
|
+
): MiddlewareHandler {
|
|
76
|
+
const ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
|
|
77
|
+
return async (c, next) => {
|
|
78
|
+
if (!MUTATING_METHODS.has(c.req.method.toUpperCase())) {
|
|
79
|
+
return next();
|
|
80
|
+
}
|
|
81
|
+
const rawKey = c.req.header('idempotency-key');
|
|
82
|
+
if (rawKey === undefined || rawKey.length === 0) {
|
|
83
|
+
return next();
|
|
84
|
+
}
|
|
85
|
+
const requestId = c.get('requestId') as string;
|
|
86
|
+
const tenantId = c.get('tenantId') as TenantId | undefined;
|
|
87
|
+
if (tenantId === undefined) {
|
|
88
|
+
// Auth middleware didn't set it — bail out and let the auth
|
|
89
|
+
// failure surface. This shouldn't happen because auth runs
|
|
90
|
+
// first, but the guard keeps the cache key well-formed.
|
|
91
|
+
return next();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const bodyText = await readRawBody(c.req.raw);
|
|
95
|
+
const bodyHash = hashBody(bodyText);
|
|
96
|
+
const cacheKey = `${tenantId}|${c.req.method.toUpperCase()}:${c.req.path}|${rawKey}`;
|
|
97
|
+
const hit = await store.get(cacheKey);
|
|
98
|
+
|
|
99
|
+
if (hit !== null) {
|
|
100
|
+
if (hit.bodyHash !== bodyHash) {
|
|
101
|
+
c.status(statusFor('idempotency-key-body-mismatch') as never);
|
|
102
|
+
return c.json(
|
|
103
|
+
toWireError(
|
|
104
|
+
{
|
|
105
|
+
code: 'idempotency-key-body-mismatch',
|
|
106
|
+
message:
|
|
107
|
+
'Idempotency-Key was reused with a different request body. Use a fresh key or the original body.',
|
|
108
|
+
},
|
|
109
|
+
requestId,
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return c.body(hit.bodyText, hit.status as never, {
|
|
114
|
+
'Content-Type': hit.contentType,
|
|
115
|
+
'X-Idempotent-Replay': 'true',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Re-attach the buffered body so downstream handlers reading
|
|
120
|
+
// `c.req.json()` see the same bytes.
|
|
121
|
+
if (bodyText.length > 0) rebindBody(c, bodyText);
|
|
122
|
+
await next();
|
|
123
|
+
|
|
124
|
+
const res = c.res;
|
|
125
|
+
const savedBody = await res.clone().text();
|
|
126
|
+
const contentType = res.headers.get('Content-Type') ?? 'application/octet-stream';
|
|
127
|
+
await store.set(cacheKey, {
|
|
128
|
+
bodyHash,
|
|
129
|
+
status: res.status,
|
|
130
|
+
contentType,
|
|
131
|
+
bodyText: savedBody,
|
|
132
|
+
expiresAt: Date.now() + ttlMs,
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function readRawBody(req: Request): Promise<string> {
|
|
139
|
+
const method = req.method.toUpperCase();
|
|
140
|
+
if (method === 'GET' || method === 'HEAD') return '';
|
|
141
|
+
try {
|
|
142
|
+
return await req.clone().text();
|
|
143
|
+
} catch {
|
|
144
|
+
return '';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function hashBody(body: string): string {
|
|
149
|
+
return createHash('sha256').update(body).digest('hex');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function rebindBody(c: { req: { raw: Request } }, body: string): void {
|
|
153
|
+
const original = c.req.raw;
|
|
154
|
+
const rebuilt = new Request(original.url, {
|
|
155
|
+
method: original.method,
|
|
156
|
+
headers: original.headers,
|
|
157
|
+
body,
|
|
158
|
+
});
|
|
159
|
+
Object.defineProperty(c.req, 'raw', { value: rebuilt, configurable: true });
|
|
160
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
//
|
|
5
|
+
// principalMiddleware — Hono middleware that constructs a composite
|
|
6
|
+
// authz Principal from the values `bearerAuthMiddleware` set on the
|
|
7
|
+
// context (tenantId + optional userId + optional sessionId).
|
|
8
|
+
//
|
|
9
|
+
// Runs AFTER bearerAuthMiddleware. Every route below `/v1` gets a
|
|
10
|
+
// principal populated via `c.get('principal')`. Downstream:
|
|
11
|
+
// - `authorize(action, resource)` reads it for enforcement.
|
|
12
|
+
// - Route handlers can read it directly for conditional logic.
|
|
13
|
+
//
|
|
14
|
+
// Delegation chains (agent-on-behalf-of-user) are constructed at
|
|
15
|
+
// deeper layers (kernel start-of-run) — the request-scoped principal
|
|
16
|
+
// is always a single actor.
|
|
17
|
+
//
|
|
18
|
+
|
|
19
|
+
import type { MiddlewareHandler } from 'hono';
|
|
20
|
+
|
|
21
|
+
import { principalFromToken } from '@kindgi/authz';
|
|
22
|
+
|
|
23
|
+
import type { AppEnv } from '../types.js';
|
|
24
|
+
|
|
25
|
+
export function principalMiddleware(): MiddlewareHandler<AppEnv> {
|
|
26
|
+
return async (c, next) => {
|
|
27
|
+
const tenantId = c.get('tenantId');
|
|
28
|
+
const userId = c.get('userId');
|
|
29
|
+
const tokenId = c.get('tokenId');
|
|
30
|
+
const sessionId = c.get('sessionId');
|
|
31
|
+
|
|
32
|
+
if (tenantId === undefined) {
|
|
33
|
+
// Bearer middleware didn't populate — either the route is
|
|
34
|
+
// pre-auth (unlikely at this mount point) or the resolver
|
|
35
|
+
// failed silently. Skip; downstream authorize() will 403 in a
|
|
36
|
+
// coherent way if it needs the principal.
|
|
37
|
+
return next();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// A user when the token names one; otherwise a service account: a
|
|
41
|
+
// durable API key's id, or else the session id (which uniquely
|
|
42
|
+
// identifies the token record). With none of them, leave `principal`
|
|
43
|
+
// unset; `authorize()` treats that as "no principal" and 403s cleanly.
|
|
44
|
+
const serviceAccountId =
|
|
45
|
+
(tokenId as unknown as string | undefined) ?? (sessionId as unknown as string | undefined);
|
|
46
|
+
if (userId === undefined && serviceAccountId === undefined) {
|
|
47
|
+
return next();
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const principal = principalFromToken({
|
|
51
|
+
tenantId,
|
|
52
|
+
...(userId !== undefined ? { userId } : {}),
|
|
53
|
+
...(userId === undefined && serviceAccountId !== undefined
|
|
54
|
+
? { tokenId: serviceAccountId }
|
|
55
|
+
: {}),
|
|
56
|
+
});
|
|
57
|
+
c.set('principal', principal);
|
|
58
|
+
} catch {
|
|
59
|
+
// Defensive: principalFromToken throws when its guardrail is
|
|
60
|
+
// violated; downstream authorize() handles the missing principal.
|
|
61
|
+
}
|
|
62
|
+
return next();
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import type { MiddlewareHandler } from 'hono';
|
|
5
|
+
import { cors } from 'hono/cors';
|
|
6
|
+
|
|
7
|
+
import { OPERATIONS } from '../openapi/operations.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The routes a public run token may call: the operations registered with
|
|
11
|
+
* `security: 'bearer-or-public-run'`. Deriving the list from the
|
|
12
|
+
* operation registry keeps the middleware, the OpenAPI document and the
|
|
13
|
+
* routes in step.
|
|
14
|
+
*/
|
|
15
|
+
export function publicRunRouteMatcher(): (method: string, path: string) => boolean {
|
|
16
|
+
const routes = OPERATIONS.filter((op) => op.security === 'bearer-or-public-run').map((op) => ({
|
|
17
|
+
method: op.method.toUpperCase(),
|
|
18
|
+
pattern: new RegExp(
|
|
19
|
+
`^${op.honoPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/:[A-Za-z][A-Za-z0-9_]*/g, '[^/]+')}$`,
|
|
20
|
+
),
|
|
21
|
+
}));
|
|
22
|
+
return (method, path) =>
|
|
23
|
+
routes.some((route) => route.method === method && route.pattern.test(path));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* CORS for the public-run-token routes only, so a browser on one of
|
|
28
|
+
* `allowedOrigins` can call them with a public token. Every other route
|
|
29
|
+
* gets no CORS headers: secret API tokens stay server-side. Registered
|
|
30
|
+
* ahead of authentication, so preflights don't need a token.
|
|
31
|
+
*/
|
|
32
|
+
export function publicRunCorsMiddleware(allowedOrigins: readonly string[]): MiddlewareHandler {
|
|
33
|
+
const matchesPath = publicRunRouteMatcher();
|
|
34
|
+
const handler = cors({
|
|
35
|
+
origin: [...allowedOrigins],
|
|
36
|
+
allowMethods: ['GET'],
|
|
37
|
+
allowHeaders: ['Authorization', 'Last-Event-ID'],
|
|
38
|
+
exposeHeaders: ['X-Request-Id'],
|
|
39
|
+
maxAge: 600,
|
|
40
|
+
});
|
|
41
|
+
return async (c, next) => {
|
|
42
|
+
// A preflight asks about the method it will use; match on that.
|
|
43
|
+
const method =
|
|
44
|
+
c.req.method === 'OPTIONS'
|
|
45
|
+
? (c.req.header('access-control-request-method') ?? '').toUpperCase()
|
|
46
|
+
: c.req.method;
|
|
47
|
+
if (!matchesPath(method, c.req.path)) return next();
|
|
48
|
+
return handler(c, next);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
import type { MiddlewareHandler } from 'hono';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Assigns a request id to every incoming request. Stored on the Hono
|
|
10
|
+
* context as `c.get('requestId')`. Every error envelope embeds this so
|
|
11
|
+
* a caller reporting a bug can hand us one string to grep the logs.
|
|
12
|
+
*
|
|
13
|
+
* If the client provided `X-Request-Id`, we honor it (matching a
|
|
14
|
+
* caller-supplied id makes SDK retries observable in server logs).
|
|
15
|
+
* Otherwise we mint a new UUID.
|
|
16
|
+
*
|
|
17
|
+
* The response header `X-Request-Id` mirrors the id back.
|
|
18
|
+
*/
|
|
19
|
+
export function requestIdMiddleware(): MiddlewareHandler {
|
|
20
|
+
return async (c, next) => {
|
|
21
|
+
const provided = c.req.header('x-request-id');
|
|
22
|
+
const requestId =
|
|
23
|
+
provided !== undefined && provided.length > 0 ? provided : `req-${randomUUID()}`;
|
|
24
|
+
c.set('requestId', requestId);
|
|
25
|
+
await next();
|
|
26
|
+
c.header('X-Request-Id', requestId);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AWS Signature Version 4 verification for the `/s3/*` surface — pure
|
|
6
|
+
* functions, no HTTP framework.
|
|
7
|
+
*
|
|
8
|
+
* Verification re-derives the signature from exactly what the client
|
|
9
|
+
* signed:
|
|
10
|
+
*
|
|
11
|
+
* - the timestamp the client signed with (`x-amz-date` header, or the
|
|
12
|
+
* `X-Amz-Date` query parameter of a presigned URL) — never the
|
|
13
|
+
* server's clock. The server's clock only bounds it: requests more
|
|
14
|
+
* than {@link MAX_CLOCK_SKEW_SECONDS} away are rejected
|
|
15
|
+
* (`RequestTimeTooSkewed`), presigned URLs past `X-Amz-Expires` are
|
|
16
|
+
* rejected (`AccessDenied`);
|
|
17
|
+
* - only the headers the client listed in `SignedHeaders`. Anything a
|
|
18
|
+
* proxy or HTTP stack adds after signing is ignored;
|
|
19
|
+
* - the canonical URI / query exactly as S3 defines them (path
|
|
20
|
+
* segments not normalized; query keys sorted, first value only).
|
|
21
|
+
*
|
|
22
|
+
* The signature comparison is constant-time.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { createHash, createHmac, timingSafeEqual } from 'node:crypto';
|
|
26
|
+
|
|
27
|
+
/** S3's allowed clock skew for signed requests (15 minutes). */
|
|
28
|
+
export const MAX_CLOCK_SKEW_SECONDS = 15 * 60;
|
|
29
|
+
/** S3's maximum presigned-URL lifetime (7 days). */
|
|
30
|
+
export const MAX_PRESIGN_EXPIRES_SECONDS = 7 * 24 * 60 * 60;
|
|
31
|
+
|
|
32
|
+
const ALGORITHM = 'AWS4-HMAC-SHA256';
|
|
33
|
+
const AMZ_DATE = /^(\d{8})T\d{6}Z$/;
|
|
34
|
+
const UNSIGNED_PAYLOAD = 'UNSIGNED-PAYLOAD';
|
|
35
|
+
|
|
36
|
+
export interface SigV4Error {
|
|
37
|
+
/** S3 error code. */
|
|
38
|
+
readonly code: string;
|
|
39
|
+
readonly message: string;
|
|
40
|
+
readonly status: 400 | 403;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type SigV4Result<T> =
|
|
44
|
+
| { readonly kind: 'ok'; readonly value: T }
|
|
45
|
+
| { readonly kind: 'err'; readonly error: SigV4Error };
|
|
46
|
+
|
|
47
|
+
/** What a request claims about its signature, before verification. */
|
|
48
|
+
export interface SigV4Claim {
|
|
49
|
+
readonly kind: 'header' | 'presigned';
|
|
50
|
+
readonly accessKeyId: string;
|
|
51
|
+
/** Credential-scope date, `YYYYMMDD`. */
|
|
52
|
+
readonly scopeDate: string;
|
|
53
|
+
readonly region: string;
|
|
54
|
+
readonly service: string;
|
|
55
|
+
/** Lowercase header names, as the client listed them. */
|
|
56
|
+
readonly signedHeaders: readonly string[];
|
|
57
|
+
readonly signature: string;
|
|
58
|
+
/** `YYYYMMDDTHHMMSSZ`. */
|
|
59
|
+
readonly amzDate: string;
|
|
60
|
+
/** Presigned only: `X-Amz-Expires`, seconds. */
|
|
61
|
+
readonly expiresSeconds?: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface SigV4Request {
|
|
65
|
+
readonly method: string;
|
|
66
|
+
/** The request URL as received (pathname still percent-encoded). */
|
|
67
|
+
readonly url: URL;
|
|
68
|
+
readonly headers: Headers;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function err(code: string, message: string, status: 400 | 403): { kind: 'err'; error: SigV4Error } {
|
|
72
|
+
return { kind: 'err', error: { code, message, status } };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Parse the signature claim from the `Authorization` header or the
|
|
77
|
+
* presigned-URL query parameters. Does not verify anything.
|
|
78
|
+
*/
|
|
79
|
+
export function parseSigV4(request: SigV4Request): SigV4Result<SigV4Claim> {
|
|
80
|
+
const query = request.url.searchParams;
|
|
81
|
+
const presignedAlgorithm = query.get('X-Amz-Algorithm');
|
|
82
|
+
if (presignedAlgorithm !== null) {
|
|
83
|
+
if (presignedAlgorithm !== ALGORITHM) {
|
|
84
|
+
return err('InvalidRequest', `Unsupported presign algorithm "${presignedAlgorithm}"`, 403);
|
|
85
|
+
}
|
|
86
|
+
const credential = query.get('X-Amz-Credential');
|
|
87
|
+
const signature = query.get('X-Amz-Signature');
|
|
88
|
+
const amzDate = query.get('X-Amz-Date');
|
|
89
|
+
const signedHeaders = query.get('X-Amz-SignedHeaders');
|
|
90
|
+
const expires = query.get('X-Amz-Expires');
|
|
91
|
+
if (credential === null || signature === null || amzDate === null || signedHeaders === null) {
|
|
92
|
+
return err(
|
|
93
|
+
'AuthorizationQueryParametersError',
|
|
94
|
+
'Presigned URL must carry X-Amz-Credential, X-Amz-Date, X-Amz-SignedHeaders and X-Amz-Signature',
|
|
95
|
+
400,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
const scope = parseCredentialScope(credential);
|
|
99
|
+
if (scope === null) return err('InvalidRequest', 'Malformed X-Amz-Credential', 403);
|
|
100
|
+
const expiresSeconds = expires === null ? MAX_PRESIGN_EXPIRES_SECONDS : Number(expires);
|
|
101
|
+
if (
|
|
102
|
+
!Number.isInteger(expiresSeconds) ||
|
|
103
|
+
expiresSeconds < 1 ||
|
|
104
|
+
expiresSeconds > MAX_PRESIGN_EXPIRES_SECONDS
|
|
105
|
+
) {
|
|
106
|
+
return err(
|
|
107
|
+
'AuthorizationQueryParametersError',
|
|
108
|
+
`X-Amz-Expires must be an integer between 1 and ${MAX_PRESIGN_EXPIRES_SECONDS}`,
|
|
109
|
+
400,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
kind: 'ok',
|
|
114
|
+
value: {
|
|
115
|
+
kind: 'presigned',
|
|
116
|
+
...scope,
|
|
117
|
+
signedHeaders: signedHeaders.split(';'),
|
|
118
|
+
signature,
|
|
119
|
+
amzDate,
|
|
120
|
+
expiresSeconds,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const authorization = request.headers.get('authorization') ?? '';
|
|
126
|
+
if (authorization.length === 0) return err('AccessDenied', 'Missing Authorization header', 403);
|
|
127
|
+
const match = /^AWS4-HMAC-SHA256\s+(.+)$/i.exec(authorization);
|
|
128
|
+
if (match === null) {
|
|
129
|
+
return err('InvalidRequest', 'Only AWS4-HMAC-SHA256 signatures are supported', 403);
|
|
130
|
+
}
|
|
131
|
+
const fields = new Map<string, string>();
|
|
132
|
+
for (const part of (match[1] as string).split(',')) {
|
|
133
|
+
const eq = part.indexOf('=');
|
|
134
|
+
if (eq > 0) fields.set(part.slice(0, eq).trim(), part.slice(eq + 1).trim());
|
|
135
|
+
}
|
|
136
|
+
const credential = fields.get('Credential');
|
|
137
|
+
const signedHeaders = fields.get('SignedHeaders');
|
|
138
|
+
const signature = fields.get('Signature');
|
|
139
|
+
if (credential === undefined || signedHeaders === undefined || signature === undefined) {
|
|
140
|
+
return err(
|
|
141
|
+
'InvalidRequest',
|
|
142
|
+
'Malformed Authorization header (needs Credential, SignedHeaders, Signature)',
|
|
143
|
+
403,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const scope = parseCredentialScope(credential);
|
|
147
|
+
if (scope === null) return err('InvalidRequest', 'Malformed credential scope', 403);
|
|
148
|
+
const amzDate = request.headers.get('x-amz-date');
|
|
149
|
+
if (amzDate === null) {
|
|
150
|
+
return err('AccessDenied', 'Signed requests must carry an x-amz-date header', 403);
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
kind: 'ok',
|
|
154
|
+
value: {
|
|
155
|
+
kind: 'header',
|
|
156
|
+
...scope,
|
|
157
|
+
signedHeaders: signedHeaders.split(';'),
|
|
158
|
+
signature,
|
|
159
|
+
amzDate,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Check the claim's timestamp and shape against the server clock:
|
|
166
|
+
* `X-Amz-Date` well-formed and consistent with the credential scope,
|
|
167
|
+
* within {@link MAX_CLOCK_SKEW_SECONDS} (header auth / presigned not
|
|
168
|
+
* dated in the future), and — presigned — not expired. Also checks the
|
|
169
|
+
* signed-header list is sorted, lowercase, and covers what must be
|
|
170
|
+
* signed.
|
|
171
|
+
*/
|
|
172
|
+
export function checkSigV4Claim(claim: SigV4Claim, now: Date): SigV4Result<void> {
|
|
173
|
+
const dateMatch = AMZ_DATE.exec(claim.amzDate);
|
|
174
|
+
if (dateMatch === null) {
|
|
175
|
+
return err('AccessDenied', 'X-Amz-Date must be formatted as YYYYMMDDTHHMMSSZ', 403);
|
|
176
|
+
}
|
|
177
|
+
if (dateMatch[1] !== claim.scopeDate) {
|
|
178
|
+
return err('SignatureDoesNotMatch', 'The credential scope date does not match X-Amz-Date', 403);
|
|
179
|
+
}
|
|
180
|
+
const signedAt = parseAmzDate(claim.amzDate);
|
|
181
|
+
const skewSeconds = (now.getTime() - signedAt.getTime()) / 1000;
|
|
182
|
+
if (skewSeconds < -MAX_CLOCK_SKEW_SECONDS) {
|
|
183
|
+
return err(
|
|
184
|
+
'RequestTimeTooSkewed',
|
|
185
|
+
"The difference between the request time and the server's time is too large.",
|
|
186
|
+
403,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (claim.kind === 'header' && skewSeconds > MAX_CLOCK_SKEW_SECONDS) {
|
|
190
|
+
return err(
|
|
191
|
+
'RequestTimeTooSkewed',
|
|
192
|
+
"The difference between the request time and the server's time is too large.",
|
|
193
|
+
403,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
if (claim.kind === 'presigned' && skewSeconds > (claim.expiresSeconds ?? 0)) {
|
|
197
|
+
return err('AccessDenied', 'Request has expired', 403);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const sorted = [...claim.signedHeaders].sort();
|
|
201
|
+
if (
|
|
202
|
+
claim.signedHeaders.some((h, i) => h !== h.toLowerCase() || h !== sorted[i] || h === '') ||
|
|
203
|
+
new Set(claim.signedHeaders).size !== claim.signedHeaders.length
|
|
204
|
+
) {
|
|
205
|
+
return err('InvalidRequest', 'SignedHeaders must be lowercase, sorted and unique', 403);
|
|
206
|
+
}
|
|
207
|
+
const required =
|
|
208
|
+
claim.kind === 'header' ? ['host', 'x-amz-content-sha256', 'x-amz-date'] : ['host'];
|
|
209
|
+
const missing = required.filter((h) => !claim.signedHeaders.includes(h));
|
|
210
|
+
if (missing.length > 0) {
|
|
211
|
+
return err('AccessDenied', `SignedHeaders must include: ${missing.join(', ')}`, 403);
|
|
212
|
+
}
|
|
213
|
+
return { kind: 'ok', value: undefined };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Re-derive the signature from exactly what the client signed and compare
|
|
218
|
+
* it (constant-time) with the claimed one. `payloadHash` is the header
|
|
219
|
+
* `x-amz-content-sha256` for header auth (the caller verifies it against
|
|
220
|
+
* the body); presigned requests always sign `UNSIGNED-PAYLOAD`.
|
|
221
|
+
*/
|
|
222
|
+
export function verifySigV4Signature(
|
|
223
|
+
request: SigV4Request,
|
|
224
|
+
claim: SigV4Claim,
|
|
225
|
+
secretKey: string,
|
|
226
|
+
): SigV4Result<void> {
|
|
227
|
+
const canonical = canonicalRequest(request, claim);
|
|
228
|
+
if (canonical.kind === 'err') return canonical;
|
|
229
|
+
const scope = `${claim.scopeDate}/${claim.region}/${claim.service}/aws4_request`;
|
|
230
|
+
const stringToSign = [ALGORITHM, claim.amzDate, scope, sha256Hex(canonical.value)].join('\n');
|
|
231
|
+
const kDate = hmac(`AWS4${secretKey}`, claim.scopeDate);
|
|
232
|
+
const kRegion = hmac(kDate, claim.region);
|
|
233
|
+
const kService = hmac(kRegion, claim.service);
|
|
234
|
+
const kSigning = hmac(kService, 'aws4_request');
|
|
235
|
+
const expected = createHmac('sha256', kSigning).update(stringToSign, 'utf8').digest('hex');
|
|
236
|
+
if (!constantTimeEqual(expected, claim.signature.toLowerCase())) {
|
|
237
|
+
return err(
|
|
238
|
+
'SignatureDoesNotMatch',
|
|
239
|
+
'The request signature we calculated does not match the signature you provided.',
|
|
240
|
+
403,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
return { kind: 'ok', value: undefined };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** The SigV4 canonical request (exported for tests). */
|
|
247
|
+
export function canonicalRequest(request: SigV4Request, claim: SigV4Claim): SigV4Result<string> {
|
|
248
|
+
const headerLines: string[] = [];
|
|
249
|
+
for (const name of claim.signedHeaders) {
|
|
250
|
+
const value =
|
|
251
|
+
name === 'host'
|
|
252
|
+
? (request.headers.get('host') ?? request.url.host)
|
|
253
|
+
: request.headers.get(name);
|
|
254
|
+
if (value === null) {
|
|
255
|
+
return err(
|
|
256
|
+
'SignatureDoesNotMatch',
|
|
257
|
+
`Signed header "${name}" is missing from the request`,
|
|
258
|
+
403,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
headerLines.push(`${name}:${value.trim().replace(/\s+/g, ' ')}`);
|
|
262
|
+
}
|
|
263
|
+
const payloadHash =
|
|
264
|
+
claim.kind === 'presigned'
|
|
265
|
+
? UNSIGNED_PAYLOAD
|
|
266
|
+
: (request.headers.get('x-amz-content-sha256') ?? UNSIGNED_PAYLOAD);
|
|
267
|
+
return {
|
|
268
|
+
kind: 'ok',
|
|
269
|
+
value: [
|
|
270
|
+
request.method.toUpperCase(),
|
|
271
|
+
canonicalUri(request.url.pathname),
|
|
272
|
+
canonicalQuery(request.url.search, claim.kind === 'presigned'),
|
|
273
|
+
`${headerLines.join('\n')}\n`,
|
|
274
|
+
claim.signedHeaders.join(';'),
|
|
275
|
+
payloadHash,
|
|
276
|
+
].join('\n'),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// -------------------- canonicalization (S3 rules) --------------------
|
|
281
|
+
|
|
282
|
+
/** RFC 3986 encoding: everything but unreserved characters. */
|
|
283
|
+
function encodeRfc3986(value: string): string {
|
|
284
|
+
return encodeURIComponent(value).replace(
|
|
285
|
+
/[!'()*]/g,
|
|
286
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Decode, tolerating stray `%` that is not a valid escape. */
|
|
291
|
+
function safeDecode(value: string): string {
|
|
292
|
+
try {
|
|
293
|
+
return decodeURIComponent(value);
|
|
294
|
+
} catch {
|
|
295
|
+
return value;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* S3 canonical URI: each path segment decoded, then RFC 3986-encoded;
|
|
301
|
+
* segments are not normalized (`.` / `..` / `//` are significant for S3
|
|
302
|
+
* keys) and an encoded slash stays a slash. `+` in the path is read as a
|
|
303
|
+
* space, matching the common S3 signers.
|
|
304
|
+
*/
|
|
305
|
+
function canonicalUri(rawPath: string): string {
|
|
306
|
+
if (rawPath === '' || rawPath === '/') return '/';
|
|
307
|
+
const encoded = rawPath
|
|
308
|
+
.split('/')
|
|
309
|
+
.map((segment) => encodeRfc3986(safeDecode(segment.replace(/\+/g, ' '))))
|
|
310
|
+
.join('/')
|
|
311
|
+
.replace(/%2F/g, '/');
|
|
312
|
+
return encoded.startsWith('/') ? encoded : `/${encoded}`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* S3 canonical query: parameters decoded, re-encoded (RFC 3986), sorted
|
|
317
|
+
* by key; for a repeated key only its first value counts. A presigned
|
|
318
|
+
* URL's `X-Amz-Signature` is not part of what was signed.
|
|
319
|
+
*/
|
|
320
|
+
function canonicalQuery(rawSearch: string, presigned: boolean): string {
|
|
321
|
+
const search = rawSearch.startsWith('?') ? rawSearch.slice(1) : rawSearch;
|
|
322
|
+
if (search === '') return '';
|
|
323
|
+
const first = new Map<string, string>();
|
|
324
|
+
for (const pair of search.split('&')) {
|
|
325
|
+
if (pair === '') continue;
|
|
326
|
+
const eq = pair.indexOf('=');
|
|
327
|
+
const rawKey = eq < 0 ? pair : pair.slice(0, eq);
|
|
328
|
+
const rawValue = eq < 0 ? '' : pair.slice(eq + 1);
|
|
329
|
+
const key = safeDecode(rawKey.replace(/\+/g, ' '));
|
|
330
|
+
if (key === '' || (presigned && key === 'X-Amz-Signature')) continue;
|
|
331
|
+
const encodedKey = encodeRfc3986(key);
|
|
332
|
+
if (!first.has(encodedKey)) {
|
|
333
|
+
first.set(encodedKey, encodeRfc3986(safeDecode(rawValue.replace(/\+/g, ' '))));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return [...first.keys()]
|
|
337
|
+
.sort()
|
|
338
|
+
.map((key) => `${key}=${first.get(key)}`)
|
|
339
|
+
.join('&');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// -------------------- helpers --------------------
|
|
343
|
+
|
|
344
|
+
function parseCredentialScope(
|
|
345
|
+
raw: string,
|
|
346
|
+
): { accessKeyId: string; scopeDate: string; region: string; service: string } | null {
|
|
347
|
+
// <accessKeyId>/<YYYYMMDD>/<region>/<service>/aws4_request
|
|
348
|
+
const parts = raw.split('/');
|
|
349
|
+
if (parts.length !== 5 || parts[4] !== 'aws4_request') return null;
|
|
350
|
+
const [accessKeyId, scopeDate, region, service] = parts as [string, string, string, string];
|
|
351
|
+
if (accessKeyId === '' || !/^\d{8}$/.test(scopeDate) || region === '' || service === '')
|
|
352
|
+
return null;
|
|
353
|
+
return { accessKeyId, scopeDate, region, service };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function parseAmzDate(amzDate: string): Date {
|
|
357
|
+
const iso = `${amzDate.slice(0, 4)}-${amzDate.slice(4, 6)}-${amzDate.slice(6, 8)}T${amzDate.slice(9, 11)}:${amzDate.slice(11, 13)}:${amzDate.slice(13, 15)}Z`;
|
|
358
|
+
return new Date(iso);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function hmac(key: string | Buffer, data: string): Buffer {
|
|
362
|
+
return createHmac('sha256', key).update(data, 'utf8').digest();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function sha256Hex(data: string): string {
|
|
366
|
+
return createHash('sha256').update(data, 'utf8').digest('hex');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function constantTimeEqual(a: string, b: string): boolean {
|
|
370
|
+
const aBuf = Buffer.from(a, 'utf8');
|
|
371
|
+
const bBuf = Buffer.from(b, 'utf8');
|
|
372
|
+
if (aBuf.length !== bBuf.length) return false;
|
|
373
|
+
return timingSafeEqual(aBuf, bBuf);
|
|
374
|
+
}
|