@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,704 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
2
|
+
import type { StartedPostgreSqlContainer } from '@testcontainers/postgresql';
|
|
3
|
+
import { createDatabase, createTestApp, readJson, startPostgres, type TestApp, host } from './helpers.js';
|
|
4
|
+
import { FakeHub, type HubTokenOverrides, type HubUserFixture } from '@antasphere/chassis-server/testing';
|
|
5
|
+
import { HUB_SSO_SCOPES } from '@antasphere/chassis-server/identity';
|
|
6
|
+
import * as sso from './sso-helpers.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* "Sign in with Antasphere" under the user-scoped federation model
|
|
10
|
+
* (internal/federation.md), exercised against the FakeHub (real OIDC discovery
|
|
11
|
+
* + JWKS + token endpoint with refresh rotation + a caller-scoped
|
|
12
|
+
* /api/v1/orgs):
|
|
13
|
+
*
|
|
14
|
+
* - identity comes from the ID TOKEN ONLY (aud = client id); the access
|
|
15
|
+
* token is HUB-audienced (`resource=<hub>/mcp` on the code exchange) and
|
|
16
|
+
* never claim-bearing — org truth is the login-time reconcile's read of
|
|
17
|
+
* the hub's caller-scoped org list AS THE USER;
|
|
18
|
+
* - the login reconcile is FAIL-CLOSED: a pass that cannot definitively
|
|
19
|
+
* read the org list revokes the just-minted session
|
|
20
|
+
* (sso_projection_failed) — a cloud login without its projection must
|
|
21
|
+
* not exist;
|
|
22
|
+
* - the offline grant (`offline_access account:read`) persists ENCRYPTED
|
|
23
|
+
* on the account row — the between-logins credential;
|
|
24
|
+
* - JIT provisioning, D9 operator trusted-link, D10 email sync, the
|
|
25
|
+
* identity-conflict guards, and the fail-closed id_token negative space
|
|
26
|
+
* all survive the reshape;
|
|
27
|
+
* - oss: zero SSO surface.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const OWNER = { email: 'owner@sso.test', name: 'Op Erator', password: 'op-erator-password-123' };
|
|
31
|
+
|
|
32
|
+
const ORG_ACME = '11111111-aaaa-4bbb-8ccc-000000000001';
|
|
33
|
+
const ORG_BETA = '11111111-aaaa-4bbb-8ccc-000000000002';
|
|
34
|
+
const ORG_RACE = '11111111-aaaa-4bbb-8ccc-000000000003';
|
|
35
|
+
const ORG_LEFT = '11111111-aaaa-4bbb-8ccc-000000000004';
|
|
36
|
+
const ORG_RIGHT = '11111111-aaaa-4bbb-8ccc-000000000005';
|
|
37
|
+
const ORG_DARK = '11111111-aaaa-4bbb-8ccc-000000000006';
|
|
38
|
+
const ORG_WEIRD = '11111111-aaaa-4bbb-8ccc-000000000007';
|
|
39
|
+
|
|
40
|
+
const json = (body: unknown) => ({
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'content-type': 'application/json' },
|
|
43
|
+
body: JSON.stringify(body)
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
let container: StartedPostgreSqlContainer;
|
|
47
|
+
let hub: FakeHub;
|
|
48
|
+
|
|
49
|
+
beforeAll(async () => {
|
|
50
|
+
[container, hub] = await Promise.all([startPostgres(), FakeHub.start({ clientId: host.hubClientId })]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
afterAll(async () => {
|
|
54
|
+
await Promise.all([container?.stop(), hub?.stop()]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
function cloudEnv() {
|
|
58
|
+
return {
|
|
59
|
+
EDITION: 'cloud',
|
|
60
|
+
HUB_ISSUER_URL: hub.issuer,
|
|
61
|
+
HUB_CLIENT_ID: host.hubClientId,
|
|
62
|
+
HUB_CLIENT_SECRET: 'integration-test-hub-secret-0001'
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// The dance itself lives in sso-helpers.ts; these wrappers bind this hub.
|
|
67
|
+
const ssoInitiate = (app: TestApp) => sso.ssoInitiate(app);
|
|
68
|
+
const ssoDance = (app: TestApp, fixture: HubUserFixture) => sso.ssoDance(app, hub, fixture);
|
|
69
|
+
const ssoLogin = (app: TestApp, fixture: HubUserFixture) => sso.ssoLogin(app, hub, fixture);
|
|
70
|
+
const expectFailedLogin = sso.expectFailedLogin;
|
|
71
|
+
|
|
72
|
+
describe('cloud edition: the SSO entrance', () => {
|
|
73
|
+
let app: TestApp;
|
|
74
|
+
|
|
75
|
+
beforeAll(async () => {
|
|
76
|
+
app = await createTestApp(await createDatabase(container, 'hub_sso'), cloudEnv());
|
|
77
|
+
const res = await app.app.request(
|
|
78
|
+
'/api/v1/setup',
|
|
79
|
+
json({ setupToken: 'integration-test-setup-token', instanceName: 'SSO', owner: OWNER })
|
|
80
|
+
);
|
|
81
|
+
expect(res.status).toBe(201);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
afterAll(async () => {
|
|
85
|
+
await app.stop();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const u1: HubUserFixture = {
|
|
89
|
+
sub: 'hub-u1',
|
|
90
|
+
email: 'alice@acme.test',
|
|
91
|
+
name: 'Alice Acme',
|
|
92
|
+
workspaceId: ORG_ACME,
|
|
93
|
+
role: 'member',
|
|
94
|
+
workspaceName: 'Acme Corp'
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
it('first login JIT-provisions the user; the LOGIN RECONCILE projects the org from /orgs', async () => {
|
|
98
|
+
const cookie = await ssoLogin(app, u1);
|
|
99
|
+
|
|
100
|
+
// The session is real and lands in the projected workspace.
|
|
101
|
+
const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie } }));
|
|
102
|
+
expect(me.user.email).toBe('alice@acme.test');
|
|
103
|
+
const projected = me.workspaces.find((w: { name: string }) => w.name === 'Acme Corp');
|
|
104
|
+
expect(projected).toBeTruthy();
|
|
105
|
+
expect(me.activeWorkspaceId).toBe(projected.id);
|
|
106
|
+
|
|
107
|
+
// Deck list serves from the projected workspace (empty but authorized).
|
|
108
|
+
const decks = await app.app.request(host.probeRoute, { headers: { cookie } });
|
|
109
|
+
expect(decks.status).toBe(200);
|
|
110
|
+
|
|
111
|
+
// DB truth: verified user, projection stamped with the hub org id,
|
|
112
|
+
// membership carries the hub role verbatim with origin='hub'.
|
|
113
|
+
const { rows: users } = await app.db.pool.query(
|
|
114
|
+
`SELECT id, email_verified FROM "user" WHERE email = 'alice@acme.test'`
|
|
115
|
+
);
|
|
116
|
+
expect(users).toHaveLength(1);
|
|
117
|
+
expect(users[0].email_verified).toBe(true);
|
|
118
|
+
const { rows: ws } = await app.db.pool.query(
|
|
119
|
+
`SELECT id, name, hub_status FROM workspaces WHERE central_account_id = $1`,
|
|
120
|
+
[ORG_ACME]
|
|
121
|
+
);
|
|
122
|
+
expect(ws).toHaveLength(1);
|
|
123
|
+
expect(ws[0].name).toBe('Acme Corp');
|
|
124
|
+
expect(ws[0].hub_status).toBe('active');
|
|
125
|
+
const { rows: members } = await app.db.pool.query(
|
|
126
|
+
`SELECT role, origin, is_active FROM workspace_members WHERE workspace_id = $1 AND user_id = $2`,
|
|
127
|
+
[ws[0].id, users[0].id]
|
|
128
|
+
);
|
|
129
|
+
expect(members).toEqual([{ role: 'member', origin: 'hub', is_active: true }]);
|
|
130
|
+
|
|
131
|
+
// The login's org read hit the caller-scoped /orgs AS THE USER, with a
|
|
132
|
+
// hub-audienced bearer — never a service key.
|
|
133
|
+
expect(hub.orgsRequests.length).toBeGreaterThan(0);
|
|
134
|
+
for (const req of hub.orgsRequests) {
|
|
135
|
+
expect(req.auth).toMatch(/^Bearer /);
|
|
136
|
+
expect(req.auth).not.toContain('ant_');
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('the code exchange carries the HUB-audienced resource (the tokenResource seam) + the offline scopes', async () => {
|
|
141
|
+
// RFC 8707 resource = <hub>/mcp — the flip from <own>/mcp: the callback
|
|
142
|
+
// access token is a bearer for the HUB's /api/v1, not for ours.
|
|
143
|
+
expect(hub.lastTokenRequest?.get('resource')).toBe(`${hub.issuer}/mcp`);
|
|
144
|
+
// Confidential client: the secret rode the token request too.
|
|
145
|
+
expect(hub.lastTokenRequest?.get('client_secret')).toBe('integration-test-hub-secret-0001');
|
|
146
|
+
// The authorize leg requests the offline grant scopes.
|
|
147
|
+
const init = json({ providerId: 'antasphere', callbackURL: '/' });
|
|
148
|
+
const signIn = await app.app.request('/api/v1/auth/sign-in/oauth2', {
|
|
149
|
+
...init,
|
|
150
|
+
headers: { ...init.headers, 'x-forwarded-for': sso.nextIp() }
|
|
151
|
+
});
|
|
152
|
+
const { url } = await readJson(signIn);
|
|
153
|
+
const scope = new URL(url).searchParams.get('scope') ?? '';
|
|
154
|
+
expect(scope).toContain('offline_access');
|
|
155
|
+
expect(scope).toContain('account:read');
|
|
156
|
+
// PRDCT-2443: the hub's dedicated scope for POST /orgs — and the list is
|
|
157
|
+
// EXACTLY the stated one (a scope the hub does not know fails the whole
|
|
158
|
+
// sign-in, so nothing rides along unannounced; account:write never does).
|
|
159
|
+
expect(scope.split(' ').sort()).toEqual([...HUB_SSO_SCOPES].sort());
|
|
160
|
+
expect(scope.split(' ')).toContain('orgs:create');
|
|
161
|
+
expect(scope.split(' ')).not.toContain('account:write');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('persists the offline grant ENCRYPTED on the account row (never plaintext at rest)', async () => {
|
|
165
|
+
const { rows } = await app.db.pool.query(
|
|
166
|
+
`SELECT a.access_token, a.refresh_token FROM account a JOIN "user" u ON u.id = a.user_id
|
|
167
|
+
WHERE u.email = 'alice@acme.test' AND a.provider_id = 'antasphere'`
|
|
168
|
+
);
|
|
169
|
+
expect(rows).toHaveLength(1);
|
|
170
|
+
const { access_token, refresh_token } = rows[0] as { access_token: string; refresh_token: string };
|
|
171
|
+
expect(refresh_token).toBeTruthy();
|
|
172
|
+
expect(access_token).toBeTruthy();
|
|
173
|
+
// The fake mints raw refresh tokens as `rt_<uuid>`; what is stored must
|
|
174
|
+
// be Better Auth's symmetric ciphertext (hex or $ba$ envelope), never
|
|
175
|
+
// the raw secret.
|
|
176
|
+
expect(refresh_token).not.toMatch(/^rt_/);
|
|
177
|
+
expect(/^\$ba\$/.test(refresh_token) || /^[0-9a-f]+$/i.test(refresh_token)).toBe(true);
|
|
178
|
+
expect(access_token.split('.').length).not.toBe(3); // not the raw JWT
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('second login re-syncs email (D10) and follows the hub org list (role + name) — no duplicate projection', async () => {
|
|
182
|
+
const cookie = await ssoLogin(app, {
|
|
183
|
+
...u1,
|
|
184
|
+
email: 'alice.renamed@acme.test',
|
|
185
|
+
role: 'admin',
|
|
186
|
+
workspaceName: 'Acme Corp GmbH'
|
|
187
|
+
});
|
|
188
|
+
const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie } }));
|
|
189
|
+
expect(me.user.email).toBe('alice.renamed@acme.test');
|
|
190
|
+
|
|
191
|
+
const { rows: ws } = await app.db.pool.query(
|
|
192
|
+
`SELECT id, name FROM workspaces WHERE central_account_id = $1`,
|
|
193
|
+
[ORG_ACME]
|
|
194
|
+
);
|
|
195
|
+
expect(ws).toHaveLength(1); // the unique partial index held — same row, renamed
|
|
196
|
+
expect(ws[0].name).toBe('Acme Corp GmbH');
|
|
197
|
+
const { rows: members } = await app.db.pool.query(
|
|
198
|
+
`SELECT m.role, m.origin, m.is_active FROM workspace_members m
|
|
199
|
+
JOIN "user" u ON u.id = m.user_id WHERE m.workspace_id = $1 AND u.email = $2`,
|
|
200
|
+
[ws[0].id, 'alice.renamed@acme.test']
|
|
201
|
+
);
|
|
202
|
+
expect(members).toEqual([{ role: 'admin', origin: 'hub', is_active: true }]);
|
|
203
|
+
// The old address is gone — one user, synced in place.
|
|
204
|
+
const { rows: old } = await app.db.pool.query(`SELECT 1 FROM "user" WHERE email = 'alice@acme.test'`);
|
|
205
|
+
expect(old).toHaveLength(0);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('reactivates a deactivated hub membership at the next login', async () => {
|
|
209
|
+
await app.db.pool.query(
|
|
210
|
+
`UPDATE workspace_members SET is_active = false
|
|
211
|
+
WHERE workspace_id = (SELECT id FROM workspaces WHERE central_account_id = $1)`,
|
|
212
|
+
[ORG_ACME]
|
|
213
|
+
);
|
|
214
|
+
await ssoLogin(app, { ...u1, email: 'alice.renamed@acme.test', role: 'admin' });
|
|
215
|
+
const { rows } = await app.db.pool.query(
|
|
216
|
+
`SELECT is_active FROM workspace_members
|
|
217
|
+
WHERE workspace_id = (SELECT id FROM workspaces WHERE central_account_id = $1)`,
|
|
218
|
+
[ORG_ACME]
|
|
219
|
+
);
|
|
220
|
+
expect(rows).toEqual([{ is_active: true }]);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('projects under a placeholder when the hub omits the org name, and self-heals later', async () => {
|
|
224
|
+
const u2: HubUserFixture = {
|
|
225
|
+
sub: 'hub-u2',
|
|
226
|
+
email: 'bob@beta.test',
|
|
227
|
+
workspaceId: ORG_BETA,
|
|
228
|
+
role: 'owner',
|
|
229
|
+
workspaceName: null // hub without a name on the entry
|
|
230
|
+
};
|
|
231
|
+
await ssoLogin(app, u2);
|
|
232
|
+
const { rows: before } = await app.db.pool.query(
|
|
233
|
+
`SELECT name FROM workspaces WHERE central_account_id = $1`,
|
|
234
|
+
[ORG_BETA]
|
|
235
|
+
);
|
|
236
|
+
expect(before[0].name).toBe(`Antasphere workspace ${ORG_BETA.slice(0, 8)}`);
|
|
237
|
+
|
|
238
|
+
// The name appears at a later login — the projection follows.
|
|
239
|
+
await ssoLogin(app, { ...u2, workspaceName: 'Beta LLC' });
|
|
240
|
+
const { rows: after } = await app.db.pool.query(
|
|
241
|
+
`SELECT name FROM workspaces WHERE central_account_id = $1`,
|
|
242
|
+
[ORG_BETA]
|
|
243
|
+
);
|
|
244
|
+
expect(after[0].name).toBe('Beta LLC');
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('materializes the hub-level default org on the membership rows (is_default)', async () => {
|
|
248
|
+
// Give alice a second org and mark ACME as her hub default.
|
|
249
|
+
hub.setUserOrg('hub-u1', ORG_BETA, { name: 'Beta LLC', role: 'member' });
|
|
250
|
+
hub.setUserOrg('hub-u1', ORG_ACME, { name: 'Acme Corp GmbH', role: 'admin', isDefault: true });
|
|
251
|
+
const cookie = await ssoLogin(app, {
|
|
252
|
+
...u1,
|
|
253
|
+
email: 'alice.renamed@acme.test',
|
|
254
|
+
role: 'admin',
|
|
255
|
+
workspaceName: 'Acme Corp GmbH'
|
|
256
|
+
});
|
|
257
|
+
const { rows } = await app.db.pool.query(
|
|
258
|
+
`SELECT w.central_account_id, m.is_default FROM workspace_members m
|
|
259
|
+
JOIN workspaces w ON w.id = m.workspace_id
|
|
260
|
+
JOIN "user" u ON u.id = m.user_id
|
|
261
|
+
WHERE u.email = 'alice.renamed@acme.test' ORDER BY w.central_account_id`
|
|
262
|
+
);
|
|
263
|
+
expect(rows).toEqual([
|
|
264
|
+
{ central_account_id: ORG_ACME, is_default: true },
|
|
265
|
+
{ central_account_id: ORG_BETA, is_default: false }
|
|
266
|
+
]);
|
|
267
|
+
// Selector-less requests land on the default — and /me marks it
|
|
268
|
+
// explicitly (clients read the flag, never index 0).
|
|
269
|
+
const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie } }));
|
|
270
|
+
const acme = me.workspaces.find((w: { name: string }) => w.name === 'Acme Corp GmbH');
|
|
271
|
+
expect(me.activeWorkspaceId).toBe(acme.id);
|
|
272
|
+
expect(acme.default).toBe(true);
|
|
273
|
+
|
|
274
|
+
// The default moves hub-side → the flag follows at the next login.
|
|
275
|
+
hub.setUserOrg('hub-u1', ORG_ACME, { name: 'Acme Corp GmbH', role: 'admin' });
|
|
276
|
+
hub.setUserOrg('hub-u1', ORG_BETA, { name: 'Beta LLC', role: 'member', isDefault: true });
|
|
277
|
+
await ssoLogin(app, {
|
|
278
|
+
...u1,
|
|
279
|
+
email: 'alice.renamed@acme.test',
|
|
280
|
+
role: 'admin',
|
|
281
|
+
workspaceName: 'Acme Corp GmbH'
|
|
282
|
+
});
|
|
283
|
+
const { rows: moved } = await app.db.pool.query(
|
|
284
|
+
`SELECT w.central_account_id, m.is_default FROM workspace_members m
|
|
285
|
+
JOIN workspaces w ON w.id = m.workspace_id
|
|
286
|
+
JOIN "user" u ON u.id = m.user_id
|
|
287
|
+
WHERE u.email = 'alice.renamed@acme.test' ORDER BY w.central_account_id`
|
|
288
|
+
);
|
|
289
|
+
expect(moved).toEqual([
|
|
290
|
+
{ central_account_id: ORG_ACME, is_default: false },
|
|
291
|
+
{ central_account_id: ORG_BETA, is_default: true }
|
|
292
|
+
]);
|
|
293
|
+
// Back to a single-org alice for the tests below.
|
|
294
|
+
hub.removeUserOrg('hub-u1', ORG_BETA);
|
|
295
|
+
hub.setUserOrg('hub-u1', ORG_ACME, { name: 'Acme Corp GmbH', role: 'admin' });
|
|
296
|
+
await ssoLogin(app, {
|
|
297
|
+
...u1,
|
|
298
|
+
email: 'alice.renamed@acme.test',
|
|
299
|
+
role: 'admin',
|
|
300
|
+
workspaceName: 'Acme Corp GmbH'
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('links the setup operator via the D9 trusted-provider path instead of duplicating', async () => {
|
|
305
|
+
const cookie = await ssoLogin(app, {
|
|
306
|
+
sub: 'hub-operator',
|
|
307
|
+
email: OWNER.email,
|
|
308
|
+
name: OWNER.name,
|
|
309
|
+
workspaceId: ORG_ACME,
|
|
310
|
+
role: 'owner',
|
|
311
|
+
workspaceName: 'Acme Corp GmbH'
|
|
312
|
+
});
|
|
313
|
+
// Still exactly ONE local user for the operator's email…
|
|
314
|
+
const { rows: users } = await app.db.pool.query(`SELECT id FROM "user" WHERE email = $1`, [OWNER.email]);
|
|
315
|
+
expect(users).toHaveLength(1);
|
|
316
|
+
// …now carrying the hub account link…
|
|
317
|
+
const { rows: accounts } = await app.db.pool.query(
|
|
318
|
+
`SELECT account_id FROM account WHERE user_id = $1 AND provider_id = 'antasphere'`,
|
|
319
|
+
[users[0].id]
|
|
320
|
+
);
|
|
321
|
+
expect(accounts).toEqual([{ account_id: 'hub-operator' }]);
|
|
322
|
+
// …with exactly the projection: cloud setup mints NO workspace
|
|
323
|
+
// (user-scoped federation), so the operator's only membership is the
|
|
324
|
+
// hub org their SSO login reconciled in.
|
|
325
|
+
const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie } }));
|
|
326
|
+
const names = me.workspaces.map((w: { name: string }) => w.name).sort();
|
|
327
|
+
expect(names).toContain('Acme Corp GmbH');
|
|
328
|
+
expect(me.workspaces.length).toBe(1);
|
|
329
|
+
const { rows: memberships } = await app.db.pool.query(
|
|
330
|
+
`SELECT origin, role FROM workspace_members m JOIN workspaces w ON w.id = m.workspace_id
|
|
331
|
+
WHERE m.user_id = $1 AND w.central_account_id = $2`,
|
|
332
|
+
[users[0].id, ORG_ACME]
|
|
333
|
+
);
|
|
334
|
+
expect(memberships).toEqual([{ origin: 'hub', role: 'owner' }]);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('two concurrent first-logins into a fresh org project exactly ONE workspace', async () => {
|
|
338
|
+
const fixtures: HubUserFixture[] = [
|
|
339
|
+
{
|
|
340
|
+
sub: 'hub-race-1',
|
|
341
|
+
email: 'r1@race.test',
|
|
342
|
+
workspaceId: ORG_RACE,
|
|
343
|
+
role: 'member',
|
|
344
|
+
workspaceName: 'Race Org'
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
sub: 'hub-race-2',
|
|
348
|
+
email: 'r2@race.test',
|
|
349
|
+
workspaceId: ORG_RACE,
|
|
350
|
+
role: 'admin',
|
|
351
|
+
workspaceName: 'Race Org'
|
|
352
|
+
}
|
|
353
|
+
];
|
|
354
|
+
// Two full states first, then both callbacks in flight together.
|
|
355
|
+
const dances = [];
|
|
356
|
+
for (const f of fixtures) {
|
|
357
|
+
const { state, stateCookie } = await ssoInitiate(app);
|
|
358
|
+
dances.push({ code: hub.mintCode(f), state, stateCookie });
|
|
359
|
+
}
|
|
360
|
+
const results = await Promise.all(
|
|
361
|
+
dances.map(({ code, state, stateCookie }) =>
|
|
362
|
+
app.app.request(
|
|
363
|
+
`/api/v1/auth/oauth2/callback/antasphere?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`,
|
|
364
|
+
{ headers: { cookie: stateCookie } }
|
|
365
|
+
)
|
|
366
|
+
)
|
|
367
|
+
);
|
|
368
|
+
for (const res of results) {
|
|
369
|
+
expect(res.status).toBe(302);
|
|
370
|
+
expect(res.headers.get('location')).toBe('/');
|
|
371
|
+
}
|
|
372
|
+
const { rows: ws } = await app.db.pool.query(`SELECT id FROM workspaces WHERE central_account_id = $1`, [
|
|
373
|
+
ORG_RACE
|
|
374
|
+
]);
|
|
375
|
+
expect(ws).toHaveLength(1);
|
|
376
|
+
const { rows: members } = await app.db.pool.query(
|
|
377
|
+
`SELECT role FROM workspace_members WHERE workspace_id = $1 ORDER BY role`,
|
|
378
|
+
[ws[0].id]
|
|
379
|
+
);
|
|
380
|
+
expect(members).toEqual([{ role: 'admin' }, { role: 'member' }]);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('parallel logins of ONE user project the org-list union with per-org roles intact', async () => {
|
|
384
|
+
const base = { sub: 'hub-multi', email: 'multi@orgs.test', name: 'Multi Org' };
|
|
385
|
+
// Establish the user first (two parallel FIRST logins of a brand-new
|
|
386
|
+
// user race the JIT insert; the loser fails cleanly on uniqueness).
|
|
387
|
+
await ssoLogin(app, { ...base, workspaceId: ORG_LEFT, role: 'owner', workspaceName: 'Left Org' });
|
|
388
|
+
hub.setUserOrg('hub-multi', ORG_RIGHT, { name: 'Right Org', role: 'member' });
|
|
389
|
+
|
|
390
|
+
const dances = [];
|
|
391
|
+
for (let i = 0; i < 2; i++) {
|
|
392
|
+
const { state, stateCookie } = await ssoInitiate(app);
|
|
393
|
+
dances.push({
|
|
394
|
+
code: hub.mintCode({ ...base, workspaceId: ORG_LEFT, role: 'owner', workspaceName: 'Left Org' }),
|
|
395
|
+
state,
|
|
396
|
+
stateCookie
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
const results = await Promise.all(
|
|
400
|
+
dances.map(({ code, state, stateCookie }) =>
|
|
401
|
+
app.app.request(
|
|
402
|
+
`/api/v1/auth/oauth2/callback/antasphere?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`,
|
|
403
|
+
{ headers: { cookie: stateCookie } }
|
|
404
|
+
)
|
|
405
|
+
)
|
|
406
|
+
);
|
|
407
|
+
for (const res of results) {
|
|
408
|
+
expect(res.status).toBe(302);
|
|
409
|
+
expect(res.headers.get('location')).toBe('/');
|
|
410
|
+
}
|
|
411
|
+
// Both orgs projected, each with the role the HUB LIST asserts for it —
|
|
412
|
+
// the org list is per-user hub truth, not per-login claims.
|
|
413
|
+
const { rows } = await app.db.pool.query(
|
|
414
|
+
`SELECT w.central_account_id, m.role FROM workspace_members m
|
|
415
|
+
JOIN workspaces w ON w.id = m.workspace_id
|
|
416
|
+
JOIN "user" u ON u.id = m.user_id
|
|
417
|
+
WHERE u.email = 'multi@orgs.test' ORDER BY w.central_account_id`,
|
|
418
|
+
[]
|
|
419
|
+
);
|
|
420
|
+
expect(rows).toEqual([
|
|
421
|
+
{ central_account_id: ORG_LEFT, role: 'owner' },
|
|
422
|
+
{ central_account_id: ORG_RIGHT, role: 'member' }
|
|
423
|
+
]);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe('the FAIL-CLOSED login reconcile (a login without its projection must not exist)', () => {
|
|
427
|
+
it('an /orgs outage during the callback revokes the session (sso_projection_failed)', async () => {
|
|
428
|
+
const dana: HubUserFixture = {
|
|
429
|
+
sub: 'hub-dark',
|
|
430
|
+
email: 'dana@dark.test',
|
|
431
|
+
workspaceId: ORG_DARK,
|
|
432
|
+
role: 'owner',
|
|
433
|
+
workspaceName: 'Dark Org'
|
|
434
|
+
};
|
|
435
|
+
hub.orgsMode = 'network';
|
|
436
|
+
try {
|
|
437
|
+
const res = await ssoDance(app, dana);
|
|
438
|
+
await expectFailedLogin(app, res, 'error=sso_projection_failed');
|
|
439
|
+
} finally {
|
|
440
|
+
hub.orgsMode = 'ok';
|
|
441
|
+
}
|
|
442
|
+
// No session, no projection. (The JIT user row exists — created
|
|
443
|
+
// before the after-hook; the orphan purge covers it, and a later
|
|
444
|
+
// successful login adopts it.)
|
|
445
|
+
const { rows: ws } = await app.db.pool.query(`SELECT 1 FROM workspaces WHERE central_account_id = $1`, [
|
|
446
|
+
ORG_DARK
|
|
447
|
+
]);
|
|
448
|
+
expect(ws).toHaveLength(0);
|
|
449
|
+
// The retry heals end-to-end once the hub answers again.
|
|
450
|
+
const cookie = await ssoLogin(app, dana);
|
|
451
|
+
const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie } }));
|
|
452
|
+
expect(me.workspaces.map((w: { name: string }) => w.name)).toContain('Dark Org');
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('a hub 500 on /orgs fails the login the same way', async () => {
|
|
456
|
+
hub.orgsMode = 'http500';
|
|
457
|
+
try {
|
|
458
|
+
const res = await ssoDance(app, {
|
|
459
|
+
sub: 'hub-dark',
|
|
460
|
+
email: 'dana@dark.test',
|
|
461
|
+
workspaceId: ORG_DARK,
|
|
462
|
+
role: 'owner',
|
|
463
|
+
workspaceName: 'Dark Org'
|
|
464
|
+
});
|
|
465
|
+
await expectFailedLogin(app, res, 'error=sso_projection_failed');
|
|
466
|
+
} finally {
|
|
467
|
+
hub.orgsMode = 'ok';
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
describe('the access token is a bearer, not an identity: wrong shapes self-heal via refresh', () => {
|
|
473
|
+
it('an OPAQUE callback access token still logs in (one refresh retry mints a hub-callable JWT)', async () => {
|
|
474
|
+
const opal: HubUserFixture = {
|
|
475
|
+
sub: 'hub-opal',
|
|
476
|
+
email: 'opal@selfheal.test',
|
|
477
|
+
workspaceId: ORG_BETA,
|
|
478
|
+
role: 'member',
|
|
479
|
+
workspaceName: 'Beta LLC',
|
|
480
|
+
overrides: { forceOpaque: true }
|
|
481
|
+
};
|
|
482
|
+
const refreshesBefore = hub.refreshCount();
|
|
483
|
+
const cookie = await ssoLogin(app, opal);
|
|
484
|
+
expect(hub.refreshCount()).toBeGreaterThan(refreshesBefore); // the heal was a refresh
|
|
485
|
+
const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie } }));
|
|
486
|
+
expect(me.workspaces.map((w: { name: string }) => w.name)).toContain('Beta LLC');
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it('a WRONG-AUDIENCE callback access token (a hub not honoring resource) self-heals too', async () => {
|
|
490
|
+
const wanda: HubUserFixture = {
|
|
491
|
+
sub: 'hub-wanda',
|
|
492
|
+
email: 'wanda@selfheal.test',
|
|
493
|
+
workspaceId: ORG_BETA,
|
|
494
|
+
role: 'member',
|
|
495
|
+
workspaceName: 'Beta LLC',
|
|
496
|
+
overrides: { accessAud: ['http://other-tool.test/mcp'] }
|
|
497
|
+
};
|
|
498
|
+
const cookie = await ssoLogin(app, wanda);
|
|
499
|
+
const me = await readJson(await app.app.request('/api/v1/me', { headers: { cookie } }));
|
|
500
|
+
expect(me.workspaces.map((w: { name: string }) => w.name)).toContain('Beta LLC');
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
describe('fail-closed id_token negatives (no user, no session)', () => {
|
|
505
|
+
const intruder = (overrides: HubTokenOverrides): HubUserFixture => ({
|
|
506
|
+
sub: 'hub-evil',
|
|
507
|
+
email: 'evil@negative.test',
|
|
508
|
+
workspaceId: '11111111-aaaa-4bbb-8ccc-00000000dead',
|
|
509
|
+
role: 'member',
|
|
510
|
+
overrides
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
async function expectRejected(fixture: HubUserFixture) {
|
|
514
|
+
const res = await ssoDance(app, fixture);
|
|
515
|
+
// getUserInfo returned null → the plugin redirects to its error URL.
|
|
516
|
+
await expectFailedLogin(app, res, 'error=');
|
|
517
|
+
const { rows } = await app.db.pool.query(`SELECT 1 FROM "user" WHERE email = $1`, [fixture.email]);
|
|
518
|
+
expect(rows).toHaveLength(0);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
it('rejects tokens from a foreign issuer', async () => {
|
|
522
|
+
await expectRejected(intruder({ iss: 'http://evil-hub.test' }));
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('rejects an expired id_token', async () => {
|
|
526
|
+
await expectRejected(intruder({ expiresInSeconds: -60 }));
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('an unknown hub role is never projected — the login succeeds but grants nothing (D11)', async () => {
|
|
531
|
+
// Role semantics are the org list's business now: the id_token carries
|
|
532
|
+
// no role, so the login itself cannot be refused on one — instead the
|
|
533
|
+
// reconcile refuses to project an unknown role (derived, never mapped).
|
|
534
|
+
const weird: HubUserFixture = {
|
|
535
|
+
sub: 'hub-weird',
|
|
536
|
+
email: 'weird@role.test',
|
|
537
|
+
workspaceId: ORG_WEIRD,
|
|
538
|
+
role: 'emperor',
|
|
539
|
+
workspaceName: 'Weird Org'
|
|
540
|
+
};
|
|
541
|
+
const cookie = await ssoLogin(app, weird);
|
|
542
|
+
const { rows: ws } = await app.db.pool.query(`SELECT 1 FROM workspaces WHERE central_account_id = $1`, [
|
|
543
|
+
ORG_WEIRD
|
|
544
|
+
]);
|
|
545
|
+
expect(ws).toHaveLength(0); // nothing projected
|
|
546
|
+
// The session exists but reaches no workspace (the sole org's unknown
|
|
547
|
+
// role projected nothing) — /me answers the zero-membership zero state
|
|
548
|
+
// (200, empty list), not a 401: the dashboard renders the
|
|
549
|
+
// no-organization page, never a login bounce (user-scoped federation).
|
|
550
|
+
const me = await app.app.request('/api/v1/me', { headers: { cookie } });
|
|
551
|
+
expect(me.status).toBe(200);
|
|
552
|
+
const meBody = await readJson(me);
|
|
553
|
+
expect(meBody.workspaces).toEqual([]);
|
|
554
|
+
expect(meBody.activeWorkspaceId).toBeNull();
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('fails the login cleanly when the hub-asserted email collides with another local user (D10)', async () => {
|
|
558
|
+
const u6: HubUserFixture = {
|
|
559
|
+
sub: 'hub-u6',
|
|
560
|
+
email: 'carol@collide.test',
|
|
561
|
+
workspaceId: ORG_BETA,
|
|
562
|
+
role: 'member',
|
|
563
|
+
workspaceName: 'Beta LLC'
|
|
564
|
+
};
|
|
565
|
+
await ssoLogin(app, u6); // establishes carol
|
|
566
|
+
// The hub now asserts the OPERATOR's address for carol (hub-side email
|
|
567
|
+
// change to an address someone else still holds locally).
|
|
568
|
+
const res = await ssoDance(app, { ...u6, email: OWNER.email });
|
|
569
|
+
await expectFailedLogin(app, res, 'error=sso_email_conflict');
|
|
570
|
+
// Nothing corrupted: carol keeps her email, the operator keeps theirs.
|
|
571
|
+
const { rows: carol } = await app.db.pool.query(
|
|
572
|
+
`SELECT email FROM "user" u JOIN account a ON a.user_id = u.id
|
|
573
|
+
WHERE a.provider_id = 'antasphere' AND a.account_id = 'hub-u6'`
|
|
574
|
+
);
|
|
575
|
+
expect(carol).toEqual([{ email: 'carol@collide.test' }]);
|
|
576
|
+
const { rows: owners } = await app.db.pool.query(
|
|
577
|
+
`SELECT COUNT(*)::int AS n FROM "user" WHERE email = $1`,
|
|
578
|
+
[OWNER.email]
|
|
579
|
+
);
|
|
580
|
+
expect(owners).toEqual([{ n: 1 }]);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('refuses to merge two hub identities onto one local user (stale-email takeover)', async () => {
|
|
584
|
+
// dave logs in with u7 …
|
|
585
|
+
const dave: HubUserFixture = {
|
|
586
|
+
sub: 'hub-u7',
|
|
587
|
+
email: 'dave@stale.test',
|
|
588
|
+
workspaceId: ORG_BETA,
|
|
589
|
+
role: 'member',
|
|
590
|
+
workspaceName: 'Beta LLC'
|
|
591
|
+
};
|
|
592
|
+
await ssoLogin(app, dave);
|
|
593
|
+
// … then a DIFFERENT hub identity asserts dave's (now stale hub-side)
|
|
594
|
+
// address: the trusted email-match link would merge it onto dave's local
|
|
595
|
+
// user — the guard undoes the link and fails the login.
|
|
596
|
+
const res = await ssoDance(app, { ...dave, sub: 'hub-u8', name: 'Mallory' });
|
|
597
|
+
await expectFailedLogin(app, res, 'error=sso_identity_conflict');
|
|
598
|
+
const { rows: accounts } = await app.db.pool.query(
|
|
599
|
+
`SELECT a.account_id FROM account a JOIN "user" u ON u.id = a.user_id
|
|
600
|
+
WHERE u.email = 'dave@stale.test' AND a.provider_id = 'antasphere' ORDER BY a.account_id`
|
|
601
|
+
);
|
|
602
|
+
expect(accounts).toEqual([{ account_id: 'hub-u7' }]); // the merge left no trace
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('maps the hub email_verified claim honestly on JIT (unverified stays unverified)', async () => {
|
|
606
|
+
await ssoLogin(app, {
|
|
607
|
+
sub: 'hub-u9',
|
|
608
|
+
email: 'eve@unverified.test',
|
|
609
|
+
emailVerified: false,
|
|
610
|
+
workspaceId: ORG_BETA,
|
|
611
|
+
role: 'member',
|
|
612
|
+
workspaceName: 'Beta LLC'
|
|
613
|
+
});
|
|
614
|
+
const { rows } = await app.db.pool.query(`SELECT email_verified FROM "user" WHERE email = $1`, [
|
|
615
|
+
'eve@unverified.test'
|
|
616
|
+
]);
|
|
617
|
+
expect(rows).toEqual([{ email_verified: false }]);
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
it('never syncs emailVerified DOWNWARD for an unchanged address (the break-glass latch)', async () => {
|
|
621
|
+
const grace: HubUserFixture = {
|
|
622
|
+
sub: 'hub-u11',
|
|
623
|
+
email: 'grace@latch.test',
|
|
624
|
+
workspaceId: ORG_BETA,
|
|
625
|
+
role: 'member',
|
|
626
|
+
workspaceName: 'Beta LLC'
|
|
627
|
+
};
|
|
628
|
+
await ssoLogin(app, grace); // JIT, verified (hub default)
|
|
629
|
+
// The hub now asserts email_verified=false for the SAME address (a hub
|
|
630
|
+
// posture change / bug): the local latch must hold — break-glass refuses
|
|
631
|
+
// unverified users, so a downward flip would close the operator door.
|
|
632
|
+
await ssoLogin(app, { ...grace, emailVerified: false });
|
|
633
|
+
const { rows } = await app.db.pool.query(`SELECT email_verified FROM "user" WHERE email = $1`, [
|
|
634
|
+
'grace@latch.test'
|
|
635
|
+
]);
|
|
636
|
+
expect(rows).toEqual([{ email_verified: true }]);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('takes the hub-asserted verification state when the address CHANGES (no false latch)', async () => {
|
|
640
|
+
// grace's hub email changes to a NEW address asserted unverified: the
|
|
641
|
+
// new mailbox is unproven, so the honest state is unverified.
|
|
642
|
+
await ssoLogin(app, {
|
|
643
|
+
sub: 'hub-u11',
|
|
644
|
+
email: 'grace.moved@latch.test',
|
|
645
|
+
emailVerified: false,
|
|
646
|
+
workspaceId: ORG_BETA,
|
|
647
|
+
role: 'member',
|
|
648
|
+
workspaceName: 'Beta LLC'
|
|
649
|
+
});
|
|
650
|
+
const { rows } = await app.db.pool.query(`SELECT email, email_verified FROM "user" WHERE email = $1`, [
|
|
651
|
+
'grace.moved@latch.test'
|
|
652
|
+
]);
|
|
653
|
+
expect(rows).toEqual([{ email: 'grace.moved@latch.test', email_verified: false }]);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it('a residue second hub link is undone in favor of the OLDER identity (guard keys on recency)', async () => {
|
|
657
|
+
const frank: HubUserFixture = {
|
|
658
|
+
sub: 'hub-u10',
|
|
659
|
+
email: 'frank@residue.test',
|
|
660
|
+
workspaceId: ORG_BETA,
|
|
661
|
+
role: 'member',
|
|
662
|
+
workspaceName: 'Beta LLC'
|
|
663
|
+
};
|
|
664
|
+
await ssoLogin(app, frank);
|
|
665
|
+
const { rows: users } = await app.db.pool.query(`SELECT id FROM "user" WHERE email = $1`, [
|
|
666
|
+
'frank@residue.test'
|
|
667
|
+
]);
|
|
668
|
+
// Simulate the residue a crash between better-auth's link and the
|
|
669
|
+
// after-hook (or an explicit /oauth2/link) leaves behind: a SECOND,
|
|
670
|
+
// newer antasphere row on the same user that no guard ever saw.
|
|
671
|
+
await app.db.pool.query(
|
|
672
|
+
`INSERT INTO account (id, account_id, provider_id, user_id, created_at, updated_at)
|
|
673
|
+
VALUES ('residue-acct-1', 'hub-ghost', 'antasphere', $1, now() + interval '1 second', now())`,
|
|
674
|
+
[users[0].id]
|
|
675
|
+
);
|
|
676
|
+
// Frank's own (older, legitimate) identity logs in: the guard must
|
|
677
|
+
// delete the GHOST row, not frank's — deleting the current-sub row here
|
|
678
|
+
// would destroy the established identity and strand the user.
|
|
679
|
+
const res = await ssoDance(app, frank);
|
|
680
|
+
await expectFailedLogin(app, res, 'error=sso_identity_conflict');
|
|
681
|
+
const { rows: accounts } = await app.db.pool.query(
|
|
682
|
+
`SELECT account_id FROM account WHERE user_id = $1 AND provider_id = 'antasphere'`,
|
|
683
|
+
[users[0].id]
|
|
684
|
+
);
|
|
685
|
+
expect(accounts).toEqual([{ account_id: 'hub-u10' }]); // ghost gone, frank intact
|
|
686
|
+
// Self-healed: the retry logs straight in.
|
|
687
|
+
await ssoLogin(app, frank);
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
describe('oss edition: zero SSO surface', () => {
|
|
692
|
+
it('registers no OAuth relying party at all', async () => {
|
|
693
|
+
const app = await createTestApp(await createDatabase(container, 'hub_sso_oss'));
|
|
694
|
+
// The genericOAuth plugin is absent → its routes do not exist.
|
|
695
|
+
const res = await app.app.request(
|
|
696
|
+
'/api/v1/auth/sign-in/oauth2',
|
|
697
|
+
json({ providerId: 'antasphere', callbackURL: '/' })
|
|
698
|
+
);
|
|
699
|
+
expect(res.status).toBe(404);
|
|
700
|
+
const cb = await app.app.request('/api/v1/auth/oauth2/callback/antasphere?code=x&state=y');
|
|
701
|
+
expect(cb.status).toBe(404);
|
|
702
|
+
await app.stop();
|
|
703
|
+
});
|
|
704
|
+
});
|