@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,612 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
|
3
|
+
import {
|
|
4
|
+
createDatabase,
|
|
5
|
+
createTestApp,
|
|
6
|
+
extractCookie,
|
|
7
|
+
readJson,
|
|
8
|
+
startPostgres,
|
|
9
|
+
type TestApp,
|
|
10
|
+
host as chassisHost
|
|
11
|
+
} from './helpers.js';
|
|
12
|
+
import { FakeHub, type HubUserFixture } from '@antasphere/chassis-server/testing';
|
|
13
|
+
import * as sso from './sso-helpers.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The live principal gate (identity/hub-live-gate.ts) — the enforcement
|
|
17
|
+
* face of the user-scoped federation, replacing the retired master-key P4
|
|
18
|
+
* gates. Every verdict is judged on state reconciled AS THE USER via their
|
|
19
|
+
* OWN grant:
|
|
20
|
+
*
|
|
21
|
+
* - hub-side removal → 401 `membership_revoked` on the very request whose
|
|
22
|
+
* pass swept the row; all three credential kinds lock out;
|
|
23
|
+
* - `hub_status='suspended'` → 403 `account_suspended` for sessions, API
|
|
24
|
+
* keys, AND OAuth bearers — EXCEPT `GET /me` (visible-but-blocked);
|
|
25
|
+
* - hub outage: local state serves inside the stale window, then 403
|
|
26
|
+
* `hub_unavailable`, then recovery;
|
|
27
|
+
* - grant dead (revoked at the hub) → 401 `hub_grant_expired` immediately
|
|
28
|
+
* (no stale grace); a browser re-login heals it;
|
|
29
|
+
* - a hub role delta applies to THE request that reconciled it (D11);
|
|
30
|
+
* - the ACCEPTED BOUND: a guest in a hub-suspended org keeps deck access
|
|
31
|
+
* until a MEMBER's reconcile materializes the suspension locally — and
|
|
32
|
+
* is then blocked by the local column, never by a borrowed grant.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const OWNER = { email: 'owner@gate.test', name: 'Op Gate', password: 'op-gate-password-123' };
|
|
36
|
+
|
|
37
|
+
const ORG_REMOVE = '55555555-aaaa-4bbb-8ccc-000000000001';
|
|
38
|
+
const ORG_SUSPEND = '55555555-aaaa-4bbb-8ccc-000000000002';
|
|
39
|
+
const ORG_OUTAGE = '55555555-aaaa-4bbb-8ccc-000000000003';
|
|
40
|
+
const ORG_DEAD = '55555555-aaaa-4bbb-8ccc-000000000004';
|
|
41
|
+
const ORG_ROLE = '55555555-aaaa-4bbb-8ccc-000000000005';
|
|
42
|
+
const ORG_DEFAULT_A = '55555555-aaaa-4bbb-8ccc-000000000008';
|
|
43
|
+
const ORG_DEFAULT_B = '55555555-aaaa-4bbb-8ccc-000000000009';
|
|
44
|
+
|
|
45
|
+
const DIALS = {
|
|
46
|
+
reconcileTtlMs: 120,
|
|
47
|
+
// Wide enough that "inside the stale window" can never flake under
|
|
48
|
+
// parallel-suite container load, small enough that the fail-closed leg
|
|
49
|
+
// costs the outage test ~2.5 s.
|
|
50
|
+
reconcileStaleMaxMs: 2_500,
|
|
51
|
+
retryMs: 100,
|
|
52
|
+
orgsTimeoutMs: 400,
|
|
53
|
+
tokenTimeoutMs: 2_000
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
57
|
+
const expireTtl = () => sleep(DIALS.reconcileTtlMs + 40);
|
|
58
|
+
|
|
59
|
+
let container: StartedPostgreSqlContainer;
|
|
60
|
+
let hub: FakeHub;
|
|
61
|
+
let app: TestApp;
|
|
62
|
+
|
|
63
|
+
beforeAll(async () => {
|
|
64
|
+
[container, hub] = await Promise.all([
|
|
65
|
+
startPostgres(),
|
|
66
|
+
FakeHub.start({ clientId: chassisHost.hubClientId })
|
|
67
|
+
]);
|
|
68
|
+
app = await createTestApp(
|
|
69
|
+
await createDatabase(container, 'hub_live_gate'),
|
|
70
|
+
{
|
|
71
|
+
EDITION: 'cloud',
|
|
72
|
+
HUB_ISSUER_URL: hub.issuer,
|
|
73
|
+
HUB_CLIENT_ID: chassisHost.hubClientId,
|
|
74
|
+
HUB_CLIENT_SECRET: 'integration-test-hub-secret-0001'
|
|
75
|
+
},
|
|
76
|
+
{ hubDials: DIALS }
|
|
77
|
+
);
|
|
78
|
+
const res = await app.app.request(
|
|
79
|
+
'/api/v1/setup',
|
|
80
|
+
sso.json({ setupToken: 'integration-test-setup-token', instanceName: 'Gate', owner: OWNER })
|
|
81
|
+
);
|
|
82
|
+
expect(res.status).toBe(201);
|
|
83
|
+
}, 240_000);
|
|
84
|
+
|
|
85
|
+
afterAll(async () => {
|
|
86
|
+
await app?.stop();
|
|
87
|
+
await Promise.all([container?.stop(), hub?.stop()]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const me = async (cookie: string, workspaceId?: string) =>
|
|
91
|
+
app.app.request('/api/v1/me', {
|
|
92
|
+
headers: { cookie, ...(workspaceId ? { 'x-workspace-id': workspaceId } : {}) }
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
async function mintApiKey(cookie: string): Promise<string> {
|
|
96
|
+
const create = await app.app.request('/api/v1/api-keys', {
|
|
97
|
+
method: 'POST',
|
|
98
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
99
|
+
body: JSON.stringify({ name: 'gate key', scopes: [chassisHost.scopes.read, chassisHost.scopes.write] })
|
|
100
|
+
});
|
|
101
|
+
expect(create.status).toBe(201);
|
|
102
|
+
return (await readJson(create)).key;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
describe('hub-side removal → membership_revoked, all three credential kinds', () => {
|
|
106
|
+
const max: HubUserFixture = {
|
|
107
|
+
sub: 'hub-max',
|
|
108
|
+
email: 'max@remove.test',
|
|
109
|
+
name: 'Max Removed',
|
|
110
|
+
workspaceId: ORG_REMOVE,
|
|
111
|
+
role: 'owner',
|
|
112
|
+
workspaceName: 'Remove Org'
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
it('locks out sessions, API keys, AND OAuth bearers; the row deactivates with ONE audit row', async () => {
|
|
116
|
+
const cookie = await sso.ssoLogin(app, hub, max);
|
|
117
|
+
const key = await mintApiKey(cookie);
|
|
118
|
+
const bearer = await sso.oauthBearer(app, cookie);
|
|
119
|
+
|
|
120
|
+
// All three credential paths live.
|
|
121
|
+
expect((await me(cookie)).status).toBe(200);
|
|
122
|
+
const keyReq = () =>
|
|
123
|
+
app.app.request(chassisHost.probeRoute, { headers: { authorization: `Bearer ${key}` } });
|
|
124
|
+
const bearerReq = () =>
|
|
125
|
+
app.app.request(chassisHost.probeRoute, { headers: { authorization: `Bearer ${bearer}` } });
|
|
126
|
+
expect((await keyReq()).status).toBe(200);
|
|
127
|
+
expect((await bearerReq()).status).toBe(200);
|
|
128
|
+
|
|
129
|
+
// Removed on the hub: the next pass sweeps the row, and the REQUEST
|
|
130
|
+
// that ran the pass carries the gate's own verdict.
|
|
131
|
+
hub.removeUserOrg('hub-max', ORG_REMOVE);
|
|
132
|
+
await expireTtl();
|
|
133
|
+
const denied = await me(cookie);
|
|
134
|
+
expect(denied.status).toBe(401);
|
|
135
|
+
expect((await readJson(denied)).error.code).toBe('membership_revoked');
|
|
136
|
+
|
|
137
|
+
// Instant lockout of the ORG everywhere: the live-membership re-check
|
|
138
|
+
// now fails at RESOLUTION. Max was removed from his SOLE org, so a
|
|
139
|
+
// selector-less session /me is the zero-membership zero state (200,
|
|
140
|
+
// empty list — the no-organization shell), while NAMING the swept
|
|
141
|
+
// workspace, and every machine credential, still fail closed (401).
|
|
142
|
+
const zero = await me(cookie);
|
|
143
|
+
expect(zero.status).toBe(200);
|
|
144
|
+
expect((await readJson(zero)).workspaces).toEqual([]);
|
|
145
|
+
const wsRemove = (
|
|
146
|
+
await app.db.pool.query(`SELECT id FROM workspaces WHERE central_account_id = $1`, [ORG_REMOVE])
|
|
147
|
+
).rows[0].id as string;
|
|
148
|
+
expect((await me(cookie, wsRemove)).status).toBe(401);
|
|
149
|
+
expect((await keyReq()).status).toBe(401);
|
|
150
|
+
expect((await bearerReq()).status).toBe(401);
|
|
151
|
+
|
|
152
|
+
// DB truth: deactivated, still origin='hub'; audited once, as system.
|
|
153
|
+
const { rows } = await app.db.pool.query(
|
|
154
|
+
`SELECT m.is_active, m.origin FROM workspace_members m
|
|
155
|
+
JOIN workspaces w ON w.id = m.workspace_id WHERE w.central_account_id = $1`,
|
|
156
|
+
[ORG_REMOVE]
|
|
157
|
+
);
|
|
158
|
+
expect(rows).toEqual([{ is_active: false, origin: 'hub' }]);
|
|
159
|
+
const { rows: audit } = await app.db.pool.query(
|
|
160
|
+
`SELECT actor_via FROM audit_log WHERE action = 'member.deactivate'
|
|
161
|
+
AND metadata->>'reason' = 'hub_reconcile'`
|
|
162
|
+
);
|
|
163
|
+
expect(audit).toEqual([{ actor_via: 'system' }]);
|
|
164
|
+
|
|
165
|
+
// Re-granted at the hub → the next login restores everything.
|
|
166
|
+
hub.setUserOrg('hub-max', ORG_REMOVE, { name: 'Remove Org', role: 'owner' });
|
|
167
|
+
const again = await sso.ssoLogin(app, hub, max);
|
|
168
|
+
expect((await me(again)).status).toBe(200);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('suspension: visible but blocked (GET /me exempt)', () => {
|
|
173
|
+
const sam: HubUserFixture = {
|
|
174
|
+
sub: 'hub-sam',
|
|
175
|
+
email: 'sam@suspend.test',
|
|
176
|
+
name: 'Sam Suspended',
|
|
177
|
+
workspaceId: ORG_SUSPEND,
|
|
178
|
+
role: 'owner',
|
|
179
|
+
workspaceName: 'Suspend Org'
|
|
180
|
+
};
|
|
181
|
+
let cookie: string;
|
|
182
|
+
let key: string;
|
|
183
|
+
|
|
184
|
+
beforeAll(async () => {
|
|
185
|
+
cookie = await sso.ssoLogin(app, hub, sam);
|
|
186
|
+
key = await mintApiKey(cookie);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('suspension blocks every surface except GET /me — for sessions AND machine credentials', async () => {
|
|
190
|
+
expect((await me(cookie)).status).toBe(200);
|
|
191
|
+
hub.setUserOrg('hub-sam', ORG_SUSPEND, { name: 'Suspend Org', role: 'owner', status: 'suspended' });
|
|
192
|
+
await expireTtl();
|
|
193
|
+
|
|
194
|
+
// Blocked with the reason…
|
|
195
|
+
const denied = await app.app.request(chassisHost.probeRoute, { headers: { cookie } });
|
|
196
|
+
expect(denied.status).toBe(403);
|
|
197
|
+
expect((await readJson(denied)).error.code).toBe('account_suspended');
|
|
198
|
+
const viaKey = await app.app.request(chassisHost.probeRoute, {
|
|
199
|
+
headers: { authorization: `Bearer ${key}` }
|
|
200
|
+
});
|
|
201
|
+
expect(viaKey.status).toBe(403);
|
|
202
|
+
expect((await readJson(viaKey)).error.code).toBe('account_suspended');
|
|
203
|
+
|
|
204
|
+
// …but /me stays alive and SHOWS the suspension (the dashboard's badge
|
|
205
|
+
// instead of a stranded shell) — for the key too.
|
|
206
|
+
const meRes = await me(cookie);
|
|
207
|
+
expect(meRes.status).toBe(200);
|
|
208
|
+
const body = await readJson(meRes);
|
|
209
|
+
const suspended = body.workspaces.find((w: { name: string }) => w.name === 'Suspend Org');
|
|
210
|
+
expect(suspended.suspended).toBe(true);
|
|
211
|
+
const keyMe = await app.app.request('/api/v1/me', { headers: { authorization: `Bearer ${key}` } });
|
|
212
|
+
expect(keyMe.status).toBe(200);
|
|
213
|
+
|
|
214
|
+
// The column is locally materialized truth.
|
|
215
|
+
const { rows } = await app.db.pool.query(
|
|
216
|
+
`SELECT hub_status FROM workspaces WHERE central_account_id = $1`,
|
|
217
|
+
[ORG_SUSPEND]
|
|
218
|
+
);
|
|
219
|
+
expect(rows).toEqual([{ hub_status: 'suspended' }]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('unsuspension restores access within the window', async () => {
|
|
223
|
+
hub.setUserOrg('hub-sam', ORG_SUSPEND, { name: 'Suspend Org', role: 'owner', status: 'active' });
|
|
224
|
+
await expireTtl();
|
|
225
|
+
const restored = await app.app.request(chassisHost.probeRoute, { headers: { cookie } });
|
|
226
|
+
expect(restored.status).toBe(200);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe('hub outage: stale-served inside the window, then hub_unavailable, then recovery', () => {
|
|
231
|
+
const eva: HubUserFixture = {
|
|
232
|
+
sub: 'hub-eva',
|
|
233
|
+
email: 'eva@outage.test',
|
|
234
|
+
name: 'Eva Outage',
|
|
235
|
+
workspaceId: ORG_OUTAGE,
|
|
236
|
+
role: 'member',
|
|
237
|
+
workspaceName: 'Outage Org'
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
it('serves local state within the stale window, fails closed after it, recovers', async () => {
|
|
241
|
+
const cookie = await sso.ssoLogin(app, hub, eva);
|
|
242
|
+
expect((await me(cookie)).status).toBe(200); // definitive pass cached
|
|
243
|
+
hub.orgsMode = 'network';
|
|
244
|
+
try {
|
|
245
|
+
// Inside the stale window: local rows keep serving.
|
|
246
|
+
await expireTtl();
|
|
247
|
+
expect((await me(cookie)).status).toBe(200);
|
|
248
|
+
// Beyond it: fail closed with its own reason code.
|
|
249
|
+
await sleep(DIALS.reconcileStaleMaxMs + 100);
|
|
250
|
+
const closed = await me(cookie);
|
|
251
|
+
expect(closed.status).toBe(403);
|
|
252
|
+
expect((await readJson(closed)).error.code).toBe('hub_unavailable');
|
|
253
|
+
} finally {
|
|
254
|
+
hub.orgsMode = 'ok';
|
|
255
|
+
}
|
|
256
|
+
// Recovery: the next probe (past the retry throttle) reconciles again.
|
|
257
|
+
await sleep(DIALS.retryMs + 40);
|
|
258
|
+
expect((await me(cookie)).status).toBe(200);
|
|
259
|
+
|
|
260
|
+
// The membership survived the whole outage: errors never deactivate.
|
|
261
|
+
const { rows } = await app.db.pool.query(
|
|
262
|
+
`SELECT m.is_active FROM workspace_members m
|
|
263
|
+
JOIN workspaces w ON w.id = m.workspace_id WHERE w.central_account_id = $1`,
|
|
264
|
+
[ORG_OUTAGE]
|
|
265
|
+
);
|
|
266
|
+
expect(rows).toEqual([{ is_active: true }]);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('grant death: 401 hub_grant_expired immediately, healed by a browser re-login', () => {
|
|
271
|
+
const dora: HubUserFixture = {
|
|
272
|
+
sub: 'hub-dora',
|
|
273
|
+
email: 'dora@dead.test',
|
|
274
|
+
name: 'Dora Dead',
|
|
275
|
+
workspaceId: ORG_DEAD,
|
|
276
|
+
role: 'owner',
|
|
277
|
+
workspaceName: 'Dead Org'
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
it('a hub-side grant revocation cuts the user on the next pass — no stale grace', async () => {
|
|
281
|
+
const cookie = await sso.ssoLogin(app, hub, dora);
|
|
282
|
+
const key = await mintApiKey(cookie);
|
|
283
|
+
expect((await me(cookie)).status).toBe(200);
|
|
284
|
+
|
|
285
|
+
// The hub revokes this tool's access for dora: her access tokens stop
|
|
286
|
+
// working and her refresh family is dead.
|
|
287
|
+
hub.revokeGrants('hub-dora');
|
|
288
|
+
await expireTtl();
|
|
289
|
+
const denied = await me(cookie);
|
|
290
|
+
expect(denied.status).toBe(401);
|
|
291
|
+
expect((await readJson(denied)).error.code).toBe('hub_grant_expired');
|
|
292
|
+
// Machine credentials answer the same — a key must not outlive the grant.
|
|
293
|
+
const viaKey = await app.app.request(chassisHost.probeRoute, {
|
|
294
|
+
headers: { authorization: `Bearer ${key}` }
|
|
295
|
+
});
|
|
296
|
+
expect(viaKey.status).toBe(401);
|
|
297
|
+
expect((await readJson(viaKey)).error.code).toBe('hub_grant_expired');
|
|
298
|
+
|
|
299
|
+
// The row was NOT swept (org truth was never read) — the grant is what
|
|
300
|
+
// died. The dead marker is on the account row.
|
|
301
|
+
const { rows } = await app.db.pool.query(
|
|
302
|
+
`SELECT m.is_active FROM workspace_members m
|
|
303
|
+
JOIN workspaces w ON w.id = m.workspace_id WHERE w.central_account_id = $1`,
|
|
304
|
+
[ORG_DEAD]
|
|
305
|
+
);
|
|
306
|
+
expect(rows).toEqual([{ is_active: true }]);
|
|
307
|
+
const { rows: grant } = await app.db.pool.query(
|
|
308
|
+
`SELECT a.refresh_token FROM account a JOIN "user" u ON u.id = a.user_id
|
|
309
|
+
WHERE u.email = $1 AND a.provider_id = 'antasphere'`,
|
|
310
|
+
[dora.email]
|
|
311
|
+
);
|
|
312
|
+
expect(grant).toEqual([{ refresh_token: null }]);
|
|
313
|
+
|
|
314
|
+
// One browser SSO re-login re-seeds the grant; everything works again
|
|
315
|
+
// (the fresh force pass overwrites the cached dead verdict).
|
|
316
|
+
const again = await sso.ssoLogin(app, hub, dora);
|
|
317
|
+
expect((await me(again)).status).toBe(200);
|
|
318
|
+
expect(
|
|
319
|
+
(await app.app.request(chassisHost.probeRoute, { headers: { authorization: `Bearer ${key}` } })).status
|
|
320
|
+
).toBe(200);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('role deltas apply to the request that reconciled them (D11)', () => {
|
|
325
|
+
const rita: HubUserFixture = {
|
|
326
|
+
sub: 'hub-rita',
|
|
327
|
+
email: 'rita@role.test',
|
|
328
|
+
name: 'Rita Role',
|
|
329
|
+
workspaceId: ORG_ROLE,
|
|
330
|
+
role: 'admin',
|
|
331
|
+
workspaceName: 'Role Org'
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
it('a demotion closes the admin surface on the next pass; a promotion reopens it', async () => {
|
|
335
|
+
const cookie = await sso.ssoLogin(app, hub, rita);
|
|
336
|
+
expect((await app.app.request('/api/v1/audit', { headers: { cookie } })).status).toBe(200);
|
|
337
|
+
|
|
338
|
+
hub.setUserOrg('hub-rita', ORG_ROLE, { name: 'Role Org', role: 'member' });
|
|
339
|
+
await expireTtl();
|
|
340
|
+
const demoted = await app.app.request('/api/v1/audit', { headers: { cookie } });
|
|
341
|
+
expect(demoted.status).toBe(403); // the delta bit on THIS request
|
|
342
|
+
expect((await readJson(await me(cookie))).role).toBe('member');
|
|
343
|
+
const { rows } = await app.db.pool.query(
|
|
344
|
+
`SELECT m.role FROM workspace_members m
|
|
345
|
+
JOIN workspaces w ON w.id = m.workspace_id WHERE w.central_account_id = $1`,
|
|
346
|
+
[ORG_ROLE]
|
|
347
|
+
);
|
|
348
|
+
expect(rows).toEqual([{ role: 'member' }]);
|
|
349
|
+
|
|
350
|
+
hub.setUserOrg('hub-rita', ORG_ROLE, { name: 'Role Org', role: 'admin' });
|
|
351
|
+
await expireTtl();
|
|
352
|
+
expect((await app.app.request('/api/v1/audit', { headers: { cookie } })).status).toBe(200);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
describe('the ACCEPTED BOUND: guest suspension staleness (documented, deliberate)', () => {
|
|
357
|
+
it('a guest is gated by the LOCALLY MATERIALIZED hub_status only — refreshed by a member, never a borrowed grant', async () => {
|
|
358
|
+
// A member workspace + a guest row planted in it (the claim path's
|
|
359
|
+
// shape, minimal fixture). The guest signs in via the break-glass-style
|
|
360
|
+
// local door on oss… on cloud guests hold hub identities — model that:
|
|
361
|
+
// the guest is a hub user whose OWN org list never includes this org.
|
|
362
|
+
const host: HubUserFixture = {
|
|
363
|
+
sub: 'hub-host',
|
|
364
|
+
email: 'host@guestbound.test',
|
|
365
|
+
workspaceId: '55555555-aaaa-4bbb-8ccc-000000000006',
|
|
366
|
+
role: 'owner',
|
|
367
|
+
workspaceName: 'Guest Host Org'
|
|
368
|
+
};
|
|
369
|
+
const guest: HubUserFixture = {
|
|
370
|
+
sub: 'hub-guesty',
|
|
371
|
+
email: 'guesty@guestbound.test',
|
|
372
|
+
workspaceId: '55555555-aaaa-4bbb-8ccc-000000000007',
|
|
373
|
+
role: 'owner',
|
|
374
|
+
workspaceName: 'Guesty Personal'
|
|
375
|
+
};
|
|
376
|
+
const hostCookie = await sso.ssoLogin(app, hub, host);
|
|
377
|
+
const guestCookie = await sso.ssoLogin(app, hub, guest);
|
|
378
|
+
const { rows: hostWs } = await app.db.pool.query(
|
|
379
|
+
`SELECT id FROM workspaces WHERE central_account_id = $1`,
|
|
380
|
+
[host.workspaceId]
|
|
381
|
+
);
|
|
382
|
+
const hostWorkspaceId = hostWs[0].id as string;
|
|
383
|
+
await app.db.pool.query(
|
|
384
|
+
`INSERT INTO workspace_members (workspace_id, user_id, role, origin, is_active)
|
|
385
|
+
VALUES ($1, (SELECT id FROM "user" WHERE email = $2), 'member', 'guest', true)`,
|
|
386
|
+
[hostWorkspaceId, guest.email]
|
|
387
|
+
);
|
|
388
|
+
// The guest reaches the host workspace (their grant's whole point).
|
|
389
|
+
expect((await me(guestCookie, hostWorkspaceId)).status).toBe(200);
|
|
390
|
+
|
|
391
|
+
// The org is suspended at the hub. The guest's OWN reconciles never
|
|
392
|
+
// list this org, so the local hub_status stays stale — the guest KEEPS
|
|
393
|
+
// access. This is the accepted bound (same class as ungated share-link
|
|
394
|
+
// viewing): every cheap fix leaks or borrows credentials.
|
|
395
|
+
hub.setUserOrg('hub-host', host.workspaceId, {
|
|
396
|
+
name: 'Guest Host Org',
|
|
397
|
+
role: 'owner',
|
|
398
|
+
status: 'suspended'
|
|
399
|
+
});
|
|
400
|
+
await expireTtl();
|
|
401
|
+
const preMaterialize = await app.app.request(chassisHost.probeRoute, {
|
|
402
|
+
headers: { cookie: guestCookie, 'x-workspace-id': hostWorkspaceId }
|
|
403
|
+
});
|
|
404
|
+
expect(preMaterialize.status).toBe(200); // stale — accepted
|
|
405
|
+
|
|
406
|
+
// A MEMBER touches the tool: their pass materializes the suspension…
|
|
407
|
+
await expireTtl();
|
|
408
|
+
const memberBlocked = await app.app.request(chassisHost.probeRoute, {
|
|
409
|
+
headers: { cookie: hostCookie }
|
|
410
|
+
});
|
|
411
|
+
expect(memberBlocked.status).toBe(403);
|
|
412
|
+
expect((await readJson(memberBlocked)).error.code).toBe('account_suspended');
|
|
413
|
+
|
|
414
|
+
// …and NOW the guest is blocked too, by the local column.
|
|
415
|
+
const postMaterialize = await app.app.request(chassisHost.probeRoute, {
|
|
416
|
+
headers: { cookie: guestCookie, 'x-workspace-id': hostWorkspaceId }
|
|
417
|
+
});
|
|
418
|
+
expect(postMaterialize.status).toBe(403);
|
|
419
|
+
expect((await readJson(postMaterialize)).error.code).toBe('account_suspended');
|
|
420
|
+
// The guest's /me stays alive (visible-but-blocked applies to them too).
|
|
421
|
+
expect((await me(guestCookie, hostWorkspaceId)).status).toBe(200);
|
|
422
|
+
// The guest row itself was never touched by any hub machinery.
|
|
423
|
+
const { rows: guestRow } = await app.db.pool.query(
|
|
424
|
+
`SELECT is_active, origin FROM workspace_members
|
|
425
|
+
WHERE workspace_id = $1 AND user_id = (SELECT id FROM "user" WHERE email = $2)`,
|
|
426
|
+
[hostWorkspaceId, guest.email]
|
|
427
|
+
);
|
|
428
|
+
expect(guestRow).toEqual([{ is_active: true, origin: 'guest' }]);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
describe('workspaces with no hub projection never touch the hub', () => {
|
|
433
|
+
it('a cloud-LOCAL workspace costs zero hub calls', async () => {
|
|
434
|
+
// Cloud setup mints no workspace (user-scoped federation), so seed a
|
|
435
|
+
// cloud-LOCAL one for the operator — exactly the shape a break-glass
|
|
436
|
+
// claim leaves. Its requests carry NO accountRef, so the live gate
|
|
437
|
+
// short-circuits with zero hub surface.
|
|
438
|
+
const localWs = await sso.seedLocalWorkspace(app, 'Operator Local', OWNER.email);
|
|
439
|
+
const signIn = await app.app.request(
|
|
440
|
+
'/api/v1/auth/sign-in/email',
|
|
441
|
+
sso.json({ email: OWNER.email, password: OWNER.password })
|
|
442
|
+
);
|
|
443
|
+
expect(signIn.status).toBe(200);
|
|
444
|
+
const cookie = extractCookie(signIn);
|
|
445
|
+
const before = hub.orgsRequests.length + hub.refreshRequests.length;
|
|
446
|
+
for (let i = 0; i < 3; i += 1) {
|
|
447
|
+
await sleep(DIALS.reconcileTtlMs + 20);
|
|
448
|
+
expect((await me(cookie, localWs)).status).toBe(200);
|
|
449
|
+
}
|
|
450
|
+
expect(
|
|
451
|
+
(
|
|
452
|
+
await app.app.request(chassisHost.probeRoute, {
|
|
453
|
+
headers: { cookie, 'x-workspace-id': localWs }
|
|
454
|
+
})
|
|
455
|
+
).status
|
|
456
|
+
).toBe(200);
|
|
457
|
+
expect(hub.orgsRequests.length + hub.refreshRequests.length).toBe(before);
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
describe('the default moved at the hub lands the request that reconciled it (PRDCT-2958, F2)', () => {
|
|
462
|
+
const dana: HubUserFixture = {
|
|
463
|
+
sub: 'hub-dana',
|
|
464
|
+
email: 'dana@default.test',
|
|
465
|
+
name: 'Dana Default',
|
|
466
|
+
workspaceId: ORG_DEFAULT_A,
|
|
467
|
+
role: 'owner',
|
|
468
|
+
workspaceName: 'Default A Org'
|
|
469
|
+
};
|
|
470
|
+
let cookie: string;
|
|
471
|
+
let key: string;
|
|
472
|
+
let pinnedKey: string;
|
|
473
|
+
let wsA: string;
|
|
474
|
+
let wsB: string;
|
|
475
|
+
|
|
476
|
+
const meAs = async (authorization: string, workspaceId?: string) =>
|
|
477
|
+
app.app.request('/api/v1/me', {
|
|
478
|
+
headers: { authorization, ...(workspaceId ? { 'x-workspace-id': workspaceId } : {}) }
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
const moveDefaultTo = (org: string) => {
|
|
482
|
+
hub.setUserOrg('hub-dana', ORG_DEFAULT_A, {
|
|
483
|
+
name: 'Default A Org',
|
|
484
|
+
role: 'owner',
|
|
485
|
+
...(org === ORG_DEFAULT_A ? { isDefault: true } : {})
|
|
486
|
+
});
|
|
487
|
+
hub.setUserOrg('hub-dana', ORG_DEFAULT_B, {
|
|
488
|
+
name: 'Default B Org',
|
|
489
|
+
role: 'member',
|
|
490
|
+
...(org === ORG_DEFAULT_B ? { isDefault: true } : {})
|
|
491
|
+
});
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
beforeAll(async () => {
|
|
495
|
+
// Two organizations at the hub, A the default, before the first login.
|
|
496
|
+
moveDefaultTo(ORG_DEFAULT_A);
|
|
497
|
+
cookie = await sso.ssoLogin(app, hub, dana);
|
|
498
|
+
// The workspace ids come off /me's rows by their hub organization id —
|
|
499
|
+
// never by position.
|
|
500
|
+
const body = await readJson(await me(cookie));
|
|
501
|
+
const byOrg = (org: string) =>
|
|
502
|
+
body.workspaces.find((w: { centralAccountId: string | null }) => w.centralAccountId === org)
|
|
503
|
+
.id as string;
|
|
504
|
+
wsA = byOrg(ORG_DEFAULT_A);
|
|
505
|
+
wsB = byOrg(ORG_DEFAULT_B);
|
|
506
|
+
key = await mintApiKey(cookie);
|
|
507
|
+
const pin = await app.app.request('/api/v1/api-keys', {
|
|
508
|
+
method: 'POST',
|
|
509
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
510
|
+
body: JSON.stringify({
|
|
511
|
+
name: 'gate pinned key',
|
|
512
|
+
scopes: [chassisHost.scopes.read, chassisHost.scopes.write],
|
|
513
|
+
workspaceId: wsA
|
|
514
|
+
})
|
|
515
|
+
});
|
|
516
|
+
expect(pin.status).toBe(201);
|
|
517
|
+
pinnedKey = (await readJson(pin)).key;
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('a selector-less request lands in the hub default, for the session and an unpinned key', async () => {
|
|
521
|
+
expect(wsA).not.toBe(wsB);
|
|
522
|
+
expect((await readJson(await me(cookie))).activeWorkspaceId).toBe(wsA);
|
|
523
|
+
expect((await readJson(await meAs(`Bearer ${key}`))).activeWorkspaceId).toBe(wsA);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('the default moves to B: the FIRST selector-less request after the pass lands in B', async () => {
|
|
527
|
+
moveDefaultTo(ORG_DEFAULT_B);
|
|
528
|
+
await expireTtl();
|
|
529
|
+
// The very request whose pass read the move off the hub runs in B —
|
|
530
|
+
// before the fix it still ran in A and only the next one moved.
|
|
531
|
+
const viaKey = await meAs(`Bearer ${key}`);
|
|
532
|
+
expect(viaKey.status).toBe(200);
|
|
533
|
+
expect((await readJson(viaKey)).activeWorkspaceId).toBe(wsB);
|
|
534
|
+
await expireTtl();
|
|
535
|
+
const viaSession = await me(cookie);
|
|
536
|
+
expect(viaSession.status).toBe(200);
|
|
537
|
+
expect((await readJson(viaSession)).activeWorkspaceId).toBe(wsB);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('a selector wins over the moved default', async () => {
|
|
541
|
+
await expireTtl();
|
|
542
|
+
expect((await readJson(await meAs(`Bearer ${key}`, wsA))).activeWorkspaceId).toBe(wsA);
|
|
543
|
+
await expireTtl();
|
|
544
|
+
expect((await readJson(await me(cookie, wsA))).activeWorkspaceId).toBe(wsA);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('a pinned key is never moved: no header lands in its pin, a header naming B is workspace_mismatch', async () => {
|
|
548
|
+
await expireTtl();
|
|
549
|
+
const pinned = await meAs(`Bearer ${pinnedKey}`);
|
|
550
|
+
expect(pinned.status).toBe(200);
|
|
551
|
+
expect((await readJson(pinned)).activeWorkspaceId).toBe(wsA);
|
|
552
|
+
const mismatch = await meAs(`Bearer ${pinnedKey}`, wsB);
|
|
553
|
+
expect(mismatch.status).toBe(403);
|
|
554
|
+
expect((await readJson(mismatch)).error.code).toBe('workspace_mismatch');
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('the session lands on the request that reconciled it too: the default back to A', async () => {
|
|
558
|
+
// The key's pass above already materialized B, so the session leg of
|
|
559
|
+
// the move proves nothing on its own; a second move whose FIRST reader
|
|
560
|
+
// is the session does.
|
|
561
|
+
moveDefaultTo(ORG_DEFAULT_A);
|
|
562
|
+
await expireTtl();
|
|
563
|
+
const viaSession = await me(cookie);
|
|
564
|
+
expect(viaSession.status).toBe(200);
|
|
565
|
+
expect((await readJson(viaSession)).activeWorkspaceId).toBe(wsA);
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
describe('a sweep of the default organization is told, never papered over by the moved default (PRDCT-2958, F2)', () => {
|
|
570
|
+
const ORG_SWEPT_A = '55555555-aaaa-4bbb-8ccc-00000000000a';
|
|
571
|
+
const ORG_SWEPT_B = '55555555-aaaa-4bbb-8ccc-00000000000b';
|
|
572
|
+
const sam: HubUserFixture = {
|
|
573
|
+
sub: 'hub-sam',
|
|
574
|
+
email: 'sam@swept.test',
|
|
575
|
+
name: 'Sam Swept',
|
|
576
|
+
workspaceId: ORG_SWEPT_A,
|
|
577
|
+
role: 'owner',
|
|
578
|
+
workspaceName: 'Swept A Org'
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
it('a person swept from their DEFAULT organization reads membership_revoked on the request that swept it, never a silent move (verifier M2)', async () => {
|
|
582
|
+
// Two organizations at the hub, A the default, before the first login.
|
|
583
|
+
hub.setUserOrg('hub-sam', ORG_SWEPT_A, { name: 'Swept A Org', role: 'owner', isDefault: true });
|
|
584
|
+
hub.setUserOrg('hub-sam', ORG_SWEPT_B, { name: 'Swept B Org', role: 'member' });
|
|
585
|
+
const cookie = await sso.ssoLogin(app, hub, sam);
|
|
586
|
+
const body = await readJson(await me(cookie));
|
|
587
|
+
const byOrg = (org: string) =>
|
|
588
|
+
body.workspaces.find((w: { centralAccountId: string | null }) => w.centralAccountId === org)
|
|
589
|
+
.id as string;
|
|
590
|
+
const wsA = byOrg(ORG_SWEPT_A);
|
|
591
|
+
const wsB = byOrg(ORG_SWEPT_B);
|
|
592
|
+
expect(wsA).not.toBe(wsB);
|
|
593
|
+
const key = await mintApiKey(cookie);
|
|
594
|
+
const viaKey = () => app.app.request('/api/v1/me', { headers: { authorization: `Bearer ${key}` } });
|
|
595
|
+
expect((await readJson(await viaKey())).activeWorkspaceId).toBe(wsA);
|
|
596
|
+
|
|
597
|
+
// Removed from A at the hub; B becomes the default.
|
|
598
|
+
hub.removeUserOrg('hub-sam', ORG_SWEPT_A);
|
|
599
|
+
hub.setUserOrg('hub-sam', ORG_SWEPT_B, { name: 'Swept B Org', role: 'member', isDefault: true });
|
|
600
|
+
await expireTtl();
|
|
601
|
+
|
|
602
|
+
// The request whose pass swept A carries the reason — never a 200 in B.
|
|
603
|
+
const swept = await viaKey();
|
|
604
|
+
expect(swept.status).toBe(401);
|
|
605
|
+
expect((await readJson(swept)).error.code).toBe('membership_revoked');
|
|
606
|
+
|
|
607
|
+
// The next request resolves in B, the new default.
|
|
608
|
+
const next = await viaKey();
|
|
609
|
+
expect(next.status).toBe(200);
|
|
610
|
+
expect((await readJson(next)).activeWorkspaceId).toBe(wsB);
|
|
611
|
+
});
|
|
612
|
+
});
|