@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,128 @@
|
|
|
1
|
+
import { createLocalJWKSet, jwtVerify, type JWK, type JWTVerifyOptions } from 'jose';
|
|
2
|
+
import { type Db } from '@antasphere/chassis-db';
|
|
3
|
+
import type { Principal } from '@antasphere/chassis-contract';
|
|
4
|
+
import { mcpResourceUrl, type Auth } from './better-auth.js';
|
|
5
|
+
import { isWorkspaceSelector, resolveMembership, type OnWorkspaceMiss } from './resolve-membership.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* OAuth Bearer-JWT resolution — the third credential path in auth-context.
|
|
9
|
+
*
|
|
10
|
+
* Access tokens are RS256 JWTs this instance minted itself (Better Auth jwt +
|
|
11
|
+
* oauthProvider plugins), so verification is fully local: JWKS straight from
|
|
12
|
+
* the jwt plugin (DB-backed, no HTTP), hard issuer / audience / algorithm /
|
|
13
|
+
* expiry pinning. On top of the signature, every request re-resolves the
|
|
14
|
+
* workspace membership LIVE — deactivating a member cuts token access on
|
|
15
|
+
* their next request, not at token expiry. Scope enforcement stays in the
|
|
16
|
+
* fail-closed allowlist gate (middleware/scopes.ts), identical to API keys.
|
|
17
|
+
*
|
|
18
|
+
* User-scoped credential model: a token identifies its USER ("act as you")
|
|
19
|
+
* and carries NO workspace authority — claims are never an authorization
|
|
20
|
+
* input. The request's target workspace comes from the X-Workspace-Id
|
|
21
|
+
* selector through the shared resolveMembership rule (explicit → fail-closed
|
|
22
|
+
* membership check; absent → the user's default, else oldest active), the
|
|
23
|
+
* same rule sessions and API keys use.
|
|
24
|
+
*/
|
|
25
|
+
const JWKS_TTL_MS = 10 * 60 * 1000;
|
|
26
|
+
|
|
27
|
+
export class OauthJwtVerifier {
|
|
28
|
+
private readonly issuer: string;
|
|
29
|
+
private readonly audience: string;
|
|
30
|
+
// Per-instance cache: the process verifies its OWN tokens, keys rotate
|
|
31
|
+
// rarely; on an unknown-signature failure we refetch once and retry.
|
|
32
|
+
private jwksCache: { keySet: ReturnType<typeof createLocalJWKSet>; fetchedAt: number } | null = null;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
private readonly auth: Auth,
|
|
36
|
+
private readonly db: Db,
|
|
37
|
+
publicBaseUrl: string,
|
|
38
|
+
/** Cloud only: one cached reconcile on a well-formed selector miss. */
|
|
39
|
+
private readonly onWorkspaceMiss?: OnWorkspaceMiss | undefined
|
|
40
|
+
) {
|
|
41
|
+
this.issuer = publicBaseUrl;
|
|
42
|
+
this.audience = mcpResourceUrl(publicBaseUrl);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async keySet(forceRefresh = false) {
|
|
46
|
+
if (!forceRefresh && this.jwksCache && Date.now() - this.jwksCache.fetchedAt < JWKS_TTL_MS) {
|
|
47
|
+
return this.jwksCache.keySet;
|
|
48
|
+
}
|
|
49
|
+
const jwks = (await this.auth.api.getJwks()) as { keys: JWK[] };
|
|
50
|
+
this.jwksCache = { keySet: createLocalJWKSet(jwks), fetchedAt: Date.now() };
|
|
51
|
+
return this.jwksCache.keySet;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async verify(token: string) {
|
|
55
|
+
const options: JWTVerifyOptions = {
|
|
56
|
+
issuer: this.issuer,
|
|
57
|
+
audience: this.audience,
|
|
58
|
+
algorithms: ['RS256'], // hard allowlist — implicitly rejects none/HS256
|
|
59
|
+
clockTolerance: 5,
|
|
60
|
+
requiredClaims: ['sub']
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
return await jwtVerify(token, await this.keySet(), options);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
// One retry with fresh keys covers signing-key rotation mid-cache —
|
|
66
|
+
// both directions: a token signed by a key the cache no longer trusts
|
|
67
|
+
// (JWSSignatureVerificationFailed) and a token signed by a key MINTED
|
|
68
|
+
// after the cache was warmed (JWKSNoMatchingKey — the rotate-signing-key
|
|
69
|
+
// drill would otherwise answer 401 for up to JWKS_TTL_MS, ADR 023).
|
|
70
|
+
if (
|
|
71
|
+
err instanceof Error &&
|
|
72
|
+
(err.name === 'JWSSignatureVerificationFailed' || err.name === 'JWKSNoMatchingKey')
|
|
73
|
+
) {
|
|
74
|
+
return await jwtVerify(token, await this.keySet(true), options);
|
|
75
|
+
}
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve a Bearer JWT to its principal, or null when the token fails
|
|
82
|
+
* verification or no live membership answers the selection — the same
|
|
83
|
+
* fail-closed semantics as the session and API-key paths.
|
|
84
|
+
*
|
|
85
|
+
* `requested` is the request's X-Workspace-Id (or null). Resolution is
|
|
86
|
+
* verify → sub → resolveMembership(sub, requested): the LIVE membership
|
|
87
|
+
* lookup is the instant-revocation point AND the whole workspace
|
|
88
|
+
* authorization. A legacy `workspace_id` claim (minted before the
|
|
89
|
+
* user-scoped model) is deliberately ignored — claims stopped being an
|
|
90
|
+
* authorization input entirely.
|
|
91
|
+
*/
|
|
92
|
+
async resolve(token: string, requested: string | null): Promise<Principal | null> {
|
|
93
|
+
let payload: Record<string, unknown>;
|
|
94
|
+
try {
|
|
95
|
+
({ payload } = await this.verify(token));
|
|
96
|
+
} catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const sub = typeof payload.sub === 'string' ? payload.sub : null;
|
|
100
|
+
if (!sub) return null;
|
|
101
|
+
|
|
102
|
+
let member = await resolveMembership(this.db, sub, requested);
|
|
103
|
+
if (!member && requested && this.onWorkspaceMiss && isWorkspaceSelector(requested)) {
|
|
104
|
+
// Unknown-workspace retry (cloud): the JWT verified above — the miss
|
|
105
|
+
// may be a hub org granted since the last reconcile pass. One cached
|
|
106
|
+
// reconcile, one re-run of the SAME lookup — no recursion.
|
|
107
|
+
await this.onWorkspaceMiss(sub, requested);
|
|
108
|
+
member = await resolveMembership(this.db, sub, requested);
|
|
109
|
+
}
|
|
110
|
+
if (!member) return null;
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
userId: sub,
|
|
114
|
+
email: member.email,
|
|
115
|
+
name: member.name,
|
|
116
|
+
workspaceId: member.workspaceId,
|
|
117
|
+
role: member.role,
|
|
118
|
+
origin: member.origin,
|
|
119
|
+
via: 'oauth',
|
|
120
|
+
scopes: new Set(
|
|
121
|
+
String(typeof payload.scope === 'string' ? payload.scope : '')
|
|
122
|
+
.split(' ')
|
|
123
|
+
.filter(Boolean)
|
|
124
|
+
),
|
|
125
|
+
...(member.accountRef ? { accountRef: member.accountRef } : {})
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { and, asc, desc, eq, or, sql } from 'drizzle-orm';
|
|
2
|
+
import { user as userTable, workspaceMembers, workspaces, type Db } from '@antasphere/chassis-db';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The ONE membership-selection rule of the user-scoped credential model
|
|
6
|
+
* (ADR 014 as amended): every credential — session, API key, OAuth bearer —
|
|
7
|
+
* identifies a USER, and each request resolves exactly ONE target workspace
|
|
8
|
+
* for that user:
|
|
9
|
+
*
|
|
10
|
+
* - `requested` non-null (the X-Workspace-Id header, or an API key's pin):
|
|
11
|
+
* an ACTIVE membership of that workspace is required, else null — fail
|
|
12
|
+
* closed, so an unknown workspace and a workspace the user does not
|
|
13
|
+
* belong to are indistinguishable (no oracle). The selector is the
|
|
14
|
+
* workspace's LOCAL id, or (PRDCT-2947) the id of the hub organization
|
|
15
|
+
* it projects (`workspaces.central_account_id`): a person names the
|
|
16
|
+
* organization the hub's way (`--org <hub org id>`) and the tool maps
|
|
17
|
+
* it here, in the one rule, so no credential path can fork on it. On
|
|
18
|
+
* self-hosted the column is always empty and the second arm is inert.
|
|
19
|
+
* - `requested` null: the user's DEFAULT membership (`is_default`, at most
|
|
20
|
+
* one per user), else the deterministic fallback — the OLDEST active
|
|
21
|
+
* membership (created_at, then id). While no row carries `is_default`
|
|
22
|
+
* (nothing sets it on oss; the cloud reconcile materializes the hub-level
|
|
23
|
+
* default), the ordering is inert and selection is byte-identical to the
|
|
24
|
+
* pre-default behavior.
|
|
25
|
+
*
|
|
26
|
+
* All three resolvers share THIS query so the selection rule can never fork
|
|
27
|
+
* per credential path.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Strict UUID shape — a malformed selector must never reach Postgres' uuid cast. */
|
|
31
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Well-formed-selector check, shared with the cloud miss-retry hook: only a
|
|
35
|
+
* plausibly-real workspace id ever triggers a hub reconcile — a garbage
|
|
36
|
+
* header can never buy a hub round-trip (the reconciler's TTL/throttle
|
|
37
|
+
* bounds even well-formed ones).
|
|
38
|
+
*/
|
|
39
|
+
export function isWorkspaceSelector(value: string): boolean {
|
|
40
|
+
return UUID_RE.test(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The cloud edition's unknown-workspace retry hook (internal/federation.md):
|
|
45
|
+
* a verified credential naming a workspace the local join cannot see MAY be
|
|
46
|
+
* a hub org granted since the last reconcile pass — "invited at the hub,
|
|
47
|
+
* clicks a deep link". Boot wires ONE cached reconcile; the resolver then
|
|
48
|
+
* re-runs the SAME lookup once, no recursion. undefined on oss.
|
|
49
|
+
*/
|
|
50
|
+
export type OnWorkspaceMiss = (userId: string, requested: string) => Promise<void>;
|
|
51
|
+
|
|
52
|
+
export interface ResolvedMembership {
|
|
53
|
+
workspaceId: string;
|
|
54
|
+
role: 'owner' | 'admin' | 'member';
|
|
55
|
+
origin: 'local' | 'hub' | 'guest';
|
|
56
|
+
/** The workspace's centralAccountId — null outside hub projections. */
|
|
57
|
+
accountRef: string | null;
|
|
58
|
+
/** Live user row fields, for the machine paths that carry no session. */
|
|
59
|
+
email: string;
|
|
60
|
+
name: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function resolveMembership(
|
|
64
|
+
db: Db,
|
|
65
|
+
userId: string,
|
|
66
|
+
requested: string | null
|
|
67
|
+
): Promise<ResolvedMembership | null> {
|
|
68
|
+
if (requested !== null && !UUID_RE.test(requested)) return null; // fail closed, no uuid-cast 500
|
|
69
|
+
|
|
70
|
+
const [row] = await db
|
|
71
|
+
.select({
|
|
72
|
+
workspaceId: workspaceMembers.workspaceId,
|
|
73
|
+
role: workspaceMembers.role,
|
|
74
|
+
origin: workspaceMembers.origin,
|
|
75
|
+
accountRef: workspaces.centralAccountId,
|
|
76
|
+
email: userTable.email,
|
|
77
|
+
name: userTable.name
|
|
78
|
+
})
|
|
79
|
+
.from(workspaceMembers)
|
|
80
|
+
.innerJoin(workspaces, eq(workspaceMembers.workspaceId, workspaces.id))
|
|
81
|
+
.innerJoin(userTable, eq(workspaceMembers.userId, userTable.id))
|
|
82
|
+
.where(
|
|
83
|
+
and(
|
|
84
|
+
eq(workspaceMembers.userId, userId),
|
|
85
|
+
eq(workspaceMembers.isActive, true),
|
|
86
|
+
...(requested
|
|
87
|
+
? [
|
|
88
|
+
or(
|
|
89
|
+
eq(workspaceMembers.workspaceId, requested),
|
|
90
|
+
// Case-blind like the uuid arm and the pin check (verifier
|
|
91
|
+
// round 1, F6): the column is text, the selector a uuid.
|
|
92
|
+
sql`lower(${workspaces.centralAccountId}) = lower(${requested})`
|
|
93
|
+
)
|
|
94
|
+
]
|
|
95
|
+
: [])
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
// Selector-less default: the default membership first, then the
|
|
99
|
+
// deterministic oldest. With a selector the filter pins a single row and
|
|
100
|
+
// the order is inert.
|
|
101
|
+
.orderBy(desc(workspaceMembers.isDefault), asc(workspaceMembers.createdAt), asc(workspaceMembers.id))
|
|
102
|
+
.limit(1);
|
|
103
|
+
return row ?? null;
|
|
104
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { desc, eq } from 'drizzle-orm';
|
|
2
|
+
import { exportJWK, generateKeyPair } from 'jose';
|
|
3
|
+
import { generateRandomString, symmetricDecrypt, symmetricEncrypt } from 'better-auth/crypto';
|
|
4
|
+
import { jwks, type Db } from '@antasphere/chassis-db';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* OAuth signing-key durability across AUTH_SECRET rotation (ADR 023).
|
|
8
|
+
*
|
|
9
|
+
* The jwt plugin stores its RS256 private key in the `jwks` table encrypted
|
|
10
|
+
* under the auth secret (bare xchacha20 ciphertext — createAuth passes a
|
|
11
|
+
* plain string `secret`, so no versioned envelope). Signing decrypts the
|
|
12
|
+
* NEWEST row on every mint; with no `rotationInterval` the row never
|
|
13
|
+
* expires, so a rotated AUTH_SECRET turns every token mint into a 500 —
|
|
14
|
+
* forever. The three exports here close that:
|
|
15
|
+
*
|
|
16
|
+
* - `preflightSigningKey` mirrors the plugin's signing path (newest
|
|
17
|
+
* non-expired row → decrypt) at boot and reports failure BEFORE the
|
|
18
|
+
* instance serves an authorize endpoint it cannot follow with a token.
|
|
19
|
+
* - `rotateSigningKey` mints a fresh RS256 key under the CURRENT secret —
|
|
20
|
+
* the same row shape the plugin's own createJwk writes, so the plugin
|
|
21
|
+
* signs with it immediately (newest createdAt wins). Retired rows stay in
|
|
22
|
+
* the table, and /jwks publishes every row without an expiresAt, so
|
|
23
|
+
* already-issued tokens keep verifying for as long as the old row stays —
|
|
24
|
+
* the publish-overlap window.
|
|
25
|
+
* - `retireSigningKey` deletes ONE named retired row after the overlap
|
|
26
|
+
* window; the newest (active signer) is refused.
|
|
27
|
+
*
|
|
28
|
+
* Everything here mirrors the pinned better-auth 1.6.15 jwt plugin
|
|
29
|
+
* (plugins/jwt/{sign,utils,adapter}.mjs) — re-verify the row shape, the
|
|
30
|
+
* newest-key selection, and the /jwks publish filter on ANY bump.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** The alg the chassis pins in `jwks.keyPairConfig` (identity/better-auth.ts). */
|
|
34
|
+
const SIGNING_ALG = 'RS256';
|
|
35
|
+
|
|
36
|
+
export type SigningKeyPreflight =
|
|
37
|
+
{ ok: true; kid: string | null } | { ok: false; kid: string; reason: string };
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The operator-facing diagnostic — names the cause and BOTH safe remedies.
|
|
41
|
+
* Kept in one place so boot's fatal log and the tests pin the same message.
|
|
42
|
+
*/
|
|
43
|
+
export function signingKeyPreflightMessage(kid: string): string {
|
|
44
|
+
return (
|
|
45
|
+
`OAuth signing-key preflight failed: the stored JWKS private key (kid ${kid}) ` +
|
|
46
|
+
'cannot be decrypted with the current AUTH_SECRET. This happens when AUTH_SECRET ' +
|
|
47
|
+
'was rotated without re-keying token signing; booting anyway would serve an ' +
|
|
48
|
+
'authorize endpoint whose every token mint (POST /oauth2/token) answers 500. ' +
|
|
49
|
+
'Remedies: restore the previous AUTH_SECRET, or mint a fresh signing key under ' +
|
|
50
|
+
'the current secret with `node dist/index.js rotate-signing-key` — the retired ' +
|
|
51
|
+
'key stays published on /jwks so already-issued tokens keep verifying ' +
|
|
52
|
+
'(internal/security-runbooks.md).'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Boot preflight: attempt one decrypt of the key the jwt plugin would sign
|
|
58
|
+
* with. Exactly the plugin's selection rule (sign.mjs): newest by createdAt;
|
|
59
|
+
* a missing table-or-row and an EXPIRED newest row are both fine — the next
|
|
60
|
+
* mint creates a fresh key under the current secret.
|
|
61
|
+
*/
|
|
62
|
+
export async function preflightSigningKey(db: Db, authSecret: string): Promise<SigningKeyPreflight> {
|
|
63
|
+
const [latest] = await db
|
|
64
|
+
.select({ id: jwks.id, privateKey: jwks.privateKey, expiresAt: jwks.expiresAt })
|
|
65
|
+
.from(jwks)
|
|
66
|
+
.orderBy(desc(jwks.createdAt))
|
|
67
|
+
.limit(1);
|
|
68
|
+
if (!latest) return { ok: true, kid: null };
|
|
69
|
+
if (latest.expiresAt && latest.expiresAt < new Date()) return { ok: true, kid: null };
|
|
70
|
+
try {
|
|
71
|
+
// The plugin stores JSON.stringify(symmetricEncrypt(...)) — decrypt takes
|
|
72
|
+
// the JSON.parse'd string (sign.mjs).
|
|
73
|
+
await symmetricDecrypt({ key: authSecret, data: JSON.parse(latest.privateKey) as string });
|
|
74
|
+
return { ok: true, kid: latest.id };
|
|
75
|
+
} catch {
|
|
76
|
+
return { ok: false, kid: latest.id, reason: signingKeyPreflightMessage(latest.id) };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Mint a new signing key under the given secret — the manual rotation step
|
|
82
|
+
* (a compromise drill, or re-keying after an AUTH_SECRET change). Mirrors
|
|
83
|
+
* the plugin's createJwk (utils.mjs): extractable RS256 pair, JWK-exported,
|
|
84
|
+
* private half encrypted exactly as the plugin decrypts it. The new row's
|
|
85
|
+
* createdAt makes it the active signer; every older row stays published on
|
|
86
|
+
* /jwks (the overlap) until explicitly retired.
|
|
87
|
+
*/
|
|
88
|
+
export async function rotateSigningKey(db: Db, authSecret: string): Promise<{ kid: string }> {
|
|
89
|
+
const { publicKey, privateKey } = await generateKeyPair(SIGNING_ALG, { extractable: true });
|
|
90
|
+
const publicWebKey = await exportJWK(publicKey);
|
|
91
|
+
const privateWebKey = await exportJWK(privateKey);
|
|
92
|
+
const kid = generateRandomString(32, 'a-z', 'A-Z', '0-9');
|
|
93
|
+
await db.insert(jwks).values({
|
|
94
|
+
id: kid,
|
|
95
|
+
publicKey: JSON.stringify(publicWebKey),
|
|
96
|
+
privateKey: JSON.stringify(
|
|
97
|
+
await symmetricEncrypt({ key: authSecret, data: JSON.stringify(privateWebKey) })
|
|
98
|
+
),
|
|
99
|
+
createdAt: new Date(),
|
|
100
|
+
expiresAt: null
|
|
101
|
+
});
|
|
102
|
+
return { kid };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Delete ONE retired key by kid, after the overlap window. Refuses the
|
|
107
|
+
* newest row: deleting the active signer would silently re-key the instance
|
|
108
|
+
* on the next mint with zero coordination — the exact failure the preflight
|
|
109
|
+
* exists to prevent.
|
|
110
|
+
*/
|
|
111
|
+
export async function retireSigningKey(db: Db, kid: string): Promise<void> {
|
|
112
|
+
const [latest] = await db.select({ id: jwks.id }).from(jwks).orderBy(desc(jwks.createdAt)).limit(1);
|
|
113
|
+
if (!latest) throw new Error('no signing keys exist');
|
|
114
|
+
if (latest.id === kid) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`refusing to retire ${kid}: it is the ACTIVE signing key (newest row). ` +
|
|
117
|
+
'Mint a replacement first with rotate-signing-key, wait out the overlap window, then retire.'
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const deleted = await db.delete(jwks).where(eq(jwks.id, kid)).returning({ id: jwks.id });
|
|
121
|
+
if (deleted.length === 0) throw new Error(`no signing key with kid ${kid}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* `node dist/index.js rotate-signing-key [--retire <kid>]` — the operator
|
|
126
|
+
* entry point (index.ts). Runs against the container's own env (DATABASE_URL
|
|
127
|
+
* + AUTH_SECRET/DATA_DIR), so `docker compose run --rm app …` works while
|
|
128
|
+
* the server is down — the state the preflight refusal leaves you in.
|
|
129
|
+
* Output goes to stdout on purpose: this is an interactive operator command,
|
|
130
|
+
* not the service log.
|
|
131
|
+
*/
|
|
132
|
+
export async function runSigningKeyCli(
|
|
133
|
+
db: Db,
|
|
134
|
+
authSecret: string,
|
|
135
|
+
args: readonly string[],
|
|
136
|
+
out: (line: string) => void
|
|
137
|
+
): Promise<number> {
|
|
138
|
+
const retireAt = args.indexOf('--retire');
|
|
139
|
+
if (retireAt !== -1) {
|
|
140
|
+
const kid = args[retireAt + 1];
|
|
141
|
+
if (!kid) {
|
|
142
|
+
out('usage: rotate-signing-key --retire <kid>');
|
|
143
|
+
return 1;
|
|
144
|
+
}
|
|
145
|
+
await retireSigningKey(db, kid);
|
|
146
|
+
out(`retired signing key ${kid} — it is no longer published on /jwks; tokens it signed no longer verify`);
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const before = await db.select({ id: jwks.id }).from(jwks).orderBy(desc(jwks.createdAt));
|
|
151
|
+
const { kid } = await rotateSigningKey(db, authSecret);
|
|
152
|
+
out(`minted new signing key ${kid} under the current AUTH_SECRET — it signs from the next mint on`);
|
|
153
|
+
if (before.length > 0) {
|
|
154
|
+
out(
|
|
155
|
+
`retired key(s) still published for the overlap window (already-issued tokens keep verifying): ` +
|
|
156
|
+
before.map((k) => k.id).join(', ')
|
|
157
|
+
);
|
|
158
|
+
out(
|
|
159
|
+
'after the overlap window (outstanding access tokens live 15 min; allow for external JWKS caches), ' +
|
|
160
|
+
'retire each with: rotate-signing-key --retire <kid>'
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import { bootPlatform } from './boot.js';
|
|
3
|
+
import type { BootOverrides, BootResult, ToolDefinition } from './tool-definition.js';
|
|
4
|
+
|
|
5
|
+
export { type AppDeps, createApp, postgresErrorCode } from './app.js';
|
|
6
|
+
export type { ExportEntriesFn, ExportEntry } from './api/export.js';
|
|
7
|
+
export {
|
|
8
|
+
EMPTY_TOOL_ENTITLEMENTS,
|
|
9
|
+
HUB_USAGE_SCOPE,
|
|
10
|
+
HubCreditCheck,
|
|
11
|
+
HubMachineToken,
|
|
12
|
+
HubUsagePoster,
|
|
13
|
+
EntitlementProfiles,
|
|
14
|
+
assertToolEntitlements,
|
|
15
|
+
type ToolEntitlementDeclaration
|
|
16
|
+
} from './entitlements/index.js';
|
|
17
|
+
export type {
|
|
18
|
+
ApiContext,
|
|
19
|
+
ApiRateLimitContext,
|
|
20
|
+
ApiRoutesContext,
|
|
21
|
+
BodyCap,
|
|
22
|
+
BodyLimitVerdict,
|
|
23
|
+
BootOverrides,
|
|
24
|
+
BootResult,
|
|
25
|
+
FilePolicy,
|
|
26
|
+
JobsContext,
|
|
27
|
+
PlatformCore,
|
|
28
|
+
ServiceCore,
|
|
29
|
+
ToolApiSlots,
|
|
30
|
+
ToolAppSlots,
|
|
31
|
+
ToolBuckets,
|
|
32
|
+
ToolCopy,
|
|
33
|
+
ToolDefinition,
|
|
34
|
+
ToolRateLimiters
|
|
35
|
+
} from './tool-definition.js';
|
|
36
|
+
|
|
37
|
+
/** A platform: one tool definition bound to the chassis' locked composition. */
|
|
38
|
+
export interface Platform<TEnvShape extends z.ZodRawShape, TDomain, TEvents, TToolOverrides> {
|
|
39
|
+
/**
|
|
40
|
+
* The boot sequence, in its locked order: env → logger → db probe → secret →
|
|
41
|
+
* migrations under advisory lock → app assembly → ready.
|
|
42
|
+
*/
|
|
43
|
+
boot: (
|
|
44
|
+
source?: NodeJS.ProcessEnv,
|
|
45
|
+
overrides?: BootOverrides<TToolOverrides>
|
|
46
|
+
) => Promise<BootResult<TEnvShape, TDomain, TEvents>>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* THE composition entry. A tool fills the named slots of `ToolDefinition`;
|
|
51
|
+
* the chassis owns every ordering (boot.ts, api/create-api.ts, app.ts).
|
|
52
|
+
*/
|
|
53
|
+
export function createPlatform<
|
|
54
|
+
TEnvShape extends z.ZodRawShape,
|
|
55
|
+
TDomain,
|
|
56
|
+
TBuckets extends string = never,
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
58
|
+
TEvents = {},
|
|
59
|
+
TToolOverrides = never
|
|
60
|
+
>(
|
|
61
|
+
tool: ToolDefinition<TEnvShape, TDomain, TBuckets, TEvents, TToolOverrides>
|
|
62
|
+
): Platform<TEnvShape, TDomain, TEvents, TToolOverrides> {
|
|
63
|
+
return { boot: (source, overrides) => bootPlatform(tool, source, overrides) };
|
|
64
|
+
}
|
|
65
|
+
export type { MembershipRemovalHook, RemovedMembership, RemovalCounts } from './members/removal.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { InvitationError, InvitationService } from './service.js';
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { and, eq, isNull, or, sql } from 'drizzle-orm';
|
|
3
|
+
import {
|
|
4
|
+
invitations,
|
|
5
|
+
workspaceMembers,
|
|
6
|
+
user as userTable,
|
|
7
|
+
type Db,
|
|
8
|
+
type Invitation
|
|
9
|
+
} from '@antasphere/chassis-db';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Token-bearing invitations; the database holds only sha256 hashes.
|
|
13
|
+
* Acceptance works with zero SMTP — the copyable link is the primary path,
|
|
14
|
+
* mail is a bonus.
|
|
15
|
+
*
|
|
16
|
+
* Each invitation carries TWO independent tokens for one row (ADR 009):
|
|
17
|
+
* `token` is the copyable link handed to the inviter (always in the create
|
|
18
|
+
* response), `emailToken` appears ONLY inside the invitation email. Both
|
|
19
|
+
* redeem the same invitation, but which one was presented tells the accept
|
|
20
|
+
* flow whether the accepter demonstrably controls the invited mailbox — the
|
|
21
|
+
* admin-visible link never proves that, the emailed token does.
|
|
22
|
+
*/
|
|
23
|
+
const INVITATION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
24
|
+
|
|
25
|
+
function hashToken(token: string): string {
|
|
26
|
+
return createHash('sha256').update(token).digest('hex');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A live invitation plus which of its two tokens the caller presented. */
|
|
30
|
+
export interface InvitationMatch {
|
|
31
|
+
invitation: Invitation;
|
|
32
|
+
/** True when the presented token is the one that ONLY the email carried. */
|
|
33
|
+
viaEmailToken: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class InvitationService {
|
|
37
|
+
constructor(private readonly db: Db) {}
|
|
38
|
+
|
|
39
|
+
async create(opts: {
|
|
40
|
+
workspaceId: string;
|
|
41
|
+
email: string;
|
|
42
|
+
role: 'owner' | 'admin' | 'member';
|
|
43
|
+
invitedBy: string;
|
|
44
|
+
}): Promise<{ invitation: Invitation; token: string; emailToken: string }> {
|
|
45
|
+
const email = opts.email.toLowerCase().trim();
|
|
46
|
+
|
|
47
|
+
// Refuse duplicates: an active member or an open invitation.
|
|
48
|
+
const [existingMember] = await this.db
|
|
49
|
+
.select({ id: workspaceMembers.id })
|
|
50
|
+
.from(workspaceMembers)
|
|
51
|
+
.innerJoin(userTable, eq(workspaceMembers.userId, userTable.id))
|
|
52
|
+
.where(
|
|
53
|
+
and(
|
|
54
|
+
eq(userTable.email, email),
|
|
55
|
+
eq(workspaceMembers.workspaceId, opts.workspaceId),
|
|
56
|
+
eq(workspaceMembers.isActive, true)
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
.limit(1);
|
|
60
|
+
if (existingMember) throw new InvitationError('already_member', 'This email is already an active member');
|
|
61
|
+
|
|
62
|
+
const [open] = await this.db
|
|
63
|
+
.select({ id: invitations.id })
|
|
64
|
+
.from(invitations)
|
|
65
|
+
.where(
|
|
66
|
+
and(
|
|
67
|
+
eq(invitations.email, email),
|
|
68
|
+
eq(invitations.workspaceId, opts.workspaceId),
|
|
69
|
+
isNull(invitations.acceptedAt),
|
|
70
|
+
isNull(invitations.revokedAt),
|
|
71
|
+
sql`${invitations.expiresAt} > now()`
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
.limit(1);
|
|
75
|
+
if (open)
|
|
76
|
+
throw new InvitationError('already_invited', 'An open invitation for this email already exists');
|
|
77
|
+
|
|
78
|
+
const token = randomBytes(32).toString('base64url');
|
|
79
|
+
const emailToken = randomBytes(32).toString('base64url');
|
|
80
|
+
const [invitation] = await this.db
|
|
81
|
+
.insert(invitations)
|
|
82
|
+
.values({
|
|
83
|
+
workspaceId: opts.workspaceId,
|
|
84
|
+
email,
|
|
85
|
+
role: opts.role,
|
|
86
|
+
tokenHash: hashToken(token),
|
|
87
|
+
emailTokenHash: hashToken(emailToken),
|
|
88
|
+
invitedBy: opts.invitedBy,
|
|
89
|
+
expiresAt: new Date(Date.now() + INVITATION_TTL_MS)
|
|
90
|
+
})
|
|
91
|
+
.returning();
|
|
92
|
+
if (!invitation) throw new Error('invitation insert failed');
|
|
93
|
+
return { invitation, token, emailToken };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Find a live (unaccepted, unrevoked, unexpired) invitation by raw token —
|
|
98
|
+
* either the copyable-link token or the emailed one — and report which.
|
|
99
|
+
*/
|
|
100
|
+
async findLiveByToken(token: string): Promise<InvitationMatch | null> {
|
|
101
|
+
const hash = hashToken(token);
|
|
102
|
+
const [row] = await this.db
|
|
103
|
+
.select()
|
|
104
|
+
.from(invitations)
|
|
105
|
+
.where(
|
|
106
|
+
and(
|
|
107
|
+
or(eq(invitations.tokenHash, hash), eq(invitations.emailTokenHash, hash)),
|
|
108
|
+
isNull(invitations.acceptedAt),
|
|
109
|
+
isNull(invitations.revokedAt)
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
.limit(1);
|
|
113
|
+
if (!row) return null;
|
|
114
|
+
if (row.expiresAt.getTime() < Date.now()) return null;
|
|
115
|
+
return { invitation: row, viaEmailToken: row.emailTokenHash === hash };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Join: create the membership and stamp acceptance atomically enough that
|
|
120
|
+
* a token cannot be redeemed twice (the accepted_at guard is the gate).
|
|
121
|
+
*/
|
|
122
|
+
async accept(invitation: Invitation, userId: string): Promise<boolean> {
|
|
123
|
+
return this.db.transaction(async (tx) => {
|
|
124
|
+
const updated = await tx
|
|
125
|
+
.update(invitations)
|
|
126
|
+
.set({ acceptedAt: sql`now()` })
|
|
127
|
+
.where(
|
|
128
|
+
and(
|
|
129
|
+
eq(invitations.id, invitation.id),
|
|
130
|
+
isNull(invitations.acceptedAt),
|
|
131
|
+
isNull(invitations.revokedAt),
|
|
132
|
+
sql`${invitations.expiresAt} > now()`
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
.returning({ id: invitations.id });
|
|
136
|
+
if (updated.length === 0) return false; // redeemed, revoked, or expired meanwhile
|
|
137
|
+
|
|
138
|
+
// Rejoin after deactivation reactivates the existing row.
|
|
139
|
+
const [existing] = await tx
|
|
140
|
+
.select({ id: workspaceMembers.id })
|
|
141
|
+
.from(workspaceMembers)
|
|
142
|
+
.where(
|
|
143
|
+
and(eq(workspaceMembers.workspaceId, invitation.workspaceId), eq(workspaceMembers.userId, userId))
|
|
144
|
+
)
|
|
145
|
+
.limit(1);
|
|
146
|
+
if (existing) {
|
|
147
|
+
await tx
|
|
148
|
+
.update(workspaceMembers)
|
|
149
|
+
.set({ isActive: true, role: invitation.role, invitedBy: invitation.invitedBy })
|
|
150
|
+
.where(eq(workspaceMembers.id, existing.id));
|
|
151
|
+
} else {
|
|
152
|
+
await tx.insert(workspaceMembers).values({
|
|
153
|
+
workspaceId: invitation.workspaceId,
|
|
154
|
+
userId,
|
|
155
|
+
role: invitation.role,
|
|
156
|
+
invitedBy: invitation.invitedBy
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return true;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export class InvitationError extends Error {
|
|
165
|
+
constructor(
|
|
166
|
+
public readonly code: 'already_member' | 'already_invited',
|
|
167
|
+
message: string
|
|
168
|
+
) {
|
|
169
|
+
super(message);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export {
|
|
2
|
+
type JobDeclaration,
|
|
3
|
+
type Jobs,
|
|
4
|
+
type UsageRetry,
|
|
5
|
+
DEFAULT_USAGE_RETRY,
|
|
6
|
+
USAGE_HELD_QUEUE,
|
|
7
|
+
USAGE_QUEUE,
|
|
8
|
+
PgBossUsageSink,
|
|
9
|
+
createJobs,
|
|
10
|
+
usageSendOptions
|
|
11
|
+
} from './pgboss.js';
|
|
12
|
+
export {
|
|
13
|
+
type TimerJob,
|
|
14
|
+
type TimerOptions,
|
|
15
|
+
type TimerRef,
|
|
16
|
+
type TimerRetry,
|
|
17
|
+
type TimerWhen,
|
|
18
|
+
type Timers,
|
|
19
|
+
MAX_IN_SECONDS,
|
|
20
|
+
PGBOSS_SCHEMA,
|
|
21
|
+
PgBossTimers,
|
|
22
|
+
TIMER_KEY_LOCK_CLASS,
|
|
23
|
+
TimerError,
|
|
24
|
+
startAfterOf
|
|
25
|
+
} from './timers.js';
|