@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,617 @@
|
|
|
1
|
+
import type { OpenAPIHono } from '@hono/zod-openapi';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { and, desc, eq, ne } from 'drizzle-orm';
|
|
4
|
+
import { createEmailVerificationToken } from 'better-auth/api';
|
|
5
|
+
import {
|
|
6
|
+
memberChangeEmailLinkRoute,
|
|
7
|
+
memberDeleteRoute,
|
|
8
|
+
memberRemoveRoute,
|
|
9
|
+
memberResetLinkRoute,
|
|
10
|
+
memberUpdateRoute,
|
|
11
|
+
membersListRoute
|
|
12
|
+
} from '@antasphere/chassis-contract/routes';
|
|
13
|
+
import { workspaceMembers, user as userTable, type Db } from '@antasphere/chassis-db';
|
|
14
|
+
import type { Auth } from '../identity/better-auth.js';
|
|
15
|
+
import { isLastOwnerDbError, LastOwnerError, type AccountDeletionService } from '../accounts/deletion.js';
|
|
16
|
+
import { mintRefusal as sharedMintRefusal } from '../accounts/mint-refusal.js';
|
|
17
|
+
import { cursorRowId, keysetBefore, pageOf } from '../pagination.js';
|
|
18
|
+
import { requireAuth, requireNonGuest, requireRole } from '../middleware/auth-context.js';
|
|
19
|
+
import { hubManagedMembershipGate } from '../middleware/hub-managed.js';
|
|
20
|
+
import {
|
|
21
|
+
deleteMembershipGrants,
|
|
22
|
+
mergeRemovalCounts,
|
|
23
|
+
type MembershipRemovalHook,
|
|
24
|
+
type RemovalCounts
|
|
25
|
+
} from '../members/removal.js';
|
|
26
|
+
|
|
27
|
+
export interface MemberRouteDeps {
|
|
28
|
+
db: Db;
|
|
29
|
+
auth: Auth;
|
|
30
|
+
publicBaseUrl: string;
|
|
31
|
+
accountDeletion: AccountDeletionService;
|
|
32
|
+
/**
|
|
33
|
+
* Cloud edition only (P7, internal/federation.md): when set, every membership
|
|
34
|
+
* MUTATION on a hub-origin workspace answers 403 `hub_managed` with this
|
|
35
|
+
* pointer. undefined on oss — zero behavior change there.
|
|
36
|
+
*/
|
|
37
|
+
hubManaged?: { manageUrl: string } | undefined;
|
|
38
|
+
/** The tool's `guest_target` sentence (`copy.guestTarget`). */
|
|
39
|
+
guestTargetMessage: string;
|
|
40
|
+
/** The tool's half of a removal (the `membershipRemoval` slot), run in the removal's transaction. */
|
|
41
|
+
onMembershipRemoval?: MembershipRemovalHook | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const err = (code: string, message: string) => ({ error: { code, message } });
|
|
45
|
+
|
|
46
|
+
const toWire = (m: {
|
|
47
|
+
id: string;
|
|
48
|
+
userId: string;
|
|
49
|
+
email: string;
|
|
50
|
+
name: string;
|
|
51
|
+
role: 'owner' | 'admin' | 'member';
|
|
52
|
+
isActive: boolean;
|
|
53
|
+
createdAt: Date;
|
|
54
|
+
lastSeenAt: Date | null;
|
|
55
|
+
}) => ({
|
|
56
|
+
id: m.id,
|
|
57
|
+
userId: m.userId,
|
|
58
|
+
email: m.email,
|
|
59
|
+
name: m.name,
|
|
60
|
+
role: m.role,
|
|
61
|
+
isActive: m.isActive,
|
|
62
|
+
createdAt: m.createdAt.toISOString(),
|
|
63
|
+
lastSeenAt: m.lastSeenAt?.toISOString() ?? null
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export function registerMemberRoutes(api: OpenAPIHono, deps: MemberRouteDeps): void {
|
|
67
|
+
const { db, auth, publicBaseUrl, accountDeletion, hubManaged } = deps;
|
|
68
|
+
// P7 (cloud only): membership of a hub-origin workspace is the HUB's to
|
|
69
|
+
// manage — every local mutation shape under /members answers the
|
|
70
|
+
// `hub_managed` pointer. Registered BEFORE the role gates so any
|
|
71
|
+
// authenticated caller gets the truthful refusal (an anonymous caller
|
|
72
|
+
// passes through — no principal — and still 401s at requireRole below).
|
|
73
|
+
// GET /members is untouched: the gate is method-keyed and the projected
|
|
74
|
+
// roster is real. ONE wildcard mount covers the whole subtree — the
|
|
75
|
+
// collection root, `/members/:id`, the 3-segment link paths, and any
|
|
76
|
+
// FUTURE mutation registered under /members — so fail-closed holds by
|
|
77
|
+
// construction, never by remembering a per-path registration.
|
|
78
|
+
if (hubManaged) {
|
|
79
|
+
api.use('/members/*', hubManagedMembershipGate(hubManaged.manageUrl));
|
|
80
|
+
}
|
|
81
|
+
api.use('/members', requireAuth());
|
|
82
|
+
// Guest capability limit (D2, both editions): the member roster (names +
|
|
83
|
+
// emails of the whole team) is a workspace-level surface. A guest is an
|
|
84
|
+
// external party invited to ONE resource — the host tenant's directory is not
|
|
85
|
+
// theirs to read. Per-resource surfaces their grant opens are untouched.
|
|
86
|
+
api.use('/members', requireNonGuest());
|
|
87
|
+
api.openapi(membersListRoute, async (c) => {
|
|
88
|
+
const principal = c.get('principal')!;
|
|
89
|
+
const { cursor, limit } = c.req.valid('query');
|
|
90
|
+
const cursorId = cursorRowId(cursor);
|
|
91
|
+
const rows = await db
|
|
92
|
+
.select({
|
|
93
|
+
id: workspaceMembers.id,
|
|
94
|
+
userId: workspaceMembers.userId,
|
|
95
|
+
email: userTable.email,
|
|
96
|
+
name: userTable.name,
|
|
97
|
+
role: workspaceMembers.role,
|
|
98
|
+
isActive: workspaceMembers.isActive,
|
|
99
|
+
createdAt: workspaceMembers.createdAt,
|
|
100
|
+
lastSeenAt: workspaceMembers.lastSeenAt
|
|
101
|
+
})
|
|
102
|
+
.from(workspaceMembers)
|
|
103
|
+
.innerJoin(userTable, eq(workspaceMembers.userId, userTable.id))
|
|
104
|
+
.where(
|
|
105
|
+
and(
|
|
106
|
+
eq(workspaceMembers.workspaceId, principal.workspaceId),
|
|
107
|
+
...(cursorId
|
|
108
|
+
? [
|
|
109
|
+
keysetBefore({
|
|
110
|
+
table: workspaceMembers,
|
|
111
|
+
id: workspaceMembers.id,
|
|
112
|
+
createdAt: workspaceMembers.createdAt,
|
|
113
|
+
workspaceId: workspaceMembers.workspaceId,
|
|
114
|
+
cursorId,
|
|
115
|
+
workspace: principal.workspaceId
|
|
116
|
+
})
|
|
117
|
+
]
|
|
118
|
+
: [])
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
.orderBy(desc(workspaceMembers.createdAt), desc(workspaceMembers.id))
|
|
122
|
+
.limit(limit + 1);
|
|
123
|
+
const { page, nextCursor } = pageOf(rows, limit);
|
|
124
|
+
return c.json({ members: page.map(toWire), nextCursor }, 200);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
api.use('/members/:id', requireRole('admin'));
|
|
128
|
+
api.openapi(memberUpdateRoute, async (c) => {
|
|
129
|
+
const principal = c.get('principal')!;
|
|
130
|
+
const { id } = c.req.valid('param');
|
|
131
|
+
const patch = c.req.valid('json');
|
|
132
|
+
|
|
133
|
+
const [target] = await db
|
|
134
|
+
.select({
|
|
135
|
+
id: workspaceMembers.id,
|
|
136
|
+
userId: workspaceMembers.userId,
|
|
137
|
+
role: workspaceMembers.role,
|
|
138
|
+
origin: workspaceMembers.origin,
|
|
139
|
+
isActive: workspaceMembers.isActive
|
|
140
|
+
})
|
|
141
|
+
.from(workspaceMembers)
|
|
142
|
+
.where(and(eq(workspaceMembers.id, id), eq(workspaceMembers.workspaceId, principal.workspaceId)))
|
|
143
|
+
.limit(1);
|
|
144
|
+
if (!target) return c.json(err('not_found', 'Member not found'), 404);
|
|
145
|
+
|
|
146
|
+
if (target.userId === principal.userId && patch.isActive === false) {
|
|
147
|
+
return c.json(err('cannot_deactivate_self', 'You cannot deactivate yourself'), 400);
|
|
148
|
+
}
|
|
149
|
+
// Guest role-lock (D2): a guest row is 'member' by construction and the
|
|
150
|
+
// guest capability limits key on origin, which no surface upgrades —
|
|
151
|
+
// promoting one would mint a workspace admin the guest gates still
|
|
152
|
+
// refuse resource creation to (an incoherent half-state), and on cloud an
|
|
153
|
+
// administrator the hub knows nothing about. To empower the person,
|
|
154
|
+
// invite them as a real member; the claim path preserves that row's
|
|
155
|
+
// origin. Deactivate/reactivate stays available — cutting a guest off
|
|
156
|
+
// entirely is a legitimate admin act.
|
|
157
|
+
if (target.origin === 'guest' && patch.role && patch.role !== target.role) {
|
|
158
|
+
return c.json(
|
|
159
|
+
err('guest_role_locked', 'A guest membership cannot change role — invite them as a member instead'),
|
|
160
|
+
403
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
// Only owners touch owners (grant or revoke).
|
|
164
|
+
if ((target.role === 'owner' || patch.role === 'owner') && principal.role !== 'owner') {
|
|
165
|
+
return c.json(err('forbidden', 'Only an owner can change owner roles'), 403);
|
|
166
|
+
}
|
|
167
|
+
// Never leave the workspace without an active owner (shared with the
|
|
168
|
+
// account-deletion guards in accounts/deletion.ts). Demotions run under
|
|
169
|
+
// the race-free guard: workspace advisory lock + re-check, with the
|
|
170
|
+
// migration-0009 trigger as the DB-level backstop — a concurrent-race
|
|
171
|
+
// loser gets this clean 400, never a 500.
|
|
172
|
+
const losesOwner =
|
|
173
|
+
target.role === 'owner' && ((patch.role && patch.role !== 'owner') || patch.isActive === false);
|
|
174
|
+
const applyPatch = async () => {
|
|
175
|
+
await db
|
|
176
|
+
.update(workspaceMembers)
|
|
177
|
+
.set({
|
|
178
|
+
...(patch.role ? { role: patch.role } : {}),
|
|
179
|
+
...(patch.isActive !== undefined ? { isActive: patch.isActive } : {})
|
|
180
|
+
})
|
|
181
|
+
.where(eq(workspaceMembers.id, target.id));
|
|
182
|
+
};
|
|
183
|
+
try {
|
|
184
|
+
if (losesOwner) {
|
|
185
|
+
await accountDeletion.withLastOwnerGuard(principal.workspaceId, target.userId, applyPatch);
|
|
186
|
+
} else {
|
|
187
|
+
await applyPatch();
|
|
188
|
+
}
|
|
189
|
+
} catch (cause) {
|
|
190
|
+
// The guarded loser throws LastOwnerError; the unguarded branch (or a
|
|
191
|
+
// demotion racing invitation re-acceptance) can still trip the 0009
|
|
192
|
+
// trigger with P0409 — both mean the last active owner would be lost.
|
|
193
|
+
if (cause instanceof LastOwnerError || isLastOwnerDbError(cause)) {
|
|
194
|
+
const message =
|
|
195
|
+
cause instanceof LastOwnerError
|
|
196
|
+
? cause.message
|
|
197
|
+
: 'The workspace must keep at least one active owner';
|
|
198
|
+
return c.json(err('last_owner', message), 400);
|
|
199
|
+
}
|
|
200
|
+
throw cause;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const [updated] = await db
|
|
204
|
+
.select({
|
|
205
|
+
id: workspaceMembers.id,
|
|
206
|
+
userId: workspaceMembers.userId,
|
|
207
|
+
email: userTable.email,
|
|
208
|
+
name: userTable.name,
|
|
209
|
+
role: workspaceMembers.role,
|
|
210
|
+
isActive: workspaceMembers.isActive,
|
|
211
|
+
createdAt: workspaceMembers.createdAt,
|
|
212
|
+
lastSeenAt: workspaceMembers.lastSeenAt
|
|
213
|
+
})
|
|
214
|
+
.from(workspaceMembers)
|
|
215
|
+
.innerJoin(userTable, eq(workspaceMembers.userId, userTable.id))
|
|
216
|
+
.where(eq(workspaceMembers.id, target.id))
|
|
217
|
+
.limit(1);
|
|
218
|
+
|
|
219
|
+
c.set('audit', {
|
|
220
|
+
action: 'member.update',
|
|
221
|
+
resourceType: 'member',
|
|
222
|
+
resourceId: target.id,
|
|
223
|
+
metadata: { ...patch, targetUserId: target.userId }
|
|
224
|
+
});
|
|
225
|
+
return c.json(toWire(updated!), 200);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Admin account deletion (GDPR erasure, the second delete surface).
|
|
229
|
+
// Covered by the 2-segment `/members/:id` requireRole gate above; machines
|
|
230
|
+
// are excluded automatically because DELETE /members/{id} is deliberately
|
|
231
|
+
// unlisted in the fail-closed scope allowlist. Files the target uploaded
|
|
232
|
+
// stay with the workspace (created_by → NULL via FK); NO blob cleanup.
|
|
233
|
+
api.openapi(memberDeleteRoute, async (c) => {
|
|
234
|
+
const principal = c.get('principal')!;
|
|
235
|
+
const { id } = c.req.valid('param');
|
|
236
|
+
|
|
237
|
+
const [target] = await db
|
|
238
|
+
.select({
|
|
239
|
+
id: workspaceMembers.id,
|
|
240
|
+
userId: workspaceMembers.userId,
|
|
241
|
+
role: workspaceMembers.role,
|
|
242
|
+
isActive: workspaceMembers.isActive
|
|
243
|
+
})
|
|
244
|
+
.from(workspaceMembers)
|
|
245
|
+
.where(and(eq(workspaceMembers.id, id), eq(workspaceMembers.workspaceId, principal.workspaceId)))
|
|
246
|
+
.limit(1);
|
|
247
|
+
if (!target) return c.json(err('not_found', 'Member not found'), 404);
|
|
248
|
+
if (target.userId === principal.userId) {
|
|
249
|
+
return c.json(err('cannot_delete_self', 'Delete your own account from the account page'), 400);
|
|
250
|
+
}
|
|
251
|
+
if (target.role === 'owner' && principal.role !== 'owner') {
|
|
252
|
+
return c.json(err('forbidden', 'Only an owner can delete an owner'), 403);
|
|
253
|
+
}
|
|
254
|
+
// ADR 014: deleting the ACCOUNT erases the user from EVERY workspace, and
|
|
255
|
+
// an admin's authority ends at their own — refuse when the target belongs
|
|
256
|
+
// to any other workspace (remove the person from this one instead; the account
|
|
257
|
+
// holder can erase themselves). Single-workspace instances never hit this.
|
|
258
|
+
const [foreign] = await db
|
|
259
|
+
.select({ id: workspaceMembers.id })
|
|
260
|
+
.from(workspaceMembers)
|
|
261
|
+
.where(
|
|
262
|
+
and(
|
|
263
|
+
eq(workspaceMembers.userId, target.userId),
|
|
264
|
+
ne(workspaceMembers.workspaceId, principal.workspaceId)
|
|
265
|
+
)
|
|
266
|
+
)
|
|
267
|
+
.limit(1);
|
|
268
|
+
if (foreign) {
|
|
269
|
+
return c.json(
|
|
270
|
+
err(
|
|
271
|
+
'member_of_other_workspaces',
|
|
272
|
+
'This account belongs to other workspaces — remove the person from this workspace instead of deleting the account'
|
|
273
|
+
),
|
|
274
|
+
409
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Snapshot the wire member BEFORE the cascade removes the joined user row.
|
|
279
|
+
const [snapshot] = await db
|
|
280
|
+
.select({
|
|
281
|
+
id: workspaceMembers.id,
|
|
282
|
+
userId: workspaceMembers.userId,
|
|
283
|
+
email: userTable.email,
|
|
284
|
+
name: userTable.name,
|
|
285
|
+
role: workspaceMembers.role,
|
|
286
|
+
isActive: workspaceMembers.isActive,
|
|
287
|
+
createdAt: workspaceMembers.createdAt,
|
|
288
|
+
lastSeenAt: workspaceMembers.lastSeenAt
|
|
289
|
+
})
|
|
290
|
+
.from(workspaceMembers)
|
|
291
|
+
.innerJoin(userTable, eq(workspaceMembers.userId, userTable.id))
|
|
292
|
+
.where(eq(workspaceMembers.id, target.id))
|
|
293
|
+
.limit(1);
|
|
294
|
+
|
|
295
|
+
// Better Auth's own deletion path (adapter hooks included) — NOT raw
|
|
296
|
+
// drizzle. Membership, api keys, invitations they issued, and sessions
|
|
297
|
+
// cascade; audit rows and file uploader anonymize via SET NULL.
|
|
298
|
+
// Deleting an ACTIVE OWNER runs under the race-free last-owner guard
|
|
299
|
+
// (workspace advisory lock + re-check; the migration-0009 trigger
|
|
300
|
+
// backstops) so a concurrent-race loser gets a clean 400 last_owner.
|
|
301
|
+
const cascade = async () => {
|
|
302
|
+
const authCtx = await auth.$context;
|
|
303
|
+
await authCtx.internalAdapter.deleteUser(target.userId);
|
|
304
|
+
await authCtx.internalAdapter.deleteUserSessions(target.userId);
|
|
305
|
+
// OPS-3 (PRDCT-1357): this path bypasses the deleteUser hooks, so the
|
|
306
|
+
// erasure tombstone is written here — a restore must not undo this.
|
|
307
|
+
await accountDeletion.recordErasure({ id: target.userId, email: snapshot!.email });
|
|
308
|
+
};
|
|
309
|
+
try {
|
|
310
|
+
if (target.role === 'owner' && target.isActive) {
|
|
311
|
+
await accountDeletion.withLastOwnerGuard(principal.workspaceId, target.userId, cascade);
|
|
312
|
+
} else {
|
|
313
|
+
await cascade();
|
|
314
|
+
}
|
|
315
|
+
} catch (cause) {
|
|
316
|
+
// isLastOwnerDbError: the unguarded branch can still trip the trigger
|
|
317
|
+
// when the target is promoted to last owner mid-flight.
|
|
318
|
+
if (cause instanceof LastOwnerError || isLastOwnerDbError(cause)) {
|
|
319
|
+
return c.json(err('last_owner', 'The workspace must keep at least one active owner'), 400);
|
|
320
|
+
}
|
|
321
|
+
throw cause;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
c.set('audit', {
|
|
325
|
+
action: 'member.delete',
|
|
326
|
+
resourceType: 'member',
|
|
327
|
+
resourceId: target.id,
|
|
328
|
+
metadata: { targetUserId: target.userId, email: snapshot!.email }
|
|
329
|
+
});
|
|
330
|
+
return c.json(toWire(snapshot!), 200);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Removal (PRDCT-2816): the act between the pause and the account erasure,
|
|
334
|
+
// and the same one the hub's removal is on cloud. The membership row is
|
|
335
|
+
// switched off, never deleted (a new invitation reactivates the very same
|
|
336
|
+
// row), and the person's project grants, their team seats and what the
|
|
337
|
+
// tool hangs on them in this workspace go with it, in ONE transaction: a
|
|
338
|
+
// person brought back starts with none. The row is left at the role
|
|
339
|
+
// `member`: the role a person held is not kept in wait for them either, so
|
|
340
|
+
// whatever switches the row back on (an admin's Reactivate, an invitation,
|
|
341
|
+
// which names its own role) never hands an old admin or owner role back.
|
|
342
|
+
// The account, and the person's memberships of other workspaces, are untouched.
|
|
343
|
+
// An already paused member can be removed: that turns the pause into a
|
|
344
|
+
// removal. The 2-segment gate above does NOT cover this 3-segment path, so
|
|
345
|
+
// it is gated explicitly. Machines never reach it: the path is deliberately
|
|
346
|
+
// UNLISTED in the fail-closed scope allowlist, like the account deletion.
|
|
347
|
+
// A hub-origin workspace died at the subtree gate with `hub_managed`.
|
|
348
|
+
api.use('/members/:id/remove', requireRole('admin'));
|
|
349
|
+
api.openapi(memberRemoveRoute, async (c) => {
|
|
350
|
+
const principal = c.get('principal')!;
|
|
351
|
+
const { id } = c.req.valid('param');
|
|
352
|
+
|
|
353
|
+
const [target] = await db
|
|
354
|
+
.select({
|
|
355
|
+
id: workspaceMembers.id,
|
|
356
|
+
userId: workspaceMembers.userId,
|
|
357
|
+
role: workspaceMembers.role,
|
|
358
|
+
isActive: workspaceMembers.isActive
|
|
359
|
+
})
|
|
360
|
+
.from(workspaceMembers)
|
|
361
|
+
.where(and(eq(workspaceMembers.id, id), eq(workspaceMembers.workspaceId, principal.workspaceId)))
|
|
362
|
+
.limit(1);
|
|
363
|
+
if (!target) return c.json(err('not_found', 'Member not found'), 404);
|
|
364
|
+
if (target.userId === principal.userId) {
|
|
365
|
+
return c.json(err('cannot_remove_self', 'You cannot remove yourself'), 400);
|
|
366
|
+
}
|
|
367
|
+
if (target.role === 'owner' && principal.role !== 'owner') {
|
|
368
|
+
return c.json(err('forbidden', 'Only an owner can remove an owner'), 403);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// The WHERE names the workspace again: the row is the one read above,
|
|
372
|
+
// and the statement could not reach another workspace's even if it were not.
|
|
373
|
+
// What the person held goes first, the row second: the same lock order
|
|
374
|
+
// as an invitation's accept, which locks the invitation and then the
|
|
375
|
+
// membership row (members/removal.ts, ORDER).
|
|
376
|
+
const remove = () =>
|
|
377
|
+
db.transaction(async (tx) => {
|
|
378
|
+
const removedRow = [
|
|
379
|
+
{ memberId: target.id, workspaceId: principal.workspaceId, userId: target.userId }
|
|
380
|
+
];
|
|
381
|
+
const counts = await deleteMembershipGrants(tx, removedRow, deps.onMembershipRemoval);
|
|
382
|
+
await tx
|
|
383
|
+
.update(workspaceMembers)
|
|
384
|
+
.set({ isActive: false, isDefault: false, role: 'member' })
|
|
385
|
+
.where(
|
|
386
|
+
and(eq(workspaceMembers.id, target.id), eq(workspaceMembers.workspaceId, principal.workspaceId))
|
|
387
|
+
);
|
|
388
|
+
// The second pass, after the row is off (the end-to-end verification,
|
|
389
|
+
// finding 5): an add that held the membership row FOR SHARE before the
|
|
390
|
+
// update (`holdLiveMembership`: a project grant, a team seat, a demo
|
|
391
|
+
// pass) made the update wait, and committed its right between the
|
|
392
|
+
// first pass and the update. This pass takes it. Any add after the
|
|
393
|
+
// update re-reads the row off and writes nothing, so two passes end it.
|
|
394
|
+
const late = await deleteMembershipGrants(tx, removedRow, deps.onMembershipRemoval);
|
|
395
|
+
return mergeRemovalCounts(counts, late);
|
|
396
|
+
});
|
|
397
|
+
let removed: RemovalCounts;
|
|
398
|
+
try {
|
|
399
|
+
// Removing an ACTIVE owner runs under the race-free last-owner guard,
|
|
400
|
+
// as the pause and the account deletion do.
|
|
401
|
+
removed =
|
|
402
|
+
target.role === 'owner' && target.isActive
|
|
403
|
+
? await accountDeletion.withLastOwnerGuard(principal.workspaceId, target.userId, remove)
|
|
404
|
+
: await remove();
|
|
405
|
+
} catch (cause) {
|
|
406
|
+
if (cause instanceof LastOwnerError || isLastOwnerDbError(cause)) {
|
|
407
|
+
return c.json(err('last_owner', 'The workspace must keep at least one active owner'), 400);
|
|
408
|
+
}
|
|
409
|
+
throw cause;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const [after] = await db
|
|
413
|
+
.select({
|
|
414
|
+
id: workspaceMembers.id,
|
|
415
|
+
userId: workspaceMembers.userId,
|
|
416
|
+
email: userTable.email,
|
|
417
|
+
name: userTable.name,
|
|
418
|
+
role: workspaceMembers.role,
|
|
419
|
+
isActive: workspaceMembers.isActive,
|
|
420
|
+
createdAt: workspaceMembers.createdAt,
|
|
421
|
+
lastSeenAt: workspaceMembers.lastSeenAt
|
|
422
|
+
})
|
|
423
|
+
.from(workspaceMembers)
|
|
424
|
+
.innerJoin(userTable, eq(workspaceMembers.userId, userTable.id))
|
|
425
|
+
.where(eq(workspaceMembers.id, target.id))
|
|
426
|
+
.limit(1);
|
|
427
|
+
|
|
428
|
+
c.set('audit', {
|
|
429
|
+
action: 'member.remove',
|
|
430
|
+
resourceType: 'member',
|
|
431
|
+
resourceId: target.id,
|
|
432
|
+
// The tool's counts first: a name the chassis writes is the chassis's.
|
|
433
|
+
metadata: {
|
|
434
|
+
...removed.tool,
|
|
435
|
+
targetUserId: target.userId,
|
|
436
|
+
roleBefore: target.role,
|
|
437
|
+
projectGrants: removed.projectGrants,
|
|
438
|
+
teamSeats: removed.teamSeats,
|
|
439
|
+
invitations: removed.invitations,
|
|
440
|
+
demoPasses: removed.demoPasses
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
return c.json(toWire(after!), 200);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// The shared refusal of every route that mints a sign-in-equivalent
|
|
447
|
+
// credential for another user (PRDCT-1354): guest targets and targets
|
|
448
|
+
// belonging to another workspace. Its own module (accounts/mint-refusal.ts),
|
|
449
|
+
// shared with the demo pass mint; bound here to this router's db and copy.
|
|
450
|
+
const mintRefusal = (principal: { workspaceId: string }, target: { userId: string; origin: string }) =>
|
|
451
|
+
sharedMintRefusal(db, deps.guestTargetMessage, principal, target);
|
|
452
|
+
|
|
453
|
+
// Owner-generated password reset link — the recovery path that works with
|
|
454
|
+
// no email driver (invitations' copyable-link pattern). The 2-segment gate
|
|
455
|
+
// above does NOT cover this 3-segment path, so gate it explicitly — and at
|
|
456
|
+
// OWNER, not admin (AUTH-8: see mintRefusal).
|
|
457
|
+
api.use('/members/:id/reset-link', requireRole('owner'));
|
|
458
|
+
api.openapi(memberResetLinkRoute, async (c) => {
|
|
459
|
+
// Cloud edition (`hubManaged` is present iff EDITION=cloud): the token a
|
|
460
|
+
// mint would produce lands on POST /reset-password, which the D1 hub-only
|
|
461
|
+
// posture refuses there (identity/better-auth.ts isPasswordResetPath) —
|
|
462
|
+
// so minting would hand admins a dead link. Refuse at the source instead.
|
|
463
|
+
// Hub-origin workspaces already died at the subtree gate above with
|
|
464
|
+
// `hub_managed`; this covers cloud-LOCAL workspaces (the operator's,
|
|
465
|
+
// guest hosts). Recovery for local accounts there: hub SSO for
|
|
466
|
+
// humans, break-glass for the operator. oss is untouched.
|
|
467
|
+
if (hubManaged) {
|
|
468
|
+
return c.json(
|
|
469
|
+
err(
|
|
470
|
+
'password_reset_disabled',
|
|
471
|
+
'Local password reset is disabled on this edition — credentials are managed at the Antasphere hub'
|
|
472
|
+
),
|
|
473
|
+
403
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
const principal = c.get('principal')!;
|
|
477
|
+
const { id } = c.req.valid('param');
|
|
478
|
+
|
|
479
|
+
const [target] = await db
|
|
480
|
+
.select({
|
|
481
|
+
id: workspaceMembers.id,
|
|
482
|
+
userId: workspaceMembers.userId,
|
|
483
|
+
role: workspaceMembers.role,
|
|
484
|
+
origin: workspaceMembers.origin
|
|
485
|
+
})
|
|
486
|
+
.from(workspaceMembers)
|
|
487
|
+
.where(and(eq(workspaceMembers.id, id), eq(workspaceMembers.workspaceId, principal.workspaceId)))
|
|
488
|
+
.limit(1);
|
|
489
|
+
if (!target) return c.json(err('not_found', 'Member not found'), 404);
|
|
490
|
+
const refusal = await mintRefusal(principal, target);
|
|
491
|
+
if (refusal) return c.json(err(refusal.code, refusal.message), 403);
|
|
492
|
+
|
|
493
|
+
// Mint a single-use token in Better Auth's own verification table, in the
|
|
494
|
+
// exact shape POST /reset-password consumes (`reset-password:<token>`).
|
|
495
|
+
const token = randomBytes(24).toString('base64url');
|
|
496
|
+
const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1h
|
|
497
|
+
const authCtx = await auth.$context;
|
|
498
|
+
await authCtx.internalAdapter.createVerificationValue({
|
|
499
|
+
identifier: `reset-password:${token}`,
|
|
500
|
+
value: target.userId,
|
|
501
|
+
expiresAt
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
c.set('audit', {
|
|
505
|
+
action: 'member.reset_link',
|
|
506
|
+
resourceType: 'member',
|
|
507
|
+
resourceId: target.id,
|
|
508
|
+
metadata: { targetUserId: target.userId }
|
|
509
|
+
});
|
|
510
|
+
return c.json(
|
|
511
|
+
{
|
|
512
|
+
resetUrl: `${publicBaseUrl.replace(/\/+$/, '')}/reset-password?token=${token}`,
|
|
513
|
+
expiresAt: expiresAt.toISOString()
|
|
514
|
+
},
|
|
515
|
+
200
|
|
516
|
+
);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// Owner-generated email change link — the email-change path that works
|
|
520
|
+
// with no email driver (same copyable-link pattern as the reset link).
|
|
521
|
+
// The 2-segment gate above does NOT cover this 3-segment path either, so
|
|
522
|
+
// gate it explicitly — at OWNER, like its sibling (AUTH-8).
|
|
523
|
+
api.use('/members/:id/change-email-link', requireRole('owner'));
|
|
524
|
+
api.openapi(memberChangeEmailLinkRoute, async (c) => {
|
|
525
|
+
// Cloud edition (`hubManaged` is present iff EDITION=cloud): the SAME
|
|
526
|
+
// closure the reset-link sibling carries, which this route was missing
|
|
527
|
+
// (AUTH-1). It is the stronger of the two — the minted JWT is
|
|
528
|
+
// sign-in-equivalent AND rewrites the address the hub identity is keyed
|
|
529
|
+
// on, while on cloud a user's email is the hub's to own (D10 re-syncs it
|
|
530
|
+
// on every login, so a local rewrite is either overwritten or a
|
|
531
|
+
// hijack). Hub-origin workspaces already died at the P7 subtree gate
|
|
532
|
+
// with `hub_managed`; this covers cloud-LOCAL workspaces (the
|
|
533
|
+
// operator's, guest hosts). oss is untouched.
|
|
534
|
+
if (hubManaged) {
|
|
535
|
+
return c.json(
|
|
536
|
+
err(
|
|
537
|
+
'email_change_disabled',
|
|
538
|
+
'Local email changes are disabled on this edition — identities are managed at the Antasphere hub'
|
|
539
|
+
),
|
|
540
|
+
403
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
const principal = c.get('principal')!;
|
|
544
|
+
const { id } = c.req.valid('param');
|
|
545
|
+
const body = c.req.valid('json');
|
|
546
|
+
|
|
547
|
+
const [target] = await db
|
|
548
|
+
.select({
|
|
549
|
+
id: workspaceMembers.id,
|
|
550
|
+
userId: workspaceMembers.userId,
|
|
551
|
+
role: workspaceMembers.role,
|
|
552
|
+
origin: workspaceMembers.origin,
|
|
553
|
+
email: userTable.email
|
|
554
|
+
})
|
|
555
|
+
.from(workspaceMembers)
|
|
556
|
+
.innerJoin(userTable, eq(workspaceMembers.userId, userTable.id))
|
|
557
|
+
.where(and(eq(workspaceMembers.id, id), eq(workspaceMembers.workspaceId, principal.workspaceId)))
|
|
558
|
+
.limit(1);
|
|
559
|
+
if (!target) return c.json(err('not_found', 'Member not found'), 404);
|
|
560
|
+
const refusal = await mintRefusal(principal, target);
|
|
561
|
+
if (refusal) return c.json(err(refusal.code, refusal.message), 403);
|
|
562
|
+
|
|
563
|
+
// Better Auth stores emails lowercased — compare and mint in that form.
|
|
564
|
+
// ⚠️ RACE-7: this lookup is ADVISORY ONLY, never the boundary. It is a
|
|
565
|
+
// read outside any lock, and the mint writes nothing (the token is a
|
|
566
|
+
// stateless JWT — LESSONS.md M6), so two concurrent mints for the same
|
|
567
|
+
// `newEmail` both pass it. The REAL enforcement is the `user.email`
|
|
568
|
+
// UNIQUE constraint at CONSUMPTION time: whichever link is consumed
|
|
569
|
+
// first wins the address and the loser's link fails, leaving exactly one
|
|
570
|
+
// account on that email (asserted in test/integration/minted-credentials
|
|
571
|
+
// .test.ts). Never promote this pre-check to a uniqueness guarantee.
|
|
572
|
+
// RACE-7 scope choice: joined on THIS workspace's membership rows — an
|
|
573
|
+
// unscoped user lookup made the 409-vs-200 an instance-global account
|
|
574
|
+
// oracle; the owner already sees these emails on the roster.
|
|
575
|
+
// ⚠️ The constraint at consumption is OBSERVABLE, not a silent backstop
|
|
576
|
+
// (PRDCT-1437): a colliding consume used to answer a raw 500 against the
|
|
577
|
+
// free case's 302 — a mint-then-consume account oracle. The consume path
|
|
578
|
+
// now answers the SAME redirect + session on both branches (the
|
|
579
|
+
// collision rewrite at the /auth mount — rewriteChangeEmailCollision in
|
|
580
|
+
// api/index.ts re-signs the token as a no-op change so Better Auth runs
|
|
581
|
+
// its native success path), and the honest residual — the owner can always
|
|
582
|
+
// read back whether the change TOOK, because email uniqueness is
|
|
583
|
+
// instance-global — is documented in ADR 013's amendment.
|
|
584
|
+
const newEmail = body.newEmail.toLowerCase();
|
|
585
|
+
const [existing] = await db
|
|
586
|
+
.select({ id: userTable.id })
|
|
587
|
+
.from(userTable)
|
|
588
|
+
.innerJoin(workspaceMembers, eq(workspaceMembers.userId, userTable.id))
|
|
589
|
+
.where(and(eq(userTable.email, newEmail), eq(workspaceMembers.workspaceId, principal.workspaceId)))
|
|
590
|
+
.limit(1);
|
|
591
|
+
if (existing) {
|
|
592
|
+
return c.json(err('email_taken', 'That email is already in use'), 409);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Mint the exact stateless JWT GET /verify-email consumes for an email
|
|
596
|
+
// change ({email: current, updateTo: new, requestType}), signed with the
|
|
597
|
+
// auth secret — Better Auth's own helper, never hand-rolled jose.
|
|
598
|
+
// ⚠️ Consuming this link updates the email AND signs the target in (a
|
|
599
|
+
// session is created when none exists) — it is sign-in-equivalent. Hand
|
|
600
|
+
// it to the target member only, never to anyone else.
|
|
601
|
+
const expiresIn = 3600; // 1h
|
|
602
|
+
const expiresAt = new Date(Date.now() + expiresIn * 1000);
|
|
603
|
+
const authCtx = await auth.$context;
|
|
604
|
+
const token = await createEmailVerificationToken(authCtx.secret, target.email, newEmail, expiresIn, {
|
|
605
|
+
requestType: 'change-email-verification'
|
|
606
|
+
});
|
|
607
|
+
const verifyUrl = `${publicBaseUrl.replace(/\/+$/, '')}/api/v1/auth/verify-email?token=${token}&callbackURL=%2Faccount`;
|
|
608
|
+
|
|
609
|
+
c.set('audit', {
|
|
610
|
+
action: 'member.change_email_link',
|
|
611
|
+
resourceType: 'member',
|
|
612
|
+
resourceId: target.id,
|
|
613
|
+
metadata: { targetUserId: target.userId, newEmail }
|
|
614
|
+
});
|
|
615
|
+
return c.json({ verifyUrl, newEmail, expiresAt: expiresAt.toISOString() }, 200);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { OpenAPIHono } from '@hono/zod-openapi';
|
|
2
|
+
import { sql } from 'drizzle-orm';
|
|
3
|
+
import { onboardingDismissRoute } from '@antasphere/chassis-contract/routes';
|
|
4
|
+
import { userOnboarding, type Db } from '@antasphere/chassis-db';
|
|
5
|
+
import type { Auth } from '../identity/better-auth.js';
|
|
6
|
+
|
|
7
|
+
const err = (code: string, message: string) => ({ error: { code, message } });
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* POST /me/onboarding/dismiss — the explicit act that hides the first-run
|
|
11
|
+
* welcome (SL-6, cloud edition ONLY; api/index.ts registers this module iff
|
|
12
|
+
* the instance boots EDITION=cloud, so oss answers the JSON 404 terminator).
|
|
13
|
+
*
|
|
14
|
+
* Session-only, same construction as /sso/logout: the path is deliberately
|
|
15
|
+
* UNLISTED in the fail-closed scope allowlist (middleware/scopes.ts) — every
|
|
16
|
+
* API key / OAuth bearer 403s in authContext before this runs — and the
|
|
17
|
+
* session resolves ROUTE-LOCALLY (the /me zero-state pattern) so a
|
|
18
|
+
* zero-membership session can dismiss too.
|
|
19
|
+
*
|
|
20
|
+
* The upsert is idempotent and preserves the FIRST dismissal timestamp: a
|
|
21
|
+
* second dismiss (double-click, replayed request) changes nothing. Only this
|
|
22
|
+
* route (and the deploy backfill) ever sets dismissed_at — the login path's
|
|
23
|
+
* lazy insert writes NULL — so `firstRunPending` flips exactly on the user's
|
|
24
|
+
* explicit act, retry-safe on every other path.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export interface OnboardingRouteDeps {
|
|
28
|
+
db: Db;
|
|
29
|
+
auth: Auth;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function registerOnboardingRoutes(api: OpenAPIHono, deps: OnboardingRouteDeps): void {
|
|
33
|
+
const { db, auth } = deps;
|
|
34
|
+
|
|
35
|
+
api.openapi(onboardingDismissRoute, async (c) => {
|
|
36
|
+
const session = await auth.api.getSession({ headers: c.req.raw.headers });
|
|
37
|
+
if (!session?.user) {
|
|
38
|
+
return c.json(err('unauthenticated', 'Authentication required'), 401);
|
|
39
|
+
}
|
|
40
|
+
await db
|
|
41
|
+
.insert(userOnboarding)
|
|
42
|
+
.values({ userId: session.user.id, dismissedAt: new Date() })
|
|
43
|
+
.onConflictDoUpdate({
|
|
44
|
+
target: userOnboarding.userId,
|
|
45
|
+
// Keep an earlier dismissal's timestamp; fill only a NULL.
|
|
46
|
+
set: { dismissedAt: sql`coalesce(${userOnboarding.dismissedAt}, excluded.dismissed_at)` }
|
|
47
|
+
});
|
|
48
|
+
return c.json({ dismissed: true as const }, 200);
|
|
49
|
+
});
|
|
50
|
+
}
|