@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,289 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { ToolEntitlements } from '@antasphere/chassis-contract';
|
|
3
|
+
import { EntitlementProfiles, HubMachineToken } from '@antasphere/chassis-server';
|
|
4
|
+
import type { Logger } from '@antasphere/chassis-server/logger';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The per-account plan cache of the cloud edition (the billing rail, §7):
|
|
8
|
+
* one hub read per account per TTL, the hub's overrides winning over the
|
|
9
|
+
* tool's tier values, the last known plan kept through an outage for the
|
|
10
|
+
* stale window and then `free`, a never-answered account `free` from the
|
|
11
|
+
* start, and a failure cached like an answer so a storm stays one read.
|
|
12
|
+
* The read is off the request path (PRDCT-2633): a stale entry is served at
|
|
13
|
+
* once while the refresh runs behind it, a cold account waits at most the
|
|
14
|
+
* cold budget. The hub's answer shape is the contract's copy (checked
|
|
15
|
+
* against the hub's wire snapshot, PRDCT-2677), a boolean limit included
|
|
16
|
+
* (PRDCT-2636), and since phase 2 the hub's upgrade page and its
|
|
17
|
+
* features list as the truth once it serves limit rows (PRDCT-2664).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const logger = { error: () => {}, warn: () => {}, info: () => {}, debug: () => {} } as unknown as Logger;
|
|
21
|
+
const ISSUER = 'https://hub.test';
|
|
22
|
+
const TOOL: ToolEntitlements = {
|
|
23
|
+
actions: [],
|
|
24
|
+
limits: { 'files.maxBytes': { oss: 10, free: 100, pro: 500 }, seats: { oss: null, free: 3, pro: null } },
|
|
25
|
+
features: { sso: { free: false, pro: true }, basic: { free: true, pro: true } }
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** The hub's whole answer (the shape the contract mirrors verbatim), with the test's fields over it. */
|
|
29
|
+
const answerOf = (over: Record<string, unknown>) =>
|
|
30
|
+
Response.json({
|
|
31
|
+
accountRef: '77777777-aaaa-4bbb-8ccc-000000000001',
|
|
32
|
+
plan: 'free',
|
|
33
|
+
planUntil: null,
|
|
34
|
+
limits: {},
|
|
35
|
+
features: [],
|
|
36
|
+
// Required on the wire since PRDCT-2677: every hub since 0.11.0 sends it.
|
|
37
|
+
upgradeUrl: `${ISSUER}/billing/upgrade?org=acct&tool=things&plan=free`,
|
|
38
|
+
...over
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function profiles(
|
|
42
|
+
answer: () => Response | Promise<Response>,
|
|
43
|
+
clock: { now: number },
|
|
44
|
+
dials: { coldWaitMs?: number } = {}
|
|
45
|
+
) {
|
|
46
|
+
let reads = 0;
|
|
47
|
+
const fetchImpl = (async (input: string | URL | Request) => {
|
|
48
|
+
const url = String(input);
|
|
49
|
+
if (url.endsWith('/api/v1/auth/oauth2/token')) {
|
|
50
|
+
return Response.json({ access_token: 'mach_1', expires_in: 3600 });
|
|
51
|
+
}
|
|
52
|
+
reads += 1;
|
|
53
|
+
return answer();
|
|
54
|
+
}) as typeof fetch;
|
|
55
|
+
const token = new HubMachineToken({
|
|
56
|
+
issuerUrl: ISSUER,
|
|
57
|
+
clientId: 'tool-things',
|
|
58
|
+
clientSecret: 's'.repeat(20),
|
|
59
|
+
resource: `${ISSUER}/mcp`,
|
|
60
|
+
logger,
|
|
61
|
+
fetchImpl,
|
|
62
|
+
now: () => clock.now
|
|
63
|
+
});
|
|
64
|
+
const p = new EntitlementProfiles({
|
|
65
|
+
issuerUrl: ISSUER,
|
|
66
|
+
token,
|
|
67
|
+
logger,
|
|
68
|
+
fetchImpl,
|
|
69
|
+
now: () => clock.now,
|
|
70
|
+
dials: { ttlMs: 30_000, staleMs: 15 * 60_000, ...dials }
|
|
71
|
+
});
|
|
72
|
+
return { p, reads: () => reads };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
describe('EntitlementProfiles', () => {
|
|
76
|
+
it('reads once per account per TTL, single-flight, and resolves the tier values', async () => {
|
|
77
|
+
const clock = { now: 1_000_000 };
|
|
78
|
+
const { p, reads } = profiles(() => answerOf({ plan: 'pro' }), clock);
|
|
79
|
+
const [a, b] = await Promise.all([p.get('acct', TOOL), p.get('acct', TOOL)]);
|
|
80
|
+
expect(reads()).toBe(1);
|
|
81
|
+
expect(a).toEqual(b);
|
|
82
|
+
expect(a.plan).toBe('pro');
|
|
83
|
+
expect(a.source).toBe('hub');
|
|
84
|
+
expect(a.limits).toEqual({ 'files.maxBytes': 500, seats: null });
|
|
85
|
+
expect([...a.features].sort()).toEqual(['basic', 'sso']);
|
|
86
|
+
clock.now += 29_000;
|
|
87
|
+
await p.get('acct', TOOL);
|
|
88
|
+
expect(reads()).toBe(1);
|
|
89
|
+
clock.now += 2_000;
|
|
90
|
+
// Past the TTL: served at once from the last answer, the refresh behind it.
|
|
91
|
+
const served = await p.get('acct', TOOL);
|
|
92
|
+
expect(served.plan).toBe('pro');
|
|
93
|
+
expect(served.source).toBe('stale');
|
|
94
|
+
await p.settle();
|
|
95
|
+
expect(reads()).toBe(2);
|
|
96
|
+
expect((await p.get('acct', TOOL)).source).toBe('hub');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('the hub’s overrides win over the tier’s values, features included', async () => {
|
|
100
|
+
const clock = { now: 1_000_000 };
|
|
101
|
+
const { p } = profiles(
|
|
102
|
+
() => answerOf({ plan: 'free', limits: { 'files.maxBytes': 250 }, features: ['sso'] }),
|
|
103
|
+
clock
|
|
104
|
+
);
|
|
105
|
+
const profile = await p.get('acct', TOOL);
|
|
106
|
+
expect(profile.plan).toBe('free');
|
|
107
|
+
expect(profile.limits).toEqual({ 'files.maxBytes': 250, seats: 3 });
|
|
108
|
+
expect([...profile.features]).toEqual(['sso']);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('an account the hub never answered for is free, and the miss is cached for the TTL', async () => {
|
|
112
|
+
const clock = { now: 1_000_000 };
|
|
113
|
+
const { p, reads } = profiles(
|
|
114
|
+
() => Response.json({ error: { code: 'not_found' } }, { status: 404 }),
|
|
115
|
+
clock
|
|
116
|
+
);
|
|
117
|
+
const a = await p.get('acct', TOOL);
|
|
118
|
+
expect(a.plan).toBe('free');
|
|
119
|
+
expect(a.source).toBe('default');
|
|
120
|
+
expect(a.limits['files.maxBytes']).toBe(100);
|
|
121
|
+
await p.get('acct', TOOL);
|
|
122
|
+
expect(reads()).toBe(1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('keeps the last known plan through an outage for the stale window, then falls to free', async () => {
|
|
126
|
+
const clock = { now: 1_000_000 };
|
|
127
|
+
let mode: 'ok' | 'down' = 'ok';
|
|
128
|
+
const { p, reads } = profiles(
|
|
129
|
+
() => (mode === 'ok' ? answerOf({ plan: 'pro' }) : Response.json({ error: {} }, { status: 500 })),
|
|
130
|
+
clock
|
|
131
|
+
);
|
|
132
|
+
expect((await p.get('acct', TOOL)).plan).toBe('pro');
|
|
133
|
+
mode = 'down';
|
|
134
|
+
clock.now += 31_000;
|
|
135
|
+
const stale = await p.get('acct', TOOL);
|
|
136
|
+
expect(stale.plan).toBe('pro');
|
|
137
|
+
expect(stale.source).toBe('stale'); // served now, the refresh behind it
|
|
138
|
+
await p.settle();
|
|
139
|
+
expect(reads()).toBe(2);
|
|
140
|
+
expect((await p.get('acct', TOOL)).source).toBe('hub'); // re-cached from the last answer for another TTL
|
|
141
|
+
clock.now += 14 * 60_000; // 14.5 min after the answer: still inside the window
|
|
142
|
+
expect((await p.get('acct', TOOL)).plan).toBe('pro');
|
|
143
|
+
await p.settle();
|
|
144
|
+
clock.now += 61_000; // past 15 min
|
|
145
|
+
const fallen = await p.get('acct', TOOL);
|
|
146
|
+
expect(fallen.plan).toBe('free');
|
|
147
|
+
expect(fallen.source).toBe('default');
|
|
148
|
+
expect(fallen.limits['files.maxBytes']).toBe(100);
|
|
149
|
+
await p.settle(); // the miss the fallen read started, behind the request
|
|
150
|
+
mode = 'ok';
|
|
151
|
+
clock.now += 31_000;
|
|
152
|
+
await p.get('acct', TOOL);
|
|
153
|
+
await p.settle();
|
|
154
|
+
expect((await p.get('acct', TOOL)).plan).toBe('pro');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('a slow hub costs a warm request nothing (PRDCT-2633): the stale plan is served at once, the refresh lands behind it', async () => {
|
|
158
|
+
const clock = { now: 1_000_000 };
|
|
159
|
+
let release: (() => void) | null = null;
|
|
160
|
+
let slow = false;
|
|
161
|
+
const { p, reads } = profiles(async () => {
|
|
162
|
+
if (slow) await new Promise<void>((r) => (release = r));
|
|
163
|
+
return answerOf({ plan: 'pro' });
|
|
164
|
+
}, clock);
|
|
165
|
+
expect((await p.get('acct', TOOL)).plan).toBe('pro');
|
|
166
|
+
slow = true;
|
|
167
|
+
clock.now += 31_000;
|
|
168
|
+
const started = Date.now();
|
|
169
|
+
const served = await p.get('acct', TOOL);
|
|
170
|
+
expect(Date.now() - started).toBeLessThan(200);
|
|
171
|
+
expect(served.plan).toBe('pro');
|
|
172
|
+
expect(served.source).toBe('stale');
|
|
173
|
+
expect(reads()).toBe(2); // the refresh is in flight, single-flight
|
|
174
|
+
await p.get('acct', TOOL);
|
|
175
|
+
expect(reads()).toBe(2);
|
|
176
|
+
release!();
|
|
177
|
+
await p.settle();
|
|
178
|
+
expect((await p.get('acct', TOOL)).source).toBe('hub');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('a cold account waits for its first read up to the cold budget, then is served the default while the read finishes', async () => {
|
|
182
|
+
const clock = { now: 1_000_000 };
|
|
183
|
+
let release: (() => void) | null = null;
|
|
184
|
+
const { p, reads } = profiles(
|
|
185
|
+
async () => {
|
|
186
|
+
await new Promise<void>((r) => (release = r));
|
|
187
|
+
return answerOf({ plan: 'pro' });
|
|
188
|
+
},
|
|
189
|
+
clock,
|
|
190
|
+
{ coldWaitMs: 50 }
|
|
191
|
+
);
|
|
192
|
+
const started = Date.now();
|
|
193
|
+
const cold = await p.get('acct', TOOL);
|
|
194
|
+
expect(Date.now() - started).toBeGreaterThanOrEqual(45);
|
|
195
|
+
expect(cold.plan).toBe('free');
|
|
196
|
+
expect(cold.source).toBe('default');
|
|
197
|
+
expect(reads()).toBe(1);
|
|
198
|
+
release!();
|
|
199
|
+
await p.settle();
|
|
200
|
+
const warm = await p.get('acct', TOOL);
|
|
201
|
+
expect(warm.plan).toBe('pro');
|
|
202
|
+
expect(warm.source).toBe('hub');
|
|
203
|
+
expect(reads()).toBe(1);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('a cold read that answers within the budget is judged right on the first request', async () => {
|
|
207
|
+
const clock = { now: 1_000_000 };
|
|
208
|
+
const { p } = profiles(() => answerOf({ plan: 'pro' }), clock, { coldWaitMs: 1_000 });
|
|
209
|
+
expect((await p.get('acct', TOOL)).plan).toBe('pro');
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('every shape the hub’s contract allows parses, a boolean limit included (PRDCT-2636)', async () => {
|
|
213
|
+
const clock = { now: 1_000_000 };
|
|
214
|
+
const shapes: Array<[Record<string, unknown>, Record<string, number | null>]> = [
|
|
215
|
+
[
|
|
216
|
+
{ plan: 'pro', planUntil: '2026-12-31T00:00:00.000Z', limits: {}, features: [] },
|
|
217
|
+
{ 'files.maxBytes': 500, seats: null }
|
|
218
|
+
],
|
|
219
|
+
[
|
|
220
|
+
{ plan: 'free', limits: { 'files.maxBytes': true }, features: [] },
|
|
221
|
+
{ 'files.maxBytes': null, seats: 3 }
|
|
222
|
+
],
|
|
223
|
+
[
|
|
224
|
+
{ plan: 'pro', limits: { seats: false, 'files.maxBytes': 42 }, features: ['sso'] },
|
|
225
|
+
{ 'files.maxBytes': 42, seats: 0 }
|
|
226
|
+
],
|
|
227
|
+
[
|
|
228
|
+
{ plan: 'free', limits: { unknown: true }, features: ['sso', 'basic'] },
|
|
229
|
+
{ 'files.maxBytes': 100, seats: 3 }
|
|
230
|
+
]
|
|
231
|
+
];
|
|
232
|
+
for (const [over, limits] of shapes) {
|
|
233
|
+
const { p } = profiles(() => answerOf(over), clock);
|
|
234
|
+
const profile = await p.get('acct', TOOL);
|
|
235
|
+
expect(profile.source, JSON.stringify(over)).toBe('hub');
|
|
236
|
+
expect(profile.plan).toBe(over.plan);
|
|
237
|
+
expect(profile.limits, JSON.stringify(over)).toEqual(limits);
|
|
238
|
+
}
|
|
239
|
+
// A shape the hub's contract does NOT allow reads as a miss: the mirror is verbatim.
|
|
240
|
+
for (const bad of [
|
|
241
|
+
{ plan: 'pro', limits: { seats: 'many' } },
|
|
242
|
+
{ plan: 'pro', limits: undefined },
|
|
243
|
+
{ plan: 'pro', accountRef: 'not-a-uuid' }
|
|
244
|
+
]) {
|
|
245
|
+
const { p } = profiles(() => answerOf(bad as Record<string, unknown>), clock);
|
|
246
|
+
expect((await p.get('acct', TOOL)).source, JSON.stringify(bad)).toBe('default');
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('carries the hub’s upgrade page through resolve, and none when the plan is the default (an answer without the page no longer parses, PRDCT-2677)', async () => {
|
|
251
|
+
const clock = { now: 1_000_000 };
|
|
252
|
+
const page = `${ISSUER}/billing/upgrade?org=acct&tool=things&plan=pro`;
|
|
253
|
+
const { p } = profiles(() => answerOf({ plan: 'pro', upgradeUrl: page }), clock);
|
|
254
|
+
expect((await p.get('acct', TOOL)).upgradeUrl).toBe(page);
|
|
255
|
+
const older = profiles(() => answerOf({ plan: 'pro', upgradeUrl: undefined }), clock);
|
|
256
|
+
const olderProfile = await older.p.get('acct', TOOL);
|
|
257
|
+
expect(olderProfile.upgradeUrl).toBeNull();
|
|
258
|
+
expect(olderProfile.source).toBe('default');
|
|
259
|
+
const missing = profiles(() => Response.json({ error: {} }, { status: 500 }), clock);
|
|
260
|
+
expect((await missing.p.get('acct', TOOL)).upgradeUrl).toBeNull();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('a hub that answers limits is authoritative on features, an empty list included; one that answers none leaves the tier’s features', async () => {
|
|
264
|
+
const clock = { now: 1_000_000 };
|
|
265
|
+
// Phase 2: the hub serves its rows, and switches every feature off for this pro account.
|
|
266
|
+
const phase2 = profiles(() => answerOf({ plan: 'pro', limits: { seats: 10 }, features: [] }), clock);
|
|
267
|
+
const off = await phase2.p.get('acct', TOOL);
|
|
268
|
+
expect(off.plan).toBe('pro');
|
|
269
|
+
expect([...off.features]).toEqual([]);
|
|
270
|
+
// Phase 1 (no limit key): an empty list means "the declared tier values apply".
|
|
271
|
+
const phase1 = profiles(() => answerOf({ plan: 'pro', limits: {}, features: [] }), clock);
|
|
272
|
+
expect([...(await phase1.p.get('acct', TOOL)).features].sort()).toEqual(['basic', 'sso']);
|
|
273
|
+
// And a non-empty list still overrides there.
|
|
274
|
+
const listed = profiles(() => answerOf({ plan: 'pro', limits: {}, features: ['basic'] }), clock);
|
|
275
|
+
expect([...(await listed.p.get('acct', TOOL)).features]).toEqual(['basic']);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('a malformed profile and an unreachable hub read as a miss', async () => {
|
|
279
|
+
const clock = { now: 1_000_000 };
|
|
280
|
+
let kind: 'malformed' | 'network' = 'malformed';
|
|
281
|
+
const { p } = profiles(() => {
|
|
282
|
+
if (kind === 'network') throw new TypeError('fetch failed');
|
|
283
|
+
return answerOf({ plan: 'platinum' });
|
|
284
|
+
}, clock);
|
|
285
|
+
expect((await p.get('a', TOOL)).plan).toBe('free');
|
|
286
|
+
kind = 'network';
|
|
287
|
+
expect((await p.get('b', TOOL)).plan).toBe('free');
|
|
288
|
+
});
|
|
289
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import { emailHash, ErasureLog } from '@antasphere/chassis-server/accounts';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PRDCT-1811: the tombstone's email fingerprint is KEYED. `erasures.jsonl`
|
|
10
|
+
* rides in the unencrypted data tarball of every backup, and a plain
|
|
11
|
+
* sha256 of an address is a dictionary lookup away from the address — an
|
|
12
|
+
* off-site backup would list who asked to be forgotten. The key is the
|
|
13
|
+
* instance's auth secret, which travels only in the encrypted config
|
|
14
|
+
* archive.
|
|
15
|
+
*
|
|
16
|
+
* PRDCT-1809: a replay whose delete fails reports the tombstone as REFUSED
|
|
17
|
+
* instead of swallowing it — the boot turns that into a fail-closed verdict.
|
|
18
|
+
*/
|
|
19
|
+
const silent = { warn() {}, error() {}, info() {}, debug() {} } as unknown as ConstructorParameters<
|
|
20
|
+
typeof ErasureLog
|
|
21
|
+
>[1];
|
|
22
|
+
|
|
23
|
+
const KEY = 'unit-test-auth-secret-0123456789abcdef0123456789abcdef';
|
|
24
|
+
|
|
25
|
+
describe('emailHash (PRDCT-1811)', () => {
|
|
26
|
+
it('is an HMAC under the key: 64 hex, key-dependent, and never the plain sha256 of the address', () => {
|
|
27
|
+
const fp = emailHash('Alice@Example.com', KEY);
|
|
28
|
+
expect(fp).toMatch(/^[0-9a-f]{64}$/);
|
|
29
|
+
expect(fp).not.toBe(createHash('sha256').update('alice@example.com').digest('hex'));
|
|
30
|
+
expect(emailHash('alice@example.com', 'another-key-0123456789abcdef0123456789abcdef')).not.toBe(fp);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('normalizes the address (trim + lowercase) so the operator correlation survives casing', () => {
|
|
34
|
+
expect(emailHash(' ALICE@example.COM ', KEY)).toBe(emailHash('alice@example.com', KEY));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('ErasureLog', () => {
|
|
39
|
+
let dir: string;
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
dir = mkdtempSync(join(tmpdir(), 'erasure-log-'));
|
|
42
|
+
});
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
rmSync(dir, { recursive: true, force: true });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('append writes the keyed fingerprint, never the address', async () => {
|
|
48
|
+
const log = new ErasureLog(dir, silent, KEY);
|
|
49
|
+
await log.append({ id: 'u1', email: 'Alice@Example.com' });
|
|
50
|
+
const raw = readFileSync(join(dir, 'erasures.jsonl'), 'utf8');
|
|
51
|
+
expect(raw).not.toContain('example.com');
|
|
52
|
+
const line = JSON.parse(raw.trim()) as { userId: string; emailHash: string };
|
|
53
|
+
expect(line.userId).toBe('u1');
|
|
54
|
+
expect(line.emailHash).toBe(emailHash('alice@example.com', KEY));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('replay reports a tombstone whose delete throws as REFUSED, with its cause (PRDCT-1809)', async () => {
|
|
58
|
+
const log = new ErasureLog(dir, silent, KEY);
|
|
59
|
+
await log.append({ id: 'gone', email: 'gone@example.com' });
|
|
60
|
+
await log.append({ id: 'owner', email: 'owner@example.com' });
|
|
61
|
+
await log.append({ id: 'absent', email: 'absent@example.com' });
|
|
62
|
+
const boom = new Error('last owner');
|
|
63
|
+
const result = await log.replay(
|
|
64
|
+
async (id) => id !== 'absent',
|
|
65
|
+
async (id) => {
|
|
66
|
+
if (id === 'owner') throw boom;
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
expect(result.replayed).toEqual(['gone']);
|
|
70
|
+
expect(result.refused).toEqual([{ userId: 'owner', erasedAt: expect.any(String), cause: boom }]);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { OpenAPIHono } from '@hono/zod-openapi';
|
|
3
|
+
import { pino } from 'pino';
|
|
4
|
+
import { createApp, postgresErrorCode } from '@antasphere/chassis-server';
|
|
5
|
+
import { createRuntimeState } from '@antasphere/chassis-server/util';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SL-B4 / SL-B5: a NUL byte reaching Postgres (SQLSTATE 22021) and a
|
|
9
|
+
* stringify-breaking payload (RangeError) both surfaced as anonymous 500s —
|
|
10
|
+
* and the 22021 path logged the failing statement together with its bound
|
|
11
|
+
* parameters. Both are client input; both must answer 4xx, quietly.
|
|
12
|
+
*/
|
|
13
|
+
class PgError extends Error {
|
|
14
|
+
constructor(
|
|
15
|
+
public code: string,
|
|
16
|
+
message: string
|
|
17
|
+
) {
|
|
18
|
+
super(message);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function appThrowing(error: unknown) {
|
|
23
|
+
const lines: string[] = [];
|
|
24
|
+
const logger = pino({ level: 'error' }, { write: (chunk: string) => lines.push(chunk) });
|
|
25
|
+
const api = new OpenAPIHono();
|
|
26
|
+
api.get('/boom', () => {
|
|
27
|
+
throw error;
|
|
28
|
+
});
|
|
29
|
+
const app = await createApp({
|
|
30
|
+
logger,
|
|
31
|
+
state: createRuntimeState(),
|
|
32
|
+
publicDir: '/nonexistent-public-dir',
|
|
33
|
+
displayName: 'Things',
|
|
34
|
+
api
|
|
35
|
+
});
|
|
36
|
+
return { app, lines };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('postgresErrorCode', () => {
|
|
40
|
+
it('reads the SQLSTATE off a driver error and nothing else', () => {
|
|
41
|
+
expect(postgresErrorCode(new PgError('22021', 'invalid byte sequence'))).toBe('22021');
|
|
42
|
+
expect(postgresErrorCode(new Error('plain'))).toBeUndefined();
|
|
43
|
+
expect(postgresErrorCode(null)).toBeUndefined();
|
|
44
|
+
expect(postgresErrorCode({ code: 42 })).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('app.onError client-input mappings', () => {
|
|
49
|
+
it('maps Postgres 22021 (NUL byte in text) to a quiet 400', async () => {
|
|
50
|
+
const { app, lines } = await appThrowing(
|
|
51
|
+
new PgError('22021', 'invalid byte sequence for encoding "UTF8": 0x00')
|
|
52
|
+
);
|
|
53
|
+
const res = await app.request('/api/v1/boom');
|
|
54
|
+
expect(res.status).toBe(400);
|
|
55
|
+
expect(await res.json()).toMatchObject({ error: { code: 'invalid_characters' } });
|
|
56
|
+
// The pg error object carries the statement AND its bound parameters.
|
|
57
|
+
expect(lines.join('')).toBe('');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('maps 22P05 (untranslatable character) the same way', async () => {
|
|
61
|
+
const { app } = await appThrowing(new PgError('22P05', 'untranslatable character'));
|
|
62
|
+
expect((await app.request('/api/v1/boom')).status).toBe(400);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('maps a stringify stack overflow to 400, not 500', async () => {
|
|
66
|
+
const { app } = await appThrowing(new RangeError('Maximum call stack size exceeded'));
|
|
67
|
+
const res = await app.request('/api/v1/boom');
|
|
68
|
+
expect(res.status).toBe(400);
|
|
69
|
+
expect(await res.json()).toMatchObject({ error: { code: 'payload_too_deep' } });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('maps "Invalid string length" the same way', async () => {
|
|
73
|
+
const { app } = await appThrowing(new RangeError('Invalid string length'));
|
|
74
|
+
expect((await app.request('/api/v1/boom')).status).toBe(400);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('keeps a genuine server fault a logged 500', async () => {
|
|
78
|
+
const { app, lines } = await appThrowing(new Error('database is on fire'));
|
|
79
|
+
const res = await app.request('/api/v1/boom');
|
|
80
|
+
expect(res.status).toBe(500);
|
|
81
|
+
expect(await res.json()).toMatchObject({ error: { code: 'internal' } });
|
|
82
|
+
expect(lines.join('')).toContain('unhandled error');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('keeps an unrelated RangeError a 500 — narrow on the two known messages', async () => {
|
|
86
|
+
const { app } = await appThrowing(new RangeError('toFixed() digits argument must be between 0 and 100'));
|
|
87
|
+
expect((await app.request('/api/v1/boom')).status).toBe(500);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { RESERVED_EXPORT_ENTRY_NAMES, resolveExportEntries } from '@antasphere/chassis-server/api';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The tool's export entries, resolved before the zip stream starts: a name is
|
|
6
|
+
* sanitized like every other entry name, the rows are the JSON the bundle
|
|
7
|
+
* carries, and a name that lands on another entry is an error.
|
|
8
|
+
*/
|
|
9
|
+
describe('resolveExportEntries (the `api.exportEntries` slot)', () => {
|
|
10
|
+
it('writes each entry as <name>.json, rows as indented JSON, in the order given', () => {
|
|
11
|
+
const resolved = resolveExportEntries([
|
|
12
|
+
{ name: 'things', rows: [{ id: 't1' }] },
|
|
13
|
+
{ name: 'gadgets', rows: [] }
|
|
14
|
+
]);
|
|
15
|
+
expect(resolved.map((e) => e.entryName)).toEqual(['things.json', 'gadgets.json']);
|
|
16
|
+
expect(resolved[0]!.buffer.toString('utf8')).toBe(JSON.stringify([{ id: 't1' }], null, 2) + '\n');
|
|
17
|
+
expect(resolved[1]!.buffer.toString('utf8')).toBe('[]\n');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('an empty list resolves to nothing', () => {
|
|
21
|
+
expect(resolveExportEntries([])).toEqual([]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('sanitizes a hostile name: no separator, no control character, bounded, never empty', () => {
|
|
25
|
+
const names = resolveExportEntries([
|
|
26
|
+
{ name: '../../etc/cron.d/x', rows: [] },
|
|
27
|
+
{ name: 'a\u0000b\u001fc\u007fd\\e', rows: [] },
|
|
28
|
+
{ name: 'x'.repeat(500), rows: [] },
|
|
29
|
+
{ name: '', rows: [] }
|
|
30
|
+
]).map((e) => e.entryName);
|
|
31
|
+
expect(names).toEqual([
|
|
32
|
+
'.._.._etc_cron.d_x.json',
|
|
33
|
+
'a_b_c_d_e.json',
|
|
34
|
+
`${'x'.repeat(150)}.json`,
|
|
35
|
+
'file.json'
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('the reserved list is the chassis entries of the bundle', () => {
|
|
40
|
+
expect([...RESERVED_EXPORT_ENTRY_NAMES]).toEqual([
|
|
41
|
+
'manifest',
|
|
42
|
+
'workspace',
|
|
43
|
+
'members',
|
|
44
|
+
'invitations',
|
|
45
|
+
'api-keys',
|
|
46
|
+
'projects',
|
|
47
|
+
'project_members',
|
|
48
|
+
'teams',
|
|
49
|
+
'team_members',
|
|
50
|
+
'project_teams',
|
|
51
|
+
'audit-log',
|
|
52
|
+
'files',
|
|
53
|
+
'skipped-blobs'
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it.each([...RESERVED_EXPORT_ENTRY_NAMES])('refuses the chassis entry name %s', (name) => {
|
|
58
|
+
expect(() => resolveExportEntries([{ name, rows: [] }])).toThrow(/already an entry of the bundle/);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('refuses a collision that only differs by case, or that appears through sanitizing', () => {
|
|
62
|
+
expect(() => resolveExportEntries([{ name: 'Files', rows: [] }])).toThrow(/already an entry/);
|
|
63
|
+
expect(() =>
|
|
64
|
+
resolveExportEntries([
|
|
65
|
+
{ name: 'a/b', rows: [] },
|
|
66
|
+
{ name: 'a_b', rows: [] }
|
|
67
|
+
])
|
|
68
|
+
).toThrow(/"a_b" is already an entry/);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('refuses the same tool name twice', () => {
|
|
72
|
+
expect(() =>
|
|
73
|
+
resolveExportEntries([
|
|
74
|
+
{ name: 'things', rows: [] },
|
|
75
|
+
{ name: 'things', rows: [] }
|
|
76
|
+
])
|
|
77
|
+
).toThrow(/"things" is already an entry/);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('refuses a shape that is not { name, rows[] }, and rows that do not serialize', () => {
|
|
81
|
+
expect(() => resolveExportEntries([{ name: 'things', rows: 'nope' } as never])).toThrow(/every entry is/);
|
|
82
|
+
expect(() => resolveExportEntries({} as never)).toThrow(/must return an array/);
|
|
83
|
+
expect(() => resolveExportEntries([{ name: 'things', rows: [{ n: 1n }] }])).toThrow();
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { sanitizeEntryName } from '@antasphere/chassis-server/api';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Zip-slip guard: export entry names embed the user-supplied originalName.
|
|
6
|
+
* A hostile name must never yield a path separator (entry escaping the
|
|
7
|
+
* extraction directory) or control characters, and must stay bounded.
|
|
8
|
+
*/
|
|
9
|
+
describe('export sanitizeEntryName (zip-slip guard)', () => {
|
|
10
|
+
it('neutralizes path traversal and separators', () => {
|
|
11
|
+
expect(sanitizeEntryName('../../etc/passwd')).toBe('.._.._etc_passwd');
|
|
12
|
+
expect(sanitizeEntryName('..\\..\\windows\\system32')).toBe('.._.._windows_system32');
|
|
13
|
+
expect(sanitizeEntryName('a/b\\c')).toBe('a_b_c');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('strips control characters', () => {
|
|
17
|
+
expect(sanitizeEntryName('a\u0000b\u001fc\u007fd')).toBe('a_b_c_d');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('never returns an empty name', () => {
|
|
21
|
+
expect(sanitizeEntryName('')).toBe('file');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('caps the length at 150', () => {
|
|
25
|
+
expect(sanitizeEntryName('x'.repeat(500))).toHaveLength(150);
|
|
26
|
+
});
|
|
27
|
+
});
|