@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,5 @@
|
|
|
1
|
+
export { AccountDeletionService, LastOwnerError, isLastOwnerDbError } from './deletion.js';
|
|
2
|
+
export { ErasureLog, emailHash } from './erasure-log.js';
|
|
3
|
+
export { mintRefusal } from './mint-refusal.js';
|
|
4
|
+
export { isDuplicateAccountError } from './signup-duplicate.js';
|
|
5
|
+
export { parseSuperadminEmails } from './superadmin.js';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { and, eq, ne } from 'drizzle-orm';
|
|
2
|
+
import { workspaceMembers, type Db } from '@antasphere/chassis-db';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared refusal for the SIGN-IN-EQUIVALENT mint routes (PRDCT-1354,
|
|
6
|
+
* findings AUTH-1/AUTH-2/AUTH-8). `/members/{id}/reset-link`,
|
|
7
|
+
* `/members/{id}/change-email-link` and the demo pass mint
|
|
8
|
+
* (`POST /demo/passes`) all hand the caller a bearer credential
|
|
9
|
+
* for ANOTHER user's GLOBAL account: consuming any one of them signs the
|
|
10
|
+
* target in (LESSONS.md M6: "Consuming a change-email JWT while logged
|
|
11
|
+
* out CREATES a session for the target user"). A `user` row is
|
|
12
|
+
* instance-global, so the blast radius of a mint is every workspace that
|
|
13
|
+
* user belongs to — not just the minter's.
|
|
14
|
+
*
|
|
15
|
+
* Two target classes therefore make a mint a CROSS-TENANT takeover and
|
|
16
|
+
* are refused outright:
|
|
17
|
+
*
|
|
18
|
+
* 1. `origin='guest'` — the guest row exists for principal resolution
|
|
19
|
+
* only (D2). It was created by the per-resource claim path for an
|
|
20
|
+
* EXTERNAL party whose account is not this tenant's to administer;
|
|
21
|
+
* the host tenant never owned that credential. Admins have no
|
|
22
|
+
* recovery duty toward a guest, so there is nothing to trade away.
|
|
23
|
+
* 2. a target holding a membership in ANY OTHER workspace — the minted
|
|
24
|
+
* credential would carry into that workspace too. "Admin of the
|
|
25
|
+
* workspace" is not "owner of the person"; a user who works with two
|
|
26
|
+
* tenants must never be recoverable by either one unilaterally.
|
|
27
|
+
* Recovery for such a user is self-serve (`/request-password-reset`)
|
|
28
|
+
* or the operator's break-glass surface.
|
|
29
|
+
*
|
|
30
|
+
* The role gate is OWNER, not admin (AUTH-8): the old guards only fired
|
|
31
|
+
* when `target.role === 'owner'`, so any admin could mint against any
|
|
32
|
+
* other admin and escalate laterally. Minting someone else's credential
|
|
33
|
+
* is an owner-level act on every route that calls this.
|
|
34
|
+
*
|
|
35
|
+
* Fail-CLOSED shape: a new route that mints a credential for another user
|
|
36
|
+
* must call this helper. Returning `null` means "no refusal found".
|
|
37
|
+
* `guestTargetMessage` is the tool's `guest_target` sentence
|
|
38
|
+
* (`copy.guestTarget`).
|
|
39
|
+
*/
|
|
40
|
+
export async function mintRefusal(
|
|
41
|
+
db: Db,
|
|
42
|
+
guestTargetMessage: string,
|
|
43
|
+
principal: { workspaceId: string },
|
|
44
|
+
target: { userId: string; origin: string }
|
|
45
|
+
): Promise<{ code: string; message: string } | null> {
|
|
46
|
+
if (target.origin === 'guest') {
|
|
47
|
+
return {
|
|
48
|
+
code: 'guest_target',
|
|
49
|
+
message: guestTargetMessage
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const [foreign] = await db
|
|
53
|
+
.select({ id: workspaceMembers.id })
|
|
54
|
+
.from(workspaceMembers)
|
|
55
|
+
.where(
|
|
56
|
+
and(eq(workspaceMembers.userId, target.userId), ne(workspaceMembers.workspaceId, principal.workspaceId))
|
|
57
|
+
)
|
|
58
|
+
.limit(1);
|
|
59
|
+
if (foreign) {
|
|
60
|
+
return {
|
|
61
|
+
code: 'cross_workspace_target',
|
|
62
|
+
message:
|
|
63
|
+
'This member also belongs to another workspace — an admin-minted link would carry into it, so it is refused'
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when a failed `signUpEmail` means "this email already has an account":
|
|
3
|
+
* either Better Auth's own pre-check (APIError USER_ALREADY_EXISTS / 422) or,
|
|
4
|
+
* in the tight concurrent-signup race where two callers pass an account lookup
|
|
5
|
+
* together, the losing INSERT's Postgres unique_violation (23505) on the user
|
|
6
|
+
* email — found anywhere down the wrapped error's `cause` chain.
|
|
7
|
+
*
|
|
8
|
+
* Shared by every public account-minting endpoint (invitation accept,
|
|
9
|
+
* collaborator claim): the existence-revealing 409 is answered ONLY here, after
|
|
10
|
+
* the caller has committed real credentials, so a credential-less probe can
|
|
11
|
+
* never read it (PRDCT-1437).
|
|
12
|
+
*/
|
|
13
|
+
export function isDuplicateAccountError(e: unknown): boolean {
|
|
14
|
+
for (let cur: unknown = e, depth = 0; cur instanceof Error && depth < 10; cur = cur.cause, depth++) {
|
|
15
|
+
const anyErr = cur as { code?: unknown; status?: unknown; body?: { code?: unknown } };
|
|
16
|
+
if (anyErr.code === '23505') return true;
|
|
17
|
+
if (anyErr.body?.code === 'USER_ALREADY_EXISTS') return true;
|
|
18
|
+
if (anyErr.status === 'UNPROCESSABLE_ENTITY' || anyErr.status === 422) return true;
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Break-glass superadmin recognition (ADR 010).
|
|
3
|
+
*
|
|
4
|
+
* The capability is env-derived and off by default: `SUPERADMIN_EMAILS` is a
|
|
5
|
+
* comma-separated allowlist, and a caller is superadmin ONLY when all three
|
|
6
|
+
* hold at once:
|
|
7
|
+
*
|
|
8
|
+
* 1. the request carries a Better Auth SESSION (never a machine credential —
|
|
9
|
+
* the break-glass routes are unlisted in the fail-closed scope allowlist,
|
|
10
|
+
* and the handlers resolve the session directly);
|
|
11
|
+
* 2. the session user's email is on the allowlist;
|
|
12
|
+
* 3. that email is VERIFIED (`user.email_verified`) — an unverified match
|
|
13
|
+
* must never qualify, or squatting an allowlisted address would be enough.
|
|
14
|
+
*
|
|
15
|
+
* There is deliberately NO persistent DB super-role: unset/empty env means no
|
|
16
|
+
* superadmin exists and the capability is fully dormant.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse `SUPERADMIN_EMAILS` into a lowercased set (Better Auth stores emails
|
|
23
|
+
* lowercased, so membership checks compare in that form).
|
|
24
|
+
*
|
|
25
|
+
* Throws on malformed entries so the env schema fails LOUDLY at boot — a typo
|
|
26
|
+
* must never silently disarm break-glass. `undefined` (unset/blank through
|
|
27
|
+
* `blankToUndefined`) yields the empty set: dormant.
|
|
28
|
+
*/
|
|
29
|
+
export function parseSuperadminEmails(raw: string | undefined): Set<string> {
|
|
30
|
+
const allowlist = new Set<string>();
|
|
31
|
+
if (raw === undefined || raw.trim() === '') return allowlist;
|
|
32
|
+
const entries = raw
|
|
33
|
+
.split(',')
|
|
34
|
+
.map((entry) => entry.trim())
|
|
35
|
+
.filter((entry) => entry.length > 0);
|
|
36
|
+
if (entries.length === 0) {
|
|
37
|
+
throw new Error('SUPERADMIN_EMAILS must list at least one email address');
|
|
38
|
+
}
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
if (!EMAIL_RE.test(entry)) {
|
|
41
|
+
throw new Error(`SUPERADMIN_EMAILS entry "${entry}" is not a valid email address`);
|
|
42
|
+
}
|
|
43
|
+
allowlist.add(entry.toLowerCase());
|
|
44
|
+
}
|
|
45
|
+
return allowlist;
|
|
46
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { OpenAPIHono } from '@hono/zod-openapi';
|
|
2
|
+
import { and, desc, eq } from 'drizzle-orm';
|
|
3
|
+
import type { ScopeRoutes } from './scope-routes.js';
|
|
4
|
+
import { apiKeys, workspaceMembers, type ApiKey, type Db } from '@antasphere/chassis-db';
|
|
5
|
+
import { sql } from 'drizzle-orm';
|
|
6
|
+
import type { ApiKeyService } from '../apikeys/service.js';
|
|
7
|
+
import { cursorRowId, keysetBefore, pageOf } from '../pagination.js';
|
|
8
|
+
import { requireAuth } from '../middleware/auth-context.js';
|
|
9
|
+
|
|
10
|
+
const err = (code: string, message: string) => ({ error: { code, message } });
|
|
11
|
+
|
|
12
|
+
const toWire = (k: ApiKey) => ({
|
|
13
|
+
id: k.id,
|
|
14
|
+
name: k.name,
|
|
15
|
+
keyId: k.keyId,
|
|
16
|
+
scopes: k.scopes as string[],
|
|
17
|
+
workspaceId: k.workspaceId,
|
|
18
|
+
createdBy: k.createdBy,
|
|
19
|
+
createdAt: k.createdAt.toISOString(),
|
|
20
|
+
lastUsedAt: k.lastUsedAt?.toISOString() ?? null,
|
|
21
|
+
revokedAt: k.revokedAt?.toISOString() ?? null,
|
|
22
|
+
expiresAt: k.expiresAt?.toISOString() ?? null
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* API keys are USER credentials (user-scoped credential model): the listing
|
|
27
|
+
* and the revoke are keyed on the CREATOR, not on any workspace — a key acts
|
|
28
|
+
* as its holder wherever their memberships reach, so "my keys" is the only
|
|
29
|
+
* honest scope. Mint stays sessions-only and accepts an optional workspace
|
|
30
|
+
* PIN (least privilege); revocation is creator-only (a workspace admin has
|
|
31
|
+
* no standing over another user's credential).
|
|
32
|
+
*/
|
|
33
|
+
export function registerApiKeyRoutes(
|
|
34
|
+
api: OpenAPIHono,
|
|
35
|
+
db: Db,
|
|
36
|
+
service: ApiKeyService,
|
|
37
|
+
routes: Pick<ScopeRoutes, 'apiKeysListRoute' | 'apiKeyCreateRoute' | 'apiKeyRevokeRoute'>
|
|
38
|
+
): void {
|
|
39
|
+
const { apiKeysListRoute, apiKeyCreateRoute, apiKeyRevokeRoute } = routes;
|
|
40
|
+
api.use('/api-keys', requireAuth());
|
|
41
|
+
api.use('/api-keys/*', requireAuth());
|
|
42
|
+
|
|
43
|
+
api.openapi(apiKeysListRoute, async (c) => {
|
|
44
|
+
const principal = c.get('principal')!;
|
|
45
|
+
const { cursor, limit } = c.req.valid('query');
|
|
46
|
+
const cursorId = cursorRowId(cursor);
|
|
47
|
+
const rows = await db
|
|
48
|
+
.select()
|
|
49
|
+
.from(apiKeys)
|
|
50
|
+
.where(
|
|
51
|
+
and(
|
|
52
|
+
eq(apiKeys.createdBy, principal.userId),
|
|
53
|
+
...(cursorId
|
|
54
|
+
? [
|
|
55
|
+
keysetBefore({
|
|
56
|
+
table: apiKeys,
|
|
57
|
+
id: apiKeys.id,
|
|
58
|
+
createdAt: apiKeys.createdAt,
|
|
59
|
+
// The keyset scope column (see pagination.ts): the listing
|
|
60
|
+
// is creator-scoped, so a foreign user's cursor can never
|
|
61
|
+
// position inside this user's ordering.
|
|
62
|
+
workspaceId: apiKeys.createdBy,
|
|
63
|
+
cursorId,
|
|
64
|
+
workspace: principal.userId
|
|
65
|
+
})
|
|
66
|
+
]
|
|
67
|
+
: [])
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
.orderBy(desc(apiKeys.createdAt), desc(apiKeys.id))
|
|
71
|
+
.limit(limit + 1);
|
|
72
|
+
const { page, nextCursor } = pageOf(rows, limit);
|
|
73
|
+
return c.json({ apiKeys: page.map(toWire), nextCursor }, 200);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
api.openapi(apiKeyCreateRoute, async (c) => {
|
|
77
|
+
const principal = c.get('principal')!;
|
|
78
|
+
// Locked rule: a key never mints a key. Sessions only.
|
|
79
|
+
if (principal.via !== 'session') {
|
|
80
|
+
return c.json(err('sessions_only', 'API keys can only be created from a browser session'), 403);
|
|
81
|
+
}
|
|
82
|
+
const body = c.req.valid('json');
|
|
83
|
+
// Optional PIN: an ACTIVE membership of the pinned workspace is required
|
|
84
|
+
// — one uniform 403 whether the workspace does not exist or the caller
|
|
85
|
+
// is not a member (no oracle about other workspaces).
|
|
86
|
+
if (body.workspaceId) {
|
|
87
|
+
const [member] = await db
|
|
88
|
+
.select({ id: workspaceMembers.id })
|
|
89
|
+
.from(workspaceMembers)
|
|
90
|
+
.where(
|
|
91
|
+
and(
|
|
92
|
+
eq(workspaceMembers.userId, principal.userId),
|
|
93
|
+
eq(workspaceMembers.workspaceId, body.workspaceId),
|
|
94
|
+
eq(workspaceMembers.isActive, true)
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
.limit(1);
|
|
98
|
+
if (!member) {
|
|
99
|
+
return c.json(err('no_membership', 'This workspace is not available to this account'), 403);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// The client sends a TTL in days; the server owns the absolute expiry.
|
|
103
|
+
const expiresAt = body.expiresInDays ? new Date(Date.now() + body.expiresInDays * 86_400_000) : null;
|
|
104
|
+
const minted = await service.mint({
|
|
105
|
+
workspaceId: body.workspaceId ?? null,
|
|
106
|
+
createdBy: principal.userId,
|
|
107
|
+
name: body.name,
|
|
108
|
+
scopes: body.scopes,
|
|
109
|
+
expiresAt
|
|
110
|
+
});
|
|
111
|
+
const [row] = await db.select().from(apiKeys).where(eq(apiKeys.id, minted.id)).limit(1);
|
|
112
|
+
c.set('audit', {
|
|
113
|
+
action: 'apikey.create',
|
|
114
|
+
resourceType: 'api_key',
|
|
115
|
+
resourceId: minted.id,
|
|
116
|
+
metadata: {
|
|
117
|
+
name: body.name,
|
|
118
|
+
scopes: body.scopes,
|
|
119
|
+
workspaceId: body.workspaceId ?? null,
|
|
120
|
+
expiresAt: expiresAt?.toISOString() ?? null
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return c.json({ apiKey: toWire(row!), key: minted.key }, 201);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
api.openapi(apiKeyRevokeRoute, async (c) => {
|
|
127
|
+
const principal = c.get('principal')!;
|
|
128
|
+
const { id } = c.req.valid('param');
|
|
129
|
+
// Creator-only: a key is its creator's credential. A foreign key answers
|
|
130
|
+
// the same 404 as a nonexistent one (existence is not probeable).
|
|
131
|
+
const [key] = await db
|
|
132
|
+
.select()
|
|
133
|
+
.from(apiKeys)
|
|
134
|
+
.where(and(eq(apiKeys.id, id), eq(apiKeys.createdBy, principal.userId)))
|
|
135
|
+
.limit(1);
|
|
136
|
+
if (!key) return c.json(err('not_found', 'API key not found'), 404);
|
|
137
|
+
if (!key.revokedAt) {
|
|
138
|
+
await db
|
|
139
|
+
.update(apiKeys)
|
|
140
|
+
.set({ revokedAt: sql`now()` })
|
|
141
|
+
.where(eq(apiKeys.id, key.id));
|
|
142
|
+
}
|
|
143
|
+
const [updated] = await db.select().from(apiKeys).where(eq(apiKeys.id, key.id)).limit(1);
|
|
144
|
+
c.set('audit', { action: 'apikey.revoke', resourceType: 'api_key', resourceId: key.id });
|
|
145
|
+
return c.json(toWire(updated!), 200);
|
|
146
|
+
});
|
|
147
|
+
}
|
package/src/api/audit.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { OpenAPIHono } from '@hono/zod-openapi';
|
|
2
|
+
import { count, eq, lt, desc } from 'drizzle-orm';
|
|
3
|
+
import { auditListRoute } from '@antasphere/chassis-contract/routes';
|
|
4
|
+
import { auditLog, user as userTable, type Db } from '@antasphere/chassis-db';
|
|
5
|
+
import { requireRole } from '../middleware/auth-context.js';
|
|
6
|
+
import { auditFilterConditions, auditWhere, cursorId } from '../audit/filters.js';
|
|
7
|
+
|
|
8
|
+
export function registerAuditRoutes(api: OpenAPIHono, db: Db): void {
|
|
9
|
+
api.use('/audit', requireRole('admin'));
|
|
10
|
+
api.openapi(auditListRoute, async (c) => {
|
|
11
|
+
const principal = c.get('principal')!;
|
|
12
|
+
const query = c.req.valid('query');
|
|
13
|
+
const { limit } = query;
|
|
14
|
+
|
|
15
|
+
// SECURITY: the scope is the caller's workspace and nothing in the
|
|
16
|
+
// query can widen it; every filter narrows within it, as a bound
|
|
17
|
+
// parameter (audit/filters.ts). The cursor stays the last row id, so
|
|
18
|
+
// a page is "the next `limit` rows older than the cursor that match
|
|
19
|
+
// the same filters": a client that changes a filter starts over.
|
|
20
|
+
const scope = eq(auditLog.workspaceId, principal.workspaceId);
|
|
21
|
+
const filters = auditFilterConditions(query);
|
|
22
|
+
const cursor = cursorId(query.cursor);
|
|
23
|
+
const where = auditWhere(scope, filters, cursor !== null ? lt(auditLog.id, cursor) : null);
|
|
24
|
+
|
|
25
|
+
const rows = await db
|
|
26
|
+
.select({
|
|
27
|
+
id: auditLog.id,
|
|
28
|
+
actorUserId: auditLog.actorUserId,
|
|
29
|
+
actorEmail: userTable.email,
|
|
30
|
+
actorVia: auditLog.actorVia,
|
|
31
|
+
apiKeyId: auditLog.apiKeyId,
|
|
32
|
+
action: auditLog.action,
|
|
33
|
+
resourceType: auditLog.resourceType,
|
|
34
|
+
resourceId: auditLog.resourceId,
|
|
35
|
+
requestId: auditLog.requestId,
|
|
36
|
+
ip: auditLog.ip,
|
|
37
|
+
metadata: auditLog.metadata,
|
|
38
|
+
createdAt: auditLog.createdAt
|
|
39
|
+
})
|
|
40
|
+
.from(auditLog)
|
|
41
|
+
.leftJoin(userTable, eq(auditLog.actorUserId, userTable.id))
|
|
42
|
+
.where(where)
|
|
43
|
+
.orderBy(desc(auditLog.id))
|
|
44
|
+
.limit(limit + 1);
|
|
45
|
+
|
|
46
|
+
// The total is counted once per listing, on the first page: the count
|
|
47
|
+
// walks the same join and WHERE without the cursor, and a client keeps
|
|
48
|
+
// the figure while it loads the pages after.
|
|
49
|
+
let total: number | null = null;
|
|
50
|
+
if (cursor === null) {
|
|
51
|
+
const [counted] = await db
|
|
52
|
+
.select({ n: count() })
|
|
53
|
+
.from(auditLog)
|
|
54
|
+
.leftJoin(userTable, eq(auditLog.actorUserId, userTable.id))
|
|
55
|
+
.where(auditWhere(scope, filters, null));
|
|
56
|
+
total = counted?.n ?? 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const page = rows.slice(0, limit);
|
|
60
|
+
const nextCursor = rows.length > limit ? String(page[page.length - 1]?.id ?? '') : null;
|
|
61
|
+
return c.json(
|
|
62
|
+
{
|
|
63
|
+
entries: page.map((r) => ({
|
|
64
|
+
id: r.id,
|
|
65
|
+
actorUserId: r.actorUserId,
|
|
66
|
+
actorEmail: r.actorEmail,
|
|
67
|
+
actorVia: r.actorVia,
|
|
68
|
+
apiKeyId: r.apiKeyId,
|
|
69
|
+
action: r.action,
|
|
70
|
+
resourceType: r.resourceType,
|
|
71
|
+
resourceId: r.resourceId,
|
|
72
|
+
requestId: r.requestId,
|
|
73
|
+
ip: r.ip,
|
|
74
|
+
metadata: r.metadata,
|
|
75
|
+
createdAt: r.createdAt.toISOString()
|
|
76
|
+
})),
|
|
77
|
+
nextCursor,
|
|
78
|
+
total
|
|
79
|
+
},
|
|
80
|
+
200
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import type { OpenAPIHono } from '@hono/zod-openapi';
|
|
2
|
+
import type { Context } from 'hono';
|
|
3
|
+
import { and, eq } from 'drizzle-orm';
|
|
4
|
+
import {
|
|
5
|
+
breakGlassClaimOwnershipRoute,
|
|
6
|
+
breakGlassResetTwoFactorRoute
|
|
7
|
+
} from '@antasphere/chassis-contract/routes';
|
|
8
|
+
import { twoFactor, user as userTable, workspaceMembers, workspaces, type Db } from '@antasphere/chassis-db';
|
|
9
|
+
import type { Auth } from '../identity/better-auth.js';
|
|
10
|
+
import type { AuditService } from '../audit/service.js';
|
|
11
|
+
import type { Logger } from '../logger.js';
|
|
12
|
+
import { parseSuperadminEmails } from '../accounts/superadmin.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Break-glass superadmin recovery (ADR 010) — the instance-level recovery
|
|
16
|
+
* surface for the per-tenant hosting model. Two capabilities only:
|
|
17
|
+
*
|
|
18
|
+
* - claim-ownership: make the calling superadmin (or a named existing user)
|
|
19
|
+
* an ACTIVE OWNER of a workspace — creating the membership when absent,
|
|
20
|
+
* reactivating and promoting when present. It ADDS an owner and never
|
|
21
|
+
* removes one, so the migration-0009 last-owner trigger is satisfied by
|
|
22
|
+
* construction. The target workspace is explicit (`workspaceId`) once the
|
|
23
|
+
* instance runs several (ADR 014); a single-workspace instance keeps the
|
|
24
|
+
* old no-argument call.
|
|
25
|
+
* - reset-2fa: clear a locked-out user's second factor (the recovery ADR 009
|
|
26
|
+
* deferred to manual DB surgery). User-level — needs no workspace; its
|
|
27
|
+
* audit row is instance-attributed.
|
|
28
|
+
*
|
|
29
|
+
* SAFETY MODEL (a bug here = full instance compromise — keep all of it):
|
|
30
|
+
*
|
|
31
|
+
* - Off by default: superadmin exists ONLY while SUPERADMIN_EMAILS is set.
|
|
32
|
+
* - Session-only, twice over: these paths are deliberately UNLISTED in the
|
|
33
|
+
* fail-closed scope allowlist (middleware/scopes.ts), so machine
|
|
34
|
+
* principals 403 before reaching the handlers; AND the handlers resolve
|
|
35
|
+
* the Better Auth session directly — a request without a session cookie
|
|
36
|
+
* can never qualify, whatever headers it carries.
|
|
37
|
+
* - Verified email required: an allowlisted address that was never proven
|
|
38
|
+
* (invitation copyable-link accounts are unverified) does NOT match.
|
|
39
|
+
* - Identity is re-read from the DB by session user id — never derived from
|
|
40
|
+
* any header, body field, or client-supplied value.
|
|
41
|
+
* - No requireAuth()/requireRole() gate: the recovering operator may hold a
|
|
42
|
+
* session with NO membership (principal resolves to null), which is
|
|
43
|
+
* exactly the state break-glass exists to fix. The handlers do their own
|
|
44
|
+
* authentication and are exempted from the generic audit middleware
|
|
45
|
+
* because they write their own richer rows (see audit/service.ts).
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
export interface BreakGlassRouteDeps {
|
|
49
|
+
db: Db;
|
|
50
|
+
auth: Auth;
|
|
51
|
+
audit: AuditService;
|
|
52
|
+
logger: Logger;
|
|
53
|
+
/** Raw SUPERADMIN_EMAILS value (already validated by the env schema). */
|
|
54
|
+
superadminEmails: string | undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const err = (code: string, message: string) => ({ error: { code, message } });
|
|
58
|
+
|
|
59
|
+
type SuperadminResolution =
|
|
60
|
+
| { kind: 'ok'; caller: { userId: string; email: string; name: string } }
|
|
61
|
+
| { kind: 'unauthenticated' }
|
|
62
|
+
| { kind: 'forbidden' };
|
|
63
|
+
|
|
64
|
+
export function registerBreakGlassRoutes(api: OpenAPIHono, deps: BreakGlassRouteDeps): void {
|
|
65
|
+
const { db, auth, audit, logger } = deps;
|
|
66
|
+
const allowlist = parseSuperadminEmails(deps.superadminEmails);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the caller as a superadmin. One uniform `forbidden` for every
|
|
70
|
+
* rejection reason (dormant, not listed, unverified) so the endpoint is
|
|
71
|
+
* not an oracle for the allowlist's contents.
|
|
72
|
+
*/
|
|
73
|
+
const resolveSuperadmin = async (c: Context): Promise<SuperadminResolution> => {
|
|
74
|
+
// Session-only: the verified session identity, never a header or bearer.
|
|
75
|
+
// Machine credentials died at the fail-closed scope gate already; a
|
|
76
|
+
// cookieless request has no session and stops here.
|
|
77
|
+
const session = await auth.api.getSession({ headers: c.req.raw.headers });
|
|
78
|
+
if (!session?.user) return { kind: 'unauthenticated' };
|
|
79
|
+
if (allowlist.size === 0) return { kind: 'forbidden' };
|
|
80
|
+
// Fresh row by session user id — email + verification state are read
|
|
81
|
+
// from the database, not trusted from session claims.
|
|
82
|
+
const [u] = await db
|
|
83
|
+
.select({
|
|
84
|
+
id: userTable.id,
|
|
85
|
+
email: userTable.email,
|
|
86
|
+
emailVerified: userTable.emailVerified,
|
|
87
|
+
name: userTable.name
|
|
88
|
+
})
|
|
89
|
+
.from(userTable)
|
|
90
|
+
.where(eq(userTable.id, session.user.id))
|
|
91
|
+
.limit(1);
|
|
92
|
+
if (!u) return { kind: 'forbidden' };
|
|
93
|
+
if (!allowlist.has(u.email.toLowerCase())) return { kind: 'forbidden' };
|
|
94
|
+
if (!u.emailVerified) return { kind: 'forbidden' };
|
|
95
|
+
return { kind: 'ok', caller: { userId: u.id, email: u.email, name: u.name } };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The workspace an owner-claim targets (ADR 014). Explicit `workspaceId`
|
|
100
|
+
* wins; omitted, it resolves ONLY while the instance runs a single
|
|
101
|
+
* workspace — with several, recovery must name its target (400
|
|
102
|
+
* workspace_required), never guess one.
|
|
103
|
+
*/
|
|
104
|
+
const resolveTargetWorkspace = async (
|
|
105
|
+
explicit: string | undefined
|
|
106
|
+
): Promise<
|
|
107
|
+
{ kind: 'ok'; id: string } | { kind: 'not_found' } | { kind: 'not_setup' } | { kind: 'ambiguous' }
|
|
108
|
+
> => {
|
|
109
|
+
if (explicit) {
|
|
110
|
+
const [ws] = await db
|
|
111
|
+
.select({ id: workspaces.id })
|
|
112
|
+
.from(workspaces)
|
|
113
|
+
.where(eq(workspaces.id, explicit))
|
|
114
|
+
.limit(1);
|
|
115
|
+
return ws ? { kind: 'ok', id: ws.id } : { kind: 'not_found' };
|
|
116
|
+
}
|
|
117
|
+
const rows = await db.select({ id: workspaces.id }).from(workspaces).limit(2);
|
|
118
|
+
const [only] = rows;
|
|
119
|
+
if (!only) return { kind: 'not_setup' };
|
|
120
|
+
if (rows.length > 1) return { kind: 'ambiguous' };
|
|
121
|
+
return { kind: 'ok', id: only.id };
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
api.openapi(breakGlassClaimOwnershipRoute, async (c) => {
|
|
125
|
+
const resolved = await resolveSuperadmin(c);
|
|
126
|
+
if (resolved.kind === 'unauthenticated') {
|
|
127
|
+
return c.json(err('unauthenticated', 'Authentication required'), 401);
|
|
128
|
+
}
|
|
129
|
+
if (resolved.kind === 'forbidden') {
|
|
130
|
+
return c.json(err('forbidden', 'Break-glass is not available to this caller'), 403);
|
|
131
|
+
}
|
|
132
|
+
const caller = resolved.caller;
|
|
133
|
+
const body = c.req.valid('json');
|
|
134
|
+
|
|
135
|
+
const targetWs = await resolveTargetWorkspace(body.workspaceId);
|
|
136
|
+
if (targetWs.kind === 'not_setup') {
|
|
137
|
+
return c.json(err('not_setup', 'This instance has not completed setup'), 409);
|
|
138
|
+
}
|
|
139
|
+
if (targetWs.kind === 'not_found') {
|
|
140
|
+
return c.json(err('not_found', 'Target workspace not found'), 404);
|
|
141
|
+
}
|
|
142
|
+
if (targetWs.kind === 'ambiguous') {
|
|
143
|
+
return c.json(
|
|
144
|
+
err('workspace_required', 'This instance has several workspaces — pass workspaceId explicitly'),
|
|
145
|
+
400
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
const ws = { id: targetWs.id };
|
|
149
|
+
|
|
150
|
+
// Target: a named existing user, or the calling superadmin.
|
|
151
|
+
let target = { userId: caller.userId, email: caller.email };
|
|
152
|
+
if (body.userId) {
|
|
153
|
+
const [t] = await db
|
|
154
|
+
.select({ id: userTable.id, email: userTable.email })
|
|
155
|
+
.from(userTable)
|
|
156
|
+
.where(eq(userTable.id, body.userId))
|
|
157
|
+
.limit(1);
|
|
158
|
+
if (!t) return c.json(err('not_found', 'Target user not found'), 404);
|
|
159
|
+
target = { userId: t.id, email: t.email };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const [before] = await db
|
|
163
|
+
.select({
|
|
164
|
+
id: workspaceMembers.id,
|
|
165
|
+
role: workspaceMembers.role,
|
|
166
|
+
isActive: workspaceMembers.isActive
|
|
167
|
+
})
|
|
168
|
+
.from(workspaceMembers)
|
|
169
|
+
.where(and(eq(workspaceMembers.workspaceId, ws.id), eq(workspaceMembers.userId, target.userId)))
|
|
170
|
+
.limit(1);
|
|
171
|
+
|
|
172
|
+
// Additive by construction: create-or-promote to an ACTIVE OWNER. The
|
|
173
|
+
// 0009 last-owner trigger only rejects operations that REMOVE the last
|
|
174
|
+
// active owner — adding one is always legal, including the first owner
|
|
175
|
+
// of an ownerless workspace.
|
|
176
|
+
const [after] = await db
|
|
177
|
+
.insert(workspaceMembers)
|
|
178
|
+
// origin='local' on BOTH paths (BG-1, PRDCT-1356): a break-glass owner
|
|
179
|
+
// is the tool's own business, never hub truth. Left at 'hub' (the
|
|
180
|
+
// conflict path once kept the row's old origin), the cloud reconciler's
|
|
181
|
+
// deactivation sweep — which targets origin='hub' rows the hub no
|
|
182
|
+
// longer asserts — undid the recovery ~12 s later.
|
|
183
|
+
.values({ workspaceId: ws.id, userId: target.userId, role: 'owner', isActive: true, origin: 'local' })
|
|
184
|
+
.onConflictDoUpdate({
|
|
185
|
+
target: [workspaceMembers.workspaceId, workspaceMembers.userId],
|
|
186
|
+
set: { role: 'owner', isActive: true, origin: 'local' }
|
|
187
|
+
})
|
|
188
|
+
.returning({ id: workspaceMembers.id });
|
|
189
|
+
if (!after) throw new Error('break-glass ownership upsert returned no row');
|
|
190
|
+
|
|
191
|
+
logger.warn(
|
|
192
|
+
{
|
|
193
|
+
superadminUserId: caller.userId,
|
|
194
|
+
superadminEmail: caller.email,
|
|
195
|
+
targetUserId: target.userId,
|
|
196
|
+
targetEmail: target.email,
|
|
197
|
+
workspaceId: ws.id
|
|
198
|
+
},
|
|
199
|
+
'BREAK-GLASS: workspace ownership claimed'
|
|
200
|
+
);
|
|
201
|
+
await audit.write({
|
|
202
|
+
workspaceId: ws.id,
|
|
203
|
+
principal: { userId: caller.userId, via: 'session' },
|
|
204
|
+
action: 'break_glass.claim_ownership',
|
|
205
|
+
resourceType: 'member',
|
|
206
|
+
resourceId: after.id,
|
|
207
|
+
requestId: c.get('requestId'),
|
|
208
|
+
metadata: {
|
|
209
|
+
superadminUserId: caller.userId,
|
|
210
|
+
superadminEmail: caller.email,
|
|
211
|
+
targetUserId: target.userId,
|
|
212
|
+
targetEmail: target.email,
|
|
213
|
+
before: before ? { role: before.role, isActive: before.isActive } : null,
|
|
214
|
+
after: { role: 'owner', isActive: true },
|
|
215
|
+
created: !before
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return c.json(
|
|
220
|
+
{
|
|
221
|
+
memberId: after.id,
|
|
222
|
+
userId: target.userId,
|
|
223
|
+
email: target.email,
|
|
224
|
+
workspaceId: ws.id,
|
|
225
|
+
role: 'owner' as const,
|
|
226
|
+
isActive: true,
|
|
227
|
+
created: !before
|
|
228
|
+
},
|
|
229
|
+
200
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
api.openapi(breakGlassResetTwoFactorRoute, async (c) => {
|
|
234
|
+
const resolved = await resolveSuperadmin(c);
|
|
235
|
+
if (resolved.kind === 'unauthenticated') {
|
|
236
|
+
return c.json(err('unauthenticated', 'Authentication required'), 401);
|
|
237
|
+
}
|
|
238
|
+
if (resolved.kind === 'forbidden') {
|
|
239
|
+
return c.json(err('forbidden', 'Break-glass is not available to this caller'), 403);
|
|
240
|
+
}
|
|
241
|
+
const caller = resolved.caller;
|
|
242
|
+
const { userId } = c.req.valid('json');
|
|
243
|
+
|
|
244
|
+
const [target] = await db
|
|
245
|
+
.select({
|
|
246
|
+
id: userTable.id,
|
|
247
|
+
email: userTable.email,
|
|
248
|
+
twoFactorEnabled: userTable.twoFactorEnabled
|
|
249
|
+
})
|
|
250
|
+
.from(userTable)
|
|
251
|
+
.where(eq(userTable.id, userId))
|
|
252
|
+
.limit(1);
|
|
253
|
+
if (!target) return c.json(err('not_found', 'Target user not found'), 404);
|
|
254
|
+
|
|
255
|
+
// Clear the factor: the two_factor row (secret + backup codes) and the
|
|
256
|
+
// user flag. Idempotent — resetting an unenrolled user is a no-op 200.
|
|
257
|
+
const removed = await db
|
|
258
|
+
.delete(twoFactor)
|
|
259
|
+
.where(eq(twoFactor.userId, target.id))
|
|
260
|
+
.returning({ id: twoFactor.id });
|
|
261
|
+
await db.update(userTable).set({ twoFactorEnabled: false }).where(eq(userTable.id, target.id));
|
|
262
|
+
const hadTwoFactor = removed.length > 0 || target.twoFactorEnabled === true;
|
|
263
|
+
|
|
264
|
+
logger.warn(
|
|
265
|
+
{
|
|
266
|
+
superadminUserId: caller.userId,
|
|
267
|
+
superadminEmail: caller.email,
|
|
268
|
+
targetUserId: target.id,
|
|
269
|
+
targetEmail: target.email,
|
|
270
|
+
hadTwoFactor
|
|
271
|
+
},
|
|
272
|
+
'BREAK-GLASS: two-factor reset'
|
|
273
|
+
);
|
|
274
|
+
// INSTANCE-attributed (workspace_id NULL, ADR 014): a second factor is a
|
|
275
|
+
// user property, not any workspace's — the row needs no workspace home.
|
|
276
|
+
await audit.write({
|
|
277
|
+
workspaceId: null,
|
|
278
|
+
principal: { userId: caller.userId, via: 'session' },
|
|
279
|
+
action: 'break_glass.reset_two_factor',
|
|
280
|
+
resourceType: 'user',
|
|
281
|
+
resourceId: target.id,
|
|
282
|
+
requestId: c.get('requestId'),
|
|
283
|
+
metadata: {
|
|
284
|
+
superadminUserId: caller.userId,
|
|
285
|
+
superadminEmail: caller.email,
|
|
286
|
+
targetUserId: target.id,
|
|
287
|
+
targetEmail: target.email,
|
|
288
|
+
hadTwoFactor
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return c.json({ userId: target.id, hadTwoFactor }, 200);
|
|
293
|
+
});
|
|
294
|
+
}
|