@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,868 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
|
3
|
+
import {
|
|
4
|
+
createDatabase,
|
|
5
|
+
createTestApp,
|
|
6
|
+
expectBootRefusal,
|
|
7
|
+
extractCookie,
|
|
8
|
+
readJson,
|
|
9
|
+
RecordingEmailDriver,
|
|
10
|
+
startPostgres,
|
|
11
|
+
type TestApp,
|
|
12
|
+
host
|
|
13
|
+
} from './helpers.js';
|
|
14
|
+
import * as sso from './sso-helpers.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The edition split, Phase 2 (internal/federation.md):
|
|
18
|
+
* - setup stamps the instance's edition; the R7 boot guard refuses an env
|
|
19
|
+
* EDITION that differs on an already-set-up instance, and
|
|
20
|
+
* EDITION_CHANGE_ALLOWED=true is the one-boot acknowledgement that
|
|
21
|
+
* re-stamps it;
|
|
22
|
+
* - a fresh database boots under any edition (cloud starts from a fresh DB);
|
|
23
|
+
* - EDITION=oss keeps today's behavior byte-identical — no hub config read.
|
|
24
|
+
*
|
|
25
|
+
* The SSO wiring itself is Phase 3; these tests pin the scaffolding only.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const OWNER = { email: 'owner@edition.test', name: 'Ed Owner', password: 'ed-owner-password-123' };
|
|
29
|
+
|
|
30
|
+
/** The full hub block a cloud boot requires (dev-shaped values). */
|
|
31
|
+
const HUB_ENV = {
|
|
32
|
+
EDITION: 'cloud',
|
|
33
|
+
HUB_ISSUER_URL: 'http://hub.localhost:3300',
|
|
34
|
+
HUB_CLIENT_ID: host.hubClientId,
|
|
35
|
+
HUB_CLIENT_SECRET: 'integration-test-hub-secret-0001'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const json = (body: unknown) => ({
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: { 'content-type': 'application/json' },
|
|
41
|
+
body: JSON.stringify(body)
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
let container: StartedPostgreSqlContainer;
|
|
45
|
+
|
|
46
|
+
beforeAll(async () => {
|
|
47
|
+
container = await startPostgres();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
afterAll(async () => {
|
|
51
|
+
await container?.stop();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('R7 edition-flip boot guard', () => {
|
|
55
|
+
let connectionString: string;
|
|
56
|
+
|
|
57
|
+
beforeAll(async () => {
|
|
58
|
+
connectionString = await createDatabase(container, 'edition_guard');
|
|
59
|
+
const app = await createTestApp(connectionString);
|
|
60
|
+
const res = await app.app.request(
|
|
61
|
+
'/api/v1/setup',
|
|
62
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'Ed', owner: OWNER })
|
|
63
|
+
);
|
|
64
|
+
expect(res.status).toBe(201);
|
|
65
|
+
await app.stop();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('setup stamped the instance as oss (the test boot default)', async () => {
|
|
69
|
+
const app = await createTestApp(connectionString);
|
|
70
|
+
const { rows } = await app.db.pool.query<{ edition: string }>(`SELECT edition FROM instance_settings`);
|
|
71
|
+
expect(rows).toEqual([{ edition: 'oss' }]);
|
|
72
|
+
await app.stop();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('refuses to boot EDITION=cloud on the populated oss instance', async () => {
|
|
76
|
+
await expectBootRefusal(
|
|
77
|
+
createTestApp(connectionString, HUB_ENV),
|
|
78
|
+
/refusing to boot: EDITION=cloud but this instance was set up as 'oss'/
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('EDITION_CHANGE_ALLOWED=true re-stamps and boots; the flip then holds without the flag', async () => {
|
|
83
|
+
// The flip pre-flights (PRDCT-1356 EDIT-1): an unprojected workspace
|
|
84
|
+
// refuses it. Project the setup workspace first — cloud-lifecycle.test.ts
|
|
85
|
+
// pins the refusal itself.
|
|
86
|
+
const prep = await createTestApp(connectionString);
|
|
87
|
+
await prep.db.pool.query(
|
|
88
|
+
`UPDATE workspaces SET central_account_id = '44444444-aaaa-4bbb-8ccc-0000000000ed'`
|
|
89
|
+
);
|
|
90
|
+
await prep.stop();
|
|
91
|
+
const flipped = await createTestApp(connectionString, { ...HUB_ENV, EDITION_CHANGE_ALLOWED: 'true' });
|
|
92
|
+
const { rows } = await flipped.db.pool.query<{ edition: string }>(
|
|
93
|
+
`SELECT edition FROM instance_settings`
|
|
94
|
+
);
|
|
95
|
+
expect(rows).toEqual([{ edition: 'cloud' }]);
|
|
96
|
+
await flipped.stop();
|
|
97
|
+
|
|
98
|
+
// The stamp moved: a plain cloud boot now succeeds, and an oss boot is
|
|
99
|
+
// now the refused flip (the guard is symmetric) — and the REVERSE flip
|
|
100
|
+
// is refused even with the flag (EDIT-4, cloud-lifecycle.test.ts).
|
|
101
|
+
const cloudAgain = await createTestApp(connectionString, HUB_ENV);
|
|
102
|
+
await cloudAgain.stop();
|
|
103
|
+
await expectBootRefusal(
|
|
104
|
+
createTestApp(connectionString),
|
|
105
|
+
/refusing to boot: EDITION=oss but this instance was set up as 'cloud'/
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('cloud edition on a fresh database', () => {
|
|
111
|
+
let app: TestApp;
|
|
112
|
+
|
|
113
|
+
beforeAll(async () => {
|
|
114
|
+
app = await createTestApp(await createDatabase(container, 'edition_cloud'), HUB_ENV);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
afterAll(async () => {
|
|
118
|
+
await app.stop();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('boots (fresh DB — nothing to guard) and setup stamps edition=cloud', async () => {
|
|
122
|
+
const res = await app.app.request(
|
|
123
|
+
'/api/v1/setup',
|
|
124
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'EdCloud', owner: OWNER })
|
|
125
|
+
);
|
|
126
|
+
expect(res.status).toBe(201);
|
|
127
|
+
const body = await readJson(res);
|
|
128
|
+
// Cloud setup creates NO workspace (user-scoped federation): every
|
|
129
|
+
// cloud workspace is a hub-org projection — the operator bootstrap
|
|
130
|
+
// mints a verified USER only, and the response says so honestly.
|
|
131
|
+
expect(body.workspaceId).toBeNull();
|
|
132
|
+
expect(body.ownerUserId).toBeTruthy();
|
|
133
|
+
const workspaces = await app.db.pool.query(`SELECT id FROM workspaces`);
|
|
134
|
+
expect(workspaces.rows).toHaveLength(0);
|
|
135
|
+
const memberships = await app.db.pool.query(`SELECT id FROM workspace_members`);
|
|
136
|
+
expect(memberships.rows).toHaveLength(0);
|
|
137
|
+
const { rows } = await app.db.pool.query<{ edition: string }>(`SELECT edition FROM instance_settings`);
|
|
138
|
+
expect(rows).toEqual([{ edition: 'cloud' }]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('the zero-membership operator session gets the /me zero state (200), not a login bounce', async () => {
|
|
142
|
+
const signIn = await app.app.request(
|
|
143
|
+
'/api/v1/auth/sign-in/email',
|
|
144
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
145
|
+
);
|
|
146
|
+
expect(signIn.status).toBe(200);
|
|
147
|
+
const cookie = extractCookie(signIn);
|
|
148
|
+
const me = await app.app.request('/api/v1/me', { headers: { cookie } });
|
|
149
|
+
expect(me.status).toBe(200);
|
|
150
|
+
const body = await readJson(me);
|
|
151
|
+
expect(body.user.email).toBe(OWNER.email);
|
|
152
|
+
expect(body.workspaces).toEqual([]);
|
|
153
|
+
expect(body.workspace).toBeNull();
|
|
154
|
+
expect(body.activeWorkspaceId).toBeNull();
|
|
155
|
+
expect(body.role).toBeNull();
|
|
156
|
+
expect(body.origin).toBeNull();
|
|
157
|
+
expect(body.via).toBe('session');
|
|
158
|
+
// The zero state's CTA target: orgs are created at the hub.
|
|
159
|
+
expect(body.hubManageUrl).toBe(HUB_ENV.HUB_ISSUER_URL);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('unauthenticated and machine callers keep their 401 on /me (zero state is session-only)', async () => {
|
|
163
|
+
const anon = await app.app.request('/api/v1/me');
|
|
164
|
+
expect(anon.status).toBe(401);
|
|
165
|
+
// A syntactically valid but unknown API key still dies inside
|
|
166
|
+
// authContext — the route-local session path never runs for bearers.
|
|
167
|
+
const fakeKey = `${host.identity.apiKeyPrefix}_AAAAAAAA_` + 'a'.repeat(43);
|
|
168
|
+
const withKey = await app.app.request('/api/v1/me', {
|
|
169
|
+
headers: { authorization: `Bearer ${fakeKey}`, 'x-forwarded-for': '10.77.0.1' }
|
|
170
|
+
});
|
|
171
|
+
expect(withKey.status).toBe(401);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('a session NAMING a workspace keeps the fail-closed 401 — the zero state is selector-less only', async () => {
|
|
175
|
+
// A failed explicit selection must never soften into the 200 zero state:
|
|
176
|
+
// it is the no-oracle posture AND the dashboard's stale-selection
|
|
177
|
+
// self-heal signal (it drops the stored selector only on failure).
|
|
178
|
+
const signIn = await app.app.request(
|
|
179
|
+
'/api/v1/auth/sign-in/email',
|
|
180
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
181
|
+
);
|
|
182
|
+
const cookie = extractCookie(signIn);
|
|
183
|
+
const res = await app.app.request('/api/v1/me', {
|
|
184
|
+
headers: { cookie, 'x-workspace-id': '99999999-9999-4999-8999-999999999999' }
|
|
185
|
+
});
|
|
186
|
+
expect(res.status).toBe(401);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('reports edition=cloud in discovery', async () => {
|
|
190
|
+
const info = await readJson(await app.app.request('/api/v1/instance'));
|
|
191
|
+
expect(info.edition).toBe('cloud');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('exposes the hub SSO hint-cookie contract in discovery (SL-1)', async () => {
|
|
195
|
+
const info = await readJson(await app.app.request('/api/v1/instance'));
|
|
196
|
+
// Defaults: the cross-repo cookie name; domain = issuer host minus its
|
|
197
|
+
// first label (hub.localhost → localhost).
|
|
198
|
+
expect(info.auth.sso).toEqual({ hintCookieName: 'ant_sso_hint', hintCookieDomain: 'localhost' });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('lands OAuth-callback AS errors on /login (cloud onAPIError.errorURL, SL-1)', async () => {
|
|
202
|
+
// A prompt=none authorize against a hub with no session answers
|
|
203
|
+
// ?error=login_required at the callback; the genericOAuth callback
|
|
204
|
+
// redirects AS errors to onAPIError.errorURL BEFORE parseState (1.6.15),
|
|
205
|
+
// so no state/cookie is needed to pin the landing.
|
|
206
|
+
const res = await app.app.request(
|
|
207
|
+
'/api/v1/auth/oauth2/callback/antasphere?error=login_required&error_description=Login%20required'
|
|
208
|
+
);
|
|
209
|
+
expect(res.status).toBe(302);
|
|
210
|
+
const location = res.headers.get('location')!;
|
|
211
|
+
expect(location.startsWith('http://localhost:3000/login?error=login_required')).toBe(true);
|
|
212
|
+
// The error exit mints no session.
|
|
213
|
+
expect(res.headers.get('set-cookie')).toBeNull();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('setup minted the operator emailVerified=true (D9 — the trusted-link bootstrap)', async () => {
|
|
217
|
+
// Under hub-only login (D1, Phase 3) the operator can only enter via the
|
|
218
|
+
// hub trusted-link, which Better Auth refuses onto an unverified local
|
|
219
|
+
// email. An unverified operator = a bricked cloud instance. Pinned here
|
|
220
|
+
// so no auth refactor can silently reopen the trap.
|
|
221
|
+
const { rows } = await app.db.pool.query<{ email_verified: boolean }>(
|
|
222
|
+
`SELECT email_verified FROM "user" WHERE email = $1`,
|
|
223
|
+
[OWNER.email]
|
|
224
|
+
);
|
|
225
|
+
expect(rows).toEqual([{ email_verified: true }]);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('advertises the hub-only human entrance (D1): antasphere + machine methods, no password/OTP', async () => {
|
|
229
|
+
const info = await readJson(await app.app.request('/api/v1/instance'));
|
|
230
|
+
expect(info.auth.methods).toContain('antasphere');
|
|
231
|
+
// Hub-only posture (Phase 3): the login page renders ONLY the SSO
|
|
232
|
+
// button; password/OTP/google are hidden (the machinery stays wired for
|
|
233
|
+
// the break-glass CLI, verified in the next test). Machine entrances
|
|
234
|
+
// are edition-independent.
|
|
235
|
+
expect(info.auth.methods).not.toContain('password');
|
|
236
|
+
expect(info.auth.methods).not.toContain('email-otp');
|
|
237
|
+
expect(info.auth.methods).not.toContain('google');
|
|
238
|
+
expect(info.auth.methods).toContain('api-key');
|
|
239
|
+
expect(info.auth.methods).toContain('oauth');
|
|
240
|
+
// Credentials and email are the hub's to manage; local 2FA guards
|
|
241
|
+
// sign-in surfaces the cloud page no longer offers.
|
|
242
|
+
expect(info.auth.passwordReset).toBe(false);
|
|
243
|
+
expect(info.auth.emailChange).toBe(false);
|
|
244
|
+
expect(info.auth.twoFactor).toBe(false);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('the provider-grant routes stay closed on CLOUD too (PRDCT-1354 pin; the oss pin lives in minted-credentials)', async () => {
|
|
248
|
+
// On cloud the stored provider grant IS the hub grant (ADR 019): handing
|
|
249
|
+
// it to a caller — or rotating it outside HubGrantService's lock — is
|
|
250
|
+
// the seam PRDCT-1370 hardens. The closure is unconditional in code;
|
|
251
|
+
// this pins it on a cloud boot so an edition-gated regression cannot
|
|
252
|
+
// pass on the oss-only pin alone.
|
|
253
|
+
const signIn = await app.app.request(
|
|
254
|
+
'/api/v1/auth/sign-in/email',
|
|
255
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
256
|
+
);
|
|
257
|
+
expect(signIn.status).toBe(200);
|
|
258
|
+
const cookie = signIn.headers.get('set-cookie')!.split(';')[0]!;
|
|
259
|
+
for (const path of ['/api/v1/auth/get-access-token', '/api/v1/auth/refresh-token']) {
|
|
260
|
+
for (const headers of [{ cookie }, {}]) {
|
|
261
|
+
const res = await app.app.request(path, {
|
|
262
|
+
...json({ providerId: 'antasphere' }),
|
|
263
|
+
headers: { 'content-type': 'application/json', ...headers }
|
|
264
|
+
});
|
|
265
|
+
expect(res.status, `${path} with ${Object.keys(headers).join(',') || 'no'} cookie`).toBe(403);
|
|
266
|
+
const body = await res.text();
|
|
267
|
+
expect(body).toContain('provider_grant_forbidden');
|
|
268
|
+
expect(body).not.toMatch(/accessToken|idToken|refreshToken|access_token/);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('local password sign-in stays WIRED though hidden (the break-glass door, D1)', async () => {
|
|
274
|
+
const signIn = await app.app.request(
|
|
275
|
+
'/api/v1/auth/sign-in/email',
|
|
276
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
277
|
+
);
|
|
278
|
+
expect(signIn.status).toBe(200);
|
|
279
|
+
const cookie = signIn.headers.get('set-cookie')!.split(';')[0]!;
|
|
280
|
+
const me = await app.app.request('/api/v1/me', { headers: { cookie } });
|
|
281
|
+
expect(me.status).toBe(200);
|
|
282
|
+
expect((await readJson(me)).user.email).toBe(OWNER.email);
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
describe('cloud edition closes the local password-reset surface (P8, ADR 017)', () => {
|
|
287
|
+
// The P3 residual: on cloud, a hub-JIT user (no credential account) could
|
|
288
|
+
// use the reset surface to SET a local password, then /sign-in/email to
|
|
289
|
+
// mint sessions that skip the per-login SSO re-sync. D1's hub-only posture
|
|
290
|
+
// closes the WHOLE reset class (core + emailOTP alias routes) while the
|
|
291
|
+
// break-glass operator door — /sign-in/email with the SETUP password plus
|
|
292
|
+
// the break-glass endpoints — stays fully independent of it.
|
|
293
|
+
let app: TestApp;
|
|
294
|
+
let email: RecordingEmailDriver;
|
|
295
|
+
|
|
296
|
+
// The reset wall is 5/10min per IP (rate-limit.ts) and several reset paths
|
|
297
|
+
// share one limiter — a unique forwarded address per request (TRUST_PROXY
|
|
298
|
+
// is on in tests) keeps this suite off one shared 'unknown' bucket, so it
|
|
299
|
+
// pins the 403 REFUSAL, never a 429.
|
|
300
|
+
let ipCounter = 0;
|
|
301
|
+
const jsonIp = (body: unknown, headers: Record<string, string> = {}) => ({
|
|
302
|
+
method: 'POST',
|
|
303
|
+
headers: {
|
|
304
|
+
'content-type': 'application/json',
|
|
305
|
+
'x-forwarded-for': `198.51.100.${++ipCounter % 250}`,
|
|
306
|
+
...headers
|
|
307
|
+
},
|
|
308
|
+
body: JSON.stringify(body)
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
beforeAll(async () => {
|
|
312
|
+
email = new RecordingEmailDriver();
|
|
313
|
+
// A DELIVERING mailer + superadmin: the strongest configuration — on oss
|
|
314
|
+
// this would open self-serve reset; on cloud every reset door must still
|
|
315
|
+
// refuse while break-glass keeps working.
|
|
316
|
+
app = await createTestApp(
|
|
317
|
+
await createDatabase(container, 'edition_cloud_reset'),
|
|
318
|
+
{ ...HUB_ENV, SUPERADMIN_EMAILS: OWNER.email },
|
|
319
|
+
{ email }
|
|
320
|
+
);
|
|
321
|
+
const res = await app.app.request(
|
|
322
|
+
'/api/v1/setup',
|
|
323
|
+
jsonIp({ setupToken: 'integration-test-setup-token', instanceName: 'EdReset', owner: OWNER })
|
|
324
|
+
);
|
|
325
|
+
expect(res.status).toBe(201);
|
|
326
|
+
// Cloud setup mints no workspace — the cloud-LOCAL workspace this
|
|
327
|
+
// suite's reset-link + break-glass fixtures need is seeded directly.
|
|
328
|
+
await sso.seedLocalWorkspace(app, 'EdReset', OWNER.email);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
afterAll(async () => {
|
|
332
|
+
await app.stop();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
async function resetTokenCount(): Promise<number> {
|
|
336
|
+
const { rows } = await app.db.pool.query<{ n: number }>(
|
|
337
|
+
`SELECT count(*)::int AS n FROM verification WHERE identifier LIKE 'reset-password:%'`
|
|
338
|
+
);
|
|
339
|
+
return rows[0]!.n;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
it('refuses every reset-shaped route (core + emailOTP) despite the delivering mailer', async () => {
|
|
343
|
+
const attempts: Array<[string, Record<string, unknown>]> = [
|
|
344
|
+
[
|
|
345
|
+
'/api/v1/auth/request-password-reset',
|
|
346
|
+
{ email: OWNER.email, redirectTo: 'http://localhost:3000/reset-password' }
|
|
347
|
+
],
|
|
348
|
+
['/api/v1/auth/reset-password', { newPassword: 'sso-bypass-password-99', token: 'whatever' }],
|
|
349
|
+
['/api/v1/auth/email-otp/request-password-reset', { email: OWNER.email }],
|
|
350
|
+
['/api/v1/auth/forget-password/email-otp', { email: OWNER.email }],
|
|
351
|
+
[
|
|
352
|
+
'/api/v1/auth/email-otp/reset-password',
|
|
353
|
+
{ email: OWNER.email, otp: '123456', password: 'sso-bypass-password-99' }
|
|
354
|
+
]
|
|
355
|
+
];
|
|
356
|
+
for (const [path, body] of attempts) {
|
|
357
|
+
const res = await app.app.request(path, jsonIp(body));
|
|
358
|
+
expect(res.status, path).toBe(403);
|
|
359
|
+
}
|
|
360
|
+
// The tokened GET callback (the mailed-link landing) refuses too.
|
|
361
|
+
const cb = await app.app.request('/api/v1/auth/reset-password/some-token', {
|
|
362
|
+
headers: { 'x-forwarded-for': `198.51.100.${++ipCounter % 250}` }
|
|
363
|
+
});
|
|
364
|
+
expect(cb.status).toBe(403);
|
|
365
|
+
// Nothing was mailed and no reset token was minted.
|
|
366
|
+
expect(email.sent).toHaveLength(0);
|
|
367
|
+
expect(await resetTokenCount()).toBe(0);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it('refuses to CONSUME a valid reset token — even one minted straight into the store', async () => {
|
|
371
|
+
// An attacker (or a leftover admin link) holding a well-formed token in
|
|
372
|
+
// Better Auth's own verification table still cannot set a password: the
|
|
373
|
+
// refusal is on the route, not on token acquisition.
|
|
374
|
+
const { rows } = await app.db.pool.query<{ id: string }>(`SELECT id FROM "user" WHERE email = $1`, [
|
|
375
|
+
OWNER.email
|
|
376
|
+
]);
|
|
377
|
+
await app.db.pool.query(
|
|
378
|
+
`INSERT INTO verification (id, identifier, value, expires_at) VALUES ($1, $2, $3, $4)`,
|
|
379
|
+
[crypto.randomUUID(), 'reset-password:direct-mint-token', rows[0]!.id, new Date(Date.now() + 3600_000)]
|
|
380
|
+
);
|
|
381
|
+
const res = await app.app.request(
|
|
382
|
+
'/api/v1/auth/reset-password',
|
|
383
|
+
jsonIp({ newPassword: 'sso-bypass-password-99', token: 'direct-mint-token' })
|
|
384
|
+
);
|
|
385
|
+
expect(res.status).toBe(403);
|
|
386
|
+
// The password did not change: the setup password still signs in, the
|
|
387
|
+
// attempted one does not.
|
|
388
|
+
const bypass = await app.app.request(
|
|
389
|
+
'/api/v1/auth/sign-in/email',
|
|
390
|
+
jsonIp({ email: OWNER.email, password: 'sso-bypass-password-99' })
|
|
391
|
+
);
|
|
392
|
+
expect(bypass.status).toBe(401);
|
|
393
|
+
// Remove the hand-minted row so the next test's "the mint added nothing"
|
|
394
|
+
// count starts from zero again.
|
|
395
|
+
await app.db.pool.query(`DELETE FROM verification WHERE identifier = 'reset-password:direct-mint-token'`);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it('refuses the admin reset-link mint on a cloud-LOCAL workspace (password_reset_disabled)', async () => {
|
|
399
|
+
// Hub-origin workspaces already refuse at the P7 subtree gate
|
|
400
|
+
// (hub_managed); this pins the cloud-LOCAL half — otherwise an admin
|
|
401
|
+
// would mint links that dead-end on the refused /reset-password.
|
|
402
|
+
const signIn = await app.app.request(
|
|
403
|
+
'/api/v1/auth/sign-in/email',
|
|
404
|
+
jsonIp({ email: OWNER.email, password: OWNER.password })
|
|
405
|
+
);
|
|
406
|
+
expect(signIn.status).toBe(200);
|
|
407
|
+
const cookie = signIn.headers.get('set-cookie')!.split(';')[0]!;
|
|
408
|
+
const { members } = await readJson(await app.app.request('/api/v1/members', { headers: { cookie } }));
|
|
409
|
+
const minted = await app.app.request(`/api/v1/members/${members[0].id}/reset-link`, {
|
|
410
|
+
method: 'POST',
|
|
411
|
+
headers: { cookie }
|
|
412
|
+
});
|
|
413
|
+
expect(minted.status).toBe(403);
|
|
414
|
+
expect((await readJson(minted)).error.code).toBe('password_reset_disabled');
|
|
415
|
+
expect(await resetTokenCount()).toBe(0);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
it('refuses the admin change-email-link mint on a cloud-LOCAL workspace (email_change_disabled)', async () => {
|
|
419
|
+
// AUTH-1 (PRDCT-1354): the sibling mint had NO cloud closure at all, yet
|
|
420
|
+
// its token is the STRONGER of the two — consuming it signs the target in
|
|
421
|
+
// AND rewrites the address the hub identity is keyed on, while on cloud
|
|
422
|
+
// the email is the hub's to own (D10 re-syncs it every login). Same shape
|
|
423
|
+
// as the reset-link closure above, distinct code.
|
|
424
|
+
const signIn = await app.app.request(
|
|
425
|
+
'/api/v1/auth/sign-in/email',
|
|
426
|
+
jsonIp({ email: OWNER.email, password: OWNER.password })
|
|
427
|
+
);
|
|
428
|
+
expect(signIn.status).toBe(200);
|
|
429
|
+
const cookie = signIn.headers.get('set-cookie')!.split(';')[0]!;
|
|
430
|
+
const { members } = await readJson(await app.app.request('/api/v1/members', { headers: { cookie } }));
|
|
431
|
+
const minted = await app.app.request(`/api/v1/members/${members[0].id}/change-email-link`, {
|
|
432
|
+
...jsonIp({ newEmail: 'hijacked@edreset.test' }, { cookie })
|
|
433
|
+
});
|
|
434
|
+
expect(minted.status).toBe(403);
|
|
435
|
+
const body = await readJson(minted);
|
|
436
|
+
expect(body.error.code).toBe('email_change_disabled');
|
|
437
|
+
expect(body.verifyUrl).toBeUndefined();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('refuses the self-serve email change (403 email_change_disabled, naming the account site), the address unchanged and nothing mailed', async () => {
|
|
441
|
+
// PRDCT-2818: the dashboard hides the form on cloud, the route was open.
|
|
442
|
+
// A signed-in caller with a DELIVERING mailer is the strongest case: on
|
|
443
|
+
// oss this very call mails the verification link.
|
|
444
|
+
const signIn = await app.app.request(
|
|
445
|
+
'/api/v1/auth/sign-in/email',
|
|
446
|
+
jsonIp({ email: OWNER.email, password: OWNER.password })
|
|
447
|
+
);
|
|
448
|
+
expect(signIn.status).toBe(200);
|
|
449
|
+
const cookie = signIn.headers.get('set-cookie')!.split(';')[0]!;
|
|
450
|
+
const mailsBefore = email.sent.length;
|
|
451
|
+
const attempts: Array<[string, Record<string, unknown>]> = [
|
|
452
|
+
['/api/v1/auth/change-email', { newEmail: 'moved@edreset.test', callbackURL: '/account' }],
|
|
453
|
+
['/api/v1/auth/email-otp/request-email-change', { newEmail: 'moved@edreset.test' }],
|
|
454
|
+
['/api/v1/auth/email-otp/change-email', { newEmail: 'moved@edreset.test', otp: '000000' }]
|
|
455
|
+
];
|
|
456
|
+
for (const [path, body] of attempts) {
|
|
457
|
+
const res = await app.app.request(path, jsonIp(body, { cookie }));
|
|
458
|
+
expect(res.status, path).toBe(403);
|
|
459
|
+
const answer = await readJson(res);
|
|
460
|
+
expect(JSON.stringify(answer), path).toContain('email_change_disabled');
|
|
461
|
+
expect(JSON.stringify(answer), path).toContain(HUB_ENV.HUB_ISSUER_URL.replace(/\/+$/, ''));
|
|
462
|
+
}
|
|
463
|
+
expect(email.sent.length).toBe(mailsBefore);
|
|
464
|
+
const { rows } = await app.db.pool.query<{ n: number }>(
|
|
465
|
+
`SELECT count(*)::int AS n FROM "user" WHERE email = $1`,
|
|
466
|
+
[OWNER.email]
|
|
467
|
+
);
|
|
468
|
+
expect(rows[0]!.n).toBe(1);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it('the operator door survives the closure: /sign-in/email + break-glass need no reset route', async () => {
|
|
472
|
+
// The full recovery chain, reset-free: sign in with the SETUP password
|
|
473
|
+
// (D9 minted the operator emailVerified=true), then claim-ownership and
|
|
474
|
+
// reset-2fa — the ADR 010 surface — both work.
|
|
475
|
+
const signIn = await app.app.request(
|
|
476
|
+
'/api/v1/auth/sign-in/email',
|
|
477
|
+
jsonIp({ email: OWNER.email, password: OWNER.password })
|
|
478
|
+
);
|
|
479
|
+
expect(signIn.status).toBe(200);
|
|
480
|
+
const cookie = signIn.headers.get('set-cookie')!.split(';')[0]!;
|
|
481
|
+
|
|
482
|
+
const claim = await app.app.request('/api/v1/admin/break-glass/claim-ownership', {
|
|
483
|
+
...jsonIp({}),
|
|
484
|
+
headers: { 'content-type': 'application/json', cookie }
|
|
485
|
+
});
|
|
486
|
+
expect(claim.status).toBe(200);
|
|
487
|
+
expect((await readJson(claim)).role).toBe('owner');
|
|
488
|
+
|
|
489
|
+
const { rows } = await app.db.pool.query<{ id: string }>(`SELECT id FROM "user" WHERE email = $1`, [
|
|
490
|
+
OWNER.email
|
|
491
|
+
]);
|
|
492
|
+
const reset2fa = await app.app.request('/api/v1/admin/break-glass/reset-2fa', {
|
|
493
|
+
...jsonIp({ userId: rows[0]!.id }),
|
|
494
|
+
headers: { 'content-type': 'application/json', cookie }
|
|
495
|
+
});
|
|
496
|
+
expect(reset2fa.status).toBe(200);
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
describe('cloud edition closes the OTP entrances (D1 hub-only credentials)', () => {
|
|
501
|
+
// The ADR 017 §7 charter call, now taken: beyond the P8 reset closure,
|
|
502
|
+
// the two remaining non-SSO credential-MINTING entrances refuse on cloud
|
|
503
|
+
// so every human session AND CLI key traces through the hub (its audit
|
|
504
|
+
// log is the complete access record). Both were hub-gated per request by
|
|
505
|
+
// the P4 re-assertion already — posture, not a hole. The self-revoke
|
|
506
|
+
// (DELETE /cli/auth/key) is the deliberate exception: revocation narrows
|
|
507
|
+
// access, and the CLI's `logout --org` needs it.
|
|
508
|
+
let app: TestApp;
|
|
509
|
+
let email: RecordingEmailDriver;
|
|
510
|
+
|
|
511
|
+
let ipCounter = 0;
|
|
512
|
+
const jsonIp = (body: unknown, headers: Record<string, string> = {}) => ({
|
|
513
|
+
method: 'POST',
|
|
514
|
+
headers: {
|
|
515
|
+
'content-type': 'application/json',
|
|
516
|
+
'x-forwarded-for': `198.51.101.${++ipCounter % 250}`,
|
|
517
|
+
...headers
|
|
518
|
+
},
|
|
519
|
+
body: JSON.stringify(body)
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
beforeAll(async () => {
|
|
523
|
+
email = new RecordingEmailDriver();
|
|
524
|
+
// A DELIVERING mailer AND Google credentials set: the strongest
|
|
525
|
+
// misconfiguration — on oss both would open OTP login and Google social
|
|
526
|
+
// sign-in; on cloud every one of them must be closed by construction.
|
|
527
|
+
app = await createTestApp(
|
|
528
|
+
await createDatabase(container, 'edition_cloud_otp'),
|
|
529
|
+
{ ...HUB_ENV, GOOGLE_CLIENT_ID: 'cloud-google-id', GOOGLE_CLIENT_SECRET: 'cloud-google-secret' },
|
|
530
|
+
{ email }
|
|
531
|
+
);
|
|
532
|
+
const res = await app.app.request(
|
|
533
|
+
'/api/v1/setup',
|
|
534
|
+
jsonIp({ setupToken: 'integration-test-setup-token', instanceName: 'EdOtp', owner: OWNER })
|
|
535
|
+
);
|
|
536
|
+
expect(res.status).toBe(201);
|
|
537
|
+
// Cloud setup mints no workspace — the key-mint fixture below needs the
|
|
538
|
+
// operator to hold a membership; seed a cloud-LOCAL one.
|
|
539
|
+
await sso.seedLocalWorkspace(app, 'EdOtp', OWNER.email);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
afterAll(async () => {
|
|
543
|
+
await app.stop();
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('refuses the emailOTP session surface (send / sign-in / verify-email) despite the mailer', async () => {
|
|
547
|
+
// The enumerated 1.6.15 session-minting surface (isOtpSignInPath): the
|
|
548
|
+
// sign-in mint, the verify-email conditional mint (config insurance),
|
|
549
|
+
// and the send leg (no dead codes get mailed).
|
|
550
|
+
const attempts: Array<[string, Record<string, unknown>]> = [
|
|
551
|
+
['/api/v1/auth/email-otp/send-verification-otp', { email: OWNER.email, type: 'sign-in' }],
|
|
552
|
+
['/api/v1/auth/sign-in/email-otp', { email: OWNER.email, otp: '123456' }],
|
|
553
|
+
['/api/v1/auth/email-otp/verify-email', { email: OWNER.email, otp: '123456' }]
|
|
554
|
+
];
|
|
555
|
+
for (const [path, body] of attempts) {
|
|
556
|
+
const res = await app.app.request(path, jsonIp(body));
|
|
557
|
+
expect(res.status, path).toBe(403);
|
|
558
|
+
expect(res.headers.get('set-cookie'), path).toBeNull(); // no session minted
|
|
559
|
+
}
|
|
560
|
+
// The refusal carries the steering code, and nothing was mailed.
|
|
561
|
+
const refusal = await app.app.request(
|
|
562
|
+
'/api/v1/auth/sign-in/email-otp',
|
|
563
|
+
jsonIp({ email: OWNER.email, otp: '123456' })
|
|
564
|
+
);
|
|
565
|
+
expect((await readJson(refusal)).code).toBe('otp_signin_disabled');
|
|
566
|
+
expect(email.sent).toHaveLength(0);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it('does NOT register the Google social provider despite GOOGLE_CLIENT_ID/SECRET set', async () => {
|
|
570
|
+
// Google social is a self-host option only: on cloud the provider is
|
|
571
|
+
// not registered at all, so /sign-in/social mints no session even with
|
|
572
|
+
// the credentials configured (defense in depth — the audit-completeness
|
|
573
|
+
// guarantee holds by construction, not by "operator didn't set the env
|
|
574
|
+
// var"). 1.6.15 answers PROVIDER_NOT_FOUND (404) for an unregistered
|
|
575
|
+
// provider, and no session cookie is set.
|
|
576
|
+
const social = await app.app.request('/api/v1/auth/sign-in/social', jsonIp({ provider: 'google' }));
|
|
577
|
+
expect(social.status).toBe(404);
|
|
578
|
+
expect(social.headers.get('set-cookie')).toBeNull();
|
|
579
|
+
// Discovery never advertises google on cloud either (the hub-only
|
|
580
|
+
// descriptor), the credentials notwithstanding.
|
|
581
|
+
const info = await readJson(await app.app.request('/api/v1/instance'));
|
|
582
|
+
expect(info.auth.methods).not.toContain('google');
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it('refuses the CLI OTP mint pair (cli_otp_disabled → `antasphere login`)', async () => {
|
|
586
|
+
const request = await app.app.request('/api/v1/cli/auth/request', jsonIp({ email: OWNER.email }));
|
|
587
|
+
expect(request.status).toBe(403);
|
|
588
|
+
const requestBody = await readJson(request);
|
|
589
|
+
expect(requestBody.error.code).toBe('cli_otp_disabled');
|
|
590
|
+
expect(requestBody.error.message).toContain('antasphere login');
|
|
591
|
+
|
|
592
|
+
const complete = await app.app.request(
|
|
593
|
+
'/api/v1/cli/auth/complete',
|
|
594
|
+
jsonIp({ email: OWNER.email, otp: '123456' })
|
|
595
|
+
);
|
|
596
|
+
expect(complete.status).toBe(403);
|
|
597
|
+
expect((await readJson(complete)).error.code).toBe('cli_otp_disabled');
|
|
598
|
+
expect(email.sent).toHaveLength(0); // still nothing mailed
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
it('the self-revoke stays open: a key kills exactly ITSELF; the operator door survives', async () => {
|
|
602
|
+
// /sign-in/email still works AFTER the OTP close (the break-glass door).
|
|
603
|
+
const signIn = await app.app.request(
|
|
604
|
+
'/api/v1/auth/sign-in/email',
|
|
605
|
+
jsonIp({ email: OWNER.email, password: OWNER.password })
|
|
606
|
+
);
|
|
607
|
+
expect(signIn.status).toBe(200);
|
|
608
|
+
const cookie = signIn.headers.get('set-cookie')!.split(';')[0]!;
|
|
609
|
+
|
|
610
|
+
const mint = async (name: string) => {
|
|
611
|
+
const res = await app.app.request('/api/v1/api-keys', {
|
|
612
|
+
...jsonIp({ name, scopes: [host.scopes.read, host.scopes.write] }),
|
|
613
|
+
headers: { 'content-type': 'application/json', cookie }
|
|
614
|
+
});
|
|
615
|
+
expect(res.status).toBe(201);
|
|
616
|
+
return readJson(res);
|
|
617
|
+
};
|
|
618
|
+
const keyA = await mint('cloud-cli-a');
|
|
619
|
+
const keyB = await mint('cloud-cli-b');
|
|
620
|
+
|
|
621
|
+
const revoke = await app.app.request('/api/v1/cli/auth/key', {
|
|
622
|
+
method: 'DELETE',
|
|
623
|
+
headers: { authorization: `Bearer ${keyA.key}` }
|
|
624
|
+
});
|
|
625
|
+
expect(revoke.status).toBe(200);
|
|
626
|
+
expect(await readJson(revoke)).toEqual({ revoked: true, id: keyA.apiKey.id });
|
|
627
|
+
|
|
628
|
+
// The presenting key is dead; the OTHER key is untouched — the route
|
|
629
|
+
// names no key, so nothing else was revocable.
|
|
630
|
+
const deadMe = await app.app.request('/api/v1/me', {
|
|
631
|
+
headers: { authorization: `Bearer ${keyA.key}` }
|
|
632
|
+
});
|
|
633
|
+
expect(deadMe.status).toBe(401);
|
|
634
|
+
const liveMe = await app.app.request('/api/v1/me', {
|
|
635
|
+
headers: { authorization: `Bearer ${keyB.key}` }
|
|
636
|
+
});
|
|
637
|
+
expect(liveMe.status).toBe(200);
|
|
638
|
+
});
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
describe('session TTL split (SL-5): cloud 30d FIXED, oss 365d sliding', () => {
|
|
642
|
+
// The one-login-concept mechanics: a cloud tool session is a PROJECTION of
|
|
643
|
+
// the hub anchor — sliding it would let the anchor die under an active
|
|
644
|
+
// user, so cloud sessions are fixed-duration (disableSessionRefresh, the
|
|
645
|
+
// 1.6.15 knob) and re-derive silently from the anchor. oss keeps the
|
|
646
|
+
// template's sliding sessions byte-identically.
|
|
647
|
+
|
|
648
|
+
interface SessionRow {
|
|
649
|
+
id: string;
|
|
650
|
+
expires_at: Date;
|
|
651
|
+
created_at: Date;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
async function newestSession(app: TestApp, email: string): Promise<SessionRow> {
|
|
655
|
+
const { rows } = await app.db.pool.query<SessionRow>(
|
|
656
|
+
`SELECT s.id, s.expires_at, s.created_at FROM session s
|
|
657
|
+
JOIN "user" u ON u.id = s.user_id WHERE u.email = $1
|
|
658
|
+
ORDER BY s.created_at DESC LIMIT 1`,
|
|
659
|
+
[email]
|
|
660
|
+
);
|
|
661
|
+
expect(rows).toHaveLength(1);
|
|
662
|
+
return rows[0]!;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/** Backdate a session so the sliding rule (expiresAt − expiresIn + updateAge ≤ now) fires. */
|
|
666
|
+
async function backdate(app: TestApp, id: string): Promise<Date> {
|
|
667
|
+
const { rows } = await app.db.pool.query<{ expires_at: Date }>(
|
|
668
|
+
`UPDATE session SET expires_at = now() + interval '1 hour' WHERE id = $1 RETURNING expires_at`,
|
|
669
|
+
[id]
|
|
670
|
+
);
|
|
671
|
+
return rows[0]!.expires_at;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
it('cloud: sessions mint at 30 days and expiresAt does NOT advance on use', async () => {
|
|
675
|
+
const app = await createTestApp(await createDatabase(container, 'session_ttl_cloud'), HUB_ENV);
|
|
676
|
+
try {
|
|
677
|
+
expect(
|
|
678
|
+
(
|
|
679
|
+
await app.app.request(
|
|
680
|
+
'/api/v1/setup',
|
|
681
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'Ttl', owner: OWNER })
|
|
682
|
+
)
|
|
683
|
+
).status
|
|
684
|
+
).toBe(201);
|
|
685
|
+
const signIn = await app.app.request(
|
|
686
|
+
'/api/v1/auth/sign-in/email',
|
|
687
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
688
|
+
);
|
|
689
|
+
expect(signIn.status).toBe(200);
|
|
690
|
+
const cookie = extractCookie(signIn);
|
|
691
|
+
|
|
692
|
+
const minted = await newestSession(app, OWNER.email);
|
|
693
|
+
const ttlDays = (minted.expires_at.getTime() - minted.created_at.getTime()) / 86_400_000;
|
|
694
|
+
expect(ttlDays).toBeGreaterThan(29.9);
|
|
695
|
+
expect(ttlDays).toBeLessThan(30.1);
|
|
696
|
+
|
|
697
|
+
// Trip the sliding rule deliberately, then USE the session: with
|
|
698
|
+
// disableSessionRefresh the row must not move — the hard expiry is
|
|
699
|
+
// what forces the silent re-derivation from the hub anchor.
|
|
700
|
+
const backdated = await backdate(app, minted.id);
|
|
701
|
+
const use = await app.app.request('/api/v1/auth/get-session', { headers: { cookie } });
|
|
702
|
+
expect(use.status).toBe(200);
|
|
703
|
+
expect((await readJson(use))?.session).toBeTruthy();
|
|
704
|
+
const after = await newestSession(app, OWNER.email);
|
|
705
|
+
expect(after.id).toBe(minted.id);
|
|
706
|
+
expect(after.expires_at.toISOString()).toBe(backdated.toISOString());
|
|
707
|
+
} finally {
|
|
708
|
+
await app.stop();
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
it('oss: the template sliding behavior is unchanged — the same use DOES advance expiresAt', async () => {
|
|
713
|
+
const app = await createTestApp(await createDatabase(container, 'session_ttl_oss'));
|
|
714
|
+
try {
|
|
715
|
+
expect(
|
|
716
|
+
(
|
|
717
|
+
await app.app.request(
|
|
718
|
+
'/api/v1/setup',
|
|
719
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'TtlOss', owner: OWNER })
|
|
720
|
+
)
|
|
721
|
+
).status
|
|
722
|
+
).toBe(201);
|
|
723
|
+
const signIn = await app.app.request(
|
|
724
|
+
'/api/v1/auth/sign-in/email',
|
|
725
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
726
|
+
);
|
|
727
|
+
expect(signIn.status).toBe(200);
|
|
728
|
+
const cookie = extractCookie(signIn);
|
|
729
|
+
|
|
730
|
+
const minted = await newestSession(app, OWNER.email);
|
|
731
|
+
const ttlDays = (minted.expires_at.getTime() - minted.created_at.getTime()) / 86_400_000;
|
|
732
|
+
expect(ttlDays).toBeGreaterThan(364);
|
|
733
|
+
expect(ttlDays).toBeLessThan(366);
|
|
734
|
+
|
|
735
|
+
const backdated = await backdate(app, minted.id);
|
|
736
|
+
const use = await app.app.request('/api/v1/auth/get-session', { headers: { cookie } });
|
|
737
|
+
expect(use.status).toBe(200);
|
|
738
|
+
const after = await newestSession(app, OWNER.email);
|
|
739
|
+
expect(after.id).toBe(minted.id);
|
|
740
|
+
// Slid forward: a fresh ~365d horizon, far past the backdated hour.
|
|
741
|
+
expect(after.expires_at.getTime()).toBeGreaterThan(backdated.getTime() + 300 * 86_400_000);
|
|
742
|
+
} finally {
|
|
743
|
+
await app.stop();
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
describe('oss stays dark: zero hub-shaped calls across boot + a request matrix (fetch-spy)', () => {
|
|
749
|
+
it('an oss boot with HUB_* vars present makes NO outbound fetch at all', async () => {
|
|
750
|
+
const fetched: string[] = [];
|
|
751
|
+
const realFetch = globalThis.fetch;
|
|
752
|
+
const spy: typeof fetch = (input, init) => {
|
|
753
|
+
fetched.push(String(input instanceof Request ? input.url : input));
|
|
754
|
+
return realFetch(input as Parameters<typeof fetch>[0], init);
|
|
755
|
+
};
|
|
756
|
+
globalThis.fetch = spy;
|
|
757
|
+
try {
|
|
758
|
+
// Positive control first: the spy really intercepts global fetch.
|
|
759
|
+
await spy('http://127.0.0.1:1/spy-probe').catch(() => {});
|
|
760
|
+
expect(fetched).toEqual(['http://127.0.0.1:1/spy-probe']);
|
|
761
|
+
fetched.length = 0;
|
|
762
|
+
|
|
763
|
+
// Adversarial config: every HUB_* var set — EDITION=oss must leave
|
|
764
|
+
// them completely unread (hubConfig() is the single switch).
|
|
765
|
+
const app = await createTestApp(await createDatabase(container, 'edition_oss_dark'), {
|
|
766
|
+
HUB_ISSUER_URL: 'http://hub.localhost:3300',
|
|
767
|
+
HUB_CLIENT_ID: host.hubClientId,
|
|
768
|
+
HUB_CLIENT_SECRET: 'integration-test-hub-secret-0001'
|
|
769
|
+
});
|
|
770
|
+
try {
|
|
771
|
+
// The request matrix: setup, session login, whoami, key mint, key
|
|
772
|
+
// use, a domain read — every credential kind exercised.
|
|
773
|
+
const setup = await app.app.request(
|
|
774
|
+
'/api/v1/setup',
|
|
775
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'Dark', owner: OWNER })
|
|
776
|
+
);
|
|
777
|
+
expect(setup.status).toBe(201);
|
|
778
|
+
const signIn = await app.app.request(
|
|
779
|
+
'/api/v1/auth/sign-in/email',
|
|
780
|
+
json({ email: OWNER.email, password: OWNER.password })
|
|
781
|
+
);
|
|
782
|
+
expect(signIn.status).toBe(200);
|
|
783
|
+
const cookie = signIn.headers.get('set-cookie')!.split(';')[0]!;
|
|
784
|
+
expect((await app.app.request('/api/v1/me', { headers: { cookie } })).status).toBe(200);
|
|
785
|
+
const mint = await app.app.request('/api/v1/api-keys', {
|
|
786
|
+
method: 'POST',
|
|
787
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
788
|
+
body: JSON.stringify({ name: 'dark key', scopes: [host.scopes.read] })
|
|
789
|
+
});
|
|
790
|
+
expect(mint.status).toBe(201);
|
|
791
|
+
const { key } = await readJson(mint);
|
|
792
|
+
expect(
|
|
793
|
+
(await app.app.request('/api/v1/me', { headers: { authorization: `Bearer ${key}` } })).status
|
|
794
|
+
).toBe(200);
|
|
795
|
+
expect((await app.app.request(host.probeRoute, { headers: { cookie } })).status).toBe(200);
|
|
796
|
+
// The cloud-only surfaces stay dark 404s on oss — probed INSIDE the
|
|
797
|
+
// spy window so a regression that mounts them (or makes them phone
|
|
798
|
+
// the hub) fails the zero-fetch assertion below too.
|
|
799
|
+
const ssoLogout = await app.app.request('/api/v1/sso/logout', {
|
|
800
|
+
method: 'POST',
|
|
801
|
+
headers: { cookie }
|
|
802
|
+
});
|
|
803
|
+
expect(ssoLogout.status).toBe(404);
|
|
804
|
+
const dismiss = await app.app.request('/api/v1/me/onboarding/dismiss', {
|
|
805
|
+
method: 'POST',
|
|
806
|
+
headers: { cookie }
|
|
807
|
+
});
|
|
808
|
+
expect(dismiss.status).toBe(404);
|
|
809
|
+
} finally {
|
|
810
|
+
await app.stop();
|
|
811
|
+
}
|
|
812
|
+
// The whole boot + matrix performed ZERO outbound fetches — no hub
|
|
813
|
+
// grant, no reconcile, no token endpoint, nothing. Byte-identity is
|
|
814
|
+
// not "no hub calls", it is "no network surface constructed at all".
|
|
815
|
+
expect(fetched).toEqual([]);
|
|
816
|
+
} finally {
|
|
817
|
+
globalThis.fetch = realFetch;
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
describe('oss edition discovery (unchanged)', () => {
|
|
823
|
+
it('never advertises the hub SSO method, and never carries auth.sso (SL-1 pin)', async () => {
|
|
824
|
+
const app = await createTestApp(await createDatabase(container, 'edition_oss_disc'));
|
|
825
|
+
const info = await readJson(await app.app.request('/api/v1/instance'));
|
|
826
|
+
expect(info.edition).toBe('oss');
|
|
827
|
+
expect(info.auth.methods).not.toContain('antasphere');
|
|
828
|
+
expect(info.auth.methods).toContain('password');
|
|
829
|
+
// The discovery wire shape stays byte-identical on oss: no sso block —
|
|
830
|
+
// key ABSENT, not null.
|
|
831
|
+
expect('sso' in info.auth).toBe(false);
|
|
832
|
+
// And the error surfaces keep their pre-change defaults: the antasphere
|
|
833
|
+
// callback path does not exist (no genericOAuth plugin registered) and
|
|
834
|
+
// the better-auth error route renders its own page — no /login redirect
|
|
835
|
+
// (onAPIError.errorURL is cloud-gated).
|
|
836
|
+
const cb = await app.app.request('/api/v1/auth/oauth2/callback/antasphere?error=login_required');
|
|
837
|
+
expect(cb.status).toBe(404);
|
|
838
|
+
const errRoute = await app.app.request('/api/v1/auth/error?error=probe');
|
|
839
|
+
expect(errRoute.status).toBe(200);
|
|
840
|
+
expect(errRoute.headers.get('content-type')).toContain('text/html');
|
|
841
|
+
await app.stop();
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
it('keeps Google social sign-in when configured (the cloud gate is cloud-only)', async () => {
|
|
845
|
+
// The mirror of the cloud closure above: on oss the same GOOGLE_CLIENT_ID/
|
|
846
|
+
// SECRET REGISTER the provider — /sign-in/social builds a real Google
|
|
847
|
+
// authorization URL (200 + url, never PROVIDER_NOT_FOUND) and discovery
|
|
848
|
+
// lists 'google'. Proves the gate touches cloud alone.
|
|
849
|
+
const app = await createTestApp(await createDatabase(container, 'edition_oss_google'), {
|
|
850
|
+
GOOGLE_CLIENT_ID: 'oss-google-id',
|
|
851
|
+
GOOGLE_CLIENT_SECRET: 'oss-google-secret'
|
|
852
|
+
});
|
|
853
|
+
const info = await readJson(await app.app.request('/api/v1/instance'));
|
|
854
|
+
expect(info.edition).toBe('oss');
|
|
855
|
+
expect(info.auth.methods).toContain('google');
|
|
856
|
+
|
|
857
|
+
const social = await app.app.request('/api/v1/auth/sign-in/social', {
|
|
858
|
+
method: 'POST',
|
|
859
|
+
headers: { 'content-type': 'application/json' },
|
|
860
|
+
body: JSON.stringify({ provider: 'google' })
|
|
861
|
+
});
|
|
862
|
+
expect(social.status).toBe(200);
|
|
863
|
+
const body = await readJson(social);
|
|
864
|
+
expect(typeof body.url).toBe('string');
|
|
865
|
+
expect(body.url).toContain('accounts.google.com');
|
|
866
|
+
await app.stop();
|
|
867
|
+
});
|
|
868
|
+
});
|