@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,551 @@
|
|
|
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, workspaceTeamMembers, workspaceTeams } from '@antasphere/chassis-db';
|
|
5
|
+
import {
|
|
6
|
+
createDatabase,
|
|
7
|
+
createTestApp,
|
|
8
|
+
extractCookie,
|
|
9
|
+
readJson,
|
|
10
|
+
startPostgres,
|
|
11
|
+
type TestApp,
|
|
12
|
+
host
|
|
13
|
+
} from './helpers.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Teams on the self-hosted edition (PRDCT-2813): the tool's own, made and
|
|
17
|
+
* shaped here by owners and admins, read by every non-guest member. Pinned:
|
|
18
|
+
* the slug (derived, explicit, taken), the read/write split, the guest and
|
|
19
|
+
* anonymous refusals, a team of another workspace reading as absent, seating
|
|
20
|
+
* among the workspace's own active non-guest members, the members list's
|
|
21
|
+
* cursor surviving a removal, the cascades (a deleted team takes its seats, a
|
|
22
|
+
* deleted account takes its seats, a deactivation keeps them), the machine
|
|
23
|
+
* principals, and one audit action per change.
|
|
24
|
+
*/
|
|
25
|
+
const PASSWORD = 'a-long-teams-password-1';
|
|
26
|
+
const OWNER = { email: 'owner@teams.test', name: 'Olive Owner', password: PASSWORD };
|
|
27
|
+
|
|
28
|
+
type Actor = { email: string; cookie: string; userId: string; memberId: string };
|
|
29
|
+
|
|
30
|
+
let container: StartedPostgreSqlContainer;
|
|
31
|
+
let app: TestApp;
|
|
32
|
+
let workspaceId = '';
|
|
33
|
+
let otherWorkspaceId = '';
|
|
34
|
+
const actors: Record<string, Actor> = {};
|
|
35
|
+
|
|
36
|
+
let ipCounter = 0;
|
|
37
|
+
const nextIp = () => `10.79.${Math.floor(ipCounter / 250)}.${(ipCounter++ % 250) + 1}`;
|
|
38
|
+
|
|
39
|
+
const send = (
|
|
40
|
+
method: string,
|
|
41
|
+
path: string,
|
|
42
|
+
who: { cookie?: string; key?: string },
|
|
43
|
+
body?: unknown,
|
|
44
|
+
headers: Record<string, string> = {}
|
|
45
|
+
) =>
|
|
46
|
+
app.app.request(`/api/v1${path}`, {
|
|
47
|
+
method,
|
|
48
|
+
headers: {
|
|
49
|
+
'x-forwarded-for': nextIp(),
|
|
50
|
+
...(who.cookie ? { cookie: who.cookie } : {}),
|
|
51
|
+
...(who.key ? { authorization: `Bearer ${who.key}` } : {}),
|
|
52
|
+
...(body !== undefined ? { 'content-type': 'application/json' } : {}),
|
|
53
|
+
...headers
|
|
54
|
+
},
|
|
55
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {})
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
async function expectError(res: Response, status: number, code: string): Promise<void> {
|
|
59
|
+
const body = await readJson(res);
|
|
60
|
+
expect({ status: res.status, code: body?.error?.code }).toEqual({ status, code });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function signIn(email: string): Promise<string> {
|
|
64
|
+
const res = await send('POST', '/auth/sign-in/email', {}, { email, password: PASSWORD });
|
|
65
|
+
expect(res.status).toBe(200);
|
|
66
|
+
return extractCookie(res);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function addActor(
|
|
70
|
+
name: string,
|
|
71
|
+
opts: { role?: 'owner' | 'admin' | 'member'; origin?: 'local' | 'guest' } = {}
|
|
72
|
+
): Promise<Actor> {
|
|
73
|
+
const email = `${name}@teams.test`;
|
|
74
|
+
const created = await app.auth.api.signUpEmail({ body: { email, password: PASSWORD, name } });
|
|
75
|
+
const [row] = await app.db.db
|
|
76
|
+
.insert(workspaceMembers)
|
|
77
|
+
.values({
|
|
78
|
+
workspaceId,
|
|
79
|
+
userId: created.user.id,
|
|
80
|
+
role: opts.role ?? 'member',
|
|
81
|
+
origin: opts.origin ?? 'local'
|
|
82
|
+
})
|
|
83
|
+
.returning({ id: workspaceMembers.id });
|
|
84
|
+
const actor = { email, cookie: await signIn(email), userId: created.user.id, memberId: row!.id };
|
|
85
|
+
actors[name] = actor;
|
|
86
|
+
return actor;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function mintKey(cookie: string, scopes: string[]): Promise<string> {
|
|
90
|
+
const res = await send('POST', '/api-keys', { cookie }, { name: `key-${scopes.join('-')}`, scopes });
|
|
91
|
+
expect(res.status).toBe(201);
|
|
92
|
+
return (await readJson(res)).key;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function createTeam(who: { cookie?: string; key?: string }, name: string, slug?: string) {
|
|
96
|
+
const res = await send('POST', '/teams', who, { name, ...(slug ? { slug } : {}) });
|
|
97
|
+
expect(res.status).toBe(201);
|
|
98
|
+
return readJson(res) as Promise<{ id: string; slug: string }>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const seatsOfTeam = (teamId: string) =>
|
|
102
|
+
app.db.db.select().from(workspaceTeamMembers).where(eq(workspaceTeamMembers.teamId, teamId));
|
|
103
|
+
const seatsOfMember = (memberId: string) =>
|
|
104
|
+
app.db.db.select().from(workspaceTeamMembers).where(eq(workspaceTeamMembers.memberId, memberId));
|
|
105
|
+
|
|
106
|
+
beforeAll(async () => {
|
|
107
|
+
container = await startPostgres();
|
|
108
|
+
app = await createTestApp(await createDatabase(container, 'teams'));
|
|
109
|
+
const setup = await send(
|
|
110
|
+
'POST',
|
|
111
|
+
'/setup',
|
|
112
|
+
{},
|
|
113
|
+
{ setupToken: 'integration-test-setup-token', instanceName: 'Teams', owner: OWNER }
|
|
114
|
+
);
|
|
115
|
+
expect(setup.status).toBe(201);
|
|
116
|
+
workspaceId = (await readJson(setup)).workspaceId;
|
|
117
|
+
const ownerCookie = await signIn(OWNER.email);
|
|
118
|
+
const me = await readJson(await send('GET', '/me', { cookie: ownerCookie }));
|
|
119
|
+
const [ownerRow] = await app.db.db
|
|
120
|
+
.select({ id: workspaceMembers.id })
|
|
121
|
+
.from(workspaceMembers)
|
|
122
|
+
.where(and(eq(workspaceMembers.workspaceId, workspaceId), eq(workspaceMembers.userId, me.user.id)));
|
|
123
|
+
actors.owner = { email: OWNER.email, cookie: ownerCookie, userId: me.user.id, memberId: ownerRow!.id };
|
|
124
|
+
otherWorkspaceId = (await app.registry.workspaces.create('Elsewhere', me.user.id)).workspaceId;
|
|
125
|
+
|
|
126
|
+
await addActor('admin', { role: 'admin' });
|
|
127
|
+
for (const name of ['member', 'alice', 'bob', 'carol', 'dave', 'sleeper', 'erased', 'paused']) {
|
|
128
|
+
await addActor(name);
|
|
129
|
+
}
|
|
130
|
+
await addActor('guest', { origin: 'guest' });
|
|
131
|
+
}, 180_000);
|
|
132
|
+
|
|
133
|
+
afterAll(async () => {
|
|
134
|
+
await app?.stop();
|
|
135
|
+
await container?.stop();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('creating and shaping a team', () => {
|
|
139
|
+
it('an owner creates one: the slug is derived from the name, accents folded', async () => {
|
|
140
|
+
const res = await send('POST', '/teams', actors.owner!, { name: 'Équipe Créative' });
|
|
141
|
+
expect(res.status).toBe(201);
|
|
142
|
+
expect(await readJson(res)).toMatchObject({
|
|
143
|
+
name: 'Équipe Créative',
|
|
144
|
+
slug: 'equipe-creative',
|
|
145
|
+
hubTeamId: null,
|
|
146
|
+
membersCount: 0,
|
|
147
|
+
isMember: false
|
|
148
|
+
});
|
|
149
|
+
const [row] = await app.db.db
|
|
150
|
+
.select()
|
|
151
|
+
.from(workspaceTeams)
|
|
152
|
+
.where(eq(workspaceTeams.slug, 'equipe-creative'));
|
|
153
|
+
expect(row).toMatchObject({ workspaceId, hubTeamId: null, createdBy: actors.owner!.userId });
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('an admin creates one with an explicit slug', async () => {
|
|
157
|
+
const res = await send('POST', '/teams', actors.admin!, { name: 'Sales', slug: 'sales-emea' });
|
|
158
|
+
expect(res.status).toBe(201);
|
|
159
|
+
expect(await readJson(res)).toMatchObject({ name: 'Sales', slug: 'sales-emea', hubTeamId: null });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('a taken slug is 409 slug_taken on create AND on rename', async () => {
|
|
163
|
+
await expectError(
|
|
164
|
+
await send('POST', '/teams', actors.owner!, { name: 'Other', slug: 'sales-emea' }),
|
|
165
|
+
409,
|
|
166
|
+
'slug_taken'
|
|
167
|
+
);
|
|
168
|
+
// Derived to the same slug: the same refusal.
|
|
169
|
+
await expectError(
|
|
170
|
+
await send('POST', '/teams', actors.owner!, { name: 'equipe creative' }),
|
|
171
|
+
409,
|
|
172
|
+
'slug_taken'
|
|
173
|
+
);
|
|
174
|
+
const { id } = await createTeam(actors.owner!, 'Renamable');
|
|
175
|
+
await expectError(
|
|
176
|
+
await send('PATCH', `/teams/${id}`, actors.owner!, { slug: 'sales-emea' }),
|
|
177
|
+
409,
|
|
178
|
+
'slug_taken'
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('a name of symbols is 400, and so are a bad slug and an empty patch', async () => {
|
|
183
|
+
await expectError(
|
|
184
|
+
await send('POST', '/teams', actors.owner!, { name: '!!! ???' }),
|
|
185
|
+
400,
|
|
186
|
+
'validation_error'
|
|
187
|
+
);
|
|
188
|
+
await expectError(
|
|
189
|
+
await send('POST', '/teams', actors.owner!, { name: 'Fine', slug: 'Not A Slug' }),
|
|
190
|
+
400,
|
|
191
|
+
'validation_error'
|
|
192
|
+
);
|
|
193
|
+
const { id } = await createTeam(actors.owner!, 'Patchable');
|
|
194
|
+
await expectError(await send('PATCH', `/teams/${id}`, actors.owner!, {}), 400, 'validation_error');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('renames by name only, then by slug only', async () => {
|
|
198
|
+
const { id } = await createTeam(actors.owner!, 'Ops');
|
|
199
|
+
const byName = await send('PATCH', `/teams/${id}`, actors.admin!, { name: 'Operations' });
|
|
200
|
+
expect(byName.status).toBe(200);
|
|
201
|
+
expect(await readJson(byName)).toMatchObject({ id, name: 'Operations', slug: 'ops' });
|
|
202
|
+
const bySlug = await send('PATCH', `/teams/${id}`, actors.owner!, { slug: 'operations' });
|
|
203
|
+
expect(bySlug.status).toBe(200);
|
|
204
|
+
expect(await readJson(bySlug)).toMatchObject({ id, name: 'Operations', slug: 'operations' });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('a non-uuid id is a clean 400', async () => {
|
|
208
|
+
await expectError(await send('GET', '/teams/not-a-uuid', actors.owner!), 400, 'validation_error');
|
|
209
|
+
await expectError(
|
|
210
|
+
await send('DELETE', '/teams/not-a-uuid/members/x', actors.owner!),
|
|
211
|
+
400,
|
|
212
|
+
'validation_error'
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
describe('who reads and who writes', () => {
|
|
218
|
+
let teamId = '';
|
|
219
|
+
|
|
220
|
+
beforeAll(async () => {
|
|
221
|
+
teamId = (await createTeam(actors.owner!, 'Readers')).id;
|
|
222
|
+
expect(
|
|
223
|
+
(await send('POST', `/teams/${teamId}/members`, actors.owner!, { userId: actors.alice!.userId })).status
|
|
224
|
+
).toBe(201);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('a plain member reads the list, one team and its members', async () => {
|
|
228
|
+
const m = actors.member!;
|
|
229
|
+
const list = await send('GET', '/teams?limit=100', m);
|
|
230
|
+
expect(list.status).toBe(200);
|
|
231
|
+
expect((await readJson(list)).teams.map((t: { id: string }) => t.id)).toContain(teamId);
|
|
232
|
+
const one = await send('GET', `/teams/${teamId}`, m);
|
|
233
|
+
expect(one.status).toBe(200);
|
|
234
|
+
expect(await readJson(one)).toMatchObject({ id: teamId, membersCount: 1, isMember: false });
|
|
235
|
+
const members = await send('GET', `/teams/${teamId}/members`, m);
|
|
236
|
+
expect(members.status).toBe(200);
|
|
237
|
+
expect((await readJson(members)).members).toEqual([
|
|
238
|
+
expect.objectContaining({
|
|
239
|
+
userId: actors.alice!.userId,
|
|
240
|
+
email: 'alice@teams.test',
|
|
241
|
+
role: 'member',
|
|
242
|
+
isActive: true
|
|
243
|
+
})
|
|
244
|
+
]);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('a plain member is refused every write (403 forbidden)', async () => {
|
|
248
|
+
const m = actors.member!;
|
|
249
|
+
await expectError(await send('POST', '/teams', m, { name: 'Mine' }), 403, 'forbidden');
|
|
250
|
+
await expectError(await send('PATCH', `/teams/${teamId}`, m, { name: 'x' }), 403, 'forbidden');
|
|
251
|
+
await expectError(await send('DELETE', `/teams/${teamId}`, m), 403, 'forbidden');
|
|
252
|
+
await expectError(
|
|
253
|
+
await send('POST', `/teams/${teamId}/members`, m, { userId: m.userId }),
|
|
254
|
+
403,
|
|
255
|
+
'forbidden'
|
|
256
|
+
);
|
|
257
|
+
await expectError(
|
|
258
|
+
await send('DELETE', `/teams/${teamId}/members/${actors.alice!.userId}`, m),
|
|
259
|
+
403,
|
|
260
|
+
'forbidden'
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('a guest is refused the whole subtree (403 guest_forbidden); an anonymous caller is 401', async () => {
|
|
265
|
+
const g = actors.guest!;
|
|
266
|
+
await expectError(await send('GET', '/teams', g), 403, 'guest_forbidden');
|
|
267
|
+
await expectError(await send('GET', `/teams/${teamId}`, g), 403, 'guest_forbidden');
|
|
268
|
+
await expectError(await send('GET', `/teams/${teamId}/members`, g), 403, 'guest_forbidden');
|
|
269
|
+
await expectError(await send('POST', '/teams', g, { name: 'x' }), 403, 'guest_forbidden');
|
|
270
|
+
await expectError(await send('GET', '/teams', {}), 401, 'unauthenticated');
|
|
271
|
+
await expectError(await send('GET', `/teams/${teamId}`, {}), 401, 'unauthenticated');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('a team of another workspace is 404 from this one, for its own owner too', async () => {
|
|
275
|
+
const there = { 'x-workspace-id': otherWorkspaceId };
|
|
276
|
+
const created = await send('POST', '/teams', actors.owner!, { name: 'Elsewhere team' }, there);
|
|
277
|
+
expect(created.status).toBe(201);
|
|
278
|
+
const { id } = await readJson(created);
|
|
279
|
+
const o = actors.owner!;
|
|
280
|
+
await expectError(await send('GET', `/teams/${id}`, o), 404, 'not_found');
|
|
281
|
+
await expectError(await send('GET', `/teams/${id}/members`, o), 404, 'not_found');
|
|
282
|
+
await expectError(await send('PATCH', `/teams/${id}`, o, { name: 'x' }), 404, 'not_found');
|
|
283
|
+
await expectError(await send('DELETE', `/teams/${id}`, o), 404, 'not_found');
|
|
284
|
+
await expectError(await send('POST', `/teams/${id}/members`, o, { userId: o.userId }), 404, 'not_found');
|
|
285
|
+
const list = (await readJson(await send('GET', '/teams?limit=100', o))).teams.map(
|
|
286
|
+
(t: { id: string }) => t.id
|
|
287
|
+
);
|
|
288
|
+
expect(list).not.toContain(id);
|
|
289
|
+
expect((await send('GET', `/teams/${id}`, o, undefined, there)).status).toBe(200);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('seats: the workspace’s own active non-guest members', () => {
|
|
294
|
+
let teamId = '';
|
|
295
|
+
|
|
296
|
+
beforeAll(async () => {
|
|
297
|
+
teamId = (await createTeam(actors.owner!, 'Seating')).id;
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('seats by email and by user id; a repeat is 409 already_member', async () => {
|
|
301
|
+
const path = `/teams/${teamId}/members`;
|
|
302
|
+
const byEmail = await send('POST', path, actors.owner!, { email: 'BOB@teams.test' });
|
|
303
|
+
expect(byEmail.status).toBe(201);
|
|
304
|
+
const body = await readJson(byEmail);
|
|
305
|
+
expect(body).toMatchObject({
|
|
306
|
+
userId: actors.bob!.userId,
|
|
307
|
+
email: 'bob@teams.test',
|
|
308
|
+
role: 'member',
|
|
309
|
+
isActive: true
|
|
310
|
+
});
|
|
311
|
+
expect(typeof body.addedAt).toBe('string');
|
|
312
|
+
const byId = await send('POST', path, actors.admin!, { userId: actors.owner!.userId });
|
|
313
|
+
expect(byId.status).toBe(201);
|
|
314
|
+
expect(await readJson(byId)).toMatchObject({ userId: actors.owner!.userId, role: 'owner' });
|
|
315
|
+
await expectError(
|
|
316
|
+
await send('POST', path, actors.owner!, { userId: actors.bob!.userId }),
|
|
317
|
+
409,
|
|
318
|
+
'already_member'
|
|
319
|
+
);
|
|
320
|
+
const [seat] = await app.db.db
|
|
321
|
+
.select()
|
|
322
|
+
.from(workspaceTeamMembers)
|
|
323
|
+
.where(
|
|
324
|
+
and(eq(workspaceTeamMembers.teamId, teamId), eq(workspaceTeamMembers.memberId, actors.bob!.memberId))
|
|
325
|
+
);
|
|
326
|
+
expect(seat).toMatchObject({ addedBy: actors.owner!.userId });
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('refuses a stranger and a deactivated member (404), a guest (403 guest_target), and a bad body (400)', async () => {
|
|
330
|
+
const path = `/teams/${teamId}/members`;
|
|
331
|
+
const o = actors.owner!;
|
|
332
|
+
await expectError(await send('POST', path, o, { email: 'nobody@teams.test' }), 404, 'member_not_found');
|
|
333
|
+
await app.db.db
|
|
334
|
+
.update(workspaceMembers)
|
|
335
|
+
.set({ isActive: false })
|
|
336
|
+
.where(eq(workspaceMembers.id, actors.sleeper!.memberId));
|
|
337
|
+
await expectError(
|
|
338
|
+
await send('POST', path, o, { userId: actors.sleeper!.userId }),
|
|
339
|
+
404,
|
|
340
|
+
'member_not_found'
|
|
341
|
+
);
|
|
342
|
+
await expectError(await send('POST', path, o, { userId: actors.guest!.userId }), 403, 'guest_target');
|
|
343
|
+
await expectError(await send('POST', path, o, {}), 400, 'validation_error');
|
|
344
|
+
await expectError(
|
|
345
|
+
await send('POST', path, o, { userId: actors.bob!.userId, email: 'bob@teams.test' }),
|
|
346
|
+
400,
|
|
347
|
+
'validation_error'
|
|
348
|
+
);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('isMember is true for a seated caller, and membersCount counts', async () => {
|
|
352
|
+
const asOwner = await readJson(await send('GET', `/teams/${teamId}`, actors.owner!));
|
|
353
|
+
expect(asOwner).toMatchObject({ membersCount: 2, isMember: true });
|
|
354
|
+
const asBob = await readJson(await send('GET', `/teams/${teamId}`, actors.bob!));
|
|
355
|
+
expect(asBob).toMatchObject({ membersCount: 2, isMember: true });
|
|
356
|
+
const asCarol = await readJson(await send('GET', `/teams/${teamId}`, actors.carol!));
|
|
357
|
+
expect(asCarol).toMatchObject({ membersCount: 2, isMember: false });
|
|
358
|
+
const listed = (await readJson(await send('GET', '/teams?limit=100', actors.bob!))).teams.find(
|
|
359
|
+
(t: { id: string }) => t.id === teamId
|
|
360
|
+
);
|
|
361
|
+
expect(listed).toMatchObject({ membersCount: 2, isMember: true });
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('unseats (200 with the member), then 404 member_not_found', async () => {
|
|
365
|
+
const path = `/teams/${teamId}/members/${actors.bob!.userId}`;
|
|
366
|
+
const res = await send('DELETE', path, actors.admin!);
|
|
367
|
+
expect(res.status).toBe(200);
|
|
368
|
+
expect(await readJson(res)).toMatchObject({ userId: actors.bob!.userId, email: 'bob@teams.test' });
|
|
369
|
+
await expectError(await send('DELETE', path, actors.admin!), 404, 'member_not_found');
|
|
370
|
+
expect((await readJson(await send('GET', `/teams/${teamId}`, actors.bob!))).isMember).toBe(false);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it('the members list pages on a cursor that survives the removal of the row it names', async () => {
|
|
374
|
+
const { id: paged } = await createTeam(actors.owner!, 'Paged');
|
|
375
|
+
for (const name of ['alice', 'bob', 'carol', 'dave', 'member'] as const) {
|
|
376
|
+
const res = await send('POST', `/teams/${paged}/members`, actors.owner!, {
|
|
377
|
+
userId: actors[name]!.userId
|
|
378
|
+
});
|
|
379
|
+
expect(res.status).toBe(201);
|
|
380
|
+
}
|
|
381
|
+
const first = await readJson(await send('GET', `/teams/${paged}/members?limit=2`, actors.member!));
|
|
382
|
+
expect(first.members).toHaveLength(2);
|
|
383
|
+
expect(first.nextCursor).toMatch(/^[A-Za-z0-9_-]+$/);
|
|
384
|
+
// The row the cursor names is removed before the next page is asked for.
|
|
385
|
+
const lastOnPage = first.members[1].userId;
|
|
386
|
+
expect((await send('DELETE', `/teams/${paged}/members/${lastOnPage}`, actors.owner!)).status).toBe(200);
|
|
387
|
+
const second = await readJson(
|
|
388
|
+
await send('GET', `/teams/${paged}/members?limit=2&cursor=${first.nextCursor}`, actors.member!)
|
|
389
|
+
);
|
|
390
|
+
expect(second.members).toHaveLength(2);
|
|
391
|
+
const third = await readJson(
|
|
392
|
+
await send('GET', `/teams/${paged}/members?limit=2&cursor=${second.nextCursor}`, actors.member!)
|
|
393
|
+
);
|
|
394
|
+
expect(third.members).toHaveLength(1);
|
|
395
|
+
expect(third.nextCursor).toBeNull();
|
|
396
|
+
const seen = [...first.members, ...second.members, ...third.members].map(
|
|
397
|
+
(m: { userId: string }) => m.userId
|
|
398
|
+
);
|
|
399
|
+
expect(new Set(seen).size).toBe(5);
|
|
400
|
+
// A cursor that is not one of ours reads as no cursor, never as a 500.
|
|
401
|
+
const res = await send('GET', `/teams/${paged}/members?limit=10&cursor=garbage`, actors.member!);
|
|
402
|
+
expect(res.status).toBe(200);
|
|
403
|
+
expect((await readJson(res)).members).toHaveLength(4);
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
describe('the cascades', () => {
|
|
408
|
+
it('deleting a team deletes its seats, and the team is 404 after', async () => {
|
|
409
|
+
const { id } = await createTeam(actors.owner!, 'Doomed');
|
|
410
|
+
for (const name of ['alice', 'carol']) {
|
|
411
|
+
expect(
|
|
412
|
+
(await send('POST', `/teams/${id}/members`, actors.owner!, { userId: actors[name]!.userId })).status
|
|
413
|
+
).toBe(201);
|
|
414
|
+
}
|
|
415
|
+
expect(await seatsOfTeam(id)).toHaveLength(2);
|
|
416
|
+
const res = await send('DELETE', `/teams/${id}`, actors.admin!);
|
|
417
|
+
expect(res.status).toBe(200);
|
|
418
|
+
expect(await readJson(res)).toMatchObject({ id, slug: 'doomed', membersCount: 2 });
|
|
419
|
+
expect(await seatsOfTeam(id)).toHaveLength(0);
|
|
420
|
+
await expectError(await send('GET', `/teams/${id}`, actors.owner!), 404, 'not_found');
|
|
421
|
+
await expectError(await send('DELETE', `/teams/${id}`, actors.owner!), 404, 'not_found');
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('deleting a member’s account removes their seats', async () => {
|
|
425
|
+
const { id } = await createTeam(actors.owner!, 'Erasure');
|
|
426
|
+
const erased = actors.erased!;
|
|
427
|
+
expect(
|
|
428
|
+
(await send('POST', `/teams/${id}/members`, actors.owner!, { userId: erased.userId })).status
|
|
429
|
+
).toBe(201);
|
|
430
|
+
expect(await seatsOfMember(erased.memberId)).toHaveLength(1);
|
|
431
|
+
const res = await send('DELETE', `/members/${erased.memberId}`, actors.owner!);
|
|
432
|
+
expect(res.status).toBe(200);
|
|
433
|
+
expect(await seatsOfMember(erased.memberId)).toHaveLength(0);
|
|
434
|
+
expect((await readJson(await send('GET', `/teams/${id}`, actors.owner!))).membersCount).toBe(0);
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it('deactivating a member KEEPS the seat row', async () => {
|
|
438
|
+
const { id } = await createTeam(actors.owner!, 'Pause');
|
|
439
|
+
const paused = actors.paused!;
|
|
440
|
+
expect(
|
|
441
|
+
(await send('POST', `/teams/${id}/members`, actors.owner!, { userId: paused.userId })).status
|
|
442
|
+
).toBe(201);
|
|
443
|
+
const res = await send('PATCH', `/members/${paused.memberId}`, actors.owner!, { isActive: false });
|
|
444
|
+
expect(res.status).toBe(200);
|
|
445
|
+
expect(await seatsOfMember(paused.memberId)).toHaveLength(1);
|
|
446
|
+
const members = (await readJson(await send('GET', `/teams/${id}/members`, actors.owner!))).members;
|
|
447
|
+
expect(members).toEqual([expect.objectContaining({ userId: paused.userId, isActive: false })]);
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
describe('the team list’s cursor', () => {
|
|
452
|
+
it('survives the deletion of the team it names: none skipped, none repeated (verifier round 1, F3)', async () => {
|
|
453
|
+
// A workspace of its own, so the count is exactly the four made here
|
|
454
|
+
// (and the audit read below stays on the first workspace's own rows).
|
|
455
|
+
const pagingId = (await app.registry.workspaces.create('Paging', actors.owner!.userId)).workspaceId;
|
|
456
|
+
const there = { 'x-workspace-id': pagingId };
|
|
457
|
+
const o = actors.owner!;
|
|
458
|
+
const made: string[] = [];
|
|
459
|
+
for (const name of ['Page one', 'Page two', 'Page three', 'Page four']) {
|
|
460
|
+
const res = await send('POST', '/teams', o, { name }, there);
|
|
461
|
+
expect(res.status).toBe(201);
|
|
462
|
+
made.push((await readJson(res)).id as string);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const first = await readJson(await send('GET', '/teams?limit=1', o, undefined, there));
|
|
466
|
+
expect(first.teams).toHaveLength(1);
|
|
467
|
+
expect(first.nextCursor).toMatch(/^[A-Za-z0-9_-]+$/);
|
|
468
|
+
const cursorTeam = first.teams[0].id as string;
|
|
469
|
+
// Newest first: the page names the last one made.
|
|
470
|
+
expect(cursorTeam).toBe(made[3]);
|
|
471
|
+
expect((await send('DELETE', `/teams/${cursorTeam}`, o, undefined, there)).status).toBe(200);
|
|
472
|
+
|
|
473
|
+
const seen: string[] = [];
|
|
474
|
+
let cursor: string | null = first.nextCursor;
|
|
475
|
+
for (let i = 0; cursor !== null && i < 10; i++) {
|
|
476
|
+
const page = await readJson(await send('GET', `/teams?limit=1&cursor=${cursor}`, o, undefined, there));
|
|
477
|
+
if (i === 0) expect(page.teams.map((t: { id: string }) => t.id)).toEqual([made[2]]);
|
|
478
|
+
seen.push(...page.teams.map((t: { id: string }) => t.id));
|
|
479
|
+
cursor = page.nextCursor;
|
|
480
|
+
}
|
|
481
|
+
expect(cursor).toBeNull();
|
|
482
|
+
expect(seen).toEqual([made[2], made[1], made[0]]);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
describe('machine principals', () => {
|
|
487
|
+
it('the read scope reads the list and cannot create', async () => {
|
|
488
|
+
const readKey = await mintKey(actors.owner!.cookie, [host.scopes.read]);
|
|
489
|
+
expect((await send('GET', '/teams', { key: readKey })).status).toBe(200);
|
|
490
|
+
// The route is allowlisted under the write scope, so the gate's answer is
|
|
491
|
+
// the missing scope (`endpoint_not_allowed` is for unlisted shapes, below).
|
|
492
|
+
await expectError(
|
|
493
|
+
await send('POST', '/teams', { key: readKey }, { name: 'By a read key' }),
|
|
494
|
+
403,
|
|
495
|
+
'insufficient_scope'
|
|
496
|
+
);
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('the write scope (its holder an owner) creates and seats, and cannot read the list', async () => {
|
|
500
|
+
const writeKey = await mintKey(actors.owner!.cookie, [host.scopes.write]);
|
|
501
|
+
const { id } = await createTeam({ key: writeKey }, 'By a key');
|
|
502
|
+
const seated = await send(
|
|
503
|
+
'POST',
|
|
504
|
+
`/teams/${id}/members`,
|
|
505
|
+
{ key: writeKey },
|
|
506
|
+
{ userId: actors.dave!.userId }
|
|
507
|
+
);
|
|
508
|
+
expect(seated.status).toBe(201);
|
|
509
|
+
await expectError(await send('GET', '/teams', { key: writeKey }), 403, 'insufficient_scope');
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('a plain member’s write key is 403 forbidden', async () => {
|
|
513
|
+
const memberKey = await mintKey(actors.member!.cookie, [host.scopes.read, host.scopes.write]);
|
|
514
|
+
await expectError(await send('POST', '/teams', { key: memberKey }, { name: 'Nope' }), 403, 'forbidden');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('a shape that is not one of the eight routes stays closed to a key', async () => {
|
|
518
|
+
const both = await mintKey(actors.owner!.cookie, [host.scopes.read, host.scopes.write]);
|
|
519
|
+
const { id } = await createTeam({ key: both }, 'Closed shapes');
|
|
520
|
+
const code = 'endpoint_not_allowed';
|
|
521
|
+
await expectError(await send('PUT', `/teams/${id}`, { key: both }, {}), 403, code);
|
|
522
|
+
await expectError(await send('DELETE', '/teams', { key: both }), 403, code);
|
|
523
|
+
await expectError(await send('GET', `/teams/${id}/members/x`, { key: both }), 403, code);
|
|
524
|
+
await expectError(await send('GET', `/teams/${id}/anything`, { key: both }), 403, code);
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
describe('the record', () => {
|
|
529
|
+
it('every mutation wrote its audit action, readable at /audit by the owner', async () => {
|
|
530
|
+
for (const action of [
|
|
531
|
+
'team.create',
|
|
532
|
+
'team.update',
|
|
533
|
+
'team.delete',
|
|
534
|
+
'team.member_add',
|
|
535
|
+
'team.member_remove'
|
|
536
|
+
]) {
|
|
537
|
+
const res = await send('GET', `/audit?action=${action}&limit=5`, actors.owner!);
|
|
538
|
+
expect(res.status).toBe(200);
|
|
539
|
+
const entries = (await readJson(res)).entries as Array<{ action: string; resourceType: string }>;
|
|
540
|
+
expect(entries.length, action).toBeGreaterThan(0);
|
|
541
|
+
expect(entries[0]).toMatchObject({ action, resourceType: 'team' });
|
|
542
|
+
}
|
|
543
|
+
const added = (
|
|
544
|
+
await readJson(await send('GET', '/audit?action=team.member_add&limit=100', actors.owner!))
|
|
545
|
+
).entries as Array<{ metadata: { targetUserId?: string } }>;
|
|
546
|
+
expect(added.map((e) => e.metadata.targetUserId)).toContain(actors.bob!.userId);
|
|
547
|
+
const deleted = (await readJson(await send('GET', '/audit?action=team.delete&limit=5', actors.owner!)))
|
|
548
|
+
.entries as Array<{ metadata: Record<string, unknown> }>;
|
|
549
|
+
expect(deleted[0]!.metadata).toMatchObject({ slug: 'doomed', name: 'Doomed', membersCount: 2 });
|
|
550
|
+
});
|
|
551
|
+
});
|