@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,236 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
|
3
|
+
import {
|
|
4
|
+
createDatabase,
|
|
5
|
+
createTestApp,
|
|
6
|
+
extractCookie,
|
|
7
|
+
readJson,
|
|
8
|
+
startPostgres,
|
|
9
|
+
type TestApp
|
|
10
|
+
} from './helpers.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Password reset / account recovery (WS3 feature B):
|
|
14
|
+
* - self-serve reset is closed (400) with no email driver, open with one;
|
|
15
|
+
* - the admin-generated reset link works with NO email driver (the SMTP-free
|
|
16
|
+
* recovery path), is single-use, revokes the target's sessions, and is
|
|
17
|
+
* authorization-gated (member 403, admin-on-owner 403);
|
|
18
|
+
* - the self-serve flow does not leak whether an email exists.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const OWNER = { email: 'owner@rec.test', name: 'Rec Owner', password: 'rec-owner-password-12' };
|
|
22
|
+
const MEMBER = { email: 'member@rec.test', name: 'Rec Member', password: 'rec-member-password-1' };
|
|
23
|
+
|
|
24
|
+
let container: StartedPostgreSqlContainer;
|
|
25
|
+
|
|
26
|
+
const json = (body: unknown) => ({
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'content-type': 'application/json' },
|
|
29
|
+
body: JSON.stringify(body)
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
async function signIn(app: TestApp, email: string, password: string): Promise<Response> {
|
|
33
|
+
return app.app.request('/api/v1/auth/sign-in/email', json({ email, password }));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
beforeAll(async () => {
|
|
37
|
+
container = await startPostgres();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
afterAll(async () => {
|
|
41
|
+
await container?.stop();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('no email driver (default)', () => {
|
|
45
|
+
let app: TestApp;
|
|
46
|
+
let ownerCookie: string;
|
|
47
|
+
let memberId: string;
|
|
48
|
+
let adminMemberId: string;
|
|
49
|
+
|
|
50
|
+
beforeAll(async () => {
|
|
51
|
+
app = await createTestApp(await createDatabase(container, 'rec_none'));
|
|
52
|
+
await app.app.request(
|
|
53
|
+
'/api/v1/setup',
|
|
54
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'Rec', owner: OWNER })
|
|
55
|
+
);
|
|
56
|
+
ownerCookie = extractCookie(await signIn(app, OWNER.email, OWNER.password));
|
|
57
|
+
|
|
58
|
+
// Add a member (invite → accept) and a separate admin.
|
|
59
|
+
const inv = await readJson(
|
|
60
|
+
await app.app.request('/api/v1/invitations', {
|
|
61
|
+
...json({ email: MEMBER.email, role: 'member' }),
|
|
62
|
+
headers: { 'content-type': 'application/json', cookie: ownerCookie }
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
const token = (inv.acceptUrl as string).split('/invite/')[1]!;
|
|
66
|
+
await app.app.request(
|
|
67
|
+
'/api/v1/invitations/accept',
|
|
68
|
+
json({ token, name: MEMBER.name, password: MEMBER.password })
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const admin = { email: 'admin@rec.test', name: 'Rec Admin', password: 'rec-admin-password-12' };
|
|
72
|
+
const inv2 = await readJson(
|
|
73
|
+
await app.app.request('/api/v1/invitations', {
|
|
74
|
+
...json({ email: admin.email, role: 'member' }),
|
|
75
|
+
headers: { 'content-type': 'application/json', cookie: ownerCookie }
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
const token2 = (inv2.acceptUrl as string).split('/invite/')[1]!;
|
|
79
|
+
await app.app.request(
|
|
80
|
+
'/api/v1/invitations/accept',
|
|
81
|
+
json({ token: token2, name: admin.name, password: admin.password })
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const { members } = await readJson(
|
|
85
|
+
await app.app.request('/api/v1/members', { headers: { cookie: ownerCookie } })
|
|
86
|
+
);
|
|
87
|
+
memberId = members.find((m: { email: string }) => m.email === MEMBER.email).id;
|
|
88
|
+
adminMemberId = members.find((m: { email: string }) => m.email === admin.email).id;
|
|
89
|
+
// Promote the admin.
|
|
90
|
+
await app.app.request(`/api/v1/members/${adminMemberId}`, {
|
|
91
|
+
...json({ role: 'admin' }),
|
|
92
|
+
method: 'PATCH',
|
|
93
|
+
headers: { 'content-type': 'application/json', cookie: ownerCookie }
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
afterAll(async () => {
|
|
98
|
+
await app.stop();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('reports passwordReset: false in discovery and closes self-serve reset', async () => {
|
|
102
|
+
const info = await readJson(await app.app.request('/api/v1/instance'));
|
|
103
|
+
expect(info.auth.passwordReset).toBe(false);
|
|
104
|
+
|
|
105
|
+
const res = await app.app.request(
|
|
106
|
+
'/api/v1/auth/request-password-reset',
|
|
107
|
+
json({ email: OWNER.email, redirectTo: 'http://localhost:3000/reset-password' })
|
|
108
|
+
);
|
|
109
|
+
expect(res.status).toBe(400); // RESET_PASSWORD_DISABLED
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('admin mints a working single-use reset link that revokes the target sessions', async () => {
|
|
113
|
+
// The member has a live session before the reset.
|
|
114
|
+
const memberCookie = extractCookie(await signIn(app, MEMBER.email, MEMBER.password));
|
|
115
|
+
expect((await app.app.request('/api/v1/me', { headers: { cookie: memberCookie } })).status).toBe(200);
|
|
116
|
+
|
|
117
|
+
const minted = await app.app.request(`/api/v1/members/${memberId}/reset-link`, {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: { cookie: ownerCookie }
|
|
120
|
+
});
|
|
121
|
+
expect(minted.status).toBe(200);
|
|
122
|
+
const { resetUrl } = await readJson(minted);
|
|
123
|
+
const token = new URL(resetUrl).searchParams.get('token')!;
|
|
124
|
+
expect(token).toBeTruthy();
|
|
125
|
+
|
|
126
|
+
const newPassword = 'rec-member-new-password-9';
|
|
127
|
+
const reset = await app.app.request('/api/v1/auth/reset-password', json({ newPassword, token }));
|
|
128
|
+
expect(reset.status).toBe(200);
|
|
129
|
+
|
|
130
|
+
// Old password is dead, the new one works.
|
|
131
|
+
expect((await signIn(app, MEMBER.email, MEMBER.password)).status).toBe(401);
|
|
132
|
+
expect((await signIn(app, MEMBER.email, newPassword)).status).toBe(200);
|
|
133
|
+
|
|
134
|
+
// The token is single-use.
|
|
135
|
+
const reuse = await app.app.request('/api/v1/auth/reset-password', json({ newPassword, token }));
|
|
136
|
+
expect(reuse.status).toBe(400);
|
|
137
|
+
|
|
138
|
+
// The pre-reset session was revoked.
|
|
139
|
+
expect((await app.app.request('/api/v1/me', { headers: { cookie: memberCookie } })).status).toBe(401);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('refuses reset-link minting to non-admins and admin-on-owner', async () => {
|
|
143
|
+
// A plain member cannot mint at all (403 from the role gate).
|
|
144
|
+
const memberCookie = extractCookie(await signIn(app, MEMBER.email, 'rec-member-new-password-9'));
|
|
145
|
+
const asMember = await app.app.request(`/api/v1/members/${memberId}/reset-link`, {
|
|
146
|
+
method: 'POST',
|
|
147
|
+
headers: { cookie: memberCookie }
|
|
148
|
+
});
|
|
149
|
+
expect(asMember.status).toBe(403);
|
|
150
|
+
|
|
151
|
+
// An admin (non-owner) cannot mint for an owner.
|
|
152
|
+
const admin = { email: 'admin@rec.test', password: 'rec-admin-password-12' };
|
|
153
|
+
const adminCookie = extractCookie(await signIn(app, admin.email, admin.password));
|
|
154
|
+
const { members } = await readJson(
|
|
155
|
+
await app.app.request('/api/v1/members', { headers: { cookie: adminCookie } })
|
|
156
|
+
);
|
|
157
|
+
const ownerId = members.find((m: { email: string }) => m.email === OWNER.email).id;
|
|
158
|
+
const asAdmin = await app.app.request(`/api/v1/members/${ownerId}/reset-link`, {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
headers: { cookie: adminCookie }
|
|
161
|
+
});
|
|
162
|
+
expect(asAdmin.status).toBe(403);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe('with an email driver', () => {
|
|
167
|
+
let app: TestApp;
|
|
168
|
+
|
|
169
|
+
beforeAll(async () => {
|
|
170
|
+
// SMTP points at a dead port — sends fail, but Better Auth runs them in the
|
|
171
|
+
// background (errors swallowed), so the endpoint still answers generically
|
|
172
|
+
// and the verification row is created regardless.
|
|
173
|
+
app = await createTestApp(await createDatabase(container, 'rec_smtp'), {
|
|
174
|
+
EMAIL_DRIVER: 'smtp',
|
|
175
|
+
SMTP_URL: 'smtp://127.0.0.1:1',
|
|
176
|
+
EMAIL_FROM: 'Rec <noreply@rec.test>'
|
|
177
|
+
});
|
|
178
|
+
await app.app.request(
|
|
179
|
+
'/api/v1/setup',
|
|
180
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'RecSmtp', owner: OWNER })
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
afterAll(async () => {
|
|
185
|
+
await app.stop();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('reports passwordReset: true and completes a self-serve reset', async () => {
|
|
189
|
+
const info = await readJson(await app.app.request('/api/v1/instance'));
|
|
190
|
+
expect(info.auth.passwordReset).toBe(true);
|
|
191
|
+
|
|
192
|
+
const res = await app.app.request(
|
|
193
|
+
'/api/v1/auth/request-password-reset',
|
|
194
|
+
json({ email: OWNER.email, redirectTo: 'http://localhost:3000/reset-password' })
|
|
195
|
+
);
|
|
196
|
+
expect(res.status).toBe(200);
|
|
197
|
+
|
|
198
|
+
// Read the token the plugin stored, then complete the reset.
|
|
199
|
+
const { rows } = await app.db.pool.query<{ identifier: string }>(
|
|
200
|
+
`SELECT identifier FROM verification WHERE identifier LIKE 'reset-password:%' ORDER BY created_at DESC LIMIT 1`
|
|
201
|
+
);
|
|
202
|
+
const token = rows[0]!.identifier.replace('reset-password:', '');
|
|
203
|
+
const newPassword = 'rec-owner-new-password-77';
|
|
204
|
+
const reset = await app.app.request('/api/v1/auth/reset-password', json({ newPassword, token }));
|
|
205
|
+
expect(reset.status).toBe(200);
|
|
206
|
+
expect((await signIn(app, OWNER.email, newPassword)).status).toBe(200);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('does not reveal whether an email exists (identical 200 for unknown)', async () => {
|
|
210
|
+
const before = await app.db.pool.query(
|
|
211
|
+
`SELECT count(*)::int AS n FROM verification WHERE identifier LIKE 'reset-password:%'`
|
|
212
|
+
);
|
|
213
|
+
const res = await app.app.request(
|
|
214
|
+
'/api/v1/auth/request-password-reset',
|
|
215
|
+
json({ email: 'nobody@rec.test', redirectTo: 'http://localhost:3000/reset-password' })
|
|
216
|
+
);
|
|
217
|
+
expect(res.status).toBe(200);
|
|
218
|
+
const after = await app.db.pool.query(
|
|
219
|
+
`SELECT count(*)::int AS n FROM verification WHERE identifier LIKE 'reset-password:%'`
|
|
220
|
+
);
|
|
221
|
+
// No new reset token minted for a non-existent account.
|
|
222
|
+
expect(after.rows[0].n).toBe(before.rows[0].n);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('rate-limits repeated reset requests', async () => {
|
|
226
|
+
let sawRateLimit = false;
|
|
227
|
+
for (let i = 0; i < 10 && !sawRateLimit; i++) {
|
|
228
|
+
const r = await app.app.request(
|
|
229
|
+
'/api/v1/auth/request-password-reset',
|
|
230
|
+
json({ email: OWNER.email, redirectTo: 'http://localhost:3000/reset-password' })
|
|
231
|
+
);
|
|
232
|
+
if (r.status === 429) sawRateLimit = true;
|
|
233
|
+
}
|
|
234
|
+
expect(sawRateLimit).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
|
3
|
+
import { desc, eq } from 'drizzle-orm';
|
|
4
|
+
import { auditLog } from '@antasphere/chassis-db';
|
|
5
|
+
import {
|
|
6
|
+
createDatabase,
|
|
7
|
+
createTestApp,
|
|
8
|
+
extractCookie,
|
|
9
|
+
readJson,
|
|
10
|
+
startPostgres,
|
|
11
|
+
type TestApp
|
|
12
|
+
} from './helpers.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Self-service account management (WS3 feature C): change your own name and
|
|
16
|
+
* password through Better-Auth-native routes, mounted under /api/v1/auth.
|
|
17
|
+
* Email change (M6) needs a delivering email driver; this app has none, so
|
|
18
|
+
* the endpoint answers 400 — the full flows live in email-change.test.ts.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const OWNER = { email: 'owner@acct.test', name: 'Acct Owner', password: 'acct-owner-password-12' };
|
|
22
|
+
|
|
23
|
+
let container: StartedPostgreSqlContainer;
|
|
24
|
+
let app: TestApp;
|
|
25
|
+
|
|
26
|
+
const json = (body: unknown) => ({
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'content-type': 'application/json' },
|
|
29
|
+
body: JSON.stringify(body)
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const withCookie = (cookie: string, body: unknown) => ({
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
35
|
+
body: JSON.stringify(body)
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
async function signIn(email: string, password: string): Promise<Response> {
|
|
39
|
+
return app.app.request('/api/v1/auth/sign-in/email', json({ email, password }));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
beforeAll(async () => {
|
|
43
|
+
container = await startPostgres();
|
|
44
|
+
app = await createTestApp(await createDatabase(container, 'acct'));
|
|
45
|
+
await app.app.request(
|
|
46
|
+
'/api/v1/setup',
|
|
47
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'Acct', owner: OWNER })
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterAll(async () => {
|
|
52
|
+
await app?.stop();
|
|
53
|
+
await container?.stop();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('profile: change your own name', () => {
|
|
57
|
+
it('updates the name and /me reflects it', async () => {
|
|
58
|
+
const cookie = extractCookie(await signIn(OWNER.email, OWNER.password));
|
|
59
|
+
const res = await app.app.request(
|
|
60
|
+
'/api/v1/auth/update-user',
|
|
61
|
+
withCookie(cookie, { name: 'Renamed Owner' })
|
|
62
|
+
);
|
|
63
|
+
expect(res.status).toBe(200);
|
|
64
|
+
const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie } }));
|
|
65
|
+
expect(me.user.name).toBe('Renamed Owner');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('change password', () => {
|
|
70
|
+
it('rejects a wrong current password', async () => {
|
|
71
|
+
const cookie = extractCookie(await signIn(OWNER.email, OWNER.password));
|
|
72
|
+
const res = await app.app.request(
|
|
73
|
+
'/api/v1/auth/change-password',
|
|
74
|
+
withCookie(cookie, { currentPassword: 'not-the-password', newPassword: 'acct-owner-brandnew-1' })
|
|
75
|
+
);
|
|
76
|
+
expect(res.status).toBe(400);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('changes the password, revokes other sessions, and lands in the audit log', async () => {
|
|
80
|
+
const sessionA = extractCookie(await signIn(OWNER.email, OWNER.password));
|
|
81
|
+
const sessionB = extractCookie(await signIn(OWNER.email, OWNER.password));
|
|
82
|
+
const newPassword = 'acct-owner-changed-42';
|
|
83
|
+
|
|
84
|
+
const res = await app.app.request(
|
|
85
|
+
'/api/v1/auth/change-password',
|
|
86
|
+
withCookie(sessionA, {
|
|
87
|
+
currentPassword: OWNER.password,
|
|
88
|
+
newPassword,
|
|
89
|
+
revokeOtherSessions: true
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
expect(res.status).toBe(200);
|
|
93
|
+
|
|
94
|
+
// The other session is dead; sign-in works only with the new password.
|
|
95
|
+
expect((await app.app.request('/api/v1/me', { headers: { cookie: sessionB } })).status).toBe(401);
|
|
96
|
+
expect((await signIn(OWNER.email, OWNER.password)).status).toBe(401);
|
|
97
|
+
expect((await signIn(OWNER.email, newPassword)).status).toBe(200);
|
|
98
|
+
|
|
99
|
+
// The change was audited (Better Auth routes bypass the /api/v1 middleware;
|
|
100
|
+
// an after-hook records it).
|
|
101
|
+
const [row] = await app.db.db
|
|
102
|
+
.select()
|
|
103
|
+
.from(auditLog)
|
|
104
|
+
.where(eq(auditLog.action, 'user.password_change'))
|
|
105
|
+
.orderBy(desc(auditLog.id))
|
|
106
|
+
.limit(1);
|
|
107
|
+
expect(row).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('change email without an email driver', () => {
|
|
112
|
+
it('answers 400 (self-serve change needs a delivering driver; see email-change.test.ts)', async () => {
|
|
113
|
+
const cookie = extractCookie(await signIn(OWNER.email, 'acct-owner-changed-42'));
|
|
114
|
+
const res = await app.app.request(
|
|
115
|
+
'/api/v1/auth/change-email',
|
|
116
|
+
withCookie(cookie, { newEmail: 'new@acct.test' })
|
|
117
|
+
);
|
|
118
|
+
expect(res.status).toBe(400);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
|
3
|
+
import {
|
|
4
|
+
createDatabase,
|
|
5
|
+
createTestApp,
|
|
6
|
+
extractCookie,
|
|
7
|
+
readJson,
|
|
8
|
+
startPostgres,
|
|
9
|
+
type TestApp,
|
|
10
|
+
host
|
|
11
|
+
} from './helpers.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* I3 — general per-principal API quota. The security property under test:
|
|
15
|
+
* every authenticated /api/v1 request consumes from a bucket keyed by the
|
|
16
|
+
* AUTHENTICATED principal identity (API-key id / session user id), so one
|
|
17
|
+
* principal exhausting its quota cannot touch another's, and no
|
|
18
|
+
* client-supplied header can rotate or escape a bucket. Exempt surfaces
|
|
19
|
+
* (public discovery, setup, the Better-Auth routes, /healthz) stay outside
|
|
20
|
+
* the quota entirely.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const OWNER = { email: 'owner@quota.test', name: 'Quota Owner', password: 'quota-owner-password-1' };
|
|
24
|
+
|
|
25
|
+
const json = (body: unknown) => ({
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'content-type': 'application/json' },
|
|
28
|
+
body: JSON.stringify(body)
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
let container: StartedPostgreSqlContainer;
|
|
32
|
+
|
|
33
|
+
beforeAll(async () => {
|
|
34
|
+
container = await startPostgres();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterAll(async () => {
|
|
38
|
+
await container?.stop();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('sustained quota: isolation, threshold, headers, exemptions', () => {
|
|
42
|
+
let url: string;
|
|
43
|
+
let app: TestApp;
|
|
44
|
+
let replica: TestApp | undefined;
|
|
45
|
+
let ownerCookie: string;
|
|
46
|
+
let keyA: string;
|
|
47
|
+
let keyB: string;
|
|
48
|
+
|
|
49
|
+
const quotaEnv = { API_RATE_LIMIT_PER_MINUTE: '5', API_RATE_LIMIT_BURST: '1000' };
|
|
50
|
+
|
|
51
|
+
beforeAll(async () => {
|
|
52
|
+
url = await createDatabase(container, 'quota_sustained');
|
|
53
|
+
app = await createTestApp(url, quotaEnv);
|
|
54
|
+
await app.app.request(
|
|
55
|
+
'/api/v1/setup',
|
|
56
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'Quota', owner: OWNER })
|
|
57
|
+
);
|
|
58
|
+
const signIn = await app.app.request(
|
|
59
|
+
'/api/v1/auth/sign-in/email',
|
|
60
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
61
|
+
);
|
|
62
|
+
ownerCookie = extractCookie(signIn);
|
|
63
|
+
|
|
64
|
+
// Two keys for the SAME user — isolation must hold per key, not per user.
|
|
65
|
+
// These two mints are the session bucket's only pre-test consumption (2/5).
|
|
66
|
+
const mint = (name: string) =>
|
|
67
|
+
app.app.request('/api/v1/api-keys', {
|
|
68
|
+
...json({ name, scopes: [host.scopes.read] }),
|
|
69
|
+
headers: { 'content-type': 'application/json', cookie: ownerCookie }
|
|
70
|
+
});
|
|
71
|
+
keyA = (await readJson(await mint('key-a'))).key;
|
|
72
|
+
keyB = (await readJson(await mint('key-b'))).key;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterAll(async () => {
|
|
76
|
+
await replica?.stop();
|
|
77
|
+
await app?.stop();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const me = (headers: Record<string, string>, target: TestApp = app) =>
|
|
81
|
+
target.app.request('/api/v1/me', { headers });
|
|
82
|
+
|
|
83
|
+
it('serves RateLimit headers on successful responses so agents can self-throttle', async () => {
|
|
84
|
+
const res = await me({ authorization: `Bearer ${keyA}` });
|
|
85
|
+
expect(res.status).toBe(200);
|
|
86
|
+
expect(res.headers.get('ratelimit-limit')).toBe('5');
|
|
87
|
+
expect(res.headers.get('ratelimit-remaining')).toBe('4');
|
|
88
|
+
expect(Number(res.headers.get('ratelimit-reset'))).toBeGreaterThan(0);
|
|
89
|
+
expect(res.headers.get('x-ratelimit-limit')).toBe('5');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('trips at the configured threshold with Retry-After and the standard envelope', async () => {
|
|
93
|
+
// 4 more requests exhaust key A's 5-point bucket.
|
|
94
|
+
for (let i = 0; i < 4; i++) {
|
|
95
|
+
expect((await me({ authorization: `Bearer ${keyA}` })).status).toBe(200);
|
|
96
|
+
}
|
|
97
|
+
const limited = await me({ authorization: `Bearer ${keyA}` });
|
|
98
|
+
expect(limited.status).toBe(429);
|
|
99
|
+
const body = await readJson(limited);
|
|
100
|
+
expect(body.error.code).toBe('rate_limited');
|
|
101
|
+
const retryAfter = Number(limited.headers.get('retry-after'));
|
|
102
|
+
expect(retryAfter).toBeGreaterThanOrEqual(1);
|
|
103
|
+
expect(retryAfter).toBeLessThanOrEqual(60);
|
|
104
|
+
expect(limited.headers.get('ratelimit-remaining')).toBe('0');
|
|
105
|
+
expect(limited.headers.get('x-ratelimit-remaining')).toBe('0');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('cannot escape or rotate the bucket via client headers (the XFF trap)', async () => {
|
|
109
|
+
// TRUST_PROXY=true in the harness, so XFF IS believed for the client IP —
|
|
110
|
+
// and the quota must still hold, because its key is the principal.
|
|
111
|
+
for (const spoof of ['198.51.100.7', '198.51.100.8']) {
|
|
112
|
+
const res = await me({ authorization: `Bearer ${keyA}`, 'x-forwarded-for': spoof });
|
|
113
|
+
expect(res.status).toBe(429);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("leaves principal B untouched while A is exhausted (same user's other key)", async () => {
|
|
118
|
+
const res = await me({ authorization: `Bearer ${keyB}` });
|
|
119
|
+
expect(res.status).toBe(200);
|
|
120
|
+
// Fresh bucket: B consumed exactly one point.
|
|
121
|
+
expect(res.headers.get('ratelimit-remaining')).toBe('4');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("leaves the same user's SESSION untouched (third distinct bucket)", async () => {
|
|
125
|
+
const res = await me({ cookie: ownerCookie });
|
|
126
|
+
expect(res.status).toBe(200);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('does not catch exempt surfaces: /healthz, discovery, setup, the auth routes', async () => {
|
|
130
|
+
expect((await app.app.request('/healthz')).status).toBe(200);
|
|
131
|
+
expect((await app.app.request('/api/v1/instance')).status).toBe(200);
|
|
132
|
+
// Setup answers its own domain error (already set up), never the quota's 429.
|
|
133
|
+
const setup = await app.app.request(
|
|
134
|
+
'/api/v1/setup',
|
|
135
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'X', owner: OWNER })
|
|
136
|
+
);
|
|
137
|
+
expect(setup.status).toBe(410);
|
|
138
|
+
// The auth surfaces keep their own walls; the exhausted general quota
|
|
139
|
+
// must not block a sign-in.
|
|
140
|
+
const signIn = await app.app.request(
|
|
141
|
+
'/api/v1/auth/sign-in/email',
|
|
142
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
143
|
+
);
|
|
144
|
+
expect(signIn.status).toBe(200);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('memory backend is per-replica: a second instance has its own buckets (Redis path = scale drill)', async () => {
|
|
148
|
+
replica = await createTestApp(url, quotaEnv);
|
|
149
|
+
// Key A is exhausted on the first instance…
|
|
150
|
+
expect((await me({ authorization: `Bearer ${keyA}` })).status).toBe(429);
|
|
151
|
+
// …but the second replica (no REDIS_URL → RateLimiterMemory) starts fresh.
|
|
152
|
+
// With REDIS_URL set the buckets are shared — proven by the I2 scale
|
|
153
|
+
// drill, which this quota rides via the same limiter factory.
|
|
154
|
+
expect((await me({ authorization: `Bearer ${keyA}` }, replica)).status).toBe(200);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('burst cap: 1-second spikes 429 and a fresh window recovers', () => {
|
|
159
|
+
let app: TestApp;
|
|
160
|
+
let key: string;
|
|
161
|
+
|
|
162
|
+
beforeAll(async () => {
|
|
163
|
+
const url = await createDatabase(container, 'quota_burst');
|
|
164
|
+
app = await createTestApp(url, { API_RATE_LIMIT_PER_MINUTE: '1000', API_RATE_LIMIT_BURST: '2' });
|
|
165
|
+
await app.app.request(
|
|
166
|
+
'/api/v1/setup',
|
|
167
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'Burst', owner: OWNER })
|
|
168
|
+
);
|
|
169
|
+
const signIn = await app.app.request(
|
|
170
|
+
'/api/v1/auth/sign-in/email',
|
|
171
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
172
|
+
);
|
|
173
|
+
const cookie = extractCookie(signIn);
|
|
174
|
+
const mint = await app.app.request('/api/v1/api-keys', {
|
|
175
|
+
...json({ name: 'burst', scopes: [host.scopes.read] }),
|
|
176
|
+
headers: { 'content-type': 'application/json', cookie }
|
|
177
|
+
});
|
|
178
|
+
key = (await readJson(mint)).key;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
afterAll(async () => {
|
|
182
|
+
await app?.stop();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('caps the spike, keeps the sustained budget intact, and recovers after the window', async () => {
|
|
186
|
+
const statuses: number[] = [];
|
|
187
|
+
let limited: Response | null = null;
|
|
188
|
+
for (let i = 0; i < 3; i++) {
|
|
189
|
+
const res = await app.app.request('/api/v1/me', { headers: { authorization: `Bearer ${key}` } });
|
|
190
|
+
statuses.push(res.status);
|
|
191
|
+
if (res.status === 429) limited = res;
|
|
192
|
+
}
|
|
193
|
+
// In-process requests land within one second: 2 pass, the spike 429s.
|
|
194
|
+
expect(statuses.filter((s) => s === 200)).toHaveLength(2);
|
|
195
|
+
expect(limited).not.toBeNull();
|
|
196
|
+
expect(limited!.headers.get('retry-after')).toBe('1');
|
|
197
|
+
expect((await readJson(limited!)).error.code).toBe('rate_limited');
|
|
198
|
+
|
|
199
|
+
// A fresh 1 s window recovers…
|
|
200
|
+
await new Promise((r) => setTimeout(r, 1200));
|
|
201
|
+
const recovered = await app.app.request('/api/v1/me', {
|
|
202
|
+
headers: { authorization: `Bearer ${key}` }
|
|
203
|
+
});
|
|
204
|
+
expect(recovered.status).toBe(200);
|
|
205
|
+
// …and the rejected spike burned NO sustained points: 3 successes total.
|
|
206
|
+
expect(recovered.headers.get('ratelimit-remaining')).toBe('997');
|
|
207
|
+
});
|
|
208
|
+
});
|