@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,530 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
|
3
|
+
import { and, eq } from 'drizzle-orm';
|
|
4
|
+
import { workspaceMembers } from '@antasphere/chassis-db';
|
|
5
|
+
import {
|
|
6
|
+
createDatabase,
|
|
7
|
+
createTestApp,
|
|
8
|
+
extractCookie,
|
|
9
|
+
readJson,
|
|
10
|
+
RecordingEmailDriver,
|
|
11
|
+
startPostgres,
|
|
12
|
+
type TestApp,
|
|
13
|
+
host
|
|
14
|
+
} from './helpers.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The user-scoped credential model (ADR 014 as amended): one user in TWO
|
|
18
|
+
* workspaces, one workspace per request, selected by X-Workspace-Id for
|
|
19
|
+
* EVERY credential kind. Covers the session mechanism (default, switch,
|
|
20
|
+
* fail-closed), /me's all-workspaces shape with explicit default/suspended
|
|
21
|
+
* flags, cross-workspace data/key/invitation isolation, user-scoped vs
|
|
22
|
+
* PINNED API keys (pin resolution + the mismatch 403 pinned keys keep), the
|
|
23
|
+
* CLI-auth unpinned-by-default mint, the is_default inertness invariant,
|
|
24
|
+
* the multi-owned-workspace deletion guard, and the admin cross-workspace
|
|
25
|
+
* delete refusal.
|
|
26
|
+
*/
|
|
27
|
+
const OWNER = {
|
|
28
|
+
email: 'multi-owner@ws.test',
|
|
29
|
+
name: 'Multi Owner',
|
|
30
|
+
password: 'a-long-owner-password-123'
|
|
31
|
+
};
|
|
32
|
+
const BOB = { email: 'bob@ws.test', name: 'Bob Member', password: 'a-long-bob-password-1234' };
|
|
33
|
+
|
|
34
|
+
let container: StartedPostgreSqlContainer;
|
|
35
|
+
let app: TestApp;
|
|
36
|
+
let email: RecordingEmailDriver;
|
|
37
|
+
let ownerCookie: string;
|
|
38
|
+
let w1 = ''; // setup's workspace — the OLDEST membership, the default
|
|
39
|
+
let w2 = ''; // second workspace, created through the registry service
|
|
40
|
+
|
|
41
|
+
const WS_HEADER = 'x-workspace-id';
|
|
42
|
+
|
|
43
|
+
async function me(cookie: string, workspace?: string) {
|
|
44
|
+
return app.app.request('/api/v1/me', {
|
|
45
|
+
headers: { cookie, ...(workspace ? { [WS_HEADER]: workspace } : {}) }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
beforeAll(async () => {
|
|
50
|
+
container = await startPostgres();
|
|
51
|
+
const dbUrl = await createDatabase(container, 'ws_scoping');
|
|
52
|
+
email = new RecordingEmailDriver();
|
|
53
|
+
app = await createTestApp(dbUrl, {}, { email });
|
|
54
|
+
|
|
55
|
+
const setup = await app.app.request('/api/v1/setup', {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'content-type': 'application/json' },
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
setupToken: 'integration-test-setup-token',
|
|
60
|
+
instanceName: 'First Workspace',
|
|
61
|
+
owner: OWNER
|
|
62
|
+
})
|
|
63
|
+
});
|
|
64
|
+
expect(setup.status).toBe(201);
|
|
65
|
+
w1 = (await readJson(setup)).workspaceId;
|
|
66
|
+
|
|
67
|
+
const signIn = await app.app.request('/api/v1/auth/sign-in/email', {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: { 'content-type': 'application/json' },
|
|
70
|
+
body: JSON.stringify({ email: OWNER.email, password: OWNER.password })
|
|
71
|
+
});
|
|
72
|
+
expect(signIn.status).toBe(200);
|
|
73
|
+
ownerCookie = extractCookie(signIn);
|
|
74
|
+
|
|
75
|
+
// The registry's WorkspaceService is the blessed later-workspace path.
|
|
76
|
+
const meRes = await readJson(await me(ownerCookie));
|
|
77
|
+
const created = await app.registry.workspaces.create('Second Workspace', meRes.user.id);
|
|
78
|
+
w2 = created.workspaceId;
|
|
79
|
+
|
|
80
|
+
// Bob: a single-membership member of W2 only.
|
|
81
|
+
const bob = await app.auth.api.signUpEmail({
|
|
82
|
+
body: { email: BOB.email, password: BOB.password, name: BOB.name }
|
|
83
|
+
});
|
|
84
|
+
await app.db.db.insert(workspaceMembers).values({ workspaceId: w2, userId: bob.user.id, role: 'member' });
|
|
85
|
+
}, 240_000);
|
|
86
|
+
|
|
87
|
+
afterAll(async () => {
|
|
88
|
+
await app?.stop();
|
|
89
|
+
await container?.stop();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('session resolution (X-Workspace-Id)', () => {
|
|
93
|
+
it('defaults to the OLDEST active membership (no default set) and lists all workspaces', async () => {
|
|
94
|
+
const res = await me(ownerCookie);
|
|
95
|
+
expect(res.status).toBe(200);
|
|
96
|
+
const body = await readJson(res);
|
|
97
|
+
expect(body.activeWorkspaceId).toBe(w1);
|
|
98
|
+
expect(body.workspace.id).toBe(w1);
|
|
99
|
+
expect(body.workspace.name).toBe('First Workspace');
|
|
100
|
+
expect(body.workspaces.map((w: { id: string }) => w.id)).toEqual([w1, w2]);
|
|
101
|
+
expect(body.workspaces[1]).toEqual({
|
|
102
|
+
id: w2,
|
|
103
|
+
name: 'Second Workspace',
|
|
104
|
+
role: 'owner',
|
|
105
|
+
hubOrigin: false,
|
|
106
|
+
centralAccountId: null,
|
|
107
|
+
look: { theme: null, pattern: null, field: null, grain: null },
|
|
108
|
+
suspended: false,
|
|
109
|
+
default: false
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('the header switches the active workspace', async () => {
|
|
114
|
+
const body = await readJson(await me(ownerCookie, w2));
|
|
115
|
+
expect(body.activeWorkspaceId).toBe(w2);
|
|
116
|
+
expect(body.workspace.name).toBe('Second Workspace');
|
|
117
|
+
expect(body.role).toBe('owner');
|
|
118
|
+
// The list is header-independent — all memberships either way.
|
|
119
|
+
expect(body.workspaces).toHaveLength(2);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('fails closed on a workspace the user does not belong to (no oracle)', async () => {
|
|
123
|
+
// Bob is NOT a member of W1 — same 401 as a workspace that does not exist.
|
|
124
|
+
const bobSignIn = await app.app.request('/api/v1/auth/sign-in/email', {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: { 'content-type': 'application/json' },
|
|
127
|
+
body: JSON.stringify({ email: BOB.email, password: BOB.password })
|
|
128
|
+
});
|
|
129
|
+
const bobCookie = extractCookie(bobSignIn);
|
|
130
|
+
const foreign = await me(bobCookie, w1);
|
|
131
|
+
expect(foreign.status).toBe(401);
|
|
132
|
+
const phantom = await me(bobCookie, '00000000-0000-4000-8000-000000000000');
|
|
133
|
+
expect(phantom.status).toBe(401);
|
|
134
|
+
// Malformed header: clean 401, never a Postgres uuid-cast 500.
|
|
135
|
+
const malformed = await me(bobCookie, 'not-a-uuid');
|
|
136
|
+
expect(malformed.status).toBe(401);
|
|
137
|
+
// Sanity: without the header Bob's sole membership resolves.
|
|
138
|
+
const plain = await readJson(await me(bobCookie));
|
|
139
|
+
expect(plain.activeWorkspaceId).toBe(w2);
|
|
140
|
+
expect(plain.workspaces).toHaveLength(1);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('a well-formed uuid that is no workspace id answers 401 exactly as before (the hub-org arm is inert on self-hosted, PRDCT-2947)', async () => {
|
|
144
|
+
// The selector also matches a workspace by the hub organization it
|
|
145
|
+
// projects (central_account_id), a column that is always NULL here: a
|
|
146
|
+
// uuid naming nothing is the same fail-closed 401 as an unknown id.
|
|
147
|
+
const res = await me(ownerCookie, '11111111-2222-4333-8444-555555555555');
|
|
148
|
+
expect(res.status).toBe(401);
|
|
149
|
+
const phantom = await me(ownerCookie, '00000000-0000-4000-8000-000000000000');
|
|
150
|
+
expect(phantom.status).toBe(401);
|
|
151
|
+
expect(await res.text()).toBe(await phantom.text());
|
|
152
|
+
// And the real ids still resolve, so the 401 is the selector's and not the session's.
|
|
153
|
+
expect((await readJson(await me(ownerCookie, w2))).activeWorkspaceId).toBe(w2);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('data + invitation isolation across workspaces', () => {
|
|
158
|
+
it('files are scoped to the active workspace', async () => {
|
|
159
|
+
const up1 = await app.app.request('/api/v1/files?name=w1.txt', {
|
|
160
|
+
method: 'POST',
|
|
161
|
+
headers: { cookie: ownerCookie, 'content-type': 'text/plain' },
|
|
162
|
+
body: 'first workspace bytes'
|
|
163
|
+
});
|
|
164
|
+
expect(up1.status).toBe(201);
|
|
165
|
+
const up2 = await app.app.request('/api/v1/files?name=w2.txt', {
|
|
166
|
+
method: 'POST',
|
|
167
|
+
headers: { cookie: ownerCookie, 'content-type': 'text/plain', [WS_HEADER]: w2 },
|
|
168
|
+
body: 'second workspace bytes'
|
|
169
|
+
});
|
|
170
|
+
expect(up2.status).toBe(201);
|
|
171
|
+
|
|
172
|
+
const list1 = await readJson(
|
|
173
|
+
await app.app.request('/api/v1/files', { headers: { cookie: ownerCookie } })
|
|
174
|
+
);
|
|
175
|
+
expect(list1.files.map((f: { originalName: string }) => f.originalName)).toEqual(['w1.txt']);
|
|
176
|
+
const list2 = await readJson(
|
|
177
|
+
await app.app.request('/api/v1/files', { headers: { cookie: ownerCookie, [WS_HEADER]: w2 } })
|
|
178
|
+
);
|
|
179
|
+
expect(list2.files.map((f: { originalName: string }) => f.originalName)).toEqual(['w2.txt']);
|
|
180
|
+
|
|
181
|
+
// A W2 file id is NOT reachable in the W1 context (404, not leak).
|
|
182
|
+
const w2FileId = list2.files[0].id;
|
|
183
|
+
const cross = await app.app.request(`/api/v1/files/${w2FileId}`, {
|
|
184
|
+
headers: { cookie: ownerCookie }
|
|
185
|
+
});
|
|
186
|
+
expect(cross.status).toBe(404);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('invitations land in — and list under — the active workspace only', async () => {
|
|
190
|
+
const create = await app.app.request('/api/v1/invitations', {
|
|
191
|
+
method: 'POST',
|
|
192
|
+
headers: { cookie: ownerCookie, 'content-type': 'application/json', [WS_HEADER]: w2 },
|
|
193
|
+
body: JSON.stringify({ email: 'invitee-w2@ws.test', role: 'member' })
|
|
194
|
+
});
|
|
195
|
+
expect(create.status).toBe(201);
|
|
196
|
+
|
|
197
|
+
const listW1 = await readJson(
|
|
198
|
+
await app.app.request('/api/v1/invitations', { headers: { cookie: ownerCookie } })
|
|
199
|
+
);
|
|
200
|
+
expect(
|
|
201
|
+
listW1.invitations.filter((i: { email: string }) => i.email === 'invitee-w2@ws.test')
|
|
202
|
+
).toHaveLength(0);
|
|
203
|
+
const listW2 = await readJson(
|
|
204
|
+
await app.app.request('/api/v1/invitations', {
|
|
205
|
+
headers: { cookie: ownerCookie, [WS_HEADER]: w2 }
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
expect(
|
|
209
|
+
listW2.invitations.filter((i: { email: string }) => i.email === 'invitee-w2@ws.test')
|
|
210
|
+
).toHaveLength(1);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('members lists follow the active workspace', async () => {
|
|
214
|
+
const listW2 = await readJson(
|
|
215
|
+
await app.app.request('/api/v1/members', { headers: { cookie: ownerCookie, [WS_HEADER]: w2 } })
|
|
216
|
+
);
|
|
217
|
+
const emails = listW2.members.map((m: { email: string }) => m.email);
|
|
218
|
+
expect(emails).toContain(BOB.email);
|
|
219
|
+
const listW1 = await readJson(
|
|
220
|
+
await app.app.request('/api/v1/members', { headers: { cookie: ownerCookie } })
|
|
221
|
+
);
|
|
222
|
+
expect(listW1.members.map((m: { email: string }) => m.email)).not.toContain(BOB.email);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('API keys are user credentials with an optional pin', () => {
|
|
227
|
+
let userKey = ''; // unpinned (the mint default) — user-scoped
|
|
228
|
+
let pinnedKey = ''; // pinned to w2 at mint
|
|
229
|
+
|
|
230
|
+
it('mints UNPINNED by default: the header selects the workspace per request', async () => {
|
|
231
|
+
const mint = await app.app.request('/api/v1/api-keys', {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
headers: { cookie: ownerCookie, 'content-type': 'application/json' },
|
|
234
|
+
body: JSON.stringify({ name: 'k-user', scopes: [host.scopes.read] })
|
|
235
|
+
});
|
|
236
|
+
expect(mint.status).toBe(201);
|
|
237
|
+
const minted = await readJson(mint);
|
|
238
|
+
expect(minted.apiKey.workspaceId).toBeNull();
|
|
239
|
+
userKey = minted.key;
|
|
240
|
+
|
|
241
|
+
// No selector → the same default rule as sessions (oldest membership).
|
|
242
|
+
const files1 = await readJson(
|
|
243
|
+
await app.app.request('/api/v1/files', { headers: { authorization: `Bearer ${userKey}` } })
|
|
244
|
+
);
|
|
245
|
+
expect(files1.files.map((f: { originalName: string }) => f.originalName)).toEqual(['w1.txt']);
|
|
246
|
+
// The header is honored by the machine credential — org as a parameter.
|
|
247
|
+
const files2 = await readJson(
|
|
248
|
+
await app.app.request('/api/v1/files', {
|
|
249
|
+
headers: { authorization: `Bearer ${userKey}`, [WS_HEADER]: w2 }
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
expect(files2.files.map((f: { originalName: string }) => f.originalName)).toEqual(['w2.txt']);
|
|
253
|
+
// A workspace the creator does not belong to fails closed — same 401 as
|
|
254
|
+
// one that does not exist (no oracle).
|
|
255
|
+
const foreign = await app.app.request('/api/v1/me', {
|
|
256
|
+
headers: { authorization: `Bearer ${userKey}`, [WS_HEADER]: '00000000-0000-4000-8000-000000000000' }
|
|
257
|
+
});
|
|
258
|
+
expect(foreign.status).toBe(401);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('/me lists ALL the holder’s workspaces for a machine credential, flags explicit', async () => {
|
|
262
|
+
const meKey = await readJson(
|
|
263
|
+
await app.app.request('/api/v1/me', {
|
|
264
|
+
headers: { authorization: `Bearer ${userKey}`, [WS_HEADER]: w2 }
|
|
265
|
+
})
|
|
266
|
+
);
|
|
267
|
+
expect(meKey.activeWorkspaceId).toBe(w2);
|
|
268
|
+
expect(meKey.workspaces.map((w: { id: string }) => w.id)).toEqual([w1, w2]);
|
|
269
|
+
expect(
|
|
270
|
+
meKey.workspaces.every((w: { suspended: boolean; default: boolean }) => !w.suspended && !w.default)
|
|
271
|
+
).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('an explicit pin keeps the old binding: pin resolved, mismatching header → 403', async () => {
|
|
275
|
+
const mint = await app.app.request('/api/v1/api-keys', {
|
|
276
|
+
method: 'POST',
|
|
277
|
+
headers: { cookie: ownerCookie, 'content-type': 'application/json' },
|
|
278
|
+
body: JSON.stringify({ name: 'k-pinned', scopes: [host.scopes.read], workspaceId: w2 })
|
|
279
|
+
});
|
|
280
|
+
expect(mint.status).toBe(201);
|
|
281
|
+
const minted = await readJson(mint);
|
|
282
|
+
expect(minted.apiKey.workspaceId).toBe(w2);
|
|
283
|
+
pinnedKey = minted.key;
|
|
284
|
+
|
|
285
|
+
// The pin resolves — NOT the user's default (w1): pinned keys behave
|
|
286
|
+
// exactly as keys did before the model change.
|
|
287
|
+
const files = await readJson(
|
|
288
|
+
await app.app.request('/api/v1/files', { headers: { authorization: `Bearer ${pinnedKey}` } })
|
|
289
|
+
);
|
|
290
|
+
expect(files.files.map((f: { originalName: string }) => f.originalName)).toEqual(['w2.txt']);
|
|
291
|
+
// A MATCHING header is fine (idempotent restatement of the pin)...
|
|
292
|
+
const match = await app.app.request('/api/v1/me', {
|
|
293
|
+
headers: { authorization: `Bearer ${pinnedKey}`, [WS_HEADER]: w2 }
|
|
294
|
+
});
|
|
295
|
+
expect(match.status).toBe(200);
|
|
296
|
+
// ...a MISMATCHING one is rejected loudly, never silently served the pin.
|
|
297
|
+
const mismatch = await app.app.request('/api/v1/me', {
|
|
298
|
+
headers: { authorization: `Bearer ${pinnedKey}`, [WS_HEADER]: w1 }
|
|
299
|
+
});
|
|
300
|
+
expect(mismatch.status).toBe(403);
|
|
301
|
+
expect((await readJson(mismatch)).error.code).toBe('workspace_mismatch');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('pinning requires an ACTIVE membership of the pin (uniform 403, no oracle)', async () => {
|
|
305
|
+
const res = await app.app.request('/api/v1/api-keys', {
|
|
306
|
+
method: 'POST',
|
|
307
|
+
headers: { cookie: ownerCookie, 'content-type': 'application/json' },
|
|
308
|
+
body: JSON.stringify({
|
|
309
|
+
name: 'k-evil',
|
|
310
|
+
scopes: [host.scopes.read],
|
|
311
|
+
workspaceId: '00000000-0000-4000-8000-000000000000'
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
expect(res.status).toBe(403);
|
|
315
|
+
expect((await readJson(res)).error.code).toBe('no_membership');
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('the listing is creator-scoped (all the caller’s keys, any workspace context)', async () => {
|
|
319
|
+
const names = (
|
|
320
|
+
await readJson(await app.app.request('/api/v1/api-keys', { headers: { cookie: ownerCookie } }))
|
|
321
|
+
).apiKeys.map((k: { name: string }) => k.name);
|
|
322
|
+
expect(names).toContain('k-user');
|
|
323
|
+
expect(names).toContain('k-pinned');
|
|
324
|
+
// The active-workspace header does not re-scope the listing — keys are
|
|
325
|
+
// the USER's credentials, not workspace inventory.
|
|
326
|
+
const namesW2 = (
|
|
327
|
+
await readJson(
|
|
328
|
+
await app.app.request('/api/v1/api-keys', { headers: { cookie: ownerCookie, [WS_HEADER]: w2 } })
|
|
329
|
+
)
|
|
330
|
+
).apiKeys.map((k: { name: string }) => k.name);
|
|
331
|
+
expect(namesW2).toEqual(names);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('CLI auth mints user-scoped keys (optional pin)', () => {
|
|
336
|
+
async function completeCli(extra: Record<string, unknown> = {}) {
|
|
337
|
+
email.sent.length = 0;
|
|
338
|
+
const req = await app.app.request('/api/v1/cli/auth/request', {
|
|
339
|
+
method: 'POST',
|
|
340
|
+
headers: { 'content-type': 'application/json' },
|
|
341
|
+
body: JSON.stringify({ email: OWNER.email })
|
|
342
|
+
});
|
|
343
|
+
expect(req.status).toBe(200);
|
|
344
|
+
const mail = [...email.sent].reverse().find((m) => m.to === OWNER.email);
|
|
345
|
+
const otp = /^(\d{6}) /.exec(mail?.subject ?? '')?.[1];
|
|
346
|
+
expect(otp, 'a 6-digit code in the OTP mail subject').toBeTruthy();
|
|
347
|
+
return app.app.request('/api/v1/cli/auth/complete', {
|
|
348
|
+
method: 'POST',
|
|
349
|
+
headers: { 'content-type': 'application/json' },
|
|
350
|
+
body: JSON.stringify({ email: OWNER.email, otp, ...extra })
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
it('mints UNBOUND by default: the key follows the per-request selector', async () => {
|
|
355
|
+
const res = await completeCli();
|
|
356
|
+
expect(res.status).toBe(201);
|
|
357
|
+
const body = await readJson(res);
|
|
358
|
+
expect(body.workspaceId).toBeNull();
|
|
359
|
+
expect(body.apiKey.workspaceId).toBeNull();
|
|
360
|
+
// No selector → the default rule (oldest membership) …
|
|
361
|
+
const files1 = await readJson(
|
|
362
|
+
await app.app.request('/api/v1/files', { headers: { authorization: `Bearer ${body.key}` } })
|
|
363
|
+
);
|
|
364
|
+
expect(files1.files.map((f: { originalName: string }) => f.originalName)).toEqual(['w1.txt']);
|
|
365
|
+
// … and the header reaches the other workspace with the SAME key.
|
|
366
|
+
const files2 = await readJson(
|
|
367
|
+
await app.app.request('/api/v1/files', {
|
|
368
|
+
headers: { authorization: `Bearer ${body.key}`, [WS_HEADER]: w2 }
|
|
369
|
+
})
|
|
370
|
+
);
|
|
371
|
+
expect(files2.files.map((f: { originalName: string }) => f.originalName)).toEqual(['w2.txt']);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it('honors an explicit workspaceId as a PIN', async () => {
|
|
375
|
+
const res = await completeCli({ workspaceId: w2 });
|
|
376
|
+
expect(res.status).toBe(201);
|
|
377
|
+
const body = await readJson(res);
|
|
378
|
+
expect(body.workspaceId).toBe(w2);
|
|
379
|
+
expect(body.apiKey.workspaceId).toBe(w2);
|
|
380
|
+
// The minted key really is pinned to W2 …
|
|
381
|
+
const files = await readJson(
|
|
382
|
+
await app.app.request('/api/v1/files', { headers: { authorization: `Bearer ${body.key}` } })
|
|
383
|
+
);
|
|
384
|
+
expect(files.files.map((f: { originalName: string }) => f.originalName)).toEqual(['w2.txt']);
|
|
385
|
+
// … and keeps the pinned mismatch refusal.
|
|
386
|
+
const mismatch = await app.app.request('/api/v1/me', {
|
|
387
|
+
headers: { authorization: `Bearer ${body.key}`, [WS_HEADER]: w1 }
|
|
388
|
+
});
|
|
389
|
+
expect(mismatch.status).toBe(403);
|
|
390
|
+
expect((await readJson(mismatch)).error.code).toBe('workspace_mismatch');
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('rejects a pin the account is not an active member of (uniform 403)', async () => {
|
|
394
|
+
const res = await completeCli({ workspaceId: '00000000-0000-4000-8000-000000000000' });
|
|
395
|
+
expect(res.status).toBe(403);
|
|
396
|
+
expect((await readJson(res)).error.code).toBe('no_membership');
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
describe('is_default is INERT this phase — but the ordering already honors it', () => {
|
|
401
|
+
it('no flow above ever set a default membership (nothing writes it yet)', async () => {
|
|
402
|
+
const { rows } = await app.db.pool.query<{ n: number }>(
|
|
403
|
+
`SELECT count(*)::int AS n FROM workspace_members WHERE is_default`
|
|
404
|
+
);
|
|
405
|
+
expect(rows[0]!.n).toBe(0);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('a manually-set default flips selector-less resolution (the reconcile contract)', async () => {
|
|
409
|
+
const meBody = await readJson(await me(ownerCookie));
|
|
410
|
+
const ownerId = meBody.user.id as string;
|
|
411
|
+
expect(meBody.activeWorkspaceId).toBe(w1); // ordering no-op while all false
|
|
412
|
+
await app.db.pool.query(
|
|
413
|
+
`UPDATE workspace_members SET is_default = true WHERE user_id = $1 AND workspace_id = $2`,
|
|
414
|
+
[ownerId, w2]
|
|
415
|
+
);
|
|
416
|
+
try {
|
|
417
|
+
const flipped = await readJson(await me(ownerCookie));
|
|
418
|
+
expect(flipped.activeWorkspaceId).toBe(w2);
|
|
419
|
+
expect(
|
|
420
|
+
flipped.workspaces.find((w: { id: string }) => w.id === w2)?.default,
|
|
421
|
+
'the wire carries the default flag explicitly'
|
|
422
|
+
).toBe(true);
|
|
423
|
+
} finally {
|
|
424
|
+
await app.db.pool.query(`UPDATE workspace_members SET is_default = false WHERE user_id = $1`, [
|
|
425
|
+
ownerId
|
|
426
|
+
]);
|
|
427
|
+
}
|
|
428
|
+
// Back to the deterministic oldest once no default exists.
|
|
429
|
+
expect((await readJson(await me(ownerCookie))).activeWorkspaceId).toBe(w1);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
describe('admin delete stays inside the workspace', () => {
|
|
434
|
+
it('refuses to delete an account that belongs to other workspaces', async () => {
|
|
435
|
+
// Bob joins W1 too — the admin surface must now refuse account deletion.
|
|
436
|
+
const [bobRow] = await app.db.db
|
|
437
|
+
.select({ userId: workspaceMembers.userId })
|
|
438
|
+
.from(workspaceMembers)
|
|
439
|
+
.where(and(eq(workspaceMembers.workspaceId, w2), eq(workspaceMembers.role, 'member')));
|
|
440
|
+
await app.db.db
|
|
441
|
+
.insert(workspaceMembers)
|
|
442
|
+
.values({ workspaceId: w1, userId: bobRow!.userId, role: 'member' });
|
|
443
|
+
|
|
444
|
+
const members = await readJson(
|
|
445
|
+
await app.app.request('/api/v1/members', { headers: { cookie: ownerCookie } })
|
|
446
|
+
);
|
|
447
|
+
const bobMember = members.members.find((m: { email: string }) => m.email === BOB.email);
|
|
448
|
+
expect(bobMember).toBeTruthy();
|
|
449
|
+
|
|
450
|
+
const del = await app.app.request(`/api/v1/members/${bobMember.id}`, {
|
|
451
|
+
method: 'DELETE',
|
|
452
|
+
headers: { cookie: ownerCookie }
|
|
453
|
+
});
|
|
454
|
+
expect(del.status).toBe(409);
|
|
455
|
+
expect((await readJson(del)).error.code).toBe('member_of_other_workspaces');
|
|
456
|
+
|
|
457
|
+
// Deactivation is the in-workspace tool and still works.
|
|
458
|
+
const patch = await app.app.request(`/api/v1/members/${bobMember.id}`, {
|
|
459
|
+
method: 'PATCH',
|
|
460
|
+
headers: { cookie: ownerCookie, 'content-type': 'application/json' },
|
|
461
|
+
body: JSON.stringify({ isActive: false })
|
|
462
|
+
});
|
|
463
|
+
expect(patch.status).toBe(200);
|
|
464
|
+
// Bob's W2 standing is untouched.
|
|
465
|
+
const [w2Row] = await app.db.db
|
|
466
|
+
.select({ isActive: workspaceMembers.isActive })
|
|
467
|
+
.from(workspaceMembers)
|
|
468
|
+
.where(and(eq(workspaceMembers.workspaceId, w2), eq(workspaceMembers.userId, bobRow!.userId)));
|
|
469
|
+
expect(w2Row!.isActive).toBe(true);
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
describe('self-service deletion guards EVERY owned workspace', () => {
|
|
474
|
+
const CARL = { email: 'carl@ws.test', name: 'Carl Owner', password: 'a-long-carl-password-123' };
|
|
475
|
+
const DAVE = { email: 'dave@ws.test', name: 'Dave Owner', password: 'a-long-dave-password-123' };
|
|
476
|
+
let carlCookie = '';
|
|
477
|
+
let carlId = '';
|
|
478
|
+
let daveId = '';
|
|
479
|
+
let w3 = '';
|
|
480
|
+
let w4 = '';
|
|
481
|
+
|
|
482
|
+
beforeAll(async () => {
|
|
483
|
+
const carl = await app.auth.api.signUpEmail({
|
|
484
|
+
body: { email: CARL.email, password: CARL.password, name: CARL.name }
|
|
485
|
+
});
|
|
486
|
+
carlId = carl.user.id;
|
|
487
|
+
const dave = await app.auth.api.signUpEmail({
|
|
488
|
+
body: { email: DAVE.email, password: DAVE.password, name: DAVE.name }
|
|
489
|
+
});
|
|
490
|
+
daveId = dave.user.id;
|
|
491
|
+
w3 = (await app.registry.workspaces.create('Carl W3', carlId)).workspaceId;
|
|
492
|
+
w4 = (await app.registry.workspaces.create('Carl W4', carlId)).workspaceId;
|
|
493
|
+
const signIn = await app.app.request('/api/v1/auth/sign-in/email', {
|
|
494
|
+
method: 'POST',
|
|
495
|
+
headers: { 'content-type': 'application/json' },
|
|
496
|
+
body: JSON.stringify({ email: CARL.email, password: CARL.password })
|
|
497
|
+
});
|
|
498
|
+
carlCookie = extractCookie(signIn);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
async function deleteCarl() {
|
|
502
|
+
return app.app.request('/api/v1/auth/delete-user', {
|
|
503
|
+
method: 'POST',
|
|
504
|
+
headers: { cookie: carlCookie, 'content-type': 'application/json' },
|
|
505
|
+
body: JSON.stringify({ password: CARL.password })
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
it('rejects while the user is the sole owner of ANY workspace', async () => {
|
|
510
|
+
expect((await deleteCarl()).status).toBe(400);
|
|
511
|
+
|
|
512
|
+
// A co-owner in ONE of the two owned workspaces is not enough.
|
|
513
|
+
await app.db.db.insert(workspaceMembers).values({ workspaceId: w3, userId: daveId, role: 'owner' });
|
|
514
|
+
expect((await deleteCarl()).status).toBe(400);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('succeeds once every owned workspace keeps another owner; audits per workspace', async () => {
|
|
518
|
+
await app.db.db.insert(workspaceMembers).values({ workspaceId: w4, userId: daveId, role: 'owner' });
|
|
519
|
+
const res = await deleteCarl();
|
|
520
|
+
expect(res.status).toBe(200);
|
|
521
|
+
|
|
522
|
+
const rows = await app.db.pool.query(
|
|
523
|
+
`SELECT workspace_id FROM audit_log
|
|
524
|
+
WHERE action = 'user.account_delete' AND resource_id = $1
|
|
525
|
+
ORDER BY workspace_id`,
|
|
526
|
+
[carlId]
|
|
527
|
+
);
|
|
528
|
+
expect(rows.rows.map((r) => r.workspace_id).sort()).toEqual([w3, w4].sort());
|
|
529
|
+
});
|
|
530
|
+
});
|