@kindgi/api 0.0.0-bootstrap.0 → 0.1.1
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 +397 -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 +268 -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 +20 -0
- package/dist/middleware/error-mapper.d.ts.map +1 -0
- package/dist/middleware/error-mapper.js +34 -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 +5050 -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 +705 -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 +29649 -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 +1286 -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 +295 -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 +40 -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 +5444 -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 +892 -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,806 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (C) 2026 Kindgi Inc.
|
|
3
|
+
|
|
4
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
import { Hono } from 'hono';
|
|
7
|
+
import type { Context } from 'hono';
|
|
8
|
+
|
|
9
|
+
import type { SessionId, TenantId, UserId } from '@kindgi/types';
|
|
10
|
+
|
|
11
|
+
import { statusFor, toWireError } from '../errors.js';
|
|
12
|
+
import type {
|
|
13
|
+
ClaimMappingScopesSpec,
|
|
14
|
+
ClaimMappingSpec,
|
|
15
|
+
ExchangeCodeFn,
|
|
16
|
+
IdentityProviderBinding,
|
|
17
|
+
ProviderConfig,
|
|
18
|
+
RefreshTokenFn,
|
|
19
|
+
} from '../identity-provider-binding.js';
|
|
20
|
+
import { encodeSessionToken } from '../middleware/auth.js';
|
|
21
|
+
import type { Session, SessionStoreBinding } from '../session-store-binding.js';
|
|
22
|
+
import type { OauthStateStore } from '../state-store-binding.js';
|
|
23
|
+
import type { AppEnv } from '../types.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Auth routes. Layer OAuth 2.0 / OIDC on top of the static
|
|
27
|
+
* bearer-token infrastructure. Bearer-token routes remain byte-shape-
|
|
28
|
+
* identical; auth flow is caller-plugged via `IdentityProviderBinding`
|
|
29
|
+
* (provider catalog) + `SessionStoreBinding` (session persistence) +
|
|
30
|
+
* `OauthStateStore` (short-lived CSRF/PKCE cache).
|
|
31
|
+
*
|
|
32
|
+
* Every route runs INSIDE the `/v1/*` auth chain and requires either a
|
|
33
|
+
* bearer or session token, EXCEPT `/v1/auth/callback/:providerId` which
|
|
34
|
+
* accepts the redirect from the provider carrying no framework token
|
|
35
|
+
* (that would be a chicken-and-egg — the caller hasn't obtained a
|
|
36
|
+
* session yet). `createApp` (`app.ts`) mounts the callback outside the
|
|
37
|
+
* auth chain so unauthenticated redirects reach it.
|
|
38
|
+
*/
|
|
39
|
+
export interface AuthRouterOptions {
|
|
40
|
+
readonly sessionStore: SessionStoreBinding;
|
|
41
|
+
readonly identityProvider: IdentityProviderBinding;
|
|
42
|
+
readonly stateStore: OauthStateStore;
|
|
43
|
+
readonly exchangeCode: ExchangeCodeFn;
|
|
44
|
+
readonly refreshToken?: RefreshTokenFn;
|
|
45
|
+
/**
|
|
46
|
+
* Default TTL for the CSRF/PKCE state cache entries. 10 minutes covers
|
|
47
|
+
* a normal browser hop with margin for slow provider consent screens
|
|
48
|
+
* without keeping a lost row around forever.
|
|
49
|
+
*/
|
|
50
|
+
readonly stateTtlMs?: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const DEFAULT_STATE_TTL_MS = 10 * 60 * 1000;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build the auth router. Two Hono routers are returned — one gated
|
|
57
|
+
* (mounted inside the `/v1/*` bearer chain: provider catalog CRUD,
|
|
58
|
+
* login initiation, refresh, logout) and one public-adjacent (mounted
|
|
59
|
+
* outside the bearer chain: callback endpoint the provider redirects
|
|
60
|
+
* to; still tenant-scoped via the state row's `tenantId`).
|
|
61
|
+
*
|
|
62
|
+
* `whoami` lives at `/v1/identity/whoami` and is unconditionally
|
|
63
|
+
* mounted from `identityRouter`.
|
|
64
|
+
*/
|
|
65
|
+
export function authRouters(options: AuthRouterOptions): {
|
|
66
|
+
readonly authed: Hono<AppEnv>;
|
|
67
|
+
readonly callback: Hono<AppEnv>;
|
|
68
|
+
} {
|
|
69
|
+
const { sessionStore, identityProvider, stateStore, exchangeCode } = options;
|
|
70
|
+
const refreshToken = options.refreshToken;
|
|
71
|
+
const stateTtlMs = options.stateTtlMs ?? DEFAULT_STATE_TTL_MS;
|
|
72
|
+
|
|
73
|
+
const authed = new Hono<AppEnv>();
|
|
74
|
+
|
|
75
|
+
// ---------- GET /providers ----------
|
|
76
|
+
authed.get('/providers', async (c) => {
|
|
77
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
78
|
+
const page = await identityProvider.list({ tenantId });
|
|
79
|
+
return c.json({ data: page.data.map(serializeProviderConfig) });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ---------- POST /providers ----------
|
|
83
|
+
authed.post('/providers', async (c) => {
|
|
84
|
+
const requestId = c.get('requestId');
|
|
85
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
86
|
+
|
|
87
|
+
const parsedJson = await parseJsonBody(c);
|
|
88
|
+
if (parsedJson.kind === 'err') {
|
|
89
|
+
c.status(statusFor('bad-input') as never);
|
|
90
|
+
return c.json(toWireError(parsedJson.error, requestId));
|
|
91
|
+
}
|
|
92
|
+
const parsed = parseProviderConfig(parsedJson.value);
|
|
93
|
+
if (parsed.kind === 'err') {
|
|
94
|
+
c.status(statusFor(parsed.error.code) as never);
|
|
95
|
+
return c.json(toWireError(parsed.error, requestId));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const existing = await identityProvider.get({ tenantId, providerId: parsed.value.providerId });
|
|
99
|
+
if (existing !== null) {
|
|
100
|
+
c.status(statusFor('identity-provider-already-registered') as never);
|
|
101
|
+
return c.json(
|
|
102
|
+
toWireError(
|
|
103
|
+
{
|
|
104
|
+
code: 'identity-provider-already-registered',
|
|
105
|
+
message: `Identity provider "${parsed.value.providerId}" is already registered for this tenant`,
|
|
106
|
+
providerId: parsed.value.providerId,
|
|
107
|
+
},
|
|
108
|
+
requestId,
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const outcome = await identityProvider.register({ tenantId, config: parsed.value });
|
|
114
|
+
c.status(201);
|
|
115
|
+
return c.json({ providerId: outcome.providerId });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// ---------- POST /providers/:providerId/unregister ----------
|
|
119
|
+
authed.post('/providers/:providerId/unregister', async (c) => {
|
|
120
|
+
const requestId = c.get('requestId');
|
|
121
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
122
|
+
const providerId = c.req.param('providerId');
|
|
123
|
+
|
|
124
|
+
const outcome = await identityProvider.unregister({ tenantId, providerId });
|
|
125
|
+
if (!outcome.unregistered) {
|
|
126
|
+
c.status(statusFor('identity-provider-not-found') as never);
|
|
127
|
+
return c.json(
|
|
128
|
+
toWireError(
|
|
129
|
+
{
|
|
130
|
+
code: 'identity-provider-not-found',
|
|
131
|
+
message: `No identity provider registered with id "${providerId}"`,
|
|
132
|
+
providerId,
|
|
133
|
+
},
|
|
134
|
+
requestId,
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return c.json({ providerId, unregistered: true });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ---------- POST /login/:providerId ----------
|
|
142
|
+
authed.post('/login/:providerId', async (c) => {
|
|
143
|
+
const requestId = c.get('requestId');
|
|
144
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
145
|
+
const providerId = c.req.param('providerId');
|
|
146
|
+
|
|
147
|
+
const config = await identityProvider.get({ tenantId, providerId });
|
|
148
|
+
if (config === null) {
|
|
149
|
+
c.status(statusFor('identity-provider-not-found') as never);
|
|
150
|
+
return c.json(
|
|
151
|
+
toWireError(
|
|
152
|
+
{
|
|
153
|
+
code: 'identity-provider-not-found',
|
|
154
|
+
message: `No identity provider registered with id "${providerId}"`,
|
|
155
|
+
providerId,
|
|
156
|
+
},
|
|
157
|
+
requestId,
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let redirectUri: string | undefined;
|
|
163
|
+
const parsedBody = await parseOptionalJsonBody(c);
|
|
164
|
+
if (parsedBody.kind === 'err') {
|
|
165
|
+
c.status(statusFor('bad-input') as never);
|
|
166
|
+
return c.json(toWireError(parsedBody.error, requestId));
|
|
167
|
+
}
|
|
168
|
+
if (parsedBody.value !== undefined) {
|
|
169
|
+
const raw = (parsedBody.value as { redirectUri?: unknown }).redirectUri;
|
|
170
|
+
if (raw !== undefined) {
|
|
171
|
+
if (typeof raw !== 'string' || raw.length === 0) {
|
|
172
|
+
c.status(statusFor('bad-input') as never);
|
|
173
|
+
return c.json(
|
|
174
|
+
toWireError(
|
|
175
|
+
{ code: 'bad-input', message: '`redirectUri` must be a non-empty string' },
|
|
176
|
+
requestId,
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
redirectUri = raw;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const state = base64Url(randomBytes(32));
|
|
185
|
+
const codeVerifier = base64Url(randomBytes(64));
|
|
186
|
+
const codeChallenge = base64Url(createHash('sha256').update(codeVerifier).digest());
|
|
187
|
+
const effectiveRedirect =
|
|
188
|
+
redirectUri ??
|
|
189
|
+
(typeof config.metadata?.defaultRedirectUri === 'string'
|
|
190
|
+
? String(config.metadata?.defaultRedirectUri)
|
|
191
|
+
: '');
|
|
192
|
+
if (effectiveRedirect.length === 0) {
|
|
193
|
+
c.status(statusFor('bad-input') as never);
|
|
194
|
+
return c.json(
|
|
195
|
+
toWireError(
|
|
196
|
+
{
|
|
197
|
+
code: 'bad-input',
|
|
198
|
+
message:
|
|
199
|
+
'`redirectUri` must be supplied in the body or via `metadata.defaultRedirectUri` on the provider config',
|
|
200
|
+
},
|
|
201
|
+
requestId,
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// OAuth 2.1 BCP redirect-URI allowlist enforcement. Absent /
|
|
207
|
+
// empty list means pass-through (no allowlist check); populated
|
|
208
|
+
// list requires an exact-string match against the effective
|
|
209
|
+
// redirect URI (either the body-supplied value or the resolved
|
|
210
|
+
// `metadata.defaultRedirectUri`). This is the primary gate — the
|
|
211
|
+
// callback route re-verifies against the same list for
|
|
212
|
+
// belt-and-suspenders defense in case the allowlist tightened
|
|
213
|
+
// between login and callback.
|
|
214
|
+
if (
|
|
215
|
+
config.allowedRedirectUris !== undefined &&
|
|
216
|
+
config.allowedRedirectUris.length > 0 &&
|
|
217
|
+
!config.allowedRedirectUris.includes(effectiveRedirect)
|
|
218
|
+
) {
|
|
219
|
+
c.status(statusFor('redirect-uri-not-allowed') as never);
|
|
220
|
+
return c.json(
|
|
221
|
+
toWireError(
|
|
222
|
+
{
|
|
223
|
+
code: 'redirect-uri-not-allowed',
|
|
224
|
+
message: `redirect_uri "${effectiveRedirect}" is not in the provider's allowedRedirectUris list`,
|
|
225
|
+
providerId,
|
|
226
|
+
},
|
|
227
|
+
requestId,
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
await stateStore.put({
|
|
233
|
+
state,
|
|
234
|
+
tenantId,
|
|
235
|
+
providerId,
|
|
236
|
+
codeVerifier,
|
|
237
|
+
redirectUri: effectiveRedirect,
|
|
238
|
+
expiresAt: Date.now() + stateTtlMs,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const authorizationUrl = buildAuthorizationUrl({
|
|
242
|
+
config,
|
|
243
|
+
state,
|
|
244
|
+
codeChallenge,
|
|
245
|
+
redirectUri: effectiveRedirect,
|
|
246
|
+
});
|
|
247
|
+
return c.json({
|
|
248
|
+
authorizationUrl,
|
|
249
|
+
state,
|
|
250
|
+
codeChallenge,
|
|
251
|
+
codeChallengeMethod: 'S256',
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// ---------- POST /refresh ----------
|
|
256
|
+
authed.post('/refresh', async (c) => {
|
|
257
|
+
const requestId = c.get('requestId');
|
|
258
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
259
|
+
const sessionId = c.get('sessionId') as SessionId | undefined;
|
|
260
|
+
if (sessionId === undefined) {
|
|
261
|
+
c.status(statusFor('auth-not-session-token') as never);
|
|
262
|
+
return c.json(
|
|
263
|
+
toWireError(
|
|
264
|
+
{
|
|
265
|
+
code: 'auth-not-session-token',
|
|
266
|
+
message:
|
|
267
|
+
'Refresh is only valid for session tokens (`kgi_sk_*`); bearer tokens are managed via `/v1/tokens`',
|
|
268
|
+
},
|
|
269
|
+
requestId,
|
|
270
|
+
),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
const current = await sessionStore.get({ tenantId, sessionId });
|
|
274
|
+
if (current === null || current.revokedAt !== undefined) {
|
|
275
|
+
c.status(statusFor('session-not-found') as never);
|
|
276
|
+
return c.json(
|
|
277
|
+
toWireError(
|
|
278
|
+
{ code: 'session-not-found', message: 'Session no longer exists', sessionId },
|
|
279
|
+
requestId,
|
|
280
|
+
),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
let created: { readonly session: Session; readonly rawToken: string };
|
|
285
|
+
if (refreshToken !== undefined && current.refreshToken !== undefined) {
|
|
286
|
+
let rotated: Awaited<ReturnType<RefreshTokenFn>>;
|
|
287
|
+
try {
|
|
288
|
+
rotated = await refreshToken({
|
|
289
|
+
tenantId,
|
|
290
|
+
providerId: current.providerId,
|
|
291
|
+
refreshToken: current.refreshToken,
|
|
292
|
+
});
|
|
293
|
+
} catch (err) {
|
|
294
|
+
c.status(statusFor('oauth-refresh-failed') as never);
|
|
295
|
+
return c.json(
|
|
296
|
+
toWireError(
|
|
297
|
+
{
|
|
298
|
+
code: 'oauth-refresh-failed',
|
|
299
|
+
message: err instanceof Error ? err.message : 'refresh failed',
|
|
300
|
+
},
|
|
301
|
+
requestId,
|
|
302
|
+
),
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
const createdSession = await sessionStore.create({
|
|
306
|
+
tenantId,
|
|
307
|
+
userId: current.userId,
|
|
308
|
+
providerId: current.providerId,
|
|
309
|
+
accessToken: rotated.accessToken,
|
|
310
|
+
...(rotated.refreshToken !== undefined && { refreshToken: rotated.refreshToken }),
|
|
311
|
+
expiresAt: rotated.expiresAt.toISOString() as never,
|
|
312
|
+
scopes: rotated.scopes,
|
|
313
|
+
...(rotated.claims !== undefined && { metadata: rotated.claims }),
|
|
314
|
+
});
|
|
315
|
+
const fresh = await sessionStore.get({ tenantId, sessionId: createdSession.sessionId });
|
|
316
|
+
if (fresh === null) throw new Error('session vanished immediately after create');
|
|
317
|
+
created = { session: fresh, rawToken: encodeSessionToken(createdSession.sessionId) };
|
|
318
|
+
} else {
|
|
319
|
+
// Rotate the framework token only; keep provider tokens as-is.
|
|
320
|
+
const createdSession = await sessionStore.create({
|
|
321
|
+
tenantId,
|
|
322
|
+
userId: current.userId,
|
|
323
|
+
providerId: current.providerId,
|
|
324
|
+
accessToken: current.accessToken,
|
|
325
|
+
...(current.refreshToken !== undefined && { refreshToken: current.refreshToken }),
|
|
326
|
+
expiresAt: current.expiresAt,
|
|
327
|
+
scopes: current.scopes,
|
|
328
|
+
...(current.metadata !== undefined && { metadata: current.metadata }),
|
|
329
|
+
});
|
|
330
|
+
const fresh = await sessionStore.get({ tenantId, sessionId: createdSession.sessionId });
|
|
331
|
+
if (fresh === null) throw new Error('session vanished immediately after create');
|
|
332
|
+
created = { session: fresh, rawToken: encodeSessionToken(createdSession.sessionId) };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// OAuth 2.1 BCP: mark the old session as ROTATED (not just revoked)
|
|
336
|
+
// so the middleware can return `401 refresh-token-invalid` on reuse
|
|
337
|
+
// rather than the generic `401 auth-revoked`. Reuse detection lets
|
|
338
|
+
// compliant clients retry with the fresh token instead of forcing
|
|
339
|
+
// a full re-auth.
|
|
340
|
+
await sessionStore.revoke({ tenantId, sessionId, reason: 'rotate' });
|
|
341
|
+
|
|
342
|
+
return c.json({
|
|
343
|
+
sessionToken: created.rawToken,
|
|
344
|
+
sessionId: created.session.id,
|
|
345
|
+
expiresAt: created.session.expiresAt,
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// ---------- POST /logout ----------
|
|
350
|
+
authed.post('/logout', async (c) => {
|
|
351
|
+
const requestId = c.get('requestId');
|
|
352
|
+
const tenantId = c.get('tenantId') as TenantId;
|
|
353
|
+
const sessionId = c.get('sessionId') as SessionId | undefined;
|
|
354
|
+
if (sessionId === undefined) {
|
|
355
|
+
c.status(statusFor('auth-not-session-token') as never);
|
|
356
|
+
return c.json(
|
|
357
|
+
toWireError(
|
|
358
|
+
{
|
|
359
|
+
code: 'auth-not-session-token',
|
|
360
|
+
message:
|
|
361
|
+
'Logout is only valid for session tokens (`kgi_sk_*`); bearer tokens are managed via `/v1/tokens`',
|
|
362
|
+
},
|
|
363
|
+
requestId,
|
|
364
|
+
),
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
const outcome = await sessionStore.revoke({ tenantId, sessionId });
|
|
368
|
+
return c.json({ sessionId, revoked: outcome.revoked });
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
const callback = new Hono<AppEnv>();
|
|
372
|
+
|
|
373
|
+
// ---------- POST /callback/:providerId (mounted outside the bearer chain) ----------
|
|
374
|
+
callback.post('/:providerId', async (c) => {
|
|
375
|
+
const requestId = c.get('requestId');
|
|
376
|
+
const providerId = c.req.param('providerId');
|
|
377
|
+
|
|
378
|
+
const parsedJson = await parseJsonBody(c);
|
|
379
|
+
if (parsedJson.kind === 'err') {
|
|
380
|
+
c.status(statusFor('bad-input') as never);
|
|
381
|
+
return c.json(toWireError(parsedJson.error, requestId));
|
|
382
|
+
}
|
|
383
|
+
const parsedCallback = parseCallbackBody(parsedJson.value);
|
|
384
|
+
if (parsedCallback.kind === 'err') {
|
|
385
|
+
c.status(statusFor(parsedCallback.error.code) as never);
|
|
386
|
+
return c.json(toWireError(parsedCallback.error, requestId));
|
|
387
|
+
}
|
|
388
|
+
const { code, state } = parsedCallback.value;
|
|
389
|
+
|
|
390
|
+
// `state` alone is uniquely identifying (256 bits of entropy). The
|
|
391
|
+
// store returns the row's `tenantId` so we know which tenant the
|
|
392
|
+
// callback belongs to — cross-checking `providerId` guards against
|
|
393
|
+
// a stolen `state` being replayed against the wrong provider mount.
|
|
394
|
+
const entry = await stateStore.take({ state, providerId });
|
|
395
|
+
if (entry === null) {
|
|
396
|
+
c.status(statusFor('oauth-state-invalid') as never);
|
|
397
|
+
return c.json(
|
|
398
|
+
toWireError(
|
|
399
|
+
{
|
|
400
|
+
code: 'oauth-state-invalid',
|
|
401
|
+
message: '`state` is unknown, expired, or already consumed',
|
|
402
|
+
},
|
|
403
|
+
requestId,
|
|
404
|
+
),
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Belt-and-suspenders redirect-URI check. Login already validated
|
|
409
|
+
// the stored redirect_uri against `allowedRedirectUris` when the
|
|
410
|
+
// row was written, but the allowlist may have tightened between
|
|
411
|
+
// login and callback — if so, refuse the exchange rather than
|
|
412
|
+
// handing the caller a session under a redirect the tenant no
|
|
413
|
+
// longer trusts. Also runs when the provider config went missing
|
|
414
|
+
// (unregister mid-flight) so we don't silently proceed.
|
|
415
|
+
const currentConfig = await identityProvider.get({
|
|
416
|
+
tenantId: entry.tenantId,
|
|
417
|
+
providerId,
|
|
418
|
+
});
|
|
419
|
+
if (
|
|
420
|
+
currentConfig !== null &&
|
|
421
|
+
currentConfig.allowedRedirectUris !== undefined &&
|
|
422
|
+
currentConfig.allowedRedirectUris.length > 0 &&
|
|
423
|
+
!currentConfig.allowedRedirectUris.includes(entry.redirectUri)
|
|
424
|
+
) {
|
|
425
|
+
c.status(statusFor('redirect-uri-mismatch') as never);
|
|
426
|
+
return c.json(
|
|
427
|
+
toWireError(
|
|
428
|
+
{
|
|
429
|
+
code: 'redirect-uri-mismatch',
|
|
430
|
+
message:
|
|
431
|
+
"redirect_uri from the login state row is not in the provider's current allowedRedirectUris list",
|
|
432
|
+
providerId,
|
|
433
|
+
},
|
|
434
|
+
requestId,
|
|
435
|
+
),
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
let outcome: Awaited<ReturnType<ExchangeCodeFn>>;
|
|
440
|
+
try {
|
|
441
|
+
outcome = await exchangeCode({
|
|
442
|
+
tenantId: entry.tenantId,
|
|
443
|
+
providerId,
|
|
444
|
+
code,
|
|
445
|
+
codeVerifier: entry.codeVerifier,
|
|
446
|
+
redirectUri: entry.redirectUri,
|
|
447
|
+
});
|
|
448
|
+
} catch (err) {
|
|
449
|
+
c.status(statusFor('oauth-code-exchange-failed') as never);
|
|
450
|
+
return c.json(
|
|
451
|
+
toWireError(
|
|
452
|
+
{
|
|
453
|
+
code: 'oauth-code-exchange-failed',
|
|
454
|
+
message: err instanceof Error ? err.message : 'code exchange failed',
|
|
455
|
+
},
|
|
456
|
+
requestId,
|
|
457
|
+
),
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const created = await sessionStore.create({
|
|
462
|
+
tenantId: entry.tenantId,
|
|
463
|
+
userId: outcome.userId as unknown as UserId,
|
|
464
|
+
providerId,
|
|
465
|
+
accessToken: outcome.accessToken,
|
|
466
|
+
...(outcome.refreshToken !== undefined && { refreshToken: outcome.refreshToken }),
|
|
467
|
+
expiresAt: outcome.expiresAt.toISOString() as never,
|
|
468
|
+
scopes: outcome.scopes,
|
|
469
|
+
...(outcome.claims !== undefined && { metadata: outcome.claims }),
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
c.status(201);
|
|
473
|
+
return c.json({
|
|
474
|
+
sessionToken: encodeSessionToken(created.sessionId),
|
|
475
|
+
sessionId: created.sessionId,
|
|
476
|
+
expiresAt: created.expiresAt,
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
return { authed, callback };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ---------- helpers ----------
|
|
484
|
+
|
|
485
|
+
function base64Url(buf: Buffer): string {
|
|
486
|
+
return buf.toString('base64url');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function buildAuthorizationUrl(input: {
|
|
490
|
+
readonly config: ProviderConfig;
|
|
491
|
+
readonly state: string;
|
|
492
|
+
readonly codeChallenge: string;
|
|
493
|
+
readonly redirectUri: string;
|
|
494
|
+
}): string {
|
|
495
|
+
const { config, state, codeChallenge, redirectUri } = input;
|
|
496
|
+
const url = new URL(config.authorizationEndpoint);
|
|
497
|
+
url.searchParams.set('response_type', 'code');
|
|
498
|
+
url.searchParams.set('client_id', config.clientId);
|
|
499
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
500
|
+
url.searchParams.set('scope', config.scopes.join(' '));
|
|
501
|
+
url.searchParams.set('state', state);
|
|
502
|
+
url.searchParams.set('code_challenge', codeChallenge);
|
|
503
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
504
|
+
return url.toString();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function serializeProviderConfig(c: ProviderConfig): Record<string, unknown> {
|
|
508
|
+
// Every field except clientSecretRef (which is a REFERENCE, not the
|
|
509
|
+
// secret itself) is safe to return. Clients need enough to render an
|
|
510
|
+
// "sign in with X" button and know what scopes they're granting.
|
|
511
|
+
return {
|
|
512
|
+
providerId: c.providerId,
|
|
513
|
+
kind: c.kind,
|
|
514
|
+
clientId: c.clientId,
|
|
515
|
+
clientSecretRef: c.clientSecretRef,
|
|
516
|
+
authorizationEndpoint: c.authorizationEndpoint,
|
|
517
|
+
tokenEndpoint: c.tokenEndpoint,
|
|
518
|
+
...(c.userinfoEndpoint !== undefined && { userinfoEndpoint: c.userinfoEndpoint }),
|
|
519
|
+
scopes: c.scopes,
|
|
520
|
+
...(c.allowedRedirectUris !== undefined && {
|
|
521
|
+
allowedRedirectUris: c.allowedRedirectUris,
|
|
522
|
+
}),
|
|
523
|
+
...(c.claimMapping !== undefined && { claimMapping: c.claimMapping }),
|
|
524
|
+
...(c.metadata !== undefined && { metadata: c.metadata }),
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
type ParsedOk<T> = { readonly kind: 'ok'; readonly value: T };
|
|
529
|
+
type ParsedErr = {
|
|
530
|
+
readonly kind: 'err';
|
|
531
|
+
readonly error: { readonly code: string; readonly message: string };
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
async function parseJsonBody(c: Context<AppEnv>): Promise<ParsedOk<unknown> | ParsedErr> {
|
|
535
|
+
try {
|
|
536
|
+
const text = await c.req.text();
|
|
537
|
+
if (text.length === 0) {
|
|
538
|
+
return { kind: 'err', error: { code: 'bad-input', message: 'Request body is required' } };
|
|
539
|
+
}
|
|
540
|
+
return { kind: 'ok', value: JSON.parse(text) };
|
|
541
|
+
} catch {
|
|
542
|
+
return {
|
|
543
|
+
kind: 'err',
|
|
544
|
+
error: { code: 'bad-input', message: 'Request body must be valid JSON' },
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
async function parseOptionalJsonBody(
|
|
550
|
+
c: Context<AppEnv>,
|
|
551
|
+
): Promise<ParsedOk<unknown | undefined> | ParsedErr> {
|
|
552
|
+
try {
|
|
553
|
+
const text = await c.req.text();
|
|
554
|
+
if (text.length === 0) return { kind: 'ok', value: undefined };
|
|
555
|
+
return { kind: 'ok', value: JSON.parse(text) };
|
|
556
|
+
} catch {
|
|
557
|
+
return {
|
|
558
|
+
kind: 'err',
|
|
559
|
+
error: { code: 'bad-input', message: 'Request body must be valid JSON' },
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function parseProviderConfig(body: unknown): ParsedOk<ProviderConfig> | ParsedErr {
|
|
565
|
+
if (body === null || typeof body !== 'object' || Array.isArray(body)) {
|
|
566
|
+
return { kind: 'err', error: { code: 'bad-input', message: 'Request body must be an object' } };
|
|
567
|
+
}
|
|
568
|
+
const b = body as Record<string, unknown>;
|
|
569
|
+
const requiredStr = (k: string): string | ParsedErr => {
|
|
570
|
+
const v = b[k];
|
|
571
|
+
if (typeof v !== 'string' || v.length === 0) {
|
|
572
|
+
return {
|
|
573
|
+
kind: 'err',
|
|
574
|
+
error: {
|
|
575
|
+
code: 'invalid-provider-config',
|
|
576
|
+
message: `Field \`${k}\` must be a non-empty string`,
|
|
577
|
+
},
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
return v;
|
|
581
|
+
};
|
|
582
|
+
const providerId = requiredStr('providerId');
|
|
583
|
+
if (typeof providerId !== 'string') return providerId;
|
|
584
|
+
const clientId = requiredStr('clientId');
|
|
585
|
+
if (typeof clientId !== 'string') return clientId;
|
|
586
|
+
const clientSecretRef = requiredStr('clientSecretRef');
|
|
587
|
+
if (typeof clientSecretRef !== 'string') return clientSecretRef;
|
|
588
|
+
const authorizationEndpoint = requiredStr('authorizationEndpoint');
|
|
589
|
+
if (typeof authorizationEndpoint !== 'string') return authorizationEndpoint;
|
|
590
|
+
const tokenEndpoint = requiredStr('tokenEndpoint');
|
|
591
|
+
if (typeof tokenEndpoint !== 'string') return tokenEndpoint;
|
|
592
|
+
|
|
593
|
+
const kindRaw = b.kind;
|
|
594
|
+
if (kindRaw !== 'oauth2' && kindRaw !== 'oidc') {
|
|
595
|
+
return {
|
|
596
|
+
kind: 'err',
|
|
597
|
+
error: {
|
|
598
|
+
code: 'invalid-provider-config',
|
|
599
|
+
message: '`kind` must be `oauth2` or `oidc`',
|
|
600
|
+
},
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const scopesRaw = b.scopes;
|
|
605
|
+
if (!Array.isArray(scopesRaw) || scopesRaw.some((s) => typeof s !== 'string')) {
|
|
606
|
+
return {
|
|
607
|
+
kind: 'err',
|
|
608
|
+
error: {
|
|
609
|
+
code: 'invalid-provider-config',
|
|
610
|
+
message: '`scopes` must be an array of strings',
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
const scopes = scopesRaw as string[];
|
|
615
|
+
|
|
616
|
+
const userinfoEndpointRaw = b.userinfoEndpoint;
|
|
617
|
+
if (userinfoEndpointRaw !== undefined && typeof userinfoEndpointRaw !== 'string') {
|
|
618
|
+
return {
|
|
619
|
+
kind: 'err',
|
|
620
|
+
error: {
|
|
621
|
+
code: 'invalid-provider-config',
|
|
622
|
+
message: '`userinfoEndpoint` must be a string when supplied',
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
const metadataRaw = b.metadata;
|
|
627
|
+
if (
|
|
628
|
+
metadataRaw !== undefined &&
|
|
629
|
+
(metadataRaw === null || typeof metadataRaw !== 'object' || Array.isArray(metadataRaw))
|
|
630
|
+
) {
|
|
631
|
+
return {
|
|
632
|
+
kind: 'err',
|
|
633
|
+
error: {
|
|
634
|
+
code: 'invalid-provider-config',
|
|
635
|
+
message: '`metadata` must be an object when supplied',
|
|
636
|
+
},
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const allowedRedirectUrisRaw = b.allowedRedirectUris;
|
|
641
|
+
let allowedRedirectUris: readonly string[] | undefined;
|
|
642
|
+
if (allowedRedirectUrisRaw !== undefined) {
|
|
643
|
+
if (
|
|
644
|
+
!Array.isArray(allowedRedirectUrisRaw) ||
|
|
645
|
+
allowedRedirectUrisRaw.some((s) => typeof s !== 'string' || s.length === 0)
|
|
646
|
+
) {
|
|
647
|
+
return {
|
|
648
|
+
kind: 'err',
|
|
649
|
+
error: {
|
|
650
|
+
code: 'invalid-provider-config',
|
|
651
|
+
message: '`allowedRedirectUris` must be an array of non-empty strings when supplied',
|
|
652
|
+
},
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
allowedRedirectUris = allowedRedirectUrisRaw as string[];
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const claimMappingRaw = b.claimMapping;
|
|
659
|
+
let claimMapping: ClaimMappingSpec | undefined;
|
|
660
|
+
if (claimMappingRaw !== undefined) {
|
|
661
|
+
const parsed = parseClaimMapping(claimMappingRaw);
|
|
662
|
+
if (parsed.kind === 'err') return parsed;
|
|
663
|
+
claimMapping = parsed.value;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return {
|
|
667
|
+
kind: 'ok',
|
|
668
|
+
value: {
|
|
669
|
+
providerId,
|
|
670
|
+
kind: kindRaw,
|
|
671
|
+
clientId,
|
|
672
|
+
clientSecretRef,
|
|
673
|
+
authorizationEndpoint,
|
|
674
|
+
tokenEndpoint,
|
|
675
|
+
...(typeof userinfoEndpointRaw === 'string' && { userinfoEndpoint: userinfoEndpointRaw }),
|
|
676
|
+
scopes,
|
|
677
|
+
...(allowedRedirectUris !== undefined && { allowedRedirectUris }),
|
|
678
|
+
...(claimMapping !== undefined && { claimMapping }),
|
|
679
|
+
...(metadataRaw !== undefined && { metadata: metadataRaw as Record<string, unknown> }),
|
|
680
|
+
},
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function parseClaimMapping(raw: unknown): ParsedOk<ClaimMappingSpec> | ParsedErr {
|
|
685
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
686
|
+
return {
|
|
687
|
+
kind: 'err',
|
|
688
|
+
error: {
|
|
689
|
+
code: 'invalid-provider-config',
|
|
690
|
+
message: '`claimMapping` must be an object when supplied',
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
const m = raw as Record<string, unknown>;
|
|
695
|
+
const optionalString = (
|
|
696
|
+
key: keyof ClaimMappingSpec,
|
|
697
|
+
value: unknown,
|
|
698
|
+
): string | ParsedErr | undefined => {
|
|
699
|
+
if (value === undefined) return undefined;
|
|
700
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
701
|
+
return {
|
|
702
|
+
kind: 'err',
|
|
703
|
+
error: {
|
|
704
|
+
code: 'invalid-provider-config',
|
|
705
|
+
message: `\`claimMapping.${String(key)}\` must be a non-empty string when supplied`,
|
|
706
|
+
},
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
return value;
|
|
710
|
+
};
|
|
711
|
+
const userId = optionalString('userId', m.userId);
|
|
712
|
+
if (typeof userId !== 'string' && userId !== undefined) return userId;
|
|
713
|
+
const email = optionalString('email', m.email);
|
|
714
|
+
if (typeof email !== 'string' && email !== undefined) return email;
|
|
715
|
+
const displayName = optionalString('displayName', m.displayName);
|
|
716
|
+
if (typeof displayName !== 'string' && displayName !== undefined) return displayName;
|
|
717
|
+
|
|
718
|
+
let scopes: ClaimMappingScopesSpec | undefined;
|
|
719
|
+
if (m.scopes !== undefined) {
|
|
720
|
+
const s = m.scopes;
|
|
721
|
+
if (s === null || typeof s !== 'object' || Array.isArray(s)) {
|
|
722
|
+
return {
|
|
723
|
+
kind: 'err',
|
|
724
|
+
error: {
|
|
725
|
+
code: 'invalid-provider-config',
|
|
726
|
+
message: '`claimMapping.scopes` must be an object when supplied',
|
|
727
|
+
},
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
const so = s as Record<string, unknown>;
|
|
731
|
+
if (typeof so.claim !== 'string' || so.claim.length === 0) {
|
|
732
|
+
return {
|
|
733
|
+
kind: 'err',
|
|
734
|
+
error: {
|
|
735
|
+
code: 'invalid-provider-config',
|
|
736
|
+
message: '`claimMapping.scopes.claim` must be a non-empty string',
|
|
737
|
+
},
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
if (so.delimiter !== undefined && typeof so.delimiter !== 'string') {
|
|
741
|
+
return {
|
|
742
|
+
kind: 'err',
|
|
743
|
+
error: {
|
|
744
|
+
code: 'invalid-provider-config',
|
|
745
|
+
message: '`claimMapping.scopes.delimiter` must be a string when supplied',
|
|
746
|
+
},
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
scopes = {
|
|
750
|
+
claim: so.claim,
|
|
751
|
+
...(typeof so.delimiter === 'string' && { delimiter: so.delimiter }),
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
let metadata: readonly string[] | undefined;
|
|
756
|
+
if (m.metadata !== undefined) {
|
|
757
|
+
if (
|
|
758
|
+
!Array.isArray(m.metadata) ||
|
|
759
|
+
m.metadata.some((s) => typeof s !== 'string' || s.length === 0)
|
|
760
|
+
) {
|
|
761
|
+
return {
|
|
762
|
+
kind: 'err',
|
|
763
|
+
error: {
|
|
764
|
+
code: 'invalid-provider-config',
|
|
765
|
+
message: '`claimMapping.metadata` must be an array of non-empty strings when supplied',
|
|
766
|
+
},
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
metadata = m.metadata as string[];
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
return {
|
|
773
|
+
kind: 'ok',
|
|
774
|
+
value: {
|
|
775
|
+
...(userId !== undefined && { userId }),
|
|
776
|
+
...(email !== undefined && { email }),
|
|
777
|
+
...(displayName !== undefined && { displayName }),
|
|
778
|
+
...(scopes !== undefined && { scopes }),
|
|
779
|
+
...(metadata !== undefined && { metadata }),
|
|
780
|
+
},
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function parseCallbackBody(
|
|
785
|
+
body: unknown,
|
|
786
|
+
): ParsedOk<{ readonly code: string; readonly state: string }> | ParsedErr {
|
|
787
|
+
if (body === null || typeof body !== 'object' || Array.isArray(body)) {
|
|
788
|
+
return { kind: 'err', error: { code: 'bad-input', message: 'Request body must be an object' } };
|
|
789
|
+
}
|
|
790
|
+
const b = body as Record<string, unknown>;
|
|
791
|
+
const code = b.code;
|
|
792
|
+
if (typeof code !== 'string' || code.length === 0) {
|
|
793
|
+
return {
|
|
794
|
+
kind: 'err',
|
|
795
|
+
error: { code: 'bad-input', message: '`code` must be a non-empty string' },
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
const state = b.state;
|
|
799
|
+
if (typeof state !== 'string' || state.length === 0) {
|
|
800
|
+
return {
|
|
801
|
+
kind: 'err',
|
|
802
|
+
error: { code: 'bad-input', message: '`state` must be a non-empty string' },
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
return { kind: 'ok', value: { code, state } };
|
|
806
|
+
}
|