@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,349 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
|
3
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
4
|
+
import { createDb as createChassisDb, schema } from '@antasphere/chassis-db';
|
|
5
|
+
import { preflightSigningKey, retireSigningKey, runSigningKeyCli } from '@antasphere/chassis-server/identity';
|
|
6
|
+
import {
|
|
7
|
+
createDatabase,
|
|
8
|
+
createTestApp,
|
|
9
|
+
extractCookie,
|
|
10
|
+
readJson,
|
|
11
|
+
startPostgres,
|
|
12
|
+
type TestApp,
|
|
13
|
+
host
|
|
14
|
+
} from './helpers.js';
|
|
15
|
+
|
|
16
|
+
// The signing-key tooling reads the chassis `jwks` table only: the chassis schema is enough.
|
|
17
|
+
const createDb = (url: string) => createChassisDb(url, schema);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Signing-key durability across AUTH_SECRET rotation (ADR 023). The failure
|
|
21
|
+
* this pins: the jwt plugin's RS256 private key is stored encrypted under
|
|
22
|
+
* AUTH_SECRET with no expiry, so the plugin never replaces it — after the
|
|
23
|
+
* runbook's old rotation procedure the instance BOOTED but answered 500 on
|
|
24
|
+
* every POST /oauth2/token, forever.
|
|
25
|
+
*
|
|
26
|
+
* The contract now: boot on a database whose newest signing key does not
|
|
27
|
+
* decrypt under the current secret must REFUSE (fail-fast preflight with the
|
|
28
|
+
* actionable diagnostic) — never serve an authorize it cannot follow with a
|
|
29
|
+
* token mint. The remedy is the rotate-signing-key operator command: a new
|
|
30
|
+
* key minted under the new secret signs from then on, while the retired
|
|
31
|
+
* key's PUBLIC half stays published on /jwks, so tokens signed before the
|
|
32
|
+
* rotation still verify (the publish-overlap window) until the old key is
|
|
33
|
+
* explicitly retired.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const OWNER = { email: 'owner@skr.test', name: 'Skr Owner', password: 'skr-owner-password-123' };
|
|
37
|
+
const SECRET_A = 'signing-key-secret-A-0123456789abcdef0123456789abcdef';
|
|
38
|
+
const SECRET_B = 'signing-key-secret-B-0123456789abcdef0123456789abcdef';
|
|
39
|
+
// Loopback redirect target; never listened on — we only parse the redirect.
|
|
40
|
+
const REDIRECT_URI = 'http://127.0.0.1:39240/callback';
|
|
41
|
+
/** The instance's own audience — tokens with this aud authorize /api/v1 requests. */
|
|
42
|
+
const RESOURCE = 'http://localhost:3000/mcp';
|
|
43
|
+
|
|
44
|
+
let container: StartedPostgreSqlContainer;
|
|
45
|
+
let dbUrl: string;
|
|
46
|
+
let app: TestApp;
|
|
47
|
+
/** DCR public client, registered once in boot A; the row persists across boots. */
|
|
48
|
+
let clientId: string;
|
|
49
|
+
|
|
50
|
+
function b64url(buf: Buffer): string {
|
|
51
|
+
return buf.toString('base64url');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Follow one better-auth redirect answer: a browser-shaped request gets a
|
|
56
|
+
* 302 Location; a fetch-shaped one gets `{ redirect: true, url }` as JSON.
|
|
57
|
+
* app.request() sends no sec-fetch-mode, but pin neither shape — handle both.
|
|
58
|
+
*/
|
|
59
|
+
async function redirectTarget(res: Response): Promise<string> {
|
|
60
|
+
if (res.status === 302) return res.headers.get('location') ?? '';
|
|
61
|
+
expect(res.status).toBe(200);
|
|
62
|
+
const body = await readJson(res);
|
|
63
|
+
expect(body.redirect).toBe(true);
|
|
64
|
+
return body.url as string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Full PKCE dance for the instance's own audience; returns the token
|
|
69
|
+
* response. First pass walks the consent page; later passes may silently
|
|
70
|
+
* re-authorize on the stored consent — both paths end in a code.
|
|
71
|
+
*/
|
|
72
|
+
async function danceForToken(instance: TestApp, cookie: string): Promise<Record<string, string>> {
|
|
73
|
+
const verifier = b64url(randomBytes(48));
|
|
74
|
+
const challenge = b64url(createHash('sha256').update(verifier).digest());
|
|
75
|
+
const params = new URLSearchParams({
|
|
76
|
+
response_type: 'code',
|
|
77
|
+
client_id: clientId,
|
|
78
|
+
redirect_uri: REDIRECT_URI,
|
|
79
|
+
scope: `openid profile email offline_access ${host.scopes.read}`,
|
|
80
|
+
code_challenge: challenge,
|
|
81
|
+
code_challenge_method: 'S256',
|
|
82
|
+
resource: RESOURCE
|
|
83
|
+
});
|
|
84
|
+
const authz = await instance.app.request(`/api/v1/auth/oauth2/authorize?${params}`, {
|
|
85
|
+
headers: { cookie },
|
|
86
|
+
redirect: 'manual'
|
|
87
|
+
});
|
|
88
|
+
let location = await redirectTarget(authz);
|
|
89
|
+
|
|
90
|
+
if (location.includes('/oauth/consent?')) {
|
|
91
|
+
// The consent page posts the SIGNED query back verbatim as oauth_query.
|
|
92
|
+
const signedQuery = location.split('?')[1] ?? '';
|
|
93
|
+
const consent = await instance.app.request('/api/v1/auth/oauth2/consent', {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: { cookie, 'content-type': 'application/json' },
|
|
96
|
+
body: JSON.stringify({ accept: true, oauth_query: signedQuery })
|
|
97
|
+
});
|
|
98
|
+
if (consent.status === 302) {
|
|
99
|
+
location = consent.headers.get('location') ?? '';
|
|
100
|
+
} else {
|
|
101
|
+
expect(consent.status).toBe(200);
|
|
102
|
+
const body = await readJson(consent);
|
|
103
|
+
location = (body.redirect_uri ?? body.url ?? '') as string;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const code = new URL(location).searchParams.get('code');
|
|
107
|
+
expect(code).toBeTruthy();
|
|
108
|
+
|
|
109
|
+
const token = await instance.app.request('/api/v1/auth/oauth2/token', {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
112
|
+
body: new URLSearchParams({
|
|
113
|
+
grant_type: 'authorization_code',
|
|
114
|
+
code: code!,
|
|
115
|
+
redirect_uri: REDIRECT_URI,
|
|
116
|
+
client_id: clientId,
|
|
117
|
+
code_verifier: verifier,
|
|
118
|
+
resource: RESOURCE
|
|
119
|
+
}).toString()
|
|
120
|
+
});
|
|
121
|
+
return { status: String(token.status), ...((await readJson(token).catch(() => ({}))) as object) };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function signIn(instance: TestApp): Promise<string> {
|
|
125
|
+
const res = await instance.app.request('/api/v1/auth/sign-in/email', {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
headers: { 'content-type': 'application/json' },
|
|
128
|
+
body: JSON.stringify({ email: OWNER.email, password: OWNER.password })
|
|
129
|
+
});
|
|
130
|
+
expect(res.status).toBe(200);
|
|
131
|
+
return extractCookie(res);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function jwksKids(): Promise<string[]> {
|
|
135
|
+
const client = createDb(dbUrl);
|
|
136
|
+
try {
|
|
137
|
+
const { rows } = await client.pool.query(`SELECT id FROM jwks ORDER BY created_at ASC, id ASC`);
|
|
138
|
+
return rows.map((r: { id: string }) => r.id);
|
|
139
|
+
} finally {
|
|
140
|
+
await client.pool.end();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
beforeAll(async () => {
|
|
145
|
+
container = await startPostgres();
|
|
146
|
+
dbUrl = await createDatabase(container, 'signing_key');
|
|
147
|
+
}, 240_000);
|
|
148
|
+
|
|
149
|
+
afterAll(async () => {
|
|
150
|
+
await app?.stop();
|
|
151
|
+
await container?.stop();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('AUTH_SECRET rotation vs OAuth token signing (ADR 023)', () => {
|
|
155
|
+
let oldToken = '';
|
|
156
|
+
/**
|
|
157
|
+
* Every kid minted under SECRET_A. Usually one; the pinned plugin's FIRST
|
|
158
|
+
* issuance can race the id_token and access-token signs into TWO createJwk
|
|
159
|
+
* calls (both under the same secret — harmless, newest wins), so the test
|
|
160
|
+
* carries the list rather than assuming a count.
|
|
161
|
+
*/
|
|
162
|
+
let bootAKids: string[] = [];
|
|
163
|
+
|
|
164
|
+
it('boot A signs tokens; the access JWT authorizes API requests', async () => {
|
|
165
|
+
app = await createTestApp(dbUrl, { AUTH_SECRET: SECRET_A });
|
|
166
|
+
const setup = await app.app.request('/api/v1/setup', {
|
|
167
|
+
method: 'POST',
|
|
168
|
+
headers: { 'content-type': 'application/json' },
|
|
169
|
+
body: JSON.stringify({
|
|
170
|
+
setupToken: 'integration-test-setup-token',
|
|
171
|
+
instanceName: 'SkrInstance',
|
|
172
|
+
owner: OWNER
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
expect(setup.status).toBe(201);
|
|
176
|
+
|
|
177
|
+
const register = await app.app.request('/api/v1/auth/oauth2/register', {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: { 'content-type': 'application/json' },
|
|
180
|
+
body: JSON.stringify({
|
|
181
|
+
client_name: 'skr-client',
|
|
182
|
+
redirect_uris: [REDIRECT_URI],
|
|
183
|
+
token_endpoint_auth_method: 'none',
|
|
184
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
185
|
+
response_types: ['code']
|
|
186
|
+
})
|
|
187
|
+
});
|
|
188
|
+
expect([200, 201]).toContain(register.status);
|
|
189
|
+
clientId = (await readJson(register)).client_id;
|
|
190
|
+
expect(clientId).toBeTruthy();
|
|
191
|
+
|
|
192
|
+
const grant = await danceForToken(app, await signIn(app));
|
|
193
|
+
expect(grant.status).toBe('200');
|
|
194
|
+
oldToken = grant.access_token!;
|
|
195
|
+
expect(oldToken.split('.')).toHaveLength(3); // an RS256 JWT, not opaque
|
|
196
|
+
|
|
197
|
+
const me = await app.app.request('/api/v1/me', {
|
|
198
|
+
headers: { authorization: `Bearer ${oldToken}` }
|
|
199
|
+
});
|
|
200
|
+
expect(me.status).toBe(200);
|
|
201
|
+
|
|
202
|
+
bootAKids = await jwksKids();
|
|
203
|
+
expect(bootAKids.length).toBeGreaterThanOrEqual(1);
|
|
204
|
+
await app.stop();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('booting the SAME database under a rotated AUTH_SECRET fails FAST with the diagnostic — never a booted 500', async () => {
|
|
208
|
+
// Exactly the old runbook's steps: pin the version-1 pepper, change the
|
|
209
|
+
// secret, redeploy. Pre-fix this booted fine and then 500'd on every
|
|
210
|
+
// POST /oauth2/token; the contract now is a refused boot. Deliberately
|
|
211
|
+
// NOT expect(...).rejects: on a wrongly-successful boot that shape would
|
|
212
|
+
// serialize the entire BootResult into the assertion error (and leak the
|
|
213
|
+
// running app) — collect the outcome, stop any leak, assert the message.
|
|
214
|
+
let failure: unknown = null;
|
|
215
|
+
let booted: TestApp | null = null;
|
|
216
|
+
try {
|
|
217
|
+
booted = await createTestApp(dbUrl, {
|
|
218
|
+
AUTH_SECRET: SECRET_B,
|
|
219
|
+
API_KEY_PEPPERS: `1:${SECRET_A}`
|
|
220
|
+
});
|
|
221
|
+
} catch (cause) {
|
|
222
|
+
failure = cause;
|
|
223
|
+
}
|
|
224
|
+
if (booted) await booted.stop();
|
|
225
|
+
expect(booted === null, 'the mis-rotated boot must be REFUSED').toBe(true);
|
|
226
|
+
expect(String(failure)).toMatch(/signing-key preflight failed/);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('the preflight module reports precisely: wrong secret fails, right secret passes', async () => {
|
|
230
|
+
const client = createDb(dbUrl);
|
|
231
|
+
try {
|
|
232
|
+
const bad = await preflightSigningKey(client.db, SECRET_B);
|
|
233
|
+
expect(bad.ok).toBe(false);
|
|
234
|
+
if (!bad.ok) {
|
|
235
|
+
expect(bootAKids).toContain(bad.kid);
|
|
236
|
+
expect(bad.reason).toContain('cannot be decrypted with the current AUTH_SECRET');
|
|
237
|
+
expect(bad.reason).toContain('rotate-signing-key');
|
|
238
|
+
}
|
|
239
|
+
const good = await preflightSigningKey(client.db, SECRET_A);
|
|
240
|
+
expect(good.ok).toBe(true);
|
|
241
|
+
} finally {
|
|
242
|
+
await client.pool.end();
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('the remedy: rotate-signing-key under the NEW secret, then boot mints again and OLD tokens still verify (overlap)', async () => {
|
|
247
|
+
// The operator command the diagnostic names, run exactly as the runbook
|
|
248
|
+
// says: against the same database, holding the NEW secret.
|
|
249
|
+
const client = createDb(dbUrl);
|
|
250
|
+
const lines: string[] = [];
|
|
251
|
+
try {
|
|
252
|
+
const code = await runSigningKeyCli(client.db, SECRET_B, [], (l) => lines.push(l));
|
|
253
|
+
expect(code).toBe(0);
|
|
254
|
+
expect(lines.join('\n')).toContain('overlap');
|
|
255
|
+
} finally {
|
|
256
|
+
await client.pool.end();
|
|
257
|
+
}
|
|
258
|
+
const kids = await jwksKids();
|
|
259
|
+
expect(kids).toHaveLength(bootAKids.length + 1); // retired + active — the publish overlap
|
|
260
|
+
|
|
261
|
+
app = await createTestApp(dbUrl, {
|
|
262
|
+
AUTH_SECRET: SECRET_B,
|
|
263
|
+
API_KEY_PEPPERS: `1:${SECRET_A}`
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Token issuance works again — the durable half of the contract.
|
|
267
|
+
const grant = await danceForToken(app, await signIn(app));
|
|
268
|
+
expect(grant.status).toBe('200');
|
|
269
|
+
expect(grant.access_token).toBeTruthy();
|
|
270
|
+
|
|
271
|
+
// Both keys are published…
|
|
272
|
+
const jwksRes = await app.app.request('/api/v1/auth/jwks');
|
|
273
|
+
expect(jwksRes.status).toBe(200);
|
|
274
|
+
const jwksBody = await readJson(jwksRes);
|
|
275
|
+
expect(jwksBody.keys.map((k: { kid: string }) => k.kid).sort()).toEqual([...kids].sort());
|
|
276
|
+
|
|
277
|
+
// …so the token signed BEFORE the rotation still authorizes requests.
|
|
278
|
+
const me = await app.app.request('/api/v1/me', {
|
|
279
|
+
headers: { authorization: `Bearer ${oldToken}` }
|
|
280
|
+
});
|
|
281
|
+
expect(me.status).toBe(200);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('a LIVE rotation is picked up mid-cache: tokens signed by a just-minted key verify immediately', async () => {
|
|
285
|
+
// Warm the verifier's JWKS cache with the current key set…
|
|
286
|
+
const cookie = await signIn(app);
|
|
287
|
+
const warm = await danceForToken(app, cookie);
|
|
288
|
+
expect(warm.status).toBe('200');
|
|
289
|
+
const warmed = await app.app.request('/api/v1/me', {
|
|
290
|
+
headers: { authorization: `Bearer ${warm.access_token}` }
|
|
291
|
+
});
|
|
292
|
+
expect(warmed.status).toBe(200);
|
|
293
|
+
|
|
294
|
+
// …mint a NEW signing key while the process runs (the compromise-drill
|
|
295
|
+
// path — no restart)…
|
|
296
|
+
const client = createDb(dbUrl);
|
|
297
|
+
try {
|
|
298
|
+
expect(await runSigningKeyCli(client.db, SECRET_B, [], () => {})).toBe(0);
|
|
299
|
+
} finally {
|
|
300
|
+
await client.pool.end();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// …and a token the plugin now signs with the fresh key must authorize
|
|
304
|
+
// IMMEDIATELY: the verifier refetches once on the unknown kid instead of
|
|
305
|
+
// answering 401 until its 10-minute cache expires.
|
|
306
|
+
const fresh = await danceForToken(app, cookie);
|
|
307
|
+
expect(fresh.status).toBe('200');
|
|
308
|
+
const res = await app.app.request('/api/v1/me', {
|
|
309
|
+
headers: { authorization: `Bearer ${fresh.access_token}` }
|
|
310
|
+
});
|
|
311
|
+
expect(res.status).toBe(200);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('retiring the old key removes it from /jwks and its tokens stop verifying; the ACTIVE key is refused', async () => {
|
|
315
|
+
const client = createDb(dbUrl);
|
|
316
|
+
try {
|
|
317
|
+
const kids = await jwksKids(); // createdAt ASC — the last one signs
|
|
318
|
+
const activeKid = kids.at(-1)!;
|
|
319
|
+
await expect(retireSigningKey(client.db, activeKid)).rejects.toThrow(/ACTIVE signing key/);
|
|
320
|
+
for (const kid of kids.filter((k) => k !== activeKid)) {
|
|
321
|
+
const code = await runSigningKeyCli(client.db, SECRET_B, ['--retire', kid], () => {});
|
|
322
|
+
expect(code).toBe(0);
|
|
323
|
+
}
|
|
324
|
+
} finally {
|
|
325
|
+
await client.pool.end();
|
|
326
|
+
}
|
|
327
|
+
expect(await jwksKids()).toHaveLength(1);
|
|
328
|
+
|
|
329
|
+
const jwksRes = await app.app.request('/api/v1/auth/jwks');
|
|
330
|
+
const jwksBody = await readJson(jwksRes);
|
|
331
|
+
for (const kid of bootAKids) {
|
|
332
|
+
expect(jwksBody.keys.map((k: { kid: string }) => k.kid)).not.toContain(kid);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// End of the overlap window: once verifier caches roll over (modelled
|
|
336
|
+
// here as a fresh boot — the in-process cache holds 10 min), the retired
|
|
337
|
+
// key's tokens are refused. Same-process acceptance inside the cache TTL
|
|
338
|
+
// is the documented cache behavior, not a regression.
|
|
339
|
+
await app.stop();
|
|
340
|
+
app = await createTestApp(dbUrl, {
|
|
341
|
+
AUTH_SECRET: SECRET_B,
|
|
342
|
+
API_KEY_PEPPERS: `1:${SECRET_A}`
|
|
343
|
+
});
|
|
344
|
+
const me = await app.app.request('/api/v1/me', {
|
|
345
|
+
headers: { authorization: `Bearer ${oldToken}` }
|
|
346
|
+
});
|
|
347
|
+
expect(me.status).toBe(401);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { makeOauthBearer } from '@antasphere/chassis-server/testing';
|
|
2
|
+
import { host } from '@chassis-test/host';
|
|
3
|
+
|
|
4
|
+
/** The shared SSO dances, with the OAuth dance requesting the HOST's read scope. */
|
|
5
|
+
export {
|
|
6
|
+
json,
|
|
7
|
+
nextIp,
|
|
8
|
+
seedLocalWorkspace,
|
|
9
|
+
ssoInitiate,
|
|
10
|
+
ssoDance,
|
|
11
|
+
ssoLogin,
|
|
12
|
+
expectFailedLogin
|
|
13
|
+
} from '@antasphere/chassis-server/testing';
|
|
14
|
+
|
|
15
|
+
export const oauthBearer = makeOauthBearer(`openid ${host.scopes.read}`);
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
|
3
|
+
import { decodeJwt } from 'jose';
|
|
4
|
+
import {
|
|
5
|
+
createDatabase,
|
|
6
|
+
createTestApp,
|
|
7
|
+
extractCookie,
|
|
8
|
+
readJson,
|
|
9
|
+
startPostgres,
|
|
10
|
+
type TestApp,
|
|
11
|
+
host
|
|
12
|
+
} from './helpers.js';
|
|
13
|
+
import { FakeHub, type HubUserFixture } from '@antasphere/chassis-server/testing';
|
|
14
|
+
import * as sso from './sso-helpers.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Single logout, the server leg (SL-2) + the SL-1 silent-connect authorize
|
|
18
|
+
* seam, against the FakeHub (discovery now advertises end_session_endpoint;
|
|
19
|
+
* id_tokens optionally carry `sid`):
|
|
20
|
+
*
|
|
21
|
+
* - POST /sso/logout resolves the session ROUTE-LOCALLY (zero-membership
|
|
22
|
+
* sessions included), builds the hub end-session URL (sid required,
|
|
23
|
+
* discovery-cached endpoint, exact post_logout_redirect_uri), revokes
|
|
24
|
+
* the local session server-side, clears the shared hint cookie, and
|
|
25
|
+
* answers {url} — url null on ANY hub-leg failure, with the local
|
|
26
|
+
* revoke + hint clear untouched;
|
|
27
|
+
* - machines 403 fail-closed (unlisted in the scope allowlist); anonymous
|
|
28
|
+
* 401; oss = JSON 404 (zero hub surface);
|
|
29
|
+
* - the sign-in leg forwards ONLY the whitelisted prompt=none into the hub
|
|
30
|
+
* authorize URL.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const OWNER = { email: 'owner@logout.test', name: 'Log Out', password: 'log-out-password-123' };
|
|
34
|
+
const ORG_A = '22222222-aaaa-4bbb-8ccc-000000000001';
|
|
35
|
+
|
|
36
|
+
const json = (body: unknown) => ({
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'content-type': 'application/json' },
|
|
39
|
+
body: JSON.stringify(body)
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let container: StartedPostgreSqlContainer;
|
|
43
|
+
let hub: FakeHub;
|
|
44
|
+
|
|
45
|
+
beforeAll(async () => {
|
|
46
|
+
[container, hub] = await Promise.all([startPostgres(), FakeHub.start({ clientId: host.hubClientId })]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
afterAll(async () => {
|
|
50
|
+
await Promise.all([container?.stop(), hub?.stop()]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
function cloudEnv(issuer = hub.issuer) {
|
|
54
|
+
return {
|
|
55
|
+
EDITION: 'cloud',
|
|
56
|
+
HUB_ISSUER_URL: issuer,
|
|
57
|
+
HUB_CLIENT_ID: host.hubClientId,
|
|
58
|
+
HUB_CLIENT_SECRET: 'integration-test-hub-secret-0001',
|
|
59
|
+
// Pin the hint domain explicitly: the FakeHub issuer is an IP, so the
|
|
60
|
+
// derived default is meaningless here (the derivation rule itself is
|
|
61
|
+
// unit-pinned in env.test.ts).
|
|
62
|
+
HUB_HINT_COOKIE_DOMAIN: 'tool.test'
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const HINT_CLEAR = 'ant_sso_hint=; Max-Age=0; Domain=tool.test; Path=/; SameSite=Lax';
|
|
67
|
+
|
|
68
|
+
/** The set-cookie list of a response (Node 20 Headers). */
|
|
69
|
+
const setCookies = (res: Response) => res.headers.getSetCookie();
|
|
70
|
+
|
|
71
|
+
describe('cloud: POST /sso/logout + the prompt=none authorize seam', () => {
|
|
72
|
+
let app: TestApp;
|
|
73
|
+
|
|
74
|
+
beforeAll(async () => {
|
|
75
|
+
app = await createTestApp(await createDatabase(container, 'sso_logout'), cloudEnv());
|
|
76
|
+
const res = await app.app.request(
|
|
77
|
+
'/api/v1/setup',
|
|
78
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'Logout', owner: OWNER })
|
|
79
|
+
);
|
|
80
|
+
expect(res.status).toBe(201);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
afterAll(async () => {
|
|
84
|
+
await app.stop();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const alice: HubUserFixture = {
|
|
88
|
+
sub: 'hub-alice',
|
|
89
|
+
email: 'alice@logout.test',
|
|
90
|
+
name: 'Alice',
|
|
91
|
+
workspaceId: ORG_A,
|
|
92
|
+
role: 'member',
|
|
93
|
+
workspaceName: 'Org A',
|
|
94
|
+
overrides: { idTokenSid: 'hub-session-alice-1' }
|
|
95
|
+
};
|
|
96
|
+
const bob: HubUserFixture = {
|
|
97
|
+
sub: 'hub-bob',
|
|
98
|
+
email: 'bob@logout.test',
|
|
99
|
+
name: 'Bob',
|
|
100
|
+
workspaceId: ORG_A,
|
|
101
|
+
role: 'member',
|
|
102
|
+
workspaceName: 'Org A'
|
|
103
|
+
// no idTokenSid: the pre-flip id_token shape
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
it('forwards ONLY the whitelisted prompt=none into the hub authorize URL (SL-1)', async () => {
|
|
107
|
+
const init = (body: Record<string, unknown>) => ({
|
|
108
|
+
...json({ providerId: 'antasphere', callbackURL: '/', ...body }),
|
|
109
|
+
headers: { 'content-type': 'application/json', 'x-forwarded-for': sso.nextIp() }
|
|
110
|
+
});
|
|
111
|
+
// The silent-connect shape: prompt=none passes through.
|
|
112
|
+
const silent = await app.app.request(
|
|
113
|
+
'/api/v1/auth/sign-in/oauth2',
|
|
114
|
+
init({ additionalData: { prompt: 'none' } })
|
|
115
|
+
);
|
|
116
|
+
expect(silent.status).toBe(200);
|
|
117
|
+
const silentUrl = new URL((await readJson(silent)).url);
|
|
118
|
+
expect(silentUrl.searchParams.get('prompt')).toBe('none');
|
|
119
|
+
|
|
120
|
+
// A plain interactive sign-in carries no prompt at all.
|
|
121
|
+
const plain = await app.app.request('/api/v1/auth/sign-in/oauth2', init({}));
|
|
122
|
+
expect(plain.status).toBe(200);
|
|
123
|
+
const plainUrl = new URL((await readJson(plain)).url);
|
|
124
|
+
expect(plainUrl.searchParams.has('prompt')).toBe(false);
|
|
125
|
+
|
|
126
|
+
// Anything else in additionalData is dropped — no prompt smuggling, no
|
|
127
|
+
// extra params.
|
|
128
|
+
const hostile = await app.app.request(
|
|
129
|
+
'/api/v1/auth/sign-in/oauth2',
|
|
130
|
+
init({ additionalData: { prompt: 'consent', login_hint: 'x@y.z' } })
|
|
131
|
+
);
|
|
132
|
+
expect(hostile.status).toBe(200);
|
|
133
|
+
const hostileUrl = new URL((await readJson(hostile)).url);
|
|
134
|
+
expect(hostileUrl.searchParams.has('prompt')).toBe(false);
|
|
135
|
+
expect(hostileUrl.searchParams.has('login_hint')).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('sid-carrying login → logout answers the exact hub end-session URL, revokes, clears the hint', async () => {
|
|
139
|
+
const cookie = await sso.ssoLogin(app, hub, alice);
|
|
140
|
+
const res = await app.app.request('/api/v1/sso/logout', { method: 'POST', headers: { cookie } });
|
|
141
|
+
expect(res.status).toBe(200);
|
|
142
|
+
const { url } = await readJson(res);
|
|
143
|
+
expect(typeof url).toBe('string');
|
|
144
|
+
const endSession = new URL(url);
|
|
145
|
+
expect(`${endSession.origin}${endSession.pathname}`).toBe(`${hub.issuer}/api/v1/auth/oauth2/end-session`);
|
|
146
|
+
// The exact contract redirect — must match the hub registry entry.
|
|
147
|
+
expect(endSession.searchParams.get('post_logout_redirect_uri')).toBe(
|
|
148
|
+
'http://localhost:3000/login?signed_out=1'
|
|
149
|
+
);
|
|
150
|
+
// The hint is the stored id_token, sid intact.
|
|
151
|
+
const hint = endSession.searchParams.get('id_token_hint')!;
|
|
152
|
+
expect(decodeJwt(hint).sid).toBe('hub-session-alice-1');
|
|
153
|
+
|
|
154
|
+
// Local revoke happened server-side (session cookie cleared + row dead)…
|
|
155
|
+
const cookies = setCookies(res);
|
|
156
|
+
expect(cookies.some((v) => v.includes('better-auth.session_token=;') || /session_token=;/.test(v))).toBe(
|
|
157
|
+
true
|
|
158
|
+
);
|
|
159
|
+
// …and the shared hint cookie is cleared with the EXACT contract
|
|
160
|
+
// attributes (Domain on the parent, Path=/, Lax, NOT HttpOnly; no
|
|
161
|
+
// Secure on this http test origin).
|
|
162
|
+
const hintClear = cookies.find((v) => v.startsWith('ant_sso_hint='));
|
|
163
|
+
expect(hintClear).toBe(HINT_CLEAR);
|
|
164
|
+
expect(hintClear).not.toContain('HttpOnly');
|
|
165
|
+
const me = await app.app.request('/api/v1/me', { headers: { cookie } });
|
|
166
|
+
expect(me.status).toBe(401);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('sid-less (pre-flip) login → url null, local revoke + hint clear still happen', async () => {
|
|
170
|
+
const cookie = await sso.ssoLogin(app, hub, bob);
|
|
171
|
+
const res = await app.app.request('/api/v1/sso/logout', { method: 'POST', headers: { cookie } });
|
|
172
|
+
expect(res.status).toBe(200);
|
|
173
|
+
expect((await readJson(res)).url).toBeNull();
|
|
174
|
+
expect(setCookies(res).find((v) => v.startsWith('ant_sso_hint='))).toBe(HINT_CLEAR);
|
|
175
|
+
const me = await app.app.request('/api/v1/me', { headers: { cookie } });
|
|
176
|
+
expect(me.status).toBe(401);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('a ZERO-MEMBERSHIP operator session (no antasphere account row) logs out: url null, revoked', async () => {
|
|
180
|
+
// Cloud setup mints no workspace: the operator session resolves to a
|
|
181
|
+
// null principal upstream — the route-local session check must still
|
|
182
|
+
// let them log out (the /me zero-state pattern).
|
|
183
|
+
const signIn = await app.app.request(
|
|
184
|
+
'/api/v1/auth/sign-in/email',
|
|
185
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
186
|
+
);
|
|
187
|
+
expect(signIn.status).toBe(200);
|
|
188
|
+
const cookie = extractCookie(signIn);
|
|
189
|
+
const zeroState = await app.app.request('/api/v1/me', { headers: { cookie } });
|
|
190
|
+
expect(zeroState.status).toBe(200);
|
|
191
|
+
expect((await readJson(zeroState)).workspaces).toEqual([]);
|
|
192
|
+
|
|
193
|
+
const res = await app.app.request('/api/v1/sso/logout', { method: 'POST', headers: { cookie } });
|
|
194
|
+
expect(res.status).toBe(200);
|
|
195
|
+
expect((await readJson(res)).url).toBeNull();
|
|
196
|
+
expect(setCookies(res).find((v) => v.startsWith('ant_sso_hint='))).toBe(HINT_CLEAR);
|
|
197
|
+
const me = await app.app.request('/api/v1/me', { headers: { cookie } });
|
|
198
|
+
expect(me.status).toBe(401);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('machines 403 fail-closed (unlisted in the scope allowlist); anonymous 401', async () => {
|
|
202
|
+
// A machine credential must never be able to end its user's sessions.
|
|
203
|
+
await sso.seedLocalWorkspace(app, 'LogoutWs', OWNER.email);
|
|
204
|
+
const signIn = await app.app.request(
|
|
205
|
+
'/api/v1/auth/sign-in/email',
|
|
206
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
207
|
+
);
|
|
208
|
+
const cookie = extractCookie(signIn);
|
|
209
|
+
const mint = await app.app.request('/api/v1/api-keys', {
|
|
210
|
+
...json({
|
|
211
|
+
name: 'logout probe',
|
|
212
|
+
scopes: [host.scopes.read, host.scopes.write, host.scopes.dataExport]
|
|
213
|
+
}),
|
|
214
|
+
headers: { 'content-type': 'application/json', cookie }
|
|
215
|
+
});
|
|
216
|
+
expect(mint.status).toBe(201);
|
|
217
|
+
const { key } = await readJson(mint);
|
|
218
|
+
|
|
219
|
+
const withKey = await app.app.request('/api/v1/sso/logout', {
|
|
220
|
+
method: 'POST',
|
|
221
|
+
headers: { authorization: `Bearer ${key}` }
|
|
222
|
+
});
|
|
223
|
+
expect(withKey.status).toBe(403);
|
|
224
|
+
expect((await readJson(withKey)).error.code).toBe('endpoint_not_allowed');
|
|
225
|
+
// The key's session-holding USER is untouched: their session cookie
|
|
226
|
+
// still resolves (the machine call revoked nothing).
|
|
227
|
+
expect((await app.app.request('/api/v1/me', { headers: { cookie } })).status).toBe(200);
|
|
228
|
+
|
|
229
|
+
const anon = await app.app.request('/api/v1/sso/logout', { method: 'POST' });
|
|
230
|
+
expect(anon.status).toBe(401);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('cloud: logout degrades to local-only when hub discovery is down', () => {
|
|
235
|
+
it('a sid-carrying session still logs out — url null, session revoked, hint cleared', async () => {
|
|
236
|
+
// A dedicated hub so this suite can kill discovery without touching the
|
|
237
|
+
// shared FakeHub: log in while it lives, stop it, THEN log out.
|
|
238
|
+
const hub2 = await FakeHub.start({ clientId: host.hubClientId });
|
|
239
|
+
const app2 = await createTestApp(
|
|
240
|
+
await createDatabase(container, 'sso_logout_down'),
|
|
241
|
+
cloudEnv(hub2.issuer)
|
|
242
|
+
);
|
|
243
|
+
try {
|
|
244
|
+
const setup = await app2.app.request(
|
|
245
|
+
'/api/v1/setup',
|
|
246
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'Down', owner: OWNER })
|
|
247
|
+
);
|
|
248
|
+
expect(setup.status).toBe(201);
|
|
249
|
+
const cookie = await sso.ssoLogin(app2, hub2, {
|
|
250
|
+
sub: 'hub-carol',
|
|
251
|
+
email: 'carol@logout.test',
|
|
252
|
+
name: 'Carol',
|
|
253
|
+
workspaceId: ORG_A,
|
|
254
|
+
role: 'member',
|
|
255
|
+
workspaceName: 'Org A',
|
|
256
|
+
overrides: { idTokenSid: 'hub-session-carol-1' }
|
|
257
|
+
});
|
|
258
|
+
await hub2.stop();
|
|
259
|
+
|
|
260
|
+
const res = await app2.app.request('/api/v1/sso/logout', { method: 'POST', headers: { cookie } });
|
|
261
|
+
expect(res.status).toBe(200);
|
|
262
|
+
expect((await readJson(res)).url).toBeNull();
|
|
263
|
+
expect(setCookies(res).find((v) => v.startsWith('ant_sso_hint='))).toBe(HINT_CLEAR);
|
|
264
|
+
const me = await app2.app.request('/api/v1/me', { headers: { cookie } });
|
|
265
|
+
expect(me.status).toBe(401);
|
|
266
|
+
} finally {
|
|
267
|
+
await app2.stop();
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('oss: zero logout surface', () => {
|
|
273
|
+
it('POST /sso/logout answers the JSON 404 terminator', async () => {
|
|
274
|
+
const app = await createTestApp(await createDatabase(container, 'sso_logout_oss'));
|
|
275
|
+
try {
|
|
276
|
+
const res = await app.app.request('/api/v1/sso/logout', { method: 'POST' });
|
|
277
|
+
expect(res.status).toBe(404);
|
|
278
|
+
expect((await readJson(res)).error.code).toBe('not_found');
|
|
279
|
+
} finally {
|
|
280
|
+
await app.stop();
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
});
|