@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,205 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
|
3
|
+
import { appendFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import pg from 'pg';
|
|
6
|
+
import {
|
|
7
|
+
createDatabase,
|
|
8
|
+
createTestApp,
|
|
9
|
+
readJson,
|
|
10
|
+
startPostgres,
|
|
11
|
+
SETUP_TOKEN,
|
|
12
|
+
type TestApp
|
|
13
|
+
} from './helpers.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* PRDCT-1809 (found by the adversarial e2e pass of 2026-08-29, red repro on
|
|
17
|
+
* adv/sl-e2e@c3d1416): the boot-time tombstone replay when the erased
|
|
18
|
+
* subject is the SOLE active owner in the restored dump.
|
|
19
|
+
*
|
|
20
|
+
* Before the fix the replay called the cascade OUTSIDE the last-owner guard:
|
|
21
|
+
* the 0009 trigger refused the membership delete mid-cascade, the error was
|
|
22
|
+
* swallowed, boot continued (readyz 200, PII back, password working, no
|
|
23
|
+
* audit row) and — because Better Auth drops the account rows BEFORE the
|
|
24
|
+
* user row — the owner was left half-erased (no credential, membership
|
|
25
|
+
* intact).
|
|
26
|
+
*
|
|
27
|
+
* The contract now: FAIL CLOSED. The guard refuses before any row is
|
|
28
|
+
* touched; the boot audits the refusal, keeps /readyz red with the reason
|
|
29
|
+
* and CLOSES the service surface (503 service_closed on every route but the
|
|
30
|
+
* probes) until an operator promotes another owner and restarts, at which
|
|
31
|
+
* point the replay erases the subject under the guard and the instance
|
|
32
|
+
* serves.
|
|
33
|
+
*/
|
|
34
|
+
const OWNER = { email: 'alice@adv.test', name: 'Alice', password: 'alice-password-12345' };
|
|
35
|
+
const BOB_ID = 'bob-adv-restore-0001';
|
|
36
|
+
const json = (body: unknown) => ({
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'content-type': 'application/json' },
|
|
39
|
+
body: JSON.stringify(body)
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let container: StartedPostgreSqlContainer;
|
|
43
|
+
beforeAll(async () => {
|
|
44
|
+
container = await startPostgres();
|
|
45
|
+
}, 180_000);
|
|
46
|
+
afterAll(async () => {
|
|
47
|
+
await container?.stop();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
async function seedMember(a: TestApp, id: string, email: string): Promise<string> {
|
|
51
|
+
await a.db.pool.query(
|
|
52
|
+
`INSERT INTO "user" (id, name, email, email_verified, created_at, updated_at)
|
|
53
|
+
VALUES ($1, $2, $3, true, now(), now())`,
|
|
54
|
+
[id, id, email]
|
|
55
|
+
);
|
|
56
|
+
await a.db.pool.query(
|
|
57
|
+
`INSERT INTO account (id, user_id, provider_id, account_id, password, created_at, updated_at)
|
|
58
|
+
VALUES ($1, $2, 'credential', $3, 'hash', now(), now())`,
|
|
59
|
+
[`acc-${id}`, id, id]
|
|
60
|
+
);
|
|
61
|
+
const ws = await a.db.pool.query<{ id: string }>(`SELECT id FROM workspaces LIMIT 1`);
|
|
62
|
+
await a.db.pool.query(
|
|
63
|
+
`INSERT INTO workspace_members (workspace_id, user_id, role, origin, is_active)
|
|
64
|
+
VALUES ($1, $2, 'member', 'local', true)`,
|
|
65
|
+
[ws.rows[0]!.id, id]
|
|
66
|
+
);
|
|
67
|
+
return ws.rows[0]!.id;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
describe('PRDCT-1809: tombstone replay when the erased subject is the sole active owner in the restored dump', () => {
|
|
71
|
+
it('fails CLOSED: nothing touched, refusal audited, readiness red, the service surface 503s; a promoted owner + restart erases and serves', async () => {
|
|
72
|
+
const url = await createDatabase(container, 'adv_replay_owner');
|
|
73
|
+
let app: TestApp = await createTestApp(url);
|
|
74
|
+
const dataDir = app.env.DATA_DIR;
|
|
75
|
+
const setup = await app.app.request(
|
|
76
|
+
'/api/v1/setup',
|
|
77
|
+
json({ setupToken: SETUP_TOKEN, instanceName: 'Adv', owner: OWNER })
|
|
78
|
+
);
|
|
79
|
+
expect(setup.status).toBe(201);
|
|
80
|
+
const { ownerUserId } = await readJson(setup);
|
|
81
|
+
// Bob is a plain member in the OLDER dump being restored.
|
|
82
|
+
const workspaceId = await seedMember(app, BOB_ID, 'bob@adv.test');
|
|
83
|
+
|
|
84
|
+
// The live timeline: Alice promoted Bob, Bob erased Alice → tombstone.
|
|
85
|
+
// The dump predates the promotion: Alice is still the sole active owner.
|
|
86
|
+
appendFileSync(
|
|
87
|
+
join(dataDir, 'erasures.jsonl'),
|
|
88
|
+
JSON.stringify({ userId: ownerUserId, emailHash: 'x', at: new Date().toISOString() }) + '\n'
|
|
89
|
+
);
|
|
90
|
+
await app.stop();
|
|
91
|
+
|
|
92
|
+
// ── Boot 1: the refusal ───────────────────────────────────────────────
|
|
93
|
+
app = await createTestApp(url, { DATA_DIR: dataDir });
|
|
94
|
+
try {
|
|
95
|
+
expect(app.state.ready).toBe(false);
|
|
96
|
+
expect(app.state.closed).toMatch(/erasure tombstone/);
|
|
97
|
+
|
|
98
|
+
const ready = await app.app.request('/readyz');
|
|
99
|
+
expect(ready.status).toBe(503);
|
|
100
|
+
const readyBody = await readJson(ready);
|
|
101
|
+
expect(readyBody).toMatchObject({
|
|
102
|
+
status: 'unavailable',
|
|
103
|
+
reason: expect.stringContaining('erasure tombstone')
|
|
104
|
+
});
|
|
105
|
+
// Coarse on purpose: the probe is unauthenticated and the subject asked to be forgotten.
|
|
106
|
+
expect(JSON.stringify(readyBody)).not.toContain(ownerUserId);
|
|
107
|
+
expect(JSON.stringify(readyBody)).not.toContain(OWNER.email);
|
|
108
|
+
// …but the remedy names the cause the operator faces: a last-owner refusal, not a generic failure.
|
|
109
|
+
expect(readyBody.reason).toContain('promote another member to owner');
|
|
110
|
+
expect(readyBody.reason).not.toContain('re-erasure(s) failed');
|
|
111
|
+
expect((await app.app.request('/healthz')).status).toBe(200);
|
|
112
|
+
|
|
113
|
+
// The surface is CLOSED: the resurrected owner cannot sign in, nothing else serves either.
|
|
114
|
+
const signIn = await app.app.request(
|
|
115
|
+
'/api/v1/auth/sign-in/email',
|
|
116
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
117
|
+
);
|
|
118
|
+
expect(signIn.status).toBe(503);
|
|
119
|
+
expect(await readJson(signIn)).toMatchObject({ error: { code: 'service_closed' } });
|
|
120
|
+
expect((await app.app.request('/api/v1/me')).status).toBe(503);
|
|
121
|
+
// Every other mount is behind the closure too: the dashboard, the share-link
|
|
122
|
+
// viewer, MCP, discovery, static assets.
|
|
123
|
+
for (const path of [
|
|
124
|
+
'/',
|
|
125
|
+
'/v/not-a-real-secret/',
|
|
126
|
+
'/mcp',
|
|
127
|
+
'/.well-known/openid-configuration',
|
|
128
|
+
'/favicon.ico'
|
|
129
|
+
]) {
|
|
130
|
+
const res = await app.app.request(path, { headers: { accept: 'text/html' } });
|
|
131
|
+
expect(res.status, path).toBe(503);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// NOTHING was touched: no half-erased owner (credential, membership, row all intact).
|
|
135
|
+
const acc = await app.db.pool.query(`SELECT count(*)::int AS n FROM account WHERE user_id = $1`, [
|
|
136
|
+
ownerUserId
|
|
137
|
+
]);
|
|
138
|
+
const mem = await app.db.pool.query(
|
|
139
|
+
`SELECT count(*)::int AS n FROM workspace_members WHERE user_id = $1`,
|
|
140
|
+
[ownerUserId]
|
|
141
|
+
);
|
|
142
|
+
const u = await app.db.pool.query(`SELECT email FROM "user" WHERE id = $1`, [ownerUserId]);
|
|
143
|
+
expect(acc.rows[0].n).toBe(1);
|
|
144
|
+
expect(mem.rows[0].n).toBe(1);
|
|
145
|
+
expect(u.rows).toHaveLength(1);
|
|
146
|
+
|
|
147
|
+
// The refusal is on the record; no replay row was minted.
|
|
148
|
+
const audit = await app.db.pool.query<{
|
|
149
|
+
action: string;
|
|
150
|
+
actor_via: string | null;
|
|
151
|
+
resource_id: string;
|
|
152
|
+
}>(`SELECT action, actor_via, resource_id FROM audit_log WHERE action LIKE 'user.erasure_replay%'`);
|
|
153
|
+
expect(audit.rows).toEqual([
|
|
154
|
+
{ action: 'user.erasure_replay_refused', actor_via: 'system', resource_id: ownerUserId }
|
|
155
|
+
]);
|
|
156
|
+
} finally {
|
|
157
|
+
await app.stop();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── The operator's remedy (the runbook's psql statement), then restart ──
|
|
161
|
+
const operator = new pg.Client({ connectionString: url });
|
|
162
|
+
await operator.connect();
|
|
163
|
+
try {
|
|
164
|
+
await operator.query(
|
|
165
|
+
`UPDATE workspace_members SET role = 'owner', is_active = true WHERE workspace_id = $1 AND user_id = $2`,
|
|
166
|
+
[workspaceId, BOB_ID]
|
|
167
|
+
);
|
|
168
|
+
} finally {
|
|
169
|
+
await operator.end();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── Boot 2: the replay applies under the guard and the instance serves ─
|
|
173
|
+
app = await createTestApp(url, { DATA_DIR: dataDir });
|
|
174
|
+
try {
|
|
175
|
+
expect(app.state.closed).toBeNull();
|
|
176
|
+
expect((await app.app.request('/readyz')).status).toBe(200);
|
|
177
|
+
const users = await app.db.pool.query(`SELECT 1 FROM "user" WHERE id = $1`, [ownerUserId]);
|
|
178
|
+
expect(users.rows).toHaveLength(0);
|
|
179
|
+
const mem = await app.db.pool.query(`SELECT 1 FROM workspace_members WHERE user_id = $1`, [
|
|
180
|
+
ownerUserId
|
|
181
|
+
]);
|
|
182
|
+
expect(mem.rows).toHaveLength(0);
|
|
183
|
+
const signIn = await app.app.request(
|
|
184
|
+
'/api/v1/auth/sign-in/email',
|
|
185
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
186
|
+
);
|
|
187
|
+
expect(signIn.status).not.toBe(200);
|
|
188
|
+
const audit = await app.db.pool.query<{ action: string }>(
|
|
189
|
+
`SELECT action FROM audit_log WHERE action LIKE 'user.erasure_replay%' ORDER BY created_at`
|
|
190
|
+
);
|
|
191
|
+
expect(audit.rows.map((r) => r.action)).toEqual([
|
|
192
|
+
'user.erasure_replay_refused',
|
|
193
|
+
'user.erasure_replayed'
|
|
194
|
+
]);
|
|
195
|
+
// Bob owns the workspace; the workspace kept an active owner throughout.
|
|
196
|
+
const owners = await app.db.pool.query<{ user_id: string }>(
|
|
197
|
+
`SELECT user_id FROM workspace_members WHERE workspace_id = $1 AND role = 'owner' AND is_active`,
|
|
198
|
+
[workspaceId]
|
|
199
|
+
);
|
|
200
|
+
expect(owners.rows.map((r) => r.user_id)).toEqual([BOB_ID]);
|
|
201
|
+
} finally {
|
|
202
|
+
await app.stop();
|
|
203
|
+
}
|
|
204
|
+
}, 180_000);
|
|
205
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildPepperRegistry, parseApiKeyPeppers } from '@antasphere/chassis-server/apikeys';
|
|
3
|
+
import { buildEnvSchema } from '@antasphere/chassis-server/env';
|
|
4
|
+
|
|
5
|
+
// The chassis schema alone: API_KEY_PEPPERS is a chassis key.
|
|
6
|
+
const envSchema = buildEnvSchema({ version: '0.0.0-test' });
|
|
7
|
+
|
|
8
|
+
const AUTH_SECRET = 'unit-auth-secret-0123456789abcdef0123456789abcdef';
|
|
9
|
+
const SECRET_A = 'pepper-secret-a-0123456789abcdef0123456789abcdef';
|
|
10
|
+
const SECRET_B = 'pepper-secret-b-0123456789abcdef0123456789abcdef';
|
|
11
|
+
|
|
12
|
+
describe('buildPepperRegistry', () => {
|
|
13
|
+
it('zero-config: registry is exactly { 1: authSecret }, current = 1', () => {
|
|
14
|
+
const registry = buildPepperRegistry(AUTH_SECRET);
|
|
15
|
+
expect(registry.current).toBe(1);
|
|
16
|
+
expect(registry.v1Pinned).toBe(false);
|
|
17
|
+
// Version 1 IS the auth secret verbatim — the pre-versioning pepper.
|
|
18
|
+
expect(registry.get(1)).toBe(AUTH_SECRET);
|
|
19
|
+
expect(registry.get(2)).toBeUndefined();
|
|
20
|
+
expect(registry.get(0)).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('adding a version keeps v1 = authSecret and mints under the highest', () => {
|
|
24
|
+
const registry = buildPepperRegistry(AUTH_SECRET, `2:${SECRET_A}`);
|
|
25
|
+
expect(registry.current).toBe(2);
|
|
26
|
+
expect(registry.v1Pinned).toBe(false);
|
|
27
|
+
expect(registry.get(1)).toBe(AUTH_SECRET);
|
|
28
|
+
expect(registry.get(2)).toBe(SECRET_A);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('pinning version 1 overrides the AUTH_SECRET default (the rotation prerequisite)', () => {
|
|
32
|
+
const registry = buildPepperRegistry(
|
|
33
|
+
'rotated-auth-secret-now-something-else-entirely',
|
|
34
|
+
`1:${SECRET_A};2:${SECRET_B}`
|
|
35
|
+
);
|
|
36
|
+
expect(registry.current).toBe(2);
|
|
37
|
+
expect(registry.v1Pinned).toBe(true);
|
|
38
|
+
expect(registry.get(1)).toBe(SECRET_A);
|
|
39
|
+
expect(registry.get(2)).toBe(SECRET_B);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('current is the highest version regardless of entry order', () => {
|
|
43
|
+
const registry = buildPepperRegistry(AUTH_SECRET, `3:${SECRET_B};2:${SECRET_A}`);
|
|
44
|
+
expect(registry.current).toBe(3);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('an unknown version yields undefined — the caller fails closed', () => {
|
|
48
|
+
const registry = buildPepperRegistry(AUTH_SECRET, `2:${SECRET_A}`);
|
|
49
|
+
expect(registry.get(7)).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('parseApiKeyPeppers', () => {
|
|
54
|
+
it('tolerates whitespace and trailing separators', () => {
|
|
55
|
+
const map = parseApiKeyPeppers(` 1:${SECRET_A}; 2:${SECRET_B} ;`);
|
|
56
|
+
expect(map.get(1)).toBe(SECRET_A);
|
|
57
|
+
// Trailing whitespace after the secret survives the entry trim only at
|
|
58
|
+
// the edges — the secret itself is taken verbatim after the first colon.
|
|
59
|
+
expect(map.get(2)).toBe(`${SECRET_B}`);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('splits each entry at the FIRST colon so secrets may contain colons', () => {
|
|
63
|
+
const secretWithColon = `left:${SECRET_A}`;
|
|
64
|
+
const map = parseApiKeyPeppers(`2:${secretWithColon}`);
|
|
65
|
+
expect(map.get(2)).toBe(secretWithColon);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it.each([
|
|
69
|
+
['no colon', 'abcdef'],
|
|
70
|
+
['empty version', `:${SECRET_A}`],
|
|
71
|
+
['non-integer version', `two:${SECRET_A}`],
|
|
72
|
+
['version 0', `0:${SECRET_A}`],
|
|
73
|
+
['version beyond smallint', `32768:${SECRET_A}`],
|
|
74
|
+
['duplicate version', `2:${SECRET_A};2:${SECRET_B}`],
|
|
75
|
+
['short secret', '2:tooshort'],
|
|
76
|
+
['only separators', ' ; ; ']
|
|
77
|
+
])('rejects %s', (_label, raw) => {
|
|
78
|
+
expect(() => parseApiKeyPeppers(raw)).toThrow();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('never echoes the secret in the duplicate/short error messages', () => {
|
|
82
|
+
try {
|
|
83
|
+
parseApiKeyPeppers(`2:${SECRET_A};2:${SECRET_B}`);
|
|
84
|
+
expect.unreachable();
|
|
85
|
+
} catch (err) {
|
|
86
|
+
expect((err as Error).message).not.toContain(SECRET_A);
|
|
87
|
+
expect((err as Error).message).not.toContain(SECRET_B);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('env schema API_KEY_PEPPERS', () => {
|
|
93
|
+
const minimal = { DATABASE_URL: 'postgres://u:p@localhost:5432/db' };
|
|
94
|
+
|
|
95
|
+
it('is optional and blank means unset (compose VAR= convention)', () => {
|
|
96
|
+
expect(envSchema.parse(minimal).API_KEY_PEPPERS).toBeUndefined();
|
|
97
|
+
expect(envSchema.parse({ ...minimal, API_KEY_PEPPERS: '' }).API_KEY_PEPPERS).toBeUndefined();
|
|
98
|
+
expect(envSchema.parse({ ...minimal, API_KEY_PEPPERS: ' ' }).API_KEY_PEPPERS).toBeUndefined();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('accepts a valid map and preserves it verbatim', () => {
|
|
102
|
+
const raw = `1:${SECRET_A};2:${SECRET_B}`;
|
|
103
|
+
expect(envSchema.parse({ ...minimal, API_KEY_PEPPERS: raw }).API_KEY_PEPPERS).toBe(raw);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('refuses boot on a malformed value', () => {
|
|
107
|
+
const result = envSchema.safeParse({ ...minimal, API_KEY_PEPPERS: '2:tooshort' });
|
|
108
|
+
expect(result.success).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import type { Db } from '@antasphere/chassis-db';
|
|
3
|
+
import { ApiKeyService, buildPepperRegistry } from '@antasphere/chassis-server/apikeys';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The key prefix is the tool's, and the service builds its key pattern from
|
|
7
|
+
* it. A prefix is matched LITERALLY: a RegExp metacharacter in it (`a.b`) must
|
|
8
|
+
* never widen the pattern, or a bearer of another shape (`axb_…`) would be
|
|
9
|
+
* taken for an API key of this tool. `isToken` is the shape check alone (no
|
|
10
|
+
* lookup), so no database is needed here; `resolve` reads the same pattern.
|
|
11
|
+
*/
|
|
12
|
+
const KEY_ID = 'AAAAAAAA';
|
|
13
|
+
const SECRET = 'a'.repeat(43);
|
|
14
|
+
const peppers = buildPepperRegistry('unit-auth-secret-0123456789abcdef0123456789abcdef');
|
|
15
|
+
const service = (prefix: string) => new ApiKeyService(undefined as unknown as Db, peppers, prefix);
|
|
16
|
+
|
|
17
|
+
describe('ApiKeyService key prefix', () => {
|
|
18
|
+
it('matches a prefix holding a RegExp metacharacter literally', () => {
|
|
19
|
+
const keys = service('a.b');
|
|
20
|
+
expect(keys.isToken(`a.b_${KEY_ID}_${SECRET}`)).toBe(true);
|
|
21
|
+
expect(keys.isToken(`axb_${KEY_ID}_${SECRET}`)).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('accepts its own prefix only, in the fixed shape', () => {
|
|
25
|
+
const keys = service('thg');
|
|
26
|
+
expect(keys.isToken(`thg_${KEY_ID}_${SECRET}`)).toBe(true);
|
|
27
|
+
expect(keys.isToken(`other_${KEY_ID}_${SECRET}`)).toBe(false);
|
|
28
|
+
expect(keys.isToken(`thg_${KEY_ID}`)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('refuses to be built without a prefix', () => {
|
|
32
|
+
expect(() => service('')).toThrow('the key prefix is required');
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { and, type SQL } from 'drizzle-orm';
|
|
3
|
+
import { PgDialect } from 'drizzle-orm/pg-core';
|
|
4
|
+
import { auditListQuerySchema } from '@antasphere/chassis-contract';
|
|
5
|
+
import {
|
|
6
|
+
auditFilterConditions,
|
|
7
|
+
cursorId,
|
|
8
|
+
escapeLike,
|
|
9
|
+
parseActionList,
|
|
10
|
+
parseViaList
|
|
11
|
+
} from '@antasphere/chassis-server/audit';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The audit list's filters: what the contract admits, how the free-text
|
|
15
|
+
* ones become LIKE patterns, and that every admitted value lands in the
|
|
16
|
+
* WHERE as a bound parameter, never in the SQL text.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const parse = (query: Record<string, string>) => auditListQuerySchema.safeParse(query);
|
|
20
|
+
|
|
21
|
+
describe('the audit list query contract', () => {
|
|
22
|
+
it('admits every filter, and the page without one', () => {
|
|
23
|
+
expect(parse({}).success).toBe(true);
|
|
24
|
+
const full = parse({
|
|
25
|
+
q: 'owner@example',
|
|
26
|
+
action: 'presentation.,apikey.create,post /api/v1/things',
|
|
27
|
+
actorVia: 'session,api_key',
|
|
28
|
+
actor: 'system',
|
|
29
|
+
resourceType: 'presentation',
|
|
30
|
+
resourceId: '0f0f0f0f-0000-4000-8000-000000000000',
|
|
31
|
+
from: '2026-09-01T00:00:00.000Z',
|
|
32
|
+
to: '2026-09-19T23:59:59.999Z',
|
|
33
|
+
limit: '20'
|
|
34
|
+
});
|
|
35
|
+
expect(full.success).toBe(true);
|
|
36
|
+
if (full.success) expect(full.data.limit).toBe(20);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('refuses control characters, an unknown via, a bad date and an oversize list', () => {
|
|
40
|
+
expect(parse({ q: 'a\u0000b' }).success).toBe(false);
|
|
41
|
+
expect(parse({ resourceType: 'x\u0007' }).success).toBe(false);
|
|
42
|
+
expect(parse({ actorVia: 'session,browser' }).success).toBe(false);
|
|
43
|
+
expect(parse({ from: 'yesterday' }).success).toBe(false);
|
|
44
|
+
expect(parse({ action: "presentation.'; drop table audit_log; --" }).success).toBe(false);
|
|
45
|
+
expect(parse({ action: Array.from({ length: 21 }, (_, i) => `a${i}.x`).join(',') }).success).toBe(false);
|
|
46
|
+
expect(parse({ actor: '' }).success).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('the free-text pattern', () => {
|
|
51
|
+
it('escapes the LIKE wildcards so typed text matches itself', () => {
|
|
52
|
+
expect(escapeLike('50%_done\\')).toBe('50\\%\\_done\\\\');
|
|
53
|
+
expect(escapeLike('plain')).toBe('plain');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('the action list', () => {
|
|
58
|
+
it('splits families from exact actions and drops blanks and repeats', () => {
|
|
59
|
+
expect(parseActionList('presentation.,apikey.create, ,apikey.create,member.')).toEqual({
|
|
60
|
+
exact: ['apikey.create'],
|
|
61
|
+
families: ['presentation.', 'member.']
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('splits and de-duplicates the via list', () => {
|
|
66
|
+
expect(parseViaList('session,api_key,session')).toEqual(['session', 'api_key']);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('the cursor', () => {
|
|
71
|
+
it('serves page 1 for a missing, malformed or out-of-range cursor', () => {
|
|
72
|
+
expect(cursorId(undefined)).toBeNull();
|
|
73
|
+
expect(cursorId('banana')).toBeNull();
|
|
74
|
+
expect(cursorId('0')).toBeNull();
|
|
75
|
+
expect(cursorId('99999999999999999999')).toBeNull();
|
|
76
|
+
expect(cursorId('42')).toBe(42);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('the WHERE conditions', () => {
|
|
81
|
+
const dialect = new PgDialect();
|
|
82
|
+
const render = (conditions: SQL[]) => dialect.sqlToQuery(and(...conditions)!);
|
|
83
|
+
|
|
84
|
+
it('is empty when nothing filters', () => {
|
|
85
|
+
const parsed = parse({});
|
|
86
|
+
expect(parsed.success).toBe(true);
|
|
87
|
+
if (parsed.success) expect(auditFilterConditions(parsed.data)).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('binds every value as a parameter and never inlines it into the SQL text', () => {
|
|
91
|
+
const parsed = parse({
|
|
92
|
+
q: "o'neil%",
|
|
93
|
+
action: 'presentation.,apikey.create',
|
|
94
|
+
actorVia: 'session,api_key',
|
|
95
|
+
actor: 'user-1',
|
|
96
|
+
resourceType: 'presentation',
|
|
97
|
+
resourceId: 'deck-1',
|
|
98
|
+
from: '2026-09-01T00:00:00.000Z',
|
|
99
|
+
to: '2026-09-19T00:00:00.000Z'
|
|
100
|
+
});
|
|
101
|
+
expect(parsed.success).toBe(true);
|
|
102
|
+
if (!parsed.success) return;
|
|
103
|
+
const conditions = auditFilterConditions(parsed.data);
|
|
104
|
+
// one condition per filter, AND-ed by the route
|
|
105
|
+
expect(conditions).toHaveLength(8);
|
|
106
|
+
const { sql: text, params } = render(conditions);
|
|
107
|
+
expect(text).not.toContain("o'neil");
|
|
108
|
+
expect(text).not.toContain('presentation');
|
|
109
|
+
expect(text).not.toContain('user-1');
|
|
110
|
+
expect(text).toMatch(/ilike/i);
|
|
111
|
+
expect(params).toContain("%o'neil\\%%");
|
|
112
|
+
expect(params).toContain('presentation.%');
|
|
113
|
+
expect(params).toContain('apikey.create');
|
|
114
|
+
expect(params).toContain('session');
|
|
115
|
+
expect(params).toContain('api_key');
|
|
116
|
+
expect(params).toContain('user-1');
|
|
117
|
+
expect(params).toContain('presentation');
|
|
118
|
+
expect(params).toContain('deck-1');
|
|
119
|
+
// the two instants ride as parameters too, in the driver's own shape
|
|
120
|
+
expect(params.filter((p) => typeof p === 'string' && p.startsWith('2026-09-'))).toHaveLength(2);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('turns `system` into an IS NULL on the actor and keeps a user id as an equality', () => {
|
|
124
|
+
const system = parse({ actor: 'system' });
|
|
125
|
+
const user = parse({ actor: 'abc' });
|
|
126
|
+
expect(system.success && user.success).toBe(true);
|
|
127
|
+
if (!system.success || !user.success) return;
|
|
128
|
+
const nullCheck = render(auditFilterConditions(system.data));
|
|
129
|
+
const equality = render(auditFilterConditions(user.data));
|
|
130
|
+
expect(nullCheck.sql).toMatch(/actor_user_id" is null/);
|
|
131
|
+
expect(nullCheck.params).toEqual([]);
|
|
132
|
+
expect(equality.sql).toMatch(/actor_user_id" = \$1/);
|
|
133
|
+
expect(equality.params).toEqual(['abc']);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { signInOriginRefused, trustedOriginsFor } from '@antasphere/chassis-server/identity';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PRDCT-1352, the auth-layer second locks. The host gate keeps the auth
|
|
6
|
+
* surface off the viewer hostname and the cross-site guard refuses the viewer
|
|
7
|
+
* Origin before Better Auth ever runs — so these two arms are only reachable
|
|
8
|
+
* once BOTH have regressed. That is exactly why they must be pinned on their
|
|
9
|
+
* own: the verifier proved that with gate + guard bypassed, dropping the
|
|
10
|
+
* sign-in arm turns a viewer-origin sign-in from 403 into 200 + Set-Cookie.
|
|
11
|
+
*/
|
|
12
|
+
const APP = 'https://app.example.com';
|
|
13
|
+
const VIEWER = 'https://decks.example.net';
|
|
14
|
+
|
|
15
|
+
describe('trustedOriginsFor', () => {
|
|
16
|
+
it('trusts the public origin and the serving origin', () => {
|
|
17
|
+
const fn = trustedOriginsFor(APP, []);
|
|
18
|
+
expect(fn(new Request('http://localhost:5173/api/v1/auth/get-session'))).toEqual([
|
|
19
|
+
APP,
|
|
20
|
+
'http://localhost:5173'
|
|
21
|
+
]);
|
|
22
|
+
expect(fn()).toEqual([APP]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('never returns the viewer origin, even when it is the serving origin', () => {
|
|
26
|
+
const fn = trustedOriginsFor(APP, [VIEWER]);
|
|
27
|
+
expect(fn(new Request(`${VIEWER}/api/v1/auth/sign-in/email`))).toEqual([APP]);
|
|
28
|
+
// Served on the app origin, the serving origin is appended (a duplicate, harmless).
|
|
29
|
+
expect(fn(new Request(`${APP}/api/v1/auth/sign-in/email`))).toEqual([APP, APP]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('would not even trust the viewer origin if it were the configured public origin', () => {
|
|
33
|
+
// parseEnv refuses this shape; the filter is origin-blind on purpose.
|
|
34
|
+
expect(trustedOriginsFor(VIEWER, [VIEWER])()).toEqual([]);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('signInOriginRefused', () => {
|
|
39
|
+
const trustedApp = (o: string) => o === APP;
|
|
40
|
+
|
|
41
|
+
it('accepts the serving origin and any trusted origin', () => {
|
|
42
|
+
expect(
|
|
43
|
+
signInOriginRefused({
|
|
44
|
+
origin: 'http://localhost:5173',
|
|
45
|
+
servingOrigin: 'http://localhost:5173',
|
|
46
|
+
untrustedOrigins: [VIEWER],
|
|
47
|
+
isTrustedOrigin: trustedApp
|
|
48
|
+
})
|
|
49
|
+
).toBe(false);
|
|
50
|
+
expect(
|
|
51
|
+
signInOriginRefused({
|
|
52
|
+
origin: APP,
|
|
53
|
+
servingOrigin: null,
|
|
54
|
+
untrustedOrigins: [VIEWER],
|
|
55
|
+
isTrustedOrigin: trustedApp
|
|
56
|
+
})
|
|
57
|
+
).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('refuses a foreign origin', () => {
|
|
61
|
+
expect(
|
|
62
|
+
signInOriginRefused({
|
|
63
|
+
origin: 'https://evil.example',
|
|
64
|
+
servingOrigin: APP,
|
|
65
|
+
untrustedOrigins: [VIEWER],
|
|
66
|
+
isTrustedOrigin: trustedApp
|
|
67
|
+
})
|
|
68
|
+
).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('refuses the viewer origin even as the serving origin AND even if the trusted list says yes', () => {
|
|
72
|
+
expect(
|
|
73
|
+
signInOriginRefused({
|
|
74
|
+
origin: VIEWER,
|
|
75
|
+
servingOrigin: VIEWER,
|
|
76
|
+
untrustedOrigins: [VIEWER],
|
|
77
|
+
isTrustedOrigin: () => true
|
|
78
|
+
})
|
|
79
|
+
).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('is unchanged when no viewer origin is configured', () => {
|
|
83
|
+
expect(
|
|
84
|
+
signInOriginRefused({
|
|
85
|
+
origin: VIEWER,
|
|
86
|
+
servingOrigin: VIEWER,
|
|
87
|
+
untrustedOrigins: [],
|
|
88
|
+
isTrustedOrigin: trustedApp
|
|
89
|
+
})
|
|
90
|
+
).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { deferrableBodyCap } from '../../src/middleware/body-cap.js';
|
|
4
|
+
import { pendingBodyRefusal } from '../../src/middleware/body-refusal.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The size cap's deferral (PRDCT-2632): on a deferring route a declared
|
|
8
|
+
* oversize body is parked for the gate and the request loses its body, so
|
|
9
|
+
* no later middleware can read a byte of it; everywhere else hono's cap
|
|
10
|
+
* answers as before (the header refusal, the mid-stream cut, the pass).
|
|
11
|
+
*/
|
|
12
|
+
const cap = {
|
|
13
|
+
maxBytes: 100,
|
|
14
|
+
onError: (c: { json: (b: unknown, s: 413) => Response }) =>
|
|
15
|
+
c.json({ error: { code: 'too_big', message: 'the cap' } }, 413)
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function app(isDeferring: boolean) {
|
|
19
|
+
const seen: { pending: boolean; body: string | null; pulls: number }[] = [];
|
|
20
|
+
const a = new Hono();
|
|
21
|
+
a.use(
|
|
22
|
+
'*',
|
|
23
|
+
deferrableBodyCap(cap, () => isDeferring)
|
|
24
|
+
);
|
|
25
|
+
// A reader in the middle: the body a deferred request carries is gone.
|
|
26
|
+
a.post('/up', async (c) => {
|
|
27
|
+
const pending = Boolean(pendingBodyRefusal(c));
|
|
28
|
+
const body = c.req.raw.body ? await c.req.text() : null;
|
|
29
|
+
seen.push({ pending, body, pulls: body?.length ?? 0 });
|
|
30
|
+
const refusal = pendingBodyRefusal(c);
|
|
31
|
+
return refusal ? refusal() : c.json({ ok: true }, 201);
|
|
32
|
+
});
|
|
33
|
+
return { a, seen };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('deferrableBodyCap', () => {
|
|
37
|
+
it('on a deferring route: a declared oversize body is parked for the gate and dropped, nothing downstream reads it', async () => {
|
|
38
|
+
const { a, seen } = app(true);
|
|
39
|
+
const res = await a.request('/up', {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'content-length': '500', 'content-type': 'application/json' },
|
|
42
|
+
body: 'x'.repeat(500)
|
|
43
|
+
});
|
|
44
|
+
expect(res.status).toBe(413); // the handler fired the parked refusal
|
|
45
|
+
expect(seen).toEqual([{ pending: true, body: null, pulls: 0 }]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('elsewhere: the header refusal happens at once, before any handler', async () => {
|
|
49
|
+
const { a, seen } = app(false);
|
|
50
|
+
const res = await a.request('/up', {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: { 'content-length': '500' },
|
|
53
|
+
body: 'x'.repeat(500)
|
|
54
|
+
});
|
|
55
|
+
expect(res.status).toBe(413);
|
|
56
|
+
expect(seen).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('a body within the cap passes untouched on either kind of route', async () => {
|
|
60
|
+
for (const deferring of [true, false]) {
|
|
61
|
+
const { a, seen } = app(deferring);
|
|
62
|
+
const res = await a.request('/up', {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { 'content-length': '5' },
|
|
65
|
+
body: 'hello'
|
|
66
|
+
});
|
|
67
|
+
expect(res.status).toBe(201);
|
|
68
|
+
expect(seen).toEqual([{ pending: false, body: 'hello', pulls: 5 }]);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('an undeclared oversize body is still cut by the cap on a deferring route', async () => {
|
|
73
|
+
const { a, seen } = app(true);
|
|
74
|
+
const stream = new ReadableStream({
|
|
75
|
+
start(controller) {
|
|
76
|
+
for (let i = 0; i < 20; i += 1) controller.enqueue(new TextEncoder().encode('y'.repeat(10)));
|
|
77
|
+
controller.close();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
const res = await a.request('/up', { method: 'POST', body: stream, duplex: 'half' } as RequestInit);
|
|
81
|
+
expect(res.status).toBe(413);
|
|
82
|
+
expect(seen.every((s) => !s.pending)).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|