@antasphere/chassis-server 1.0.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 +82 -0
- package/dist/accounts/deletion.d.ts +163 -0
- package/dist/accounts/deletion.d.ts.map +1 -0
- package/dist/accounts/deletion.js +360 -0
- package/dist/accounts/deletion.js.map +1 -0
- package/dist/accounts/erasure-log.d.ts +66 -0
- package/dist/accounts/erasure-log.d.ts.map +1 -0
- package/dist/accounts/erasure-log.js +117 -0
- package/dist/accounts/erasure-log.js.map +1 -0
- package/dist/accounts/index.d.ts +6 -0
- package/dist/accounts/index.d.ts.map +1 -0
- package/dist/accounts/index.js +6 -0
- package/dist/accounts/index.js.map +1 -0
- package/dist/accounts/mint-refusal.d.ts +47 -0
- package/dist/accounts/mint-refusal.d.ts.map +1 -0
- package/dist/accounts/mint-refusal.js +59 -0
- package/dist/accounts/mint-refusal.js.map +1 -0
- package/dist/accounts/signup-duplicate.d.ts +14 -0
- package/dist/accounts/signup-duplicate.d.ts.map +1 -0
- package/dist/accounts/signup-duplicate.js +25 -0
- package/dist/accounts/signup-duplicate.js.map +1 -0
- package/dist/accounts/superadmin.d.ts +27 -0
- package/dist/accounts/superadmin.d.ts.map +1 -0
- package/dist/accounts/superadmin.js +46 -0
- package/dist/accounts/superadmin.js.map +1 -0
- package/dist/api/apikeys.d.ts +14 -0
- package/dist/api/apikeys.d.ts.map +1 -0
- package/dist/api/apikeys.js +124 -0
- package/dist/api/apikeys.js.map +1 -0
- package/dist/api/audit.d.ts +4 -0
- package/dist/api/audit.d.ts.map +1 -0
- package/dist/api/audit.js +75 -0
- package/dist/api/audit.js.map +1 -0
- package/dist/api/break-glass.d.ts +48 -0
- package/dist/api/break-glass.d.ts.map +1 -0
- package/dist/api/break-glass.js +217 -0
- package/dist/api/break-glass.js.map +1 -0
- package/dist/api/cli-auth.d.ts +84 -0
- package/dist/api/cli-auth.d.ts.map +1 -0
- package/dist/api/cli-auth.js +177 -0
- package/dist/api/cli-auth.js.map +1 -0
- package/dist/api/create-api.d.ts +123 -0
- package/dist/api/create-api.d.ts.map +1 -0
- package/dist/api/create-api.js +1165 -0
- package/dist/api/create-api.js.map +1 -0
- package/dist/api/demo-passes.d.ts +45 -0
- package/dist/api/demo-passes.d.ts.map +1 -0
- package/dist/api/demo-passes.js +143 -0
- package/dist/api/demo-passes.js.map +1 -0
- package/dist/api/errors.d.ts +5 -0
- package/dist/api/errors.d.ts.map +1 -0
- package/dist/api/errors.js +5 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/export.d.ts +63 -0
- package/dist/api/export.d.ts.map +1 -0
- package/dist/api/export.js +393 -0
- package/dist/api/export.js.map +1 -0
- package/dist/api/files.d.ts +40 -0
- package/dist/api/files.d.ts.map +1 -0
- package/dist/api/files.js +150 -0
- package/dist/api/files.js.map +1 -0
- package/dist/api/index.d.ts +17 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +17 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/invitations.d.ts +50 -0
- package/dist/api/invitations.d.ts.map +1 -0
- package/dist/api/invitations.js +313 -0
- package/dist/api/invitations.js.map +1 -0
- package/dist/api/members.d.ts +25 -0
- package/dist/api/members.d.ts.map +1 -0
- package/dist/api/members.js +522 -0
- package/dist/api/members.js.map +1 -0
- package/dist/api/onboarding.d.ts +26 -0
- package/dist/api/onboarding.d.ts.map +1 -0
- package/dist/api/onboarding.js +23 -0
- package/dist/api/onboarding.js.map +1 -0
- package/dist/api/openapi-doc.d.ts +21 -0
- package/dist/api/openapi-doc.d.ts.map +1 -0
- package/dist/api/openapi-doc.js +25 -0
- package/dist/api/openapi-doc.js.map +1 -0
- package/dist/api/projects.d.ts +24 -0
- package/dist/api/projects.d.ts.map +1 -0
- package/dist/api/projects.js +624 -0
- package/dist/api/projects.js.map +1 -0
- package/dist/api/scope-routes.d.ts +12 -0
- package/dist/api/scope-routes.d.ts.map +1 -0
- package/dist/api/scope-routes.js +2 -0
- package/dist/api/scope-routes.js.map +1 -0
- package/dist/api/sso-connect.d.ts +70 -0
- package/dist/api/sso-connect.d.ts.map +1 -0
- package/dist/api/sso-connect.js +164 -0
- package/dist/api/sso-connect.js.map +1 -0
- package/dist/api/sso-logout.d.ts +41 -0
- package/dist/api/sso-logout.d.ts.map +1 -0
- package/dist/api/sso-logout.js +34 -0
- package/dist/api/sso-logout.js.map +1 -0
- package/dist/api/teams.d.ts +43 -0
- package/dist/api/teams.d.ts.map +1 -0
- package/dist/api/teams.js +447 -0
- package/dist/api/teams.js.map +1 -0
- package/dist/api/workspaces.d.ts +114 -0
- package/dist/api/workspaces.d.ts.map +1 -0
- package/dist/api/workspaces.js +462 -0
- package/dist/api/workspaces.js.map +1 -0
- package/dist/apikeys/index.d.ts +3 -0
- package/dist/apikeys/index.d.ts.map +1 -0
- package/dist/apikeys/index.js +3 -0
- package/dist/apikeys/index.js.map +1 -0
- package/dist/apikeys/peppers.d.ts +53 -0
- package/dist/apikeys/peppers.d.ts.map +1 -0
- package/dist/apikeys/peppers.js +97 -0
- package/dist/apikeys/peppers.js.map +1 -0
- package/dist/apikeys/service.d.ts +56 -0
- package/dist/apikeys/service.d.ts.map +1 -0
- package/dist/apikeys/service.js +196 -0
- package/dist/apikeys/service.js.map +1 -0
- package/dist/app.d.ts +65 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +142 -0
- package/dist/app.js.map +1 -0
- package/dist/audit/filters.d.ts +38 -0
- package/dist/audit/filters.d.ts.map +1 -0
- package/dist/audit/filters.js +97 -0
- package/dist/audit/filters.js.map +1 -0
- package/dist/audit/index.d.ts +3 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit/index.js +3 -0
- package/dist/audit/index.js.map +1 -0
- package/dist/audit/service.d.ts +65 -0
- package/dist/audit/service.d.ts.map +1 -0
- package/dist/audit/service.js +103 -0
- package/dist/audit/service.js.map +1 -0
- package/dist/boot.d.ts +8 -0
- package/dist/boot.d.ts.map +1 -0
- package/dist/boot.js +822 -0
- package/dist/boot.js.map +1 -0
- package/dist/constant-time.d.ts +3 -0
- package/dist/constant-time.d.ts.map +1 -0
- package/dist/constant-time.js +10 -0
- package/dist/constant-time.js.map +1 -0
- package/dist/email/driver.d.ts +16 -0
- package/dist/email/driver.d.ts.map +1 -0
- package/dist/email/driver.js +136 -0
- package/dist/email/driver.js.map +1 -0
- package/dist/email/index.d.ts +4 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +4 -0
- package/dist/email/index.js.map +1 -0
- package/dist/email/shell.d.ts +72 -0
- package/dist/email/shell.d.ts.map +1 -0
- package/dist/email/shell.js +146 -0
- package/dist/email/shell.js.map +1 -0
- package/dist/email/templates.d.ts +83 -0
- package/dist/email/templates.d.ts.map +1 -0
- package/dist/email/templates.js +96 -0
- package/dist/email/templates.js.map +1 -0
- package/dist/entitlements/check.d.ts +140 -0
- package/dist/entitlements/check.d.ts.map +1 -0
- package/dist/entitlements/check.js +276 -0
- package/dist/entitlements/check.js.map +1 -0
- package/dist/entitlements/clock-skew.d.ts +27 -0
- package/dist/entitlements/clock-skew.d.ts.map +1 -0
- package/dist/entitlements/clock-skew.js +58 -0
- package/dist/entitlements/clock-skew.js.map +1 -0
- package/dist/entitlements/gate.d.ts +113 -0
- package/dist/entitlements/gate.d.ts.map +1 -0
- package/dist/entitlements/gate.js +430 -0
- package/dist/entitlements/gate.js.map +1 -0
- package/dist/entitlements/hub-machine-token.d.ts +83 -0
- package/dist/entitlements/hub-machine-token.d.ts.map +1 -0
- package/dist/entitlements/hub-machine-token.js +175 -0
- package/dist/entitlements/hub-machine-token.js.map +1 -0
- package/dist/entitlements/hub-subject.d.ts +32 -0
- package/dist/entitlements/hub-subject.d.ts.map +1 -0
- package/dist/entitlements/hub-subject.js +68 -0
- package/dist/entitlements/hub-subject.js.map +1 -0
- package/dist/entitlements/index.d.ts +9 -0
- package/dist/entitlements/index.d.ts.map +1 -0
- package/dist/entitlements/index.js +9 -0
- package/dist/entitlements/index.js.map +1 -0
- package/dist/entitlements/poster.d.ts +81 -0
- package/dist/entitlements/poster.d.ts.map +1 -0
- package/dist/entitlements/poster.js +169 -0
- package/dist/entitlements/poster.js.map +1 -0
- package/dist/entitlements/profiles.d.ts +121 -0
- package/dist/entitlements/profiles.d.ts.map +1 -0
- package/dist/entitlements/profiles.js +212 -0
- package/dist/entitlements/profiles.js.map +1 -0
- package/dist/entitlements/slot.d.ts +33 -0
- package/dist/entitlements/slot.d.ts.map +1 -0
- package/dist/entitlements/slot.js +75 -0
- package/dist/entitlements/slot.js.map +1 -0
- package/dist/env.d.ts +293 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +383 -0
- package/dist/env.js.map +1 -0
- package/dist/files/http.d.ts +23 -0
- package/dist/files/http.d.ts.map +1 -0
- package/dist/files/http.js +66 -0
- package/dist/files/http.js.map +1 -0
- package/dist/files/index.d.ts +5 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +5 -0
- package/dist/files/index.js.map +1 -0
- package/dist/files/serve.d.ts +43 -0
- package/dist/files/serve.d.ts.map +1 -0
- package/dist/files/serve.js +71 -0
- package/dist/files/serve.js.map +1 -0
- package/dist/files/service.d.ts +69 -0
- package/dist/files/service.d.ts.map +1 -0
- package/dist/files/service.js +188 -0
- package/dist/files/service.js.map +1 -0
- package/dist/files/spool.d.ts +21 -0
- package/dist/files/spool.d.ts.map +1 -0
- package/dist/files/spool.js +47 -0
- package/dist/files/spool.js.map +1 -0
- package/dist/identity/better-auth.d.ts +3927 -0
- package/dist/identity/better-auth.d.ts.map +1 -0
- package/dist/identity/better-auth.js +896 -0
- package/dist/identity/better-auth.js.map +1 -0
- package/dist/identity/demo-pass-plugin.d.ts +29 -0
- package/dist/identity/demo-pass-plugin.d.ts.map +1 -0
- package/dist/identity/demo-pass-plugin.js +88 -0
- package/dist/identity/demo-pass-plugin.js.map +1 -0
- package/dist/identity/demo-pass-rules.d.ts +84 -0
- package/dist/identity/demo-pass-rules.d.ts.map +1 -0
- package/dist/identity/demo-pass-rules.js +223 -0
- package/dist/identity/demo-pass-rules.js.map +1 -0
- package/dist/identity/demo-pass.d.ts +159 -0
- package/dist/identity/demo-pass.d.ts.map +1 -0
- package/dist/identity/demo-pass.js +319 -0
- package/dist/identity/demo-pass.js.map +1 -0
- package/dist/identity/hub-grant.d.ts +182 -0
- package/dist/identity/hub-grant.d.ts.map +1 -0
- package/dist/identity/hub-grant.js +594 -0
- package/dist/identity/hub-grant.js.map +1 -0
- package/dist/identity/hub-jwt.d.ts +22 -0
- package/dist/identity/hub-jwt.d.ts.map +1 -0
- package/dist/identity/hub-jwt.js +77 -0
- package/dist/identity/hub-jwt.js.map +1 -0
- package/dist/identity/hub-live-gate.d.ts +17 -0
- package/dist/identity/hub-live-gate.d.ts.map +1 -0
- package/dist/identity/hub-live-gate.js +146 -0
- package/dist/identity/hub-live-gate.js.map +1 -0
- package/dist/identity/hub-logout.d.ts +88 -0
- package/dist/identity/hub-logout.d.ts.map +1 -0
- package/dist/identity/hub-logout.js +158 -0
- package/dist/identity/hub-logout.js.map +1 -0
- package/dist/identity/hub-projection.d.ts +103 -0
- package/dist/identity/hub-projection.d.ts.map +1 -0
- package/dist/identity/hub-projection.js +130 -0
- package/dist/identity/hub-projection.js.map +1 -0
- package/dist/identity/hub-reconcile.d.ts +151 -0
- package/dist/identity/hub-reconcile.d.ts.map +1 -0
- package/dist/identity/hub-reconcile.js +400 -0
- package/dist/identity/hub-reconcile.js.map +1 -0
- package/dist/identity/hub-sso.d.ts +363 -0
- package/dist/identity/hub-sso.d.ts.map +1 -0
- package/dist/identity/hub-sso.js +536 -0
- package/dist/identity/hub-sso.js.map +1 -0
- package/dist/identity/hub-user-client.d.ts +222 -0
- package/dist/identity/hub-user-client.d.ts.map +1 -0
- package/dist/identity/hub-user-client.js +406 -0
- package/dist/identity/hub-user-client.js.map +1 -0
- package/dist/identity/index.d.ts +13 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +13 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/oauth-jwt.d.ts +32 -0
- package/dist/identity/oauth-jwt.d.ts.map +1 -0
- package/dist/identity/oauth-jwt.js +122 -0
- package/dist/identity/oauth-jwt.js.map +1 -0
- package/dist/identity/resolve-membership.d.ts +28 -0
- package/dist/identity/resolve-membership.d.ts.map +1 -0
- package/dist/identity/resolve-membership.js +69 -0
- package/dist/identity/resolve-membership.js.map +1 -0
- package/dist/identity/signing-key.d.ts +49 -0
- package/dist/identity/signing-key.d.ts.map +1 -0
- package/dist/identity/signing-key.js +144 -0
- package/dist/identity/signing-key.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/invitations/index.d.ts +2 -0
- package/dist/invitations/index.d.ts.map +1 -0
- package/dist/invitations/index.js +2 -0
- package/dist/invitations/index.js.map +1 -0
- package/dist/invitations/service.d.ts +36 -0
- package/dist/invitations/service.d.ts.map +1 -0
- package/dist/invitations/service.js +122 -0
- package/dist/invitations/service.js.map +1 -0
- package/dist/jobs/index.d.ts +3 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +3 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/jobs/pgboss.d.ts +119 -0
- package/dist/jobs/pgboss.d.ts.map +1 -0
- package/dist/jobs/pgboss.js +521 -0
- package/dist/jobs/pgboss.js.map +1 -0
- package/dist/jobs/timers.d.ts +127 -0
- package/dist/jobs/timers.d.ts.map +1 -0
- package/dist/jobs/timers.js +228 -0
- package/dist/jobs/timers.js.map +1 -0
- package/dist/logger.d.ts +17 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +64 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp/errors.d.ts +37 -0
- package/dist/mcp/errors.d.ts.map +1 -0
- package/dist/mcp/errors.js +77 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/http.d.ts +35 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.js +80 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/index.d.ts +8 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +8 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/inputs.d.ts +19 -0
- package/dist/mcp/inputs.d.ts.map +1 -0
- package/dist/mcp/inputs.js +28 -0
- package/dist/mcp/inputs.js.map +1 -0
- package/dist/mcp/projects.d.ts +31 -0
- package/dist/mcp/projects.d.ts.map +1 -0
- package/dist/mcp/projects.js +315 -0
- package/dist/mcp/projects.js.map +1 -0
- package/dist/mcp/server.d.ts +52 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +93 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/teams.d.ts +7 -0
- package/dist/mcp/teams.d.ts.map +1 -0
- package/dist/mcp/teams.js +62 -0
- package/dist/mcp/teams.js.map +1 -0
- package/dist/mcp/tool-kit.d.ts +67 -0
- package/dist/mcp/tool-kit.d.ts.map +1 -0
- package/dist/mcp/tool-kit.js +74 -0
- package/dist/mcp/tool-kit.js.map +1 -0
- package/dist/members/removal.d.ts +95 -0
- package/dist/members/removal.d.ts.map +1 -0
- package/dist/members/removal.js +143 -0
- package/dist/members/removal.js.map +1 -0
- package/dist/middleware/auth-body.d.ts +25 -0
- package/dist/middleware/auth-body.d.ts.map +1 -0
- package/dist/middleware/auth-body.js +55 -0
- package/dist/middleware/auth-body.js.map +1 -0
- package/dist/middleware/auth-context.d.ts +170 -0
- package/dist/middleware/auth-context.d.ts.map +1 -0
- package/dist/middleware/auth-context.js +251 -0
- package/dist/middleware/auth-context.js.map +1 -0
- package/dist/middleware/body-cap.d.ts +17 -0
- package/dist/middleware/body-cap.d.ts.map +1 -0
- package/dist/middleware/body-cap.js +31 -0
- package/dist/middleware/body-cap.js.map +1 -0
- package/dist/middleware/body-refusal.d.ts +20 -0
- package/dist/middleware/body-refusal.d.ts.map +1 -0
- package/dist/middleware/body-refusal.js +5 -0
- package/dist/middleware/body-refusal.js.map +1 -0
- package/dist/middleware/cross-site.d.ts +61 -0
- package/dist/middleware/cross-site.d.ts.map +1 -0
- package/dist/middleware/cross-site.js +59 -0
- package/dist/middleware/cross-site.js.map +1 -0
- package/dist/middleware/hub-managed.d.ts +45 -0
- package/dist/middleware/hub-managed.d.ts.map +1 -0
- package/dist/middleware/hub-managed.js +56 -0
- package/dist/middleware/hub-managed.js.map +1 -0
- package/dist/middleware/idempotency.d.ts +14 -0
- package/dist/middleware/idempotency.d.ts.map +1 -0
- package/dist/middleware/idempotency.js +222 -0
- package/dist/middleware/idempotency.js.map +1 -0
- package/dist/middleware/index.d.ts +12 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +12 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/json-depth.d.ts +32 -0
- package/dist/middleware/json-depth.d.ts.map +1 -0
- package/dist/middleware/json-depth.js +80 -0
- package/dist/middleware/json-depth.js.map +1 -0
- package/dist/middleware/no-store.d.ts +21 -0
- package/dist/middleware/no-store.d.ts.map +1 -0
- package/dist/middleware/no-store.js +32 -0
- package/dist/middleware/no-store.js.map +1 -0
- package/dist/middleware/oauth-public.d.ts +16 -0
- package/dist/middleware/oauth-public.d.ts.map +1 -0
- package/dist/middleware/oauth-public.js +50 -0
- package/dist/middleware/oauth-public.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +182 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +277 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/middleware/request-id.d.ts +10 -0
- package/dist/middleware/request-id.d.ts.map +1 -0
- package/dist/middleware/request-id.js +42 -0
- package/dist/middleware/request-id.js.map +1 -0
- package/dist/middleware/scopes.d.ts +33 -0
- package/dist/middleware/scopes.d.ts.map +1 -0
- package/dist/middleware/scopes.js +139 -0
- package/dist/middleware/scopes.js.map +1 -0
- package/dist/middleware/security-headers.d.ts +53 -0
- package/dist/middleware/security-headers.d.ts.map +1 -0
- package/dist/middleware/security-headers.js +96 -0
- package/dist/middleware/security-headers.js.map +1 -0
- package/dist/observability/index.d.ts +3 -0
- package/dist/observability/index.d.ts.map +1 -0
- package/dist/observability/index.js +3 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/observability/metrics.d.ts +17 -0
- package/dist/observability/metrics.d.ts.map +1 -0
- package/dist/observability/metrics.js +82 -0
- package/dist/observability/metrics.js.map +1 -0
- package/dist/observability/otel.d.ts +20 -0
- package/dist/observability/otel.d.ts.map +1 -0
- package/dist/observability/otel.js +54 -0
- package/dist/observability/otel.js.map +1 -0
- package/dist/pagination.d.ts +57 -0
- package/dist/pagination.d.ts.map +1 -0
- package/dist/pagination.js +90 -0
- package/dist/pagination.js.map +1 -0
- package/dist/platform/edition.d.ts +57 -0
- package/dist/platform/edition.d.ts.map +1 -0
- package/dist/platform/edition.js +90 -0
- package/dist/platform/edition.js.map +1 -0
- package/dist/platform/entitlements.d.ts +26 -0
- package/dist/platform/entitlements.d.ts.map +1 -0
- package/dist/platform/entitlements.js +28 -0
- package/dist/platform/entitlements.js.map +1 -0
- package/dist/platform/events.d.ts +87 -0
- package/dist/platform/events.d.ts.map +1 -0
- package/dist/platform/events.js +32 -0
- package/dist/platform/events.js.map +1 -0
- package/dist/platform/index.d.ts +8 -0
- package/dist/platform/index.d.ts.map +1 -0
- package/dist/platform/index.js +8 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/local-identity.d.ts +39 -0
- package/dist/platform/local-identity.d.ts.map +1 -0
- package/dist/platform/local-identity.js +87 -0
- package/dist/platform/local-identity.js.map +1 -0
- package/dist/platform/registry.d.ts +23 -0
- package/dist/platform/registry.d.ts.map +1 -0
- package/dist/platform/registry.js +5 -0
- package/dist/platform/registry.js.map +1 -0
- package/dist/platform/usage.d.ts +11 -0
- package/dist/platform/usage.d.ts.map +1 -0
- package/dist/platform/usage.js +12 -0
- package/dist/platform/usage.js.map +1 -0
- package/dist/platform/workspaces.d.ts +25 -0
- package/dist/platform/workspaces.d.ts.map +1 -0
- package/dist/platform/workspaces.js +44 -0
- package/dist/platform/workspaces.js.map +1 -0
- package/dist/projects/access.d.ts +40 -0
- package/dist/projects/access.d.ts.map +1 -0
- package/dist/projects/access.js +133 -0
- package/dist/projects/access.js.map +1 -0
- package/dist/projects/index.d.ts +2 -0
- package/dist/projects/index.d.ts.map +1 -0
- package/dist/projects/index.js +2 -0
- package/dist/projects/index.js.map +1 -0
- package/dist/route-label.d.ts +23 -0
- package/dist/route-label.d.ts.map +1 -0
- package/dist/route-label.js +29 -0
- package/dist/route-label.js.map +1 -0
- package/dist/routes/health.d.ts +30 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +54 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/index.d.ts +3 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +3 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/wellknown.d.ts +11 -0
- package/dist/routes/wellknown.d.ts.map +1 -0
- package/dist/routes/wellknown.js +45 -0
- package/dist/routes/wellknown.js.map +1 -0
- package/dist/secret.d.ts +30 -0
- package/dist/secret.d.ts.map +1 -0
- package/dist/secret.js +97 -0
- package/dist/secret.js.map +1 -0
- package/dist/setup-transport.d.ts +23 -0
- package/dist/setup-transport.d.ts.map +1 -0
- package/dist/setup-transport.js +52 -0
- package/dist/setup-transport.js.map +1 -0
- package/dist/state.d.ts +21 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +4 -0
- package/dist/state.js.map +1 -0
- package/dist/storage/driver.d.ts +38 -0
- package/dist/storage/driver.d.ts.map +1 -0
- package/dist/storage/driver.js +14 -0
- package/dist/storage/driver.js.map +1 -0
- package/dist/storage/factory.d.ts +4 -0
- package/dist/storage/factory.d.ts.map +1 -0
- package/dist/storage/factory.js +20 -0
- package/dist/storage/factory.js.map +1 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +4 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local.d.ts +21 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +88 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/s3.d.ts +29 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +82 -0
- package/dist/storage/s3.js.map +1 -0
- package/dist/testing/fake-hub.d.ts +425 -0
- package/dist/testing/fake-hub.d.ts.map +1 -0
- package/dist/testing/fake-hub.js +1074 -0
- package/dist/testing/fake-hub.js.map +1 -0
- package/dist/testing/helpers.d.ts +124 -0
- package/dist/testing/helpers.d.ts.map +1 -0
- package/dist/testing/helpers.js +174 -0
- package/dist/testing/helpers.js.map +1 -0
- package/dist/testing/index.d.ts +12 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +12 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/setup-origins.d.ts +21 -0
- package/dist/testing/setup-origins.d.ts.map +1 -0
- package/dist/testing/setup-origins.js +57 -0
- package/dist/testing/setup-origins.js.map +1 -0
- package/dist/testing/sso-helpers.d.ts +43 -0
- package/dist/testing/sso-helpers.d.ts.map +1 -0
- package/dist/testing/sso-helpers.js +159 -0
- package/dist/testing/sso-helpers.js.map +1 -0
- package/dist/tool-definition.d.ts +382 -0
- package/dist/tool-definition.d.ts.map +1 -0
- package/dist/tool-definition.js +46 -0
- package/dist/tool-definition.js.map +1 -0
- package/dist/util/index.d.ts +8 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/index.js +8 -0
- package/dist/util/index.js.map +1 -0
- package/package.json +161 -0
- package/src/accounts/deletion.ts +389 -0
- package/src/accounts/erasure-log.ts +140 -0
- package/src/accounts/index.ts +5 -0
- package/src/accounts/mint-refusal.ts +67 -0
- package/src/accounts/signup-duplicate.ts +21 -0
- package/src/accounts/superadmin.ts +46 -0
- package/src/api/apikeys.ts +147 -0
- package/src/api/audit.ts +83 -0
- package/src/api/break-glass.ts +294 -0
- package/src/api/cli-auth.ts +308 -0
- package/src/api/create-api.ts +1403 -0
- package/src/api/demo-passes.ts +207 -0
- package/src/api/errors.ts +13 -0
- package/src/api/export.ts +482 -0
- package/src/api/files.ts +200 -0
- package/src/api/index.ts +29 -0
- package/src/api/invitations.ts +396 -0
- package/src/api/members.ts +617 -0
- package/src/api/onboarding.ts +50 -0
- package/src/api/openapi-doc.ts +34 -0
- package/src/api/projects.ts +788 -0
- package/src/api/scope-routes.ts +12 -0
- package/src/api/sso-connect.ts +246 -0
- package/src/api/sso-logout.ts +76 -0
- package/src/api/teams.ts +575 -0
- package/src/api/workspaces.ts +649 -0
- package/src/apikeys/index.ts +2 -0
- package/src/apikeys/peppers.ts +118 -0
- package/src/apikeys/service.ts +223 -0
- package/src/app.ts +228 -0
- package/src/audit/filters.ts +101 -0
- package/src/audit/index.ts +9 -0
- package/src/audit/service.ts +154 -0
- package/src/boot.ts +953 -0
- package/src/constant-time.ts +9 -0
- package/src/email/driver.ts +139 -0
- package/src/email/index.ts +26 -0
- package/src/email/shell.ts +201 -0
- package/src/email/templates.ts +184 -0
- package/src/entitlements/check.ts +435 -0
- package/src/entitlements/clock-skew.ts +58 -0
- package/src/entitlements/gate.ts +636 -0
- package/src/entitlements/hub-machine-token.ts +205 -0
- package/src/entitlements/hub-subject.ts +77 -0
- package/src/entitlements/index.ts +37 -0
- package/src/entitlements/poster.ts +265 -0
- package/src/entitlements/profiles.ts +303 -0
- package/src/entitlements/slot.ts +105 -0
- package/src/env.ts +456 -0
- package/src/files/http.ts +77 -0
- package/src/files/index.ts +4 -0
- package/src/files/serve.ts +115 -0
- package/src/files/service.ts +209 -0
- package/src/files/spool.ts +58 -0
- package/src/identity/better-auth.ts +1103 -0
- package/src/identity/demo-pass-plugin.ts +121 -0
- package/src/identity/demo-pass-rules.ts +229 -0
- package/src/identity/demo-pass.ts +390 -0
- package/src/identity/hub-grant.ts +704 -0
- package/src/identity/hub-jwt.ts +78 -0
- package/src/identity/hub-live-gate.ts +167 -0
- package/src/identity/hub-logout.ts +174 -0
- package/src/identity/hub-projection.ts +251 -0
- package/src/identity/hub-reconcile.ts +572 -0
- package/src/identity/hub-sso.ts +696 -0
- package/src/identity/hub-user-client.ts +554 -0
- package/src/identity/index.ts +56 -0
- package/src/identity/oauth-jwt.ts +128 -0
- package/src/identity/resolve-membership.ts +104 -0
- package/src/identity/signing-key.ts +164 -0
- package/src/index.ts +65 -0
- package/src/invitations/index.ts +1 -0
- package/src/invitations/service.ts +171 -0
- package/src/jobs/index.ts +25 -0
- package/src/jobs/pgboss.ts +594 -0
- package/src/jobs/timers.ts +287 -0
- package/src/logger.ts +67 -0
- package/src/mcp/errors.ts +111 -0
- package/src/mcp/http.ts +129 -0
- package/src/mcp/index.ts +28 -0
- package/src/mcp/inputs.ts +33 -0
- package/src/mcp/projects.ts +480 -0
- package/src/mcp/server.ts +174 -0
- package/src/mcp/teams.ts +97 -0
- package/src/mcp/tool-kit.ts +148 -0
- package/src/members/removal.ts +216 -0
- package/src/middleware/auth-body.ts +58 -0
- package/src/middleware/auth-context.ts +384 -0
- package/src/middleware/body-cap.ts +33 -0
- package/src/middleware/body-refusal.ts +23 -0
- package/src/middleware/cross-site.ts +122 -0
- package/src/middleware/hub-managed.ts +61 -0
- package/src/middleware/idempotency.ts +264 -0
- package/src/middleware/index.ts +33 -0
- package/src/middleware/json-depth.ts +74 -0
- package/src/middleware/no-store.ts +30 -0
- package/src/middleware/oauth-public.ts +54 -0
- package/src/middleware/rate-limit.ts +432 -0
- package/src/middleware/request-id.ts +54 -0
- package/src/middleware/scopes.ts +150 -0
- package/src/middleware/security-headers.ts +129 -0
- package/src/observability/index.ts +2 -0
- package/src/observability/metrics.ts +102 -0
- package/src/observability/otel.ts +79 -0
- package/src/pagination.ts +118 -0
- package/src/platform/edition.ts +140 -0
- package/src/platform/entitlements.ts +47 -0
- package/src/platform/events.ts +71 -0
- package/src/platform/index.ts +7 -0
- package/src/platform/local-identity.ts +90 -0
- package/src/platform/registry.ts +28 -0
- package/src/platform/usage.ts +13 -0
- package/src/platform/workspaces.ts +41 -0
- package/src/projects/access.ts +160 -0
- package/src/projects/index.ts +7 -0
- package/src/route-label.ts +29 -0
- package/src/routes/health.ts +79 -0
- package/src/routes/index.ts +2 -0
- package/src/routes/wellknown.ts +67 -0
- package/src/secret.ts +115 -0
- package/src/setup-transport.ts +46 -0
- package/src/state.ts +23 -0
- package/src/storage/driver.ts +48 -0
- package/src/storage/factory.ts +34 -0
- package/src/storage/index.ts +3 -0
- package/src/storage/local.ts +93 -0
- package/src/storage/s3.ts +109 -0
- package/src/testing/fake-hub.ts +1316 -0
- package/src/testing/helpers.ts +254 -0
- package/src/testing/index.ts +11 -0
- package/src/testing/setup-origins.ts +58 -0
- package/src/testing/sso-helpers.ts +178 -0
- package/src/tool-definition.ts +463 -0
- package/src/util/index.ts +17 -0
- package/test/fixtures/onboarding-backfill.sql +13 -0
- package/test/fixtures/operator-user-id-backfill.sql +18 -0
- package/test/host/dr-lib.sh +253 -0
- package/test/host/drizzle/0000_chassis_baseline.sql +438 -0
- package/test/host/drizzle/0001_last_owner_guard.sql +42 -0
- package/test/host/drizzle/meta/0000_snapshot.json +3566 -0
- package/test/host/drizzle/meta/_journal.json +20 -0
- package/test/host/drizzle.config.ts +17 -0
- package/test/host/identity.ts +41 -0
- package/test/host/minimal-host.ts +35 -0
- package/test/host/minimal-tool.ts +123 -0
- package/test/integration/account-oracle-uniformity.test.ts +284 -0
- package/test/integration/account-recovery.test.ts +236 -0
- package/test/integration/account.test.ts +120 -0
- package/test/integration/api-quota.test.ts +208 -0
- package/test/integration/apikey-expiry.test.ts +178 -0
- package/test/integration/apikey-pepper-rotation.test.ts +190 -0
- package/test/integration/boot-posture.test.ts +169 -0
- package/test/integration/break-glass.test.ts +484 -0
- package/test/integration/cloud-lifecycle.test.ts +233 -0
- package/test/integration/credential-boundaries.test.ts +195 -0
- package/test/integration/default-workspace.test.ts +414 -0
- package/test/integration/demo-pass-rules.test.ts +332 -0
- package/test/integration/demo-pass.test.ts +1222 -0
- package/test/integration/dr-verify-real-dump.test.ts +154 -0
- package/test/integration/edition.test.ts +868 -0
- package/test/integration/email-change.test.ts +394 -0
- package/test/integration/empty-tool.test.ts +267 -0
- package/test/integration/entitlements-null-hook.test.ts +187 -0
- package/test/integration/entitlements.test.ts +693 -0
- package/test/integration/export-entries.test.ts +321 -0
- package/test/integration/files.test.ts +320 -0
- package/test/integration/first-boot-claim.test.ts +131 -0
- package/test/integration/gdpr.test.ts +532 -0
- package/test/integration/helpers.ts +29 -0
- package/test/integration/hub-grant.test.ts +505 -0
- package/test/integration/hub-live-gate.test.ts +612 -0
- package/test/integration/hub-managed-membership.test.ts +559 -0
- package/test/integration/hub-org-selector.test.ts +281 -0
- package/test/integration/hub-reconcile.test.ts +646 -0
- package/test/integration/hub-sso.test.ts +704 -0
- package/test/integration/hub-teams.test.ts +771 -0
- package/test/integration/idempotency.test.ts +261 -0
- package/test/integration/invite-verification.test.ts +210 -0
- package/test/integration/last-owner-race.test.ts +263 -0
- package/test/integration/m9-fixes.test.ts +290 -0
- package/test/integration/mcp-projects.test.ts +463 -0
- package/test/integration/member-removal-race.test.ts +466 -0
- package/test/integration/member-removal.test.ts +794 -0
- package/test/integration/oauth-mcp.test.ts +477 -0
- package/test/integration/oauth-workspace.test.ts +371 -0
- package/test/integration/observability.test.ts +183 -0
- package/test/integration/onboarding.test.ts +268 -0
- package/test/integration/orphan-purge.test.ts +451 -0
- package/test/integration/pagination.test.ts +344 -0
- package/test/integration/platform-core.test.ts +305 -0
- package/test/integration/platform.test.ts +246 -0
- package/test/integration/pool-survives-restart.test.ts +80 -0
- package/test/integration/projects-cloud.test.ts +193 -0
- package/test/integration/projects.test.ts +1310 -0
- package/test/integration/security-fixes.test.ts +190 -0
- package/test/integration/setup-transport.test.ts +118 -0
- package/test/integration/signin-wall.test.ts +103 -0
- package/test/integration/signing-key-rotation.test.ts +349 -0
- package/test/integration/sso-helpers.ts +15 -0
- package/test/integration/sso-logout.test.ts +283 -0
- package/test/integration/teams-cloud.test.ts +194 -0
- package/test/integration/teams.test.ts +551 -0
- package/test/integration/timers.test.ts +377 -0
- package/test/integration/two-factor.test.ts +301 -0
- package/test/integration/usage-window.test.ts +161 -0
- package/test/integration/workspace-create-cloud.test.ts +523 -0
- package/test/integration/workspace-create.test.ts +510 -0
- package/test/integration/workspace-scoping.test.ts +530 -0
- package/test/integration/zz-adv-replay-owner.test.ts +205 -0
- package/test/unit/apikey-peppers.test.ts +110 -0
- package/test/unit/apikey-prefix.test.ts +34 -0
- package/test/unit/audit-filters.test.ts +135 -0
- package/test/unit/auth-origins.test.ts +92 -0
- package/test/unit/body-cap.test.ts +84 -0
- package/test/unit/clock-skew.test.ts +68 -0
- package/test/unit/cross-site.test.ts +166 -0
- package/test/unit/declared-credits.test.ts +13 -0
- package/test/unit/demo-pass-rules.test.ts +192 -0
- package/test/unit/email-driver.test.ts +127 -0
- package/test/unit/end-pool.test.ts +181 -0
- package/test/unit/entitlement-check.test.ts +492 -0
- package/test/unit/entitlement-gate.test.ts +1333 -0
- package/test/unit/entitlement-profiles.test.ts +289 -0
- package/test/unit/erasure-log.test.ts +72 -0
- package/test/unit/error-mapping.test.ts +89 -0
- package/test/unit/export-entries.test.ts +85 -0
- package/test/unit/export-sanitize.test.ts +27 -0
- package/test/unit/files-head-route.test.ts +155 -0
- package/test/unit/files-http.test.ts +97 -0
- package/test/unit/head-rewrite.test.ts +72 -0
- package/test/unit/hub-create-org.test.ts +181 -0
- package/test/unit/hub-jwt.test.ts +93 -0
- package/test/unit/hub-sso-params.test.ts +39 -0
- package/test/unit/hub-subject.test.ts +83 -0
- package/test/unit/idempotency-crypto.test.ts +45 -0
- package/test/unit/json-depth.test.ts +88 -0
- package/test/unit/logger-redaction.test.ts +66 -0
- package/test/unit/mcp-errors.test.ts +110 -0
- package/test/unit/mcp-inputs.test.ts +106 -0
- package/test/unit/mcp-project-hints.test.ts +26 -0
- package/test/unit/no-store.test.ts +49 -0
- package/test/unit/no-tool-migration-filename.test.ts +70 -0
- package/test/unit/observability.test.ts +64 -0
- package/test/unit/openapi-doc.test.ts +93 -0
- package/test/unit/rate-limit-consume-on.test.ts +76 -0
- package/test/unit/rate-limit.test.ts +39 -0
- package/test/unit/readyz-storage.test.ts +157 -0
- package/test/unit/request-quota.test.ts +146 -0
- package/test/unit/scopes.test.ts +66 -0
- package/test/unit/security-headers.test.ts +131 -0
- package/test/unit/serve-content-type.test.ts +74 -0
- package/test/unit/setup-transport.test.ts +29 -0
- package/test/unit/storage-local.test.ts +53 -0
- package/test/unit/superadmin.test.ts +32 -0
- package/test/unit/timers.test.ts +287 -0
- package/test/unit/tool-definition-guards.test.ts +166 -0
- package/test/unit/tool-entitlements-guard.test.ts +168 -0
- package/test/unit/usage-poster.test.ts +542 -0
- package/test/unit/usage-window.test.ts +48 -0
- package/test/unit/workspace-create-audit.test.ts +48 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import type { Context, MiddlewareHandler } from 'hono';
|
|
2
|
+
import type { RateLimiterAbstract } from 'rate-limiter-flexible';
|
|
3
|
+
import { ACTIVE_WORKSPACE_HEADER, type Principal } from '@antasphere/chassis-contract';
|
|
4
|
+
import type { PlatformRegistry } from '../platform/registry.js';
|
|
5
|
+
import { WorkspaceMismatchError } from '../apikeys/service.js';
|
|
6
|
+
import { apiError } from '../api/errors.js';
|
|
7
|
+
import {
|
|
8
|
+
quotaHeaderEntries,
|
|
9
|
+
type ClientIpFn,
|
|
10
|
+
type QuotaDecision,
|
|
11
|
+
type RequestQuotaService
|
|
12
|
+
} from './rate-limit.js';
|
|
13
|
+
import { looksLikeJwt } from './scopes.js';
|
|
14
|
+
|
|
15
|
+
declare module 'hono' {
|
|
16
|
+
interface ContextVariableMap {
|
|
17
|
+
principal: Principal | null;
|
|
18
|
+
/** The tool's `guest_forbidden` sentence, set by `authContext` for `requireNonGuest()` to answer with. */
|
|
19
|
+
guestForbiddenMessage: string;
|
|
20
|
+
/** Demo sign-in: the pass that opened this request's session, when one did (the audit mark). */
|
|
21
|
+
demoPassId?: string;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Paths under /api/v1 that are reachable without any credential. */
|
|
26
|
+
const PUBLIC_API_PATHS = new Set([
|
|
27
|
+
'/api/v1/instance',
|
|
28
|
+
'/api/v1/setup',
|
|
29
|
+
'/api/v1/openapi.json',
|
|
30
|
+
// CLI email-OTP sign-in (api/cli-auth.ts): pre-auth by definition — the
|
|
31
|
+
// flow exists to OBTAIN a credential. Both are rate-limited in api/index.ts.
|
|
32
|
+
'/api/v1/cli/auth/request',
|
|
33
|
+
'/api/v1/cli/auth/complete',
|
|
34
|
+
// CLI cross-tool connect (api/sso-connect.ts, cloud edition): pre-auth by
|
|
35
|
+
// the same definition — the hub-minted exchange JWT in the body IS the
|
|
36
|
+
// credential, verified hard by the handler. Rate-limited in api/index.ts.
|
|
37
|
+
// The entry is inert on oss: the route is never registered there, so the
|
|
38
|
+
// path falls through to the JSON 404 terminator for every caller.
|
|
39
|
+
'/api/v1/sso/cli-connect'
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
export function isPublicApiPath(path: string): boolean {
|
|
43
|
+
return PUBLIC_API_PATHS.has(path) || path.startsWith('/api/v1/auth/');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* An edition's post-resolution verdict on an otherwise-valid principal.
|
|
48
|
+
* `ok` with a `role` means "the caller's authoritative role just changed —
|
|
49
|
+
* run THIS request under it"; `ok` with a `workspace` means "the person's
|
|
50
|
+
* DEFAULT workspace just moved (the pass this request ran read it off the
|
|
51
|
+
* hub) — run THIS request there", which the gate says only for a request
|
|
52
|
+
* that selected nothing (`request.selector` null) on a credential that pins
|
|
53
|
+
* nothing (PRDCT-2958, F2: the command that runs the pass used to land in
|
|
54
|
+
* the old default, the one after it in the new); a refusal carries the
|
|
55
|
+
* exact wire error. The seam exists for the cloud edition's hub gates (org
|
|
56
|
+
* suspension + hub membership re-assertion, internal/federation.md P4); oss
|
|
57
|
+
* never wires one. The gate also sees WHAT is being asked (`request`), so a
|
|
58
|
+
* policy can exempt specific surfaces (e.g. suspension keeping GET /me
|
|
59
|
+
* readable — the visible-but-blocked posture); implementations may ignore
|
|
60
|
+
* it.
|
|
61
|
+
*/
|
|
62
|
+
export type PrincipalGateResult =
|
|
63
|
+
| { ok: true; role?: Principal['role']; workspace?: GateWorkspace }
|
|
64
|
+
| { ok: false; status: 401 | 403; code: string; message: string };
|
|
65
|
+
|
|
66
|
+
/** The membership a gate re-resolved for the request: the fields of the principal it replaces. */
|
|
67
|
+
export interface GateWorkspace {
|
|
68
|
+
workspaceId: string;
|
|
69
|
+
role: Principal['role'];
|
|
70
|
+
origin: Principal['origin'];
|
|
71
|
+
accountRef: string | null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface PrincipalGateRequest {
|
|
75
|
+
path: string;
|
|
76
|
+
method: string;
|
|
77
|
+
/** The request's own workspace selector (the X-Workspace-Id header), null when it named none. */
|
|
78
|
+
selector: string | null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type PrincipalGate = (
|
|
82
|
+
principal: Principal,
|
|
83
|
+
request: PrincipalGateRequest
|
|
84
|
+
) => Promise<PrincipalGateResult>;
|
|
85
|
+
|
|
86
|
+
/** The gate's verdict applied: the principal to run the request as, or the exact wire refusal. */
|
|
87
|
+
export type AdmitResult =
|
|
88
|
+
{ ok: true; principal: Principal } | { ok: false; status: 401 | 403; code: string; message: string };
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The session half of the credential resolver: the identity provider's live
|
|
92
|
+
* resolve of the request's cookie (membership re-checked, the selection rule
|
|
93
|
+
* applied). One home for the call, shared by `authContext` and the
|
|
94
|
+
* zero-state re-admission of `GET /me`.
|
|
95
|
+
*/
|
|
96
|
+
export function resolveSessionPrincipal(c: Context, registry: PlatformRegistry): Promise<Principal | null> {
|
|
97
|
+
return registry.identity.resolve({
|
|
98
|
+
headers: c.req.raw.headers,
|
|
99
|
+
path: c.req.path,
|
|
100
|
+
method: c.req.method,
|
|
101
|
+
requestId: c.get('requestId')
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The edition gate on a resolved principal, with a freshly synced hub role
|
|
107
|
+
* applied to THIS request (D11): a demoted admin loses admin surfaces now, a
|
|
108
|
+
* promoted member gains them now. No gate (oss) admits the principal as it
|
|
109
|
+
* is. One home for the step, shared by `authContext` and the zero-state
|
|
110
|
+
* re-admission of `GET /me`.
|
|
111
|
+
*/
|
|
112
|
+
export async function admitPrincipal(
|
|
113
|
+
c: Context,
|
|
114
|
+
principal: Principal,
|
|
115
|
+
principalGate: PrincipalGate | undefined
|
|
116
|
+
): Promise<AdmitResult> {
|
|
117
|
+
if (!principalGate) return { ok: true, principal };
|
|
118
|
+
const verdict = await principalGate(principal, {
|
|
119
|
+
path: c.req.path,
|
|
120
|
+
method: c.req.method,
|
|
121
|
+
selector: c.req.header(ACTIVE_WORKSPACE_HEADER)?.trim() || null
|
|
122
|
+
});
|
|
123
|
+
if (!verdict.ok) return verdict;
|
|
124
|
+
let admitted = principal;
|
|
125
|
+
if (verdict.workspace && verdict.workspace.workspaceId !== principal.workspaceId) {
|
|
126
|
+
const { accountRef, ...rest } = verdict.workspace;
|
|
127
|
+
const { accountRef: _previous, ...withoutRef } = admitted;
|
|
128
|
+
admitted = { ...withoutRef, ...rest, ...(accountRef ? { accountRef } : {}) };
|
|
129
|
+
}
|
|
130
|
+
if (verdict.role !== undefined && verdict.role !== admitted.role) {
|
|
131
|
+
admitted = { ...admitted, role: verdict.role };
|
|
132
|
+
}
|
|
133
|
+
return { ok: true, principal: admitted };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface AuthContextDeps {
|
|
137
|
+
registry: PlatformRegistry;
|
|
138
|
+
/**
|
|
139
|
+
* Resolves `Bearer <prefix>_...` API keys under the user-scoped selection
|
|
140
|
+
* rule — `requested` is the request's X-Workspace-Id (or null). May throw
|
|
141
|
+
* WorkspaceMismatchError for a valid PINNED key whose pin differs from the
|
|
142
|
+
* header. Absent = keys rejected.
|
|
143
|
+
*/
|
|
144
|
+
resolveApiKey?: (token: string, requested: string | null) => Promise<Principal | null>;
|
|
145
|
+
/** Resolves OAuth Bearer JWTs (local JWKS verify + live membership selection). */
|
|
146
|
+
resolveOauthJwt?: (token: string, requested: string | null) => Promise<Principal | null>;
|
|
147
|
+
/** Distinguishes an API-key credential from other bearers. */
|
|
148
|
+
isApiKeyToken: (token: string) => boolean;
|
|
149
|
+
/** Failed key verifications consume from this bucket (per IP) — brute-force wall. */
|
|
150
|
+
keyFailureLimiter?: RateLimiterAbstract;
|
|
151
|
+
/** Client identity (socket address unless TRUST_PROXY opts into XFF). */
|
|
152
|
+
clientIp: ClientIpFn;
|
|
153
|
+
/** General per-principal request quota (I3). Absent = no general limit. */
|
|
154
|
+
requestQuota?: RequestQuotaService;
|
|
155
|
+
/**
|
|
156
|
+
* Post-resolution principal veto (cloud edition only). Runs here — in the
|
|
157
|
+
* single credential resolver — rather than inside any one identity path,
|
|
158
|
+
* so sessions, API keys, AND OAuth bearers all pass the same gate
|
|
159
|
+
* (internal/decisions/016). Absent (oss) = zero overhead, zero hub surface.
|
|
160
|
+
*/
|
|
161
|
+
principalGate?: PrincipalGate | undefined;
|
|
162
|
+
/**
|
|
163
|
+
* Demo sign-in (present only while DEMO_SIGN_IN is on, self-hosted): judges
|
|
164
|
+
* a resolved SESSION against the demo pass that opened it, if one did.
|
|
165
|
+
* `ended` = the pass has expired or was revoked and the session is gone:
|
|
166
|
+
* the request goes on signed out. Here, in the single credential resolver,
|
|
167
|
+
* so no route can be reached with a session its pass no longer backs.
|
|
168
|
+
*/
|
|
169
|
+
demoSession?:
|
|
170
|
+
((headers: Headers) => Promise<{ passId: string; sessionId: string } | 'ended' | null>) | undefined;
|
|
171
|
+
/**
|
|
172
|
+
* The composed fail-closed scope allowlist (`createScopeAllowlist` in
|
|
173
|
+
* scopes.ts: the chassis rules, then the tool's). `null` = the endpoint is
|
|
174
|
+
* not open to machine principals. Required: a tool must state its list.
|
|
175
|
+
*/
|
|
176
|
+
requiredScopeFor: (path: string, method: string) => string | null;
|
|
177
|
+
/**
|
|
178
|
+
* The tool's `guest_forbidden` sentence (`copy.guestForbidden`). Carried on
|
|
179
|
+
* the request so `requireNonGuest()` keeps its argument-less call sites, in
|
|
180
|
+
* the chassis and in the tool alike. Required: the chassis has no wording.
|
|
181
|
+
*/
|
|
182
|
+
guestForbiddenMessage: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The single credential resolver: every /api/v1 request passes through here
|
|
187
|
+
* and comes out with c.get('principal') set (or a 401/403 for bad machine
|
|
188
|
+
* credentials). Three paths, one output shape:
|
|
189
|
+
*
|
|
190
|
+
* - session cookie → IdentityProvider.resolve (live membership re-check)
|
|
191
|
+
* - Bearer API key → O(1) key lookup (M2)
|
|
192
|
+
* - Bearer JWT → OAuth access token this instance minted: local JWKS
|
|
193
|
+
* verification + live membership re-check (M6)
|
|
194
|
+
*
|
|
195
|
+
* Machine principals additionally pass the fail-closed scope allowlist:
|
|
196
|
+
* endpoints not consciously listed in scopes.ts are unreachable with a key
|
|
197
|
+
* or token, whatever the underlying user's role is.
|
|
198
|
+
*/
|
|
199
|
+
export function authContext({
|
|
200
|
+
registry,
|
|
201
|
+
resolveApiKey,
|
|
202
|
+
resolveOauthJwt,
|
|
203
|
+
isApiKeyToken,
|
|
204
|
+
keyFailureLimiter,
|
|
205
|
+
clientIp,
|
|
206
|
+
requestQuota,
|
|
207
|
+
principalGate,
|
|
208
|
+
demoSession,
|
|
209
|
+
requiredScopeFor,
|
|
210
|
+
guestForbiddenMessage
|
|
211
|
+
}: AuthContextDeps): MiddlewareHandler {
|
|
212
|
+
return async (c, next) => {
|
|
213
|
+
c.set('principal', null);
|
|
214
|
+
c.set('guestForbiddenMessage', guestForbiddenMessage);
|
|
215
|
+
|
|
216
|
+
if (isPublicApiPath(c.req.path)) {
|
|
217
|
+
return next();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const authHeader = c.req.header('authorization')?.trim() ?? '';
|
|
221
|
+
const bearer = /^Bearer\s+(.+)$/i.exec(authHeader)?.[1]?.trim() ?? null;
|
|
222
|
+
|
|
223
|
+
// The universal per-request workspace selector (user-scoped credential
|
|
224
|
+
// model): EVERY credential kind resolves it through the shared
|
|
225
|
+
// resolveMembership rule — sessions inside the identity provider,
|
|
226
|
+
// machine credentials via the threaded `requested` below. A PINNED API
|
|
227
|
+
// key is the one exception: it always resolves its pin, and a header
|
|
228
|
+
// naming a different workspace is rejected loudly (403 below) instead of
|
|
229
|
+
// being silently served the pinned one.
|
|
230
|
+
const requested = c.req.header(ACTIVE_WORKSPACE_HEADER)?.trim() || null;
|
|
231
|
+
|
|
232
|
+
let principal: Principal | null = null;
|
|
233
|
+
|
|
234
|
+
if (bearer && looksLikeJwt(bearer)) {
|
|
235
|
+
if (!resolveOauthJwt) {
|
|
236
|
+
return apiError(c, 401, 'oauth_not_enabled', 'OAuth bearer tokens are not enabled on this instance');
|
|
237
|
+
}
|
|
238
|
+
principal = await resolveOauthJwt(bearer, requested);
|
|
239
|
+
if (!principal) {
|
|
240
|
+
return apiError(c, 401, 'invalid_token', 'OAuth bearer token is invalid, expired, or revoked');
|
|
241
|
+
}
|
|
242
|
+
} else if (bearer && isApiKeyToken(bearer)) {
|
|
243
|
+
if (!resolveApiKey) {
|
|
244
|
+
return apiError(c, 401, 'invalid_api_key', 'API key not recognized');
|
|
245
|
+
}
|
|
246
|
+
const ip = clientIp(c);
|
|
247
|
+
if (keyFailureLimiter) {
|
|
248
|
+
const bucket = await keyFailureLimiter.get(ip).catch(() => null);
|
|
249
|
+
if (bucket && bucket.remainingPoints <= 0 && bucket.msBeforeNext > 0) {
|
|
250
|
+
return apiError(c, 429, 'rate_limited', 'Too many failed API key attempts');
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
principal = await resolveApiKey(bearer, requested);
|
|
255
|
+
} catch (cause) {
|
|
256
|
+
// A VALID pinned key + a mismatching selector: a client bug or a
|
|
257
|
+
// confused-deputy attempt — loud 403, and no failure-limiter charge
|
|
258
|
+
// (the credential itself verified fine).
|
|
259
|
+
if (cause instanceof WorkspaceMismatchError) {
|
|
260
|
+
return apiError(c, 403, 'workspace_mismatch', 'This credential is pinned to a different workspace');
|
|
261
|
+
}
|
|
262
|
+
throw cause;
|
|
263
|
+
}
|
|
264
|
+
if (!principal) {
|
|
265
|
+
if (keyFailureLimiter) await keyFailureLimiter.consume(ip).catch(() => {});
|
|
266
|
+
return apiError(c, 401, 'invalid_api_key', 'API key not recognized');
|
|
267
|
+
}
|
|
268
|
+
} else if (bearer) {
|
|
269
|
+
return apiError(c, 401, 'unsupported_credential', 'Unrecognized bearer credential format');
|
|
270
|
+
} else {
|
|
271
|
+
principal = await resolveSessionPrincipal(c, registry);
|
|
272
|
+
// Fail-closed: a judge that throws refuses the session for this
|
|
273
|
+
// request rather than letting a dead pass's session through.
|
|
274
|
+
if (principal && demoSession) {
|
|
275
|
+
const verdict = await demoSession(c.req.raw.headers);
|
|
276
|
+
if (verdict === 'ended') principal = null;
|
|
277
|
+
else if (verdict) c.set('demoPassId', verdict.passId);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// General per-principal request quota (I3), consumed BEFORE the scope
|
|
282
|
+
// gate so even fail-closed 403 hammering is bounded — once a credential
|
|
283
|
+
// resolves, the quota is the outermost cost control. The bucket key is
|
|
284
|
+
// derived from the principal (see principalBucketKey), never a header.
|
|
285
|
+
// Sessions are included: the default quota is far above any dashboard's
|
|
286
|
+
// request rate, and a hijacked/scripted session is the same cost vector.
|
|
287
|
+
let quota: QuotaDecision | null = null;
|
|
288
|
+
if (principal && requestQuota) {
|
|
289
|
+
quota = await requestQuota.consume(principal);
|
|
290
|
+
if (quota && !quota.ok) {
|
|
291
|
+
for (const [name, value] of quotaHeaderEntries(quota)) c.header(name, value);
|
|
292
|
+
c.header('Retry-After', String(quota.retryAfterSeconds ?? Math.max(1, quota.resetSeconds)));
|
|
293
|
+
return apiError(c, 429, 'rate_limited', 'API request quota exceeded, slow down');
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Edition principal gate (cloud: hub org-status + membership
|
|
298
|
+
// re-assertion, internal/federation.md P4). AFTER the quota — hammering a
|
|
299
|
+
// suspended org stays rate-bounded — and BEFORE the scope gate, so a
|
|
300
|
+
// definitive hub refusal wins over any per-endpoint outcome. Cache-first
|
|
301
|
+
// inside; the hub is never a hard round-trip in the hot path.
|
|
302
|
+
if (principal && principalGate) {
|
|
303
|
+
const admitted = await admitPrincipal(c, principal, principalGate);
|
|
304
|
+
if (!admitted.ok) {
|
|
305
|
+
return apiError(c, admitted.status, admitted.code, admitted.message);
|
|
306
|
+
}
|
|
307
|
+
principal = admitted.principal;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Fail-closed scope gate for machine principals only.
|
|
311
|
+
if (principal?.scopes) {
|
|
312
|
+
const needed = requiredScopeFor(c.req.path, c.req.method);
|
|
313
|
+
if (!needed) {
|
|
314
|
+
return apiError(c, 403, 'endpoint_not_allowed', 'This endpoint is not available to this credential');
|
|
315
|
+
}
|
|
316
|
+
if (!principal.scopes.has(needed)) {
|
|
317
|
+
return apiError(c, 403, 'insufficient_scope', `This credential was not granted "${needed}"`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
c.set('principal', principal);
|
|
322
|
+
if (!quota) return next();
|
|
323
|
+
|
|
324
|
+
// Success path: stamp the quota headers on whatever response the route
|
|
325
|
+
// produced (set on c.res so streamed/raw Response bodies get them too),
|
|
326
|
+
// letting well-behaved agents self-throttle before hitting 429s.
|
|
327
|
+
await next();
|
|
328
|
+
for (const [name, value] of quotaHeaderEntries(quota)) c.res.headers.set(name, value);
|
|
329
|
+
return;
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Route guard: 401 when unauthenticated. */
|
|
334
|
+
export function requireAuth(): MiddlewareHandler {
|
|
335
|
+
return async (c, next) => {
|
|
336
|
+
if (!c.get('principal')) {
|
|
337
|
+
return apiError(c, 401, 'unauthenticated', 'Authentication required');
|
|
338
|
+
}
|
|
339
|
+
return next();
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Route guard (D2, internal/federation.md P6): an `origin='guest'` membership
|
|
345
|
+
* belongs to an EXTERNAL per-resource collaborator — it exists so the platform
|
|
346
|
+
* can resolve them to a principal at all, not to make them a workspace
|
|
347
|
+
* actor. Guests keep every ADR 013 per-resource surface their grant opens
|
|
348
|
+
* (read, version push, share tokens, annotations on THAT resource) but are
|
|
349
|
+
* refused resource creation and workspace-level surfaces on BOTH editions.
|
|
350
|
+
* Judges the resolved principal, so sessions, API keys, and OAuth bearers
|
|
351
|
+
* all pass the same gate. 403 — the surfaces this guards are flat,
|
|
352
|
+
* documented workspace surfaces, not probeable per-grant resources, so the
|
|
353
|
+
* ADR 013 hide-existence 404 posture does not apply here (same stance as
|
|
354
|
+
* requireRole).
|
|
355
|
+
*/
|
|
356
|
+
export function requireNonGuest(): MiddlewareHandler {
|
|
357
|
+
return async (c, next) => {
|
|
358
|
+
const principal = c.get('principal');
|
|
359
|
+
if (!principal) {
|
|
360
|
+
return apiError(c, 401, 'unauthenticated', 'Authentication required');
|
|
361
|
+
}
|
|
362
|
+
if (principal.origin === 'guest') {
|
|
363
|
+
// A principal only ever comes out of `authContext`, which set the sentence first.
|
|
364
|
+
return apiError(c, 403, 'guest_forbidden', c.get('guestForbiddenMessage'));
|
|
365
|
+
}
|
|
366
|
+
return next();
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const ROLE_RANK = { member: 0, admin: 1, owner: 2 } as const;
|
|
371
|
+
|
|
372
|
+
/** Route guard: 403 below the required role. */
|
|
373
|
+
export function requireRole(min: keyof typeof ROLE_RANK): MiddlewareHandler {
|
|
374
|
+
return async (c, next) => {
|
|
375
|
+
const principal = c.get('principal');
|
|
376
|
+
if (!principal) {
|
|
377
|
+
return apiError(c, 401, 'unauthenticated', 'Authentication required');
|
|
378
|
+
}
|
|
379
|
+
if (ROLE_RANK[principal.role] < ROLE_RANK[min]) {
|
|
380
|
+
return apiError(c, 403, 'forbidden', `Requires ${min} role`);
|
|
381
|
+
}
|
|
382
|
+
return next();
|
|
383
|
+
};
|
|
384
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Context, MiddlewareHandler } from 'hono';
|
|
2
|
+
import { bodyLimit } from 'hono/body-limit';
|
|
3
|
+
import type { BodyCap } from '../tool-definition.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A body-size cap that DEFERS its declared-size refusal to the entitlement
|
|
7
|
+
* gate (PRDCT-2632). Built once per `BodyCap` from hono's `bodyLimit` (a
|
|
8
|
+
* declared Content-Length over the cap is refused before a byte is read; an
|
|
9
|
+
* undeclared body is counted and cut at the cap), with one difference: when
|
|
10
|
+
* the request's matched route carries a gate that judges a plan limit
|
|
11
|
+
* (`isDeferring`), a declared oversize body is not refused HERE — the
|
|
12
|
+
* refusal is parked on the context (`bodyRefusal`) for the gate to fire
|
|
13
|
+
* after the plan check, and the request is rebuilt WITHOUT a body, so
|
|
14
|
+
* nothing downstream can read a byte of it whatever the middleware order.
|
|
15
|
+
* A metered account thus meets 403 `plan_required` with its upgrade link at
|
|
16
|
+
* any size, and the cap's 413 only when the plan allows the size.
|
|
17
|
+
*/
|
|
18
|
+
export function deferrableBodyCap(cap: BodyCap, isDeferring: (c: Context) => boolean): MiddlewareHandler {
|
|
19
|
+
const streamCap = bodyLimit({ maxSize: cap.maxBytes, onError: cap.onError });
|
|
20
|
+
return (c, next) => {
|
|
21
|
+
const declared = Number(c.req.header('content-length') ?? '');
|
|
22
|
+
if (Number.isFinite(declared) && declared > cap.maxBytes && isDeferring(c)) {
|
|
23
|
+
c.set('bodyRefusal', () => cap.onError(c));
|
|
24
|
+
// Rebuilt from the URL, the method and the headers: a `body: null`
|
|
25
|
+
// in a Request init keeps the input's body (the Fetch spec), so the
|
|
26
|
+
// drop must be a request that never had one.
|
|
27
|
+
const raw = c.req.raw;
|
|
28
|
+
c.req.raw = new Request(raw.url, { method: raw.method, headers: raw.headers, signal: raw.signal });
|
|
29
|
+
return next();
|
|
30
|
+
}
|
|
31
|
+
return streamCap(c, next);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Context } from 'hono';
|
|
2
|
+
|
|
3
|
+
declare module 'hono' {
|
|
4
|
+
interface ContextVariableMap {
|
|
5
|
+
/**
|
|
6
|
+
* A body-size refusal the size cap DEFERRED (PRDCT-2632): the request
|
|
7
|
+
* declared a Content-Length over the tool's cap on a route whose gate
|
|
8
|
+
* judges a plan limit, so the gate must see the declared size first (a
|
|
9
|
+
* metered account meets 403 `plan_required` with the upgrade link at any
|
|
10
|
+
* size, never the instance-cap 413 in its place). The cap DROPS the
|
|
11
|
+
* request's body when it defers (`c.req.raw` is rebuilt without one), so
|
|
12
|
+
* nothing downstream can read a byte of it by construction; the gate
|
|
13
|
+
* fires the refusal right after the plan check, or before anything else
|
|
14
|
+
* when no plan applies. Undefined otherwise.
|
|
15
|
+
*/
|
|
16
|
+
bodyRefusal: (() => Response) | undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The pending deferred refusal of this request, if any. */
|
|
21
|
+
export function pendingBodyRefusal(c: Context): (() => Response) | undefined {
|
|
22
|
+
return c.get('bodyRefusal');
|
|
23
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { Context, MiddlewareHandler } from 'hono';
|
|
2
|
+
import { apiError } from '../api/errors.js';
|
|
3
|
+
import { isPublicOauthPath } from './oauth-public.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cross-site request forgery defence for the whole `/api/v1` surface.
|
|
7
|
+
*
|
|
8
|
+
* The session cookie authenticates every business route (`/orgs`, `/members`,
|
|
9
|
+
* `/invitations`, `/me/*`, `/apikeys`, every product route a instantiation
|
|
10
|
+
* adds), and a cookie is attached by the browser on a cross-site POST just as
|
|
11
|
+
* happily as on a same-origin one. Better Auth's own origin check covers only
|
|
12
|
+
* its `/sign-in` paths, so without this middleware a page on any origin could
|
|
13
|
+
* drive a signed-in user's whole API.
|
|
14
|
+
*
|
|
15
|
+
* Two independent signals, both browser-set and unforgeable by page script
|
|
16
|
+
* (both are Forbidden Header Names — `fetch` cannot override them):
|
|
17
|
+
*
|
|
18
|
+
* 1. `Sec-Fetch-Site: cross-site` — a request initiated by a document on a
|
|
19
|
+
* different site. Refused outright.
|
|
20
|
+
* 2. `Origin` — present on every browser request with an unsafe method.
|
|
21
|
+
* Refused unless it is the origin we are being served on, the configured
|
|
22
|
+
* public origin, or an explicitly trusted extra.
|
|
23
|
+
*
|
|
24
|
+
* Deliberately NOT refused:
|
|
25
|
+
*
|
|
26
|
+
* - Safe methods (GET/HEAD/OPTIONS). A cross-site read cannot be seen by the
|
|
27
|
+
* attacker — `/api/v1` sends no `Access-Control-Allow-Origin` except on the
|
|
28
|
+
* OAuth surfaces below — and refusing them would break legitimate
|
|
29
|
+
* cross-origin embeds of authenticated content.
|
|
30
|
+
* - Requests presenting an `Authorization` header. Those are API keys and
|
|
31
|
+
* OAuth bearers: not ambient credentials, so not forgeable cross-site (a
|
|
32
|
+
* page cannot attach the header without a preflight we never approve).
|
|
33
|
+
* Refusing them would break every scripted and browser-based MCP client.
|
|
34
|
+
* - The deliberate wildcard-CORS OAuth endpoints (oauth-public.ts): token,
|
|
35
|
+
* register, introspect, revoke, jwks and discovery are cookie-less by
|
|
36
|
+
* design and MUST answer any origin.
|
|
37
|
+
* - Requests with neither header. Non-browser clients (curl, the CLI, the
|
|
38
|
+
* SDK, server-to-server) send neither, and this middleware must not become
|
|
39
|
+
* a browser-only gate on a self-hosted API. The ambient-credential threat
|
|
40
|
+
* this closes is browser-only, and browsers always send at least one.
|
|
41
|
+
*/
|
|
42
|
+
export interface CrossSiteGuardOptions {
|
|
43
|
+
/** The instance's configured public origin (PUBLIC_BASE_URL). */
|
|
44
|
+
publicBaseUrl: string;
|
|
45
|
+
/**
|
|
46
|
+
* Additional origins allowed to drive the API with cookies — a separately
|
|
47
|
+
* hosted first-party dashboard, say. Empty on the template.
|
|
48
|
+
*/
|
|
49
|
+
extraTrustedOrigins?: readonly string[];
|
|
50
|
+
/**
|
|
51
|
+
* Origins REFUSED even when they equal the origin this request was served
|
|
52
|
+
* on: the viewer origin (VIEWER_BASE_URL, PRDCT-1352). The host gate keeps
|
|
53
|
+
* `/api/v1` (minus the token-authed `/viewer/*` subset) off that hostname
|
|
54
|
+
* altogether, so in the shipped assembly no request here carries it as the
|
|
55
|
+
* serving origin — this is the second lock, so that a gate regression or a
|
|
56
|
+
* proxy routing the viewer hostname at the app never turns "the origin we
|
|
57
|
+
* are served on" into a trust grant for author-controlled script.
|
|
58
|
+
*/
|
|
59
|
+
deniedOrigins?: readonly string[];
|
|
60
|
+
/** Escape hatch for surfaces that must stay open (paths are `/api/v1/...`). */
|
|
61
|
+
isExempt?: (path: string) => boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
65
|
+
|
|
66
|
+
function originOf(value: string): string | null {
|
|
67
|
+
try {
|
|
68
|
+
return new URL(value).origin;
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** The origin this very request was addressed to (works on localhost, previews, any domain). */
|
|
75
|
+
function servingOrigin(c: Context): string | null {
|
|
76
|
+
return originOf(c.req.url);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function crossSiteGuard({
|
|
80
|
+
publicBaseUrl,
|
|
81
|
+
extraTrustedOrigins = [],
|
|
82
|
+
deniedOrigins = [],
|
|
83
|
+
isExempt
|
|
84
|
+
}: CrossSiteGuardOptions): MiddlewareHandler {
|
|
85
|
+
const configured = new Set<string>();
|
|
86
|
+
const publicOrigin = originOf(publicBaseUrl);
|
|
87
|
+
if (publicOrigin) configured.add(publicOrigin);
|
|
88
|
+
for (const extra of extraTrustedOrigins) {
|
|
89
|
+
const origin = originOf(extra);
|
|
90
|
+
if (origin) configured.add(origin);
|
|
91
|
+
}
|
|
92
|
+
const denied = new Set<string>();
|
|
93
|
+
for (const value of deniedOrigins) {
|
|
94
|
+
const origin = originOf(value);
|
|
95
|
+
if (origin) denied.add(origin);
|
|
96
|
+
}
|
|
97
|
+
// A denied origin wins over every trust source, the serving origin included.
|
|
98
|
+
for (const origin of denied) configured.delete(origin);
|
|
99
|
+
|
|
100
|
+
return async (c, next) => {
|
|
101
|
+
if (SAFE_METHODS.has(c.req.method)) return next();
|
|
102
|
+
if (c.req.header('authorization')) return next();
|
|
103
|
+
if (isPublicOauthPath(c.req.path)) return next();
|
|
104
|
+
if (isExempt?.(c.req.path)) return next();
|
|
105
|
+
|
|
106
|
+
const secFetchSite = c.req.header('sec-fetch-site');
|
|
107
|
+
if (secFetchSite === 'cross-site') {
|
|
108
|
+
return apiError(c, 403, 'cross_site_forbidden', 'Cross-site requests are not accepted on this API');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const origin = c.req.header('origin');
|
|
112
|
+
if (origin) {
|
|
113
|
+
const serving = servingOrigin(c);
|
|
114
|
+
const trusted = !denied.has(origin) && (origin === serving || configured.has(origin));
|
|
115
|
+
if (!trusted) {
|
|
116
|
+
return apiError(c, 403, 'cross_site_forbidden', 'Cross-site requests are not accepted on this API');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return next();
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { MiddlewareHandler } from 'hono';
|
|
2
|
+
import { apiError } from '../api/errors.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* P7 (internal/federation.md "Hub-managed membership"): on the cloud edition,
|
|
6
|
+
* membership of a HUB-ORIGIN (projected) workspace is the hub's source of
|
|
7
|
+
* truth — invites, roles, and removals happen at the hub and flow in via
|
|
8
|
+
* SSO login + the P4 re-assertion. A local membership mutation on such a
|
|
9
|
+
* workspace would create divergence the next login/re-assertion fights, so
|
|
10
|
+
* every mutation on the local /members + /invitations surfaces answers this
|
|
11
|
+
* refusal instead. READS stay open: the projected roster is real.
|
|
12
|
+
*
|
|
13
|
+
* Why 403 (not 410): the member rows demonstrably exist (GET /members
|
|
14
|
+
* serves them), so "the resource is gone" would be false — what is denied
|
|
15
|
+
* is the OPERATION's local authority, for every caller, regardless of role
|
|
16
|
+
* or credential. That is RFC 9110 403's "forbidden for reasons unrelated to
|
|
17
|
+
* the credentials", and it keeps the platform's refusal vocabulary in one
|
|
18
|
+
* register (guest_forbidden, endpoint_not_allowed, workspace_mismatch are
|
|
19
|
+
* all 403). The `hub_managed` code + `details.manageUrl` carry the pointer.
|
|
20
|
+
*
|
|
21
|
+
* Fail-closed by construction:
|
|
22
|
+
* - Hub-origin detection keys on `principal.accountRef`, which every
|
|
23
|
+
* credential resolver (session: platform/local-identity.ts; API key:
|
|
24
|
+
* apikeys/service.ts; OAuth bearer: identity/oauth-jwt.ts) populates from
|
|
25
|
+
* a LIVE join on `workspaces.central_account_id` for the request's ONE
|
|
26
|
+
* workspace (ADR 014) — the same signal the live hub gate keys on
|
|
27
|
+
* (identity/hub-live-gate.ts). There is no principal-construction path
|
|
28
|
+
* that skips the join.
|
|
29
|
+
* - The gate is method-keyed, not route-enumerated, and mounted ONCE per
|
|
30
|
+
* subtree wildcard (`/members/*`, `/invitations/*` — in Hono the
|
|
31
|
+
* wildcard also matches the collection root): any FUTURE mutation
|
|
32
|
+
* registered anywhere under those surfaces is refused by default, with
|
|
33
|
+
* no per-path registration to remember.
|
|
34
|
+
* - It is registered only on EDITION=cloud (the `hubConfig` presence
|
|
35
|
+
* switch, like every cloud seam) — oss carries zero behavior change, and
|
|
36
|
+
* on oss `centralAccountId` is never set anyway (double cover).
|
|
37
|
+
*
|
|
38
|
+
* Principal-less requests pass through untouched: the public invitation
|
|
39
|
+
* accept/lookup segments are explicitly SKIPPED where this is mounted (their
|
|
40
|
+
* target workspace is the invitation's, not the caller's — the accept
|
|
41
|
+
* handler runs its own hub-origin check on the invitation's workspace), and
|
|
42
|
+
* every other gated route 401s in its requireAuth/requireRole gate.
|
|
43
|
+
*/
|
|
44
|
+
export function hubManagedMembershipGate(
|
|
45
|
+
manageUrl: string,
|
|
46
|
+
message = 'Membership of this workspace is managed at the Antasphere hub — invite, remove, and change roles there'
|
|
47
|
+
): MiddlewareHandler {
|
|
48
|
+
return async (c, next) => {
|
|
49
|
+
const method = c.req.method;
|
|
50
|
+
if (method === 'GET' || method === 'HEAD' || method === 'OPTIONS') return next();
|
|
51
|
+
const principal = c.get('principal');
|
|
52
|
+
if (principal?.accountRef) {
|
|
53
|
+
return apiError(c, 403, 'hub_managed', message, { manageUrl });
|
|
54
|
+
}
|
|
55
|
+
return next();
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The teams' sentence for the same gate (PRDCT-2813): a projected team is shaped on the account site. */
|
|
60
|
+
export const HUB_MANAGED_TEAMS_MESSAGE =
|
|
61
|
+
'Teams of this workspace are managed at the Antasphere hub — create, rename, and seat people there';
|