palaryn 0.1.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 +21 -0
- package/README.md +716 -0
- package/dist/sdk/typescript/src/client.d.ts +71 -0
- package/dist/sdk/typescript/src/client.d.ts.map +1 -0
- package/dist/sdk/typescript/src/client.js +176 -0
- package/dist/sdk/typescript/src/client.js.map +1 -0
- package/dist/sdk/typescript/src/errors.d.ts +50 -0
- package/dist/sdk/typescript/src/errors.d.ts.map +1 -0
- package/dist/sdk/typescript/src/errors.js +103 -0
- package/dist/sdk/typescript/src/errors.js.map +1 -0
- package/dist/sdk/typescript/src/index.d.ts +4 -0
- package/dist/sdk/typescript/src/index.d.ts.map +1 -0
- package/dist/sdk/typescript/src/index.js +15 -0
- package/dist/sdk/typescript/src/index.js.map +1 -0
- package/dist/sdk/typescript/src/types.d.ts +101 -0
- package/dist/sdk/typescript/src/types.d.ts.map +1 -0
- package/dist/sdk/typescript/src/types.js +6 -0
- package/dist/sdk/typescript/src/types.js.map +1 -0
- package/dist/src/admin/index.d.ts +2 -0
- package/dist/src/admin/index.d.ts.map +1 -0
- package/dist/src/admin/index.js +6 -0
- package/dist/src/admin/index.js.map +1 -0
- package/dist/src/admin/routes.d.ts +5 -0
- package/dist/src/admin/routes.d.ts.map +1 -0
- package/dist/src/admin/routes.js +471 -0
- package/dist/src/admin/routes.js.map +1 -0
- package/dist/src/admin/templates.d.ts +51 -0
- package/dist/src/admin/templates.d.ts.map +1 -0
- package/dist/src/admin/templates.js +500 -0
- package/dist/src/admin/templates.js.map +1 -0
- package/dist/src/anomaly/detector.d.ts +141 -0
- package/dist/src/anomaly/detector.d.ts.map +1 -0
- package/dist/src/anomaly/detector.js +554 -0
- package/dist/src/anomaly/detector.js.map +1 -0
- package/dist/src/anomaly/index.d.ts +2 -0
- package/dist/src/anomaly/index.d.ts.map +1 -0
- package/dist/src/anomaly/index.js +7 -0
- package/dist/src/anomaly/index.js.map +1 -0
- package/dist/src/approval/manager.d.ts +147 -0
- package/dist/src/approval/manager.d.ts.map +1 -0
- package/dist/src/approval/manager.js +511 -0
- package/dist/src/approval/manager.js.map +1 -0
- package/dist/src/approval/webhook.d.ts +36 -0
- package/dist/src/approval/webhook.d.ts.map +1 -0
- package/dist/src/approval/webhook.js +135 -0
- package/dist/src/approval/webhook.js.map +1 -0
- package/dist/src/audit/logger.d.ts +70 -0
- package/dist/src/audit/logger.d.ts.map +1 -0
- package/dist/src/audit/logger.js +440 -0
- package/dist/src/audit/logger.js.map +1 -0
- package/dist/src/auth/index.d.ts +6 -0
- package/dist/src/auth/index.d.ts.map +1 -0
- package/dist/src/auth/index.js +22 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/src/auth/password.d.ts +3 -0
- package/dist/src/auth/password.d.ts.map +1 -0
- package/dist/src/auth/password.js +25 -0
- package/dist/src/auth/password.js.map +1 -0
- package/dist/src/auth/pkce.d.ts +13 -0
- package/dist/src/auth/pkce.d.ts.map +1 -0
- package/dist/src/auth/pkce.js +58 -0
- package/dist/src/auth/pkce.js.map +1 -0
- package/dist/src/auth/providers.d.ts +28 -0
- package/dist/src/auth/providers.d.ts.map +1 -0
- package/dist/src/auth/providers.js +198 -0
- package/dist/src/auth/providers.js.map +1 -0
- package/dist/src/auth/routes.d.ts +14 -0
- package/dist/src/auth/routes.d.ts.map +1 -0
- package/dist/src/auth/routes.js +431 -0
- package/dist/src/auth/routes.js.map +1 -0
- package/dist/src/auth/session.d.ts +24 -0
- package/dist/src/auth/session.d.ts.map +1 -0
- package/dist/src/auth/session.js +105 -0
- package/dist/src/auth/session.js.map +1 -0
- package/dist/src/billing/index.d.ts +7 -0
- package/dist/src/billing/index.d.ts.map +1 -0
- package/dist/src/billing/index.js +14 -0
- package/dist/src/billing/index.js.map +1 -0
- package/dist/src/billing/plan-enforcer.d.ts +44 -0
- package/dist/src/billing/plan-enforcer.d.ts.map +1 -0
- package/dist/src/billing/plan-enforcer.js +110 -0
- package/dist/src/billing/plan-enforcer.js.map +1 -0
- package/dist/src/billing/routes.d.ts +15 -0
- package/dist/src/billing/routes.d.ts.map +1 -0
- package/dist/src/billing/routes.js +193 -0
- package/dist/src/billing/routes.js.map +1 -0
- package/dist/src/billing/stripe-client.d.ts +14 -0
- package/dist/src/billing/stripe-client.d.ts.map +1 -0
- package/dist/src/billing/stripe-client.js +51 -0
- package/dist/src/billing/stripe-client.js.map +1 -0
- package/dist/src/billing/webhook-handler.d.ts +19 -0
- package/dist/src/billing/webhook-handler.d.ts.map +1 -0
- package/dist/src/billing/webhook-handler.js +169 -0
- package/dist/src/billing/webhook-handler.js.map +1 -0
- package/dist/src/billing/webhook-routes.d.ts +5 -0
- package/dist/src/billing/webhook-routes.d.ts.map +1 -0
- package/dist/src/billing/webhook-routes.js +30 -0
- package/dist/src/billing/webhook-routes.js.map +1 -0
- package/dist/src/budget/manager.d.ts +95 -0
- package/dist/src/budget/manager.d.ts.map +1 -0
- package/dist/src/budget/manager.js +547 -0
- package/dist/src/budget/manager.js.map +1 -0
- package/dist/src/budget/usage-extractor.d.ts +38 -0
- package/dist/src/budget/usage-extractor.d.ts.map +1 -0
- package/dist/src/budget/usage-extractor.js +165 -0
- package/dist/src/budget/usage-extractor.js.map +1 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +115 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config/defaults.d.ts +3 -0
- package/dist/src/config/defaults.d.ts.map +1 -0
- package/dist/src/config/defaults.js +243 -0
- package/dist/src/config/defaults.js.map +1 -0
- package/dist/src/config/validate.d.ts +15 -0
- package/dist/src/config/validate.d.ts.map +1 -0
- package/dist/src/config/validate.js +105 -0
- package/dist/src/config/validate.js.map +1 -0
- package/dist/src/dlp/composite-scanner.d.ts +47 -0
- package/dist/src/dlp/composite-scanner.d.ts.map +1 -0
- package/dist/src/dlp/composite-scanner.js +186 -0
- package/dist/src/dlp/composite-scanner.js.map +1 -0
- package/dist/src/dlp/index.d.ts +10 -0
- package/dist/src/dlp/index.d.ts.map +1 -0
- package/dist/src/dlp/index.js +26 -0
- package/dist/src/dlp/index.js.map +1 -0
- package/dist/src/dlp/interfaces.d.ts +33 -0
- package/dist/src/dlp/interfaces.d.ts.map +1 -0
- package/dist/src/dlp/interfaces.js +3 -0
- package/dist/src/dlp/interfaces.js.map +1 -0
- package/dist/src/dlp/patterns.d.ts +9 -0
- package/dist/src/dlp/patterns.d.ts.map +1 -0
- package/dist/src/dlp/patterns.js +25 -0
- package/dist/src/dlp/patterns.js.map +1 -0
- package/dist/src/dlp/prompt-injection-backend.d.ts +68 -0
- package/dist/src/dlp/prompt-injection-backend.d.ts.map +1 -0
- package/dist/src/dlp/prompt-injection-backend.js +148 -0
- package/dist/src/dlp/prompt-injection-backend.js.map +1 -0
- package/dist/src/dlp/prompt-injection-patterns.d.ts +32 -0
- package/dist/src/dlp/prompt-injection-patterns.d.ts.map +1 -0
- package/dist/src/dlp/prompt-injection-patterns.js +290 -0
- package/dist/src/dlp/prompt-injection-patterns.js.map +1 -0
- package/dist/src/dlp/regex-backend.d.ts +32 -0
- package/dist/src/dlp/regex-backend.d.ts.map +1 -0
- package/dist/src/dlp/regex-backend.js +153 -0
- package/dist/src/dlp/regex-backend.js.map +1 -0
- package/dist/src/dlp/scanner.d.ts +122 -0
- package/dist/src/dlp/scanner.d.ts.map +1 -0
- package/dist/src/dlp/scanner.js +444 -0
- package/dist/src/dlp/scanner.js.map +1 -0
- package/dist/src/dlp/text-normalizer.d.ts +41 -0
- package/dist/src/dlp/text-normalizer.d.ts.map +1 -0
- package/dist/src/dlp/text-normalizer.js +203 -0
- package/dist/src/dlp/text-normalizer.js.map +1 -0
- package/dist/src/dlp/trufflehog-backend.d.ts +64 -0
- package/dist/src/dlp/trufflehog-backend.d.ts.map +1 -0
- package/dist/src/dlp/trufflehog-backend.js +151 -0
- package/dist/src/dlp/trufflehog-backend.js.map +1 -0
- package/dist/src/executor/http-executor.d.ts +25 -0
- package/dist/src/executor/http-executor.d.ts.map +1 -0
- package/dist/src/executor/http-executor.js +333 -0
- package/dist/src/executor/http-executor.js.map +1 -0
- package/dist/src/executor/index.d.ts +6 -0
- package/dist/src/executor/index.d.ts.map +1 -0
- package/dist/src/executor/index.js +12 -0
- package/dist/src/executor/index.js.map +1 -0
- package/dist/src/executor/interfaces.d.ts +11 -0
- package/dist/src/executor/interfaces.d.ts.map +1 -0
- package/dist/src/executor/interfaces.js +3 -0
- package/dist/src/executor/interfaces.js.map +1 -0
- package/dist/src/executor/noop-executor.d.ts +13 -0
- package/dist/src/executor/noop-executor.d.ts.map +1 -0
- package/dist/src/executor/noop-executor.js +21 -0
- package/dist/src/executor/noop-executor.js.map +1 -0
- package/dist/src/executor/registry.d.ts +30 -0
- package/dist/src/executor/registry.d.ts.map +1 -0
- package/dist/src/executor/registry.js +62 -0
- package/dist/src/executor/registry.js.map +1 -0
- package/dist/src/executor/slack-executor.d.ts +24 -0
- package/dist/src/executor/slack-executor.d.ts.map +1 -0
- package/dist/src/executor/slack-executor.js +147 -0
- package/dist/src/executor/slack-executor.js.map +1 -0
- package/dist/src/index.d.ts +25 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +74 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/auth-verifier.d.ts +23 -0
- package/dist/src/mcp/auth-verifier.d.ts.map +1 -0
- package/dist/src/mcp/auth-verifier.js +162 -0
- package/dist/src/mcp/auth-verifier.js.map +1 -0
- package/dist/src/mcp/bridge.d.ts +132 -0
- package/dist/src/mcp/bridge.d.ts.map +1 -0
- package/dist/src/mcp/bridge.js +734 -0
- package/dist/src/mcp/bridge.js.map +1 -0
- package/dist/src/mcp/http-transport.d.ts +32 -0
- package/dist/src/mcp/http-transport.d.ts.map +1 -0
- package/dist/src/mcp/http-transport.js +538 -0
- package/dist/src/mcp/http-transport.js.map +1 -0
- package/dist/src/mcp/index.d.ts +10 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +17 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/oauth-pages.d.ts +23 -0
- package/dist/src/mcp/oauth-pages.d.ts.map +1 -0
- package/dist/src/mcp/oauth-pages.js +121 -0
- package/dist/src/mcp/oauth-pages.js.map +1 -0
- package/dist/src/mcp/oauth-postgres-stores.d.ts +55 -0
- package/dist/src/mcp/oauth-postgres-stores.d.ts.map +1 -0
- package/dist/src/mcp/oauth-postgres-stores.js +226 -0
- package/dist/src/mcp/oauth-postgres-stores.js.map +1 -0
- package/dist/src/mcp/oauth-provider.d.ts +95 -0
- package/dist/src/mcp/oauth-provider.d.ts.map +1 -0
- package/dist/src/mcp/oauth-provider.js +360 -0
- package/dist/src/mcp/oauth-provider.js.map +1 -0
- package/dist/src/mcp/oauth-stores.d.ts +62 -0
- package/dist/src/mcp/oauth-stores.d.ts.map +1 -0
- package/dist/src/mcp/oauth-stores.js +154 -0
- package/dist/src/mcp/oauth-stores.js.map +1 -0
- package/dist/src/mcp/server.d.ts +18 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +51 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/metrics/collector.d.ts +106 -0
- package/dist/src/metrics/collector.d.ts.map +1 -0
- package/dist/src/metrics/collector.js +311 -0
- package/dist/src/metrics/collector.js.map +1 -0
- package/dist/src/metrics/index.d.ts +2 -0
- package/dist/src/metrics/index.d.ts.map +1 -0
- package/dist/src/metrics/index.js +6 -0
- package/dist/src/metrics/index.js.map +1 -0
- package/dist/src/middleware/auth.d.ts +77 -0
- package/dist/src/middleware/auth.d.ts.map +1 -0
- package/dist/src/middleware/auth.js +720 -0
- package/dist/src/middleware/auth.js.map +1 -0
- package/dist/src/middleware/session.d.ts +18 -0
- package/dist/src/middleware/session.d.ts.map +1 -0
- package/dist/src/middleware/session.js +67 -0
- package/dist/src/middleware/session.js.map +1 -0
- package/dist/src/middleware/validate.d.ts +3 -0
- package/dist/src/middleware/validate.d.ts.map +1 -0
- package/dist/src/middleware/validate.js +85 -0
- package/dist/src/middleware/validate.js.map +1 -0
- package/dist/src/policy/engine.d.ts +107 -0
- package/dist/src/policy/engine.d.ts.map +1 -0
- package/dist/src/policy/engine.js +646 -0
- package/dist/src/policy/engine.js.map +1 -0
- package/dist/src/policy/index.d.ts +3 -0
- package/dist/src/policy/index.d.ts.map +1 -0
- package/dist/src/policy/index.js +8 -0
- package/dist/src/policy/index.js.map +1 -0
- package/dist/src/policy/opa-engine.d.ts +176 -0
- package/dist/src/policy/opa-engine.d.ts.map +1 -0
- package/dist/src/policy/opa-engine.js +790 -0
- package/dist/src/policy/opa-engine.js.map +1 -0
- package/dist/src/proxy/forward-proxy.d.ts +30 -0
- package/dist/src/proxy/forward-proxy.d.ts.map +1 -0
- package/dist/src/proxy/forward-proxy.js +580 -0
- package/dist/src/proxy/forward-proxy.js.map +1 -0
- package/dist/src/proxy/index.d.ts +2 -0
- package/dist/src/proxy/index.d.ts.map +1 -0
- package/dist/src/proxy/index.js +8 -0
- package/dist/src/proxy/index.js.map +1 -0
- package/dist/src/ratelimit/limiter.d.ts +45 -0
- package/dist/src/ratelimit/limiter.d.ts.map +1 -0
- package/dist/src/ratelimit/limiter.js +158 -0
- package/dist/src/ratelimit/limiter.js.map +1 -0
- package/dist/src/replay/engine.d.ts +40 -0
- package/dist/src/replay/engine.d.ts.map +1 -0
- package/dist/src/replay/engine.js +106 -0
- package/dist/src/replay/engine.js.map +1 -0
- package/dist/src/replay/index.d.ts +2 -0
- package/dist/src/replay/index.d.ts.map +1 -0
- package/dist/src/replay/index.js +6 -0
- package/dist/src/replay/index.js.map +1 -0
- package/dist/src/saas/index.d.ts +2 -0
- package/dist/src/saas/index.d.ts.map +1 -0
- package/dist/src/saas/index.js +18 -0
- package/dist/src/saas/index.js.map +1 -0
- package/dist/src/saas/routes.d.ts +18 -0
- package/dist/src/saas/routes.d.ts.map +1 -0
- package/dist/src/saas/routes.js +1566 -0
- package/dist/src/saas/routes.js.map +1 -0
- package/dist/src/server/app.d.ts +44 -0
- package/dist/src/server/app.d.ts.map +1 -0
- package/dist/src/server/app.js +854 -0
- package/dist/src/server/app.js.map +1 -0
- package/dist/src/server/errors.d.ts +32 -0
- package/dist/src/server/errors.d.ts.map +1 -0
- package/dist/src/server/errors.js +39 -0
- package/dist/src/server/errors.js.map +1 -0
- package/dist/src/server/gateway.d.ts +165 -0
- package/dist/src/server/gateway.d.ts.map +1 -0
- package/dist/src/server/gateway.js +964 -0
- package/dist/src/server/gateway.js.map +1 -0
- package/dist/src/server/index.d.ts +2 -0
- package/dist/src/server/index.d.ts.map +1 -0
- package/dist/src/server/index.js +295 -0
- package/dist/src/server/index.js.map +1 -0
- package/dist/src/server/logger.d.ts +33 -0
- package/dist/src/server/logger.d.ts.map +1 -0
- package/dist/src/server/logger.js +230 -0
- package/dist/src/server/logger.js.map +1 -0
- package/dist/src/server/stream-proxy.d.ts +32 -0
- package/dist/src/server/stream-proxy.d.ts.map +1 -0
- package/dist/src/server/stream-proxy.js +184 -0
- package/dist/src/server/stream-proxy.js.map +1 -0
- package/dist/src/storage/file-persistence.d.ts +48 -0
- package/dist/src/storage/file-persistence.d.ts.map +1 -0
- package/dist/src/storage/file-persistence.js +280 -0
- package/dist/src/storage/file-persistence.js.map +1 -0
- package/dist/src/storage/index.d.ts +5 -0
- package/dist/src/storage/index.d.ts.map +1 -0
- package/dist/src/storage/index.js +21 -0
- package/dist/src/storage/index.js.map +1 -0
- package/dist/src/storage/interfaces.d.ts +237 -0
- package/dist/src/storage/interfaces.d.ts.map +1 -0
- package/dist/src/storage/interfaces.js +3 -0
- package/dist/src/storage/interfaces.js.map +1 -0
- package/dist/src/storage/memory.d.ts +162 -0
- package/dist/src/storage/memory.d.ts.map +1 -0
- package/dist/src/storage/memory.js +603 -0
- package/dist/src/storage/memory.js.map +1 -0
- package/dist/src/storage/postgres.d.ts +267 -0
- package/dist/src/storage/postgres.d.ts.map +1 -0
- package/dist/src/storage/postgres.js +1555 -0
- package/dist/src/storage/postgres.js.map +1 -0
- package/dist/src/storage/redis.d.ts +202 -0
- package/dist/src/storage/redis.d.ts.map +1 -0
- package/dist/src/storage/redis.js +629 -0
- package/dist/src/storage/redis.js.map +1 -0
- package/dist/src/tracing/index.d.ts +2 -0
- package/dist/src/tracing/index.d.ts.map +1 -0
- package/dist/src/tracing/index.js +6 -0
- package/dist/src/tracing/index.js.map +1 -0
- package/dist/src/tracing/provider.d.ts +43 -0
- package/dist/src/tracing/provider.d.ts.map +1 -0
- package/dist/src/tracing/provider.js +74 -0
- package/dist/src/tracing/provider.js.map +1 -0
- package/dist/src/trust/calculator.d.ts +54 -0
- package/dist/src/trust/calculator.d.ts.map +1 -0
- package/dist/src/trust/calculator.js +102 -0
- package/dist/src/trust/calculator.js.map +1 -0
- package/dist/src/trust/index.d.ts +2 -0
- package/dist/src/trust/index.d.ts.map +1 -0
- package/dist/src/trust/index.js +7 -0
- package/dist/src/trust/index.js.map +1 -0
- package/dist/src/types/budget.d.ts +30 -0
- package/dist/src/types/budget.d.ts.map +1 -0
- package/dist/src/types/budget.js +3 -0
- package/dist/src/types/budget.js.map +1 -0
- package/dist/src/types/config.d.ts +176 -0
- package/dist/src/types/config.d.ts.map +1 -0
- package/dist/src/types/config.js +3 -0
- package/dist/src/types/config.js.map +1 -0
- package/dist/src/types/events.d.ts +24 -0
- package/dist/src/types/events.d.ts.map +1 -0
- package/dist/src/types/events.js +3 -0
- package/dist/src/types/events.js.map +1 -0
- package/dist/src/types/index.d.ts +8 -0
- package/dist/src/types/index.d.ts.map +1 -0
- package/dist/src/types/index.js +24 -0
- package/dist/src/types/index.js.map +1 -0
- package/dist/src/types/policy.d.ts +60 -0
- package/dist/src/types/policy.d.ts.map +1 -0
- package/dist/src/types/policy.js +3 -0
- package/dist/src/types/policy.js.map +1 -0
- package/dist/src/types/stripe-config.d.ts +12 -0
- package/dist/src/types/stripe-config.d.ts.map +1 -0
- package/dist/src/types/stripe-config.js +3 -0
- package/dist/src/types/stripe-config.js.map +1 -0
- package/dist/src/types/subscription.d.ts +24 -0
- package/dist/src/types/subscription.d.ts.map +1 -0
- package/dist/src/types/subscription.js +38 -0
- package/dist/src/types/subscription.js.map +1 -0
- package/dist/src/types/tool-call.d.ts +42 -0
- package/dist/src/types/tool-call.d.ts.map +1 -0
- package/dist/src/types/tool-call.js +3 -0
- package/dist/src/types/tool-call.js.map +1 -0
- package/dist/src/types/tool-result.d.ts +58 -0
- package/dist/src/types/tool-result.d.ts.map +1 -0
- package/dist/src/types/tool-result.js +3 -0
- package/dist/src/types/tool-result.js.map +1 -0
- package/dist/src/types/user.d.ts +101 -0
- package/dist/src/types/user.d.ts.map +1 -0
- package/dist/src/types/user.js +6 -0
- package/dist/src/types/user.js.map +1 -0
- package/dist/tests/integration/api.test.d.ts +2 -0
- package/dist/tests/integration/api.test.d.ts.map +1 -0
- package/dist/tests/integration/api.test.js +1199 -0
- package/dist/tests/integration/api.test.js.map +1 -0
- package/dist/tests/integration/proxy.test.d.ts +2 -0
- package/dist/tests/integration/proxy.test.d.ts.map +1 -0
- package/dist/tests/integration/proxy.test.js +251 -0
- package/dist/tests/integration/proxy.test.js.map +1 -0
- package/dist/tests/integration/storage.test.d.ts +16 -0
- package/dist/tests/integration/storage.test.d.ts.map +1 -0
- package/dist/tests/integration/storage.test.js +826 -0
- package/dist/tests/integration/storage.test.js.map +1 -0
- package/dist/tests/unit/admin.test.d.ts +2 -0
- package/dist/tests/unit/admin.test.d.ts.map +1 -0
- package/dist/tests/unit/admin.test.js +698 -0
- package/dist/tests/unit/admin.test.js.map +1 -0
- package/dist/tests/unit/anomaly-detector.test.d.ts +2 -0
- package/dist/tests/unit/anomaly-detector.test.d.ts.map +1 -0
- package/dist/tests/unit/anomaly-detector.test.js +903 -0
- package/dist/tests/unit/anomaly-detector.test.js.map +1 -0
- package/dist/tests/unit/approval-manager.test.d.ts +2 -0
- package/dist/tests/unit/approval-manager.test.d.ts.map +1 -0
- package/dist/tests/unit/approval-manager.test.js +528 -0
- package/dist/tests/unit/approval-manager.test.js.map +1 -0
- package/dist/tests/unit/approval-webhook.test.d.ts +2 -0
- package/dist/tests/unit/approval-webhook.test.d.ts.map +1 -0
- package/dist/tests/unit/approval-webhook.test.js +355 -0
- package/dist/tests/unit/approval-webhook.test.js.map +1 -0
- package/dist/tests/unit/audit-logger.test.d.ts +2 -0
- package/dist/tests/unit/audit-logger.test.d.ts.map +1 -0
- package/dist/tests/unit/audit-logger.test.js +635 -0
- package/dist/tests/unit/audit-logger.test.js.map +1 -0
- package/dist/tests/unit/auth-routes.test.d.ts +2 -0
- package/dist/tests/unit/auth-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/auth-routes.test.js +281 -0
- package/dist/tests/unit/auth-routes.test.js.map +1 -0
- package/dist/tests/unit/auth.test.d.ts +2 -0
- package/dist/tests/unit/auth.test.d.ts.map +1 -0
- package/dist/tests/unit/auth.test.js +1382 -0
- package/dist/tests/unit/auth.test.js.map +1 -0
- package/dist/tests/unit/billing.test.d.ts +2 -0
- package/dist/tests/unit/billing.test.d.ts.map +1 -0
- package/dist/tests/unit/billing.test.js +579 -0
- package/dist/tests/unit/billing.test.js.map +1 -0
- package/dist/tests/unit/budget-manager.test.d.ts +2 -0
- package/dist/tests/unit/budget-manager.test.d.ts.map +1 -0
- package/dist/tests/unit/budget-manager.test.js +778 -0
- package/dist/tests/unit/budget-manager.test.js.map +1 -0
- package/dist/tests/unit/budget-race.test.d.ts +2 -0
- package/dist/tests/unit/budget-race.test.d.ts.map +1 -0
- package/dist/tests/unit/budget-race.test.js +58 -0
- package/dist/tests/unit/budget-race.test.js.map +1 -0
- package/dist/tests/unit/cli.test.d.ts +2 -0
- package/dist/tests/unit/cli.test.d.ts.map +1 -0
- package/dist/tests/unit/cli.test.js +93 -0
- package/dist/tests/unit/cli.test.js.map +1 -0
- package/dist/tests/unit/concurrency.test.d.ts +2 -0
- package/dist/tests/unit/concurrency.test.d.ts.map +1 -0
- package/dist/tests/unit/concurrency.test.js +1270 -0
- package/dist/tests/unit/concurrency.test.js.map +1 -0
- package/dist/tests/unit/config-validate.test.d.ts +2 -0
- package/dist/tests/unit/config-validate.test.d.ts.map +1 -0
- package/dist/tests/unit/config-validate.test.js +230 -0
- package/dist/tests/unit/config-validate.test.js.map +1 -0
- package/dist/tests/unit/defaults.test.d.ts +2 -0
- package/dist/tests/unit/defaults.test.d.ts.map +1 -0
- package/dist/tests/unit/defaults.test.js +364 -0
- package/dist/tests/unit/defaults.test.js.map +1 -0
- package/dist/tests/unit/dlp-backends.test.d.ts +2 -0
- package/dist/tests/unit/dlp-backends.test.d.ts.map +1 -0
- package/dist/tests/unit/dlp-backends.test.js +563 -0
- package/dist/tests/unit/dlp-backends.test.js.map +1 -0
- package/dist/tests/unit/dlp-scanner.test.d.ts +2 -0
- package/dist/tests/unit/dlp-scanner.test.d.ts.map +1 -0
- package/dist/tests/unit/dlp-scanner.test.js +739 -0
- package/dist/tests/unit/dlp-scanner.test.js.map +1 -0
- package/dist/tests/unit/error-responses.test.d.ts +2 -0
- package/dist/tests/unit/error-responses.test.d.ts.map +1 -0
- package/dist/tests/unit/error-responses.test.js +101 -0
- package/dist/tests/unit/error-responses.test.js.map +1 -0
- package/dist/tests/unit/executor-registry.test.d.ts +2 -0
- package/dist/tests/unit/executor-registry.test.d.ts.map +1 -0
- package/dist/tests/unit/executor-registry.test.js +390 -0
- package/dist/tests/unit/executor-registry.test.js.map +1 -0
- package/dist/tests/unit/forward-proxy.test.d.ts +2 -0
- package/dist/tests/unit/forward-proxy.test.d.ts.map +1 -0
- package/dist/tests/unit/forward-proxy.test.js +621 -0
- package/dist/tests/unit/forward-proxy.test.js.map +1 -0
- package/dist/tests/unit/gateway-features.test.d.ts +2 -0
- package/dist/tests/unit/gateway-features.test.d.ts.map +1 -0
- package/dist/tests/unit/gateway-features.test.js +753 -0
- package/dist/tests/unit/gateway-features.test.js.map +1 -0
- package/dist/tests/unit/http-executor.test.d.ts +2 -0
- package/dist/tests/unit/http-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/http-executor.test.js +310 -0
- package/dist/tests/unit/http-executor.test.js.map +1 -0
- package/dist/tests/unit/mcp-bridge.test.d.ts +2 -0
- package/dist/tests/unit/mcp-bridge.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-bridge.test.js +1136 -0
- package/dist/tests/unit/mcp-bridge.test.js.map +1 -0
- package/dist/tests/unit/mcp-http-transport.test.d.ts +2 -0
- package/dist/tests/unit/mcp-http-transport.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-http-transport.test.js +899 -0
- package/dist/tests/unit/mcp-http-transport.test.js.map +1 -0
- package/dist/tests/unit/mcp-oauth.test.d.ts +2 -0
- package/dist/tests/unit/mcp-oauth.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-oauth.test.js +759 -0
- package/dist/tests/unit/mcp-oauth.test.js.map +1 -0
- package/dist/tests/unit/mcp-server.test.d.ts +15 -0
- package/dist/tests/unit/mcp-server.test.d.ts.map +1 -0
- package/dist/tests/unit/mcp-server.test.js +158 -0
- package/dist/tests/unit/mcp-server.test.js.map +1 -0
- package/dist/tests/unit/metrics.test.d.ts +2 -0
- package/dist/tests/unit/metrics.test.d.ts.map +1 -0
- package/dist/tests/unit/metrics.test.js +208 -0
- package/dist/tests/unit/metrics.test.js.map +1 -0
- package/dist/tests/unit/oauth.test.d.ts +2 -0
- package/dist/tests/unit/oauth.test.d.ts.map +1 -0
- package/dist/tests/unit/oauth.test.js +281 -0
- package/dist/tests/unit/oauth.test.js.map +1 -0
- package/dist/tests/unit/opa-circuit-breaker.test.d.ts +2 -0
- package/dist/tests/unit/opa-circuit-breaker.test.d.ts.map +1 -0
- package/dist/tests/unit/opa-circuit-breaker.test.js +297 -0
- package/dist/tests/unit/opa-circuit-breaker.test.js.map +1 -0
- package/dist/tests/unit/opa-engine.test.d.ts +2 -0
- package/dist/tests/unit/opa-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/opa-engine.test.js +1813 -0
- package/dist/tests/unit/opa-engine.test.js.map +1 -0
- package/dist/tests/unit/pipeline-timing.test.d.ts +2 -0
- package/dist/tests/unit/pipeline-timing.test.d.ts.map +1 -0
- package/dist/tests/unit/pipeline-timing.test.js +528 -0
- package/dist/tests/unit/pipeline-timing.test.js.map +1 -0
- package/dist/tests/unit/policy-engine.test.d.ts +2 -0
- package/dist/tests/unit/policy-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/policy-engine.test.js +1345 -0
- package/dist/tests/unit/policy-engine.test.js.map +1 -0
- package/dist/tests/unit/policy-store.test.d.ts +2 -0
- package/dist/tests/unit/policy-store.test.d.ts.map +1 -0
- package/dist/tests/unit/policy-store.test.js +60 -0
- package/dist/tests/unit/policy-store.test.js.map +1 -0
- package/dist/tests/unit/postgres-storage.test.d.ts +2 -0
- package/dist/tests/unit/postgres-storage.test.d.ts.map +1 -0
- package/dist/tests/unit/postgres-storage.test.js +614 -0
- package/dist/tests/unit/postgres-storage.test.js.map +1 -0
- package/dist/tests/unit/prompt-injection-backend.test.d.ts +2 -0
- package/dist/tests/unit/prompt-injection-backend.test.d.ts.map +1 -0
- package/dist/tests/unit/prompt-injection-backend.test.js +621 -0
- package/dist/tests/unit/prompt-injection-backend.test.js.map +1 -0
- package/dist/tests/unit/proxy-hardening.test.d.ts +2 -0
- package/dist/tests/unit/proxy-hardening.test.d.ts.map +1 -0
- package/dist/tests/unit/proxy-hardening.test.js +166 -0
- package/dist/tests/unit/proxy-hardening.test.js.map +1 -0
- package/dist/tests/unit/rate-limiter.test.d.ts +2 -0
- package/dist/tests/unit/rate-limiter.test.d.ts.map +1 -0
- package/dist/tests/unit/rate-limiter.test.js +443 -0
- package/dist/tests/unit/rate-limiter.test.js.map +1 -0
- package/dist/tests/unit/redis-storage.test.d.ts +2 -0
- package/dist/tests/unit/redis-storage.test.d.ts.map +1 -0
- package/dist/tests/unit/redis-storage.test.js +766 -0
- package/dist/tests/unit/redis-storage.test.js.map +1 -0
- package/dist/tests/unit/replay-engine.test.d.ts +2 -0
- package/dist/tests/unit/replay-engine.test.d.ts.map +1 -0
- package/dist/tests/unit/replay-engine.test.js +371 -0
- package/dist/tests/unit/replay-engine.test.js.map +1 -0
- package/dist/tests/unit/saas-routes.test.d.ts +2 -0
- package/dist/tests/unit/saas-routes.test.d.ts.map +1 -0
- package/dist/tests/unit/saas-routes.test.js +1399 -0
- package/dist/tests/unit/saas-routes.test.js.map +1 -0
- package/dist/tests/unit/session.test.d.ts +2 -0
- package/dist/tests/unit/session.test.d.ts.map +1 -0
- package/dist/tests/unit/session.test.js +532 -0
- package/dist/tests/unit/session.test.js.map +1 -0
- package/dist/tests/unit/slack-executor.test.d.ts +2 -0
- package/dist/tests/unit/slack-executor.test.d.ts.map +1 -0
- package/dist/tests/unit/slack-executor.test.js +209 -0
- package/dist/tests/unit/slack-executor.test.js.map +1 -0
- package/dist/tests/unit/storage-hardening.test.d.ts +2 -0
- package/dist/tests/unit/storage-hardening.test.d.ts.map +1 -0
- package/dist/tests/unit/storage-hardening.test.js +165 -0
- package/dist/tests/unit/storage-hardening.test.js.map +1 -0
- package/dist/tests/unit/storage.test.d.ts +2 -0
- package/dist/tests/unit/storage.test.d.ts.map +1 -0
- package/dist/tests/unit/storage.test.js +698 -0
- package/dist/tests/unit/storage.test.js.map +1 -0
- package/dist/tests/unit/text-normalizer.test.d.ts +2 -0
- package/dist/tests/unit/text-normalizer.test.d.ts.map +1 -0
- package/dist/tests/unit/text-normalizer.test.js +229 -0
- package/dist/tests/unit/text-normalizer.test.js.map +1 -0
- package/dist/tests/unit/tracing.test.d.ts +2 -0
- package/dist/tests/unit/tracing.test.d.ts.map +1 -0
- package/dist/tests/unit/tracing.test.js +611 -0
- package/dist/tests/unit/tracing.test.js.map +1 -0
- package/dist/tests/unit/trust-calculator.test.d.ts +2 -0
- package/dist/tests/unit/trust-calculator.test.d.ts.map +1 -0
- package/dist/tests/unit/trust-calculator.test.js +497 -0
- package/dist/tests/unit/trust-calculator.test.js.map +1 -0
- package/dist/tests/unit/ts-sdk.test.d.ts +2 -0
- package/dist/tests/unit/ts-sdk.test.d.ts.map +1 -0
- package/dist/tests/unit/ts-sdk.test.js +421 -0
- package/dist/tests/unit/ts-sdk.test.js.map +1 -0
- package/dist/tests/unit/usage-extractor-llm.test.d.ts +2 -0
- package/dist/tests/unit/usage-extractor-llm.test.d.ts.map +1 -0
- package/dist/tests/unit/usage-extractor-llm.test.js +139 -0
- package/dist/tests/unit/usage-extractor-llm.test.js.map +1 -0
- package/dist/tests/unit/usage-extractor.test.d.ts +2 -0
- package/dist/tests/unit/usage-extractor.test.d.ts.map +1 -0
- package/dist/tests/unit/usage-extractor.test.js +271 -0
- package/dist/tests/unit/usage-extractor.test.js.map +1 -0
- package/dist/tests/unit/user-stores.test.d.ts +2 -0
- package/dist/tests/unit/user-stores.test.d.ts.map +1 -0
- package/dist/tests/unit/user-stores.test.js +687 -0
- package/dist/tests/unit/user-stores.test.js.map +1 -0
- package/dist/tests/unit/validate.test.d.ts +2 -0
- package/dist/tests/unit/validate.test.d.ts.map +1 -0
- package/dist/tests/unit/validate.test.js +545 -0
- package/dist/tests/unit/validate.test.js.map +1 -0
- package/package.json +86 -0
- package/policy-packs/README.md +42 -0
- package/policy-packs/default.yaml +46 -0
- package/policy-packs/dev_fast.yaml +54 -0
- package/policy-packs/prod_strict.yaml +83 -0
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.failedAuthAttempts = void 0;
|
|
40
|
+
exports.stopBruteForceCleanup = stopBruteForceCleanup;
|
|
41
|
+
exports.resolvePermissions = resolvePermissions;
|
|
42
|
+
exports.hasPermission = hasPermission;
|
|
43
|
+
exports.hashKeyWithSalt = hashKeyWithSalt;
|
|
44
|
+
exports.generateSalt = generateSalt;
|
|
45
|
+
exports.verifySaasKeyHash = verifySaasKeyHash;
|
|
46
|
+
exports.createSaltedKeyHash = createSaltedKeyHash;
|
|
47
|
+
exports.createAuthMiddleware = createAuthMiddleware;
|
|
48
|
+
exports.parseProxyAuth = parseProxyAuth;
|
|
49
|
+
exports.createRBACMiddleware = createRBACMiddleware;
|
|
50
|
+
const crypto = __importStar(require("crypto"));
|
|
51
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
52
|
+
const jwks_rsa_1 = __importDefault(require("jwks-rsa"));
|
|
53
|
+
const errors_1 = require("../server/errors");
|
|
54
|
+
const logger_1 = require("../server/logger");
|
|
55
|
+
const BRUTE_FORCE_WINDOW_MS = 15 * 60 * 1000; // 15 minutes
|
|
56
|
+
const BRUTE_FORCE_MAX_ATTEMPTS = 5;
|
|
57
|
+
const BRUTE_FORCE_CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
58
|
+
const BRUTE_FORCE_MAX_ENTRIES = 100000;
|
|
59
|
+
exports.failedAuthAttempts = new Map();
|
|
60
|
+
let bruteForceCleanupTimer = null;
|
|
61
|
+
function startBruteForceCleanup() {
|
|
62
|
+
if (bruteForceCleanupTimer)
|
|
63
|
+
return;
|
|
64
|
+
bruteForceCleanupTimer = setInterval(() => {
|
|
65
|
+
const now = Date.now();
|
|
66
|
+
for (const [ip, entry] of exports.failedAuthAttempts) {
|
|
67
|
+
if (now - entry.firstAttempt > BRUTE_FORCE_WINDOW_MS && entry.lockedUntil < now) {
|
|
68
|
+
exports.failedAuthAttempts.delete(ip);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}, BRUTE_FORCE_CLEANUP_INTERVAL_MS);
|
|
72
|
+
// Allow the process to exit even if the interval is running
|
|
73
|
+
if (bruteForceCleanupTimer && typeof bruteForceCleanupTimer === 'object' && 'unref' in bruteForceCleanupTimer) {
|
|
74
|
+
bruteForceCleanupTimer.unref();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function stopBruteForceCleanup() {
|
|
78
|
+
if (bruteForceCleanupTimer) {
|
|
79
|
+
clearInterval(bruteForceCleanupTimer);
|
|
80
|
+
bruteForceCleanupTimer = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function getClientIp(req, trustedProxies) {
|
|
84
|
+
const socketIp = req.socket?.remoteAddress || req.ip || 'unknown';
|
|
85
|
+
// Only trust X-Forwarded-For when the direct connection is from a trusted proxy
|
|
86
|
+
if (trustedProxies && trustedProxies.length > 0) {
|
|
87
|
+
const normalizedSocketIp = socketIp.replace(/^::ffff:/, '');
|
|
88
|
+
if (trustedProxies.includes(normalizedSocketIp)) {
|
|
89
|
+
const forwarded = req.headers['x-forwarded-for'];
|
|
90
|
+
if (typeof forwarded === 'string') {
|
|
91
|
+
return forwarded.split(',')[0].trim();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return socketIp;
|
|
96
|
+
}
|
|
97
|
+
function evictBruteForceEntries() {
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
const aggressiveWindowMs = 5 * 60 * 1000; // 5 minutes (stricter than normal 15 min)
|
|
100
|
+
// First pass: delete entries older than 5 minutes
|
|
101
|
+
for (const [ip, entry] of exports.failedAuthAttempts) {
|
|
102
|
+
if (now - entry.firstAttempt > aggressiveWindowMs && entry.lockedUntil < now) {
|
|
103
|
+
exports.failedAuthAttempts.delete(ip);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// If still over limit, delete the oldest half
|
|
107
|
+
if (exports.failedAuthAttempts.size > BRUTE_FORCE_MAX_ENTRIES) {
|
|
108
|
+
const entries = Array.from(exports.failedAuthAttempts.entries())
|
|
109
|
+
.sort((a, b) => a[1].firstAttempt - b[1].firstAttempt);
|
|
110
|
+
const toDelete = Math.ceil(entries.length / 2);
|
|
111
|
+
for (let i = 0; i < toDelete; i++) {
|
|
112
|
+
exports.failedAuthAttempts.delete(entries[i][0]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function recordAuthFailure(ip) {
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
// Cap map size to prevent unbounded memory growth
|
|
119
|
+
if (exports.failedAuthAttempts.size >= BRUTE_FORCE_MAX_ENTRIES) {
|
|
120
|
+
evictBruteForceEntries();
|
|
121
|
+
}
|
|
122
|
+
const entry = exports.failedAuthAttempts.get(ip);
|
|
123
|
+
if (!entry || now - entry.firstAttempt > BRUTE_FORCE_WINDOW_MS) {
|
|
124
|
+
// Start a new window
|
|
125
|
+
exports.failedAuthAttempts.set(ip, { count: 1, firstAttempt: now, lockedUntil: 0 });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
entry.count++;
|
|
129
|
+
if (entry.count >= BRUTE_FORCE_MAX_ATTEMPTS) {
|
|
130
|
+
// Exponential backoff: 1s, 2s, 4s, 8s, 16s for subsequent failures
|
|
131
|
+
const excessFailures = entry.count - BRUTE_FORCE_MAX_ATTEMPTS;
|
|
132
|
+
const backoffMs = Math.min(1000 * Math.pow(2, excessFailures), 16000);
|
|
133
|
+
entry.lockedUntil = now + backoffMs;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function clearAuthFailures(ip) {
|
|
137
|
+
exports.failedAuthAttempts.delete(ip);
|
|
138
|
+
}
|
|
139
|
+
function checkBruteForce(ip) {
|
|
140
|
+
const entry = exports.failedAuthAttempts.get(ip);
|
|
141
|
+
if (!entry)
|
|
142
|
+
return { locked: false };
|
|
143
|
+
const now = Date.now();
|
|
144
|
+
if (entry.lockedUntil > now) {
|
|
145
|
+
return { locked: true, retryAfterSec: Math.ceil((entry.lockedUntil - now) / 1000) };
|
|
146
|
+
}
|
|
147
|
+
return { locked: false };
|
|
148
|
+
}
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Helpers
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
/**
|
|
153
|
+
* Resolve a set of role names into a flat Permission[] array using the RBAC config.
|
|
154
|
+
*/
|
|
155
|
+
function resolvePermissions(roles, rbacConfig) {
|
|
156
|
+
if (!rbacConfig || !rbacConfig.enabled) {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
const permissionSet = new Set();
|
|
160
|
+
for (const roleName of roles) {
|
|
161
|
+
const roleDef = rbacConfig.roles[roleName];
|
|
162
|
+
if (roleDef) {
|
|
163
|
+
for (const p of roleDef.permissions) {
|
|
164
|
+
permissionSet.add(p);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return Array.from(permissionSet);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Check whether a given set of permissions satisfies a required permission.
|
|
172
|
+
* - `admin:full` always satisfies any permission.
|
|
173
|
+
* - `tool:execute` satisfies any `tool:execute:*` check.
|
|
174
|
+
*/
|
|
175
|
+
function hasPermission(permissions, required) {
|
|
176
|
+
if (permissions.includes('admin:full'))
|
|
177
|
+
return true;
|
|
178
|
+
if (permissions.includes(required))
|
|
179
|
+
return true;
|
|
180
|
+
// tool:execute grants all tool:execute:* sub-permissions
|
|
181
|
+
if (required.startsWith('tool:execute:') && permissions.includes('tool:execute')) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// JWKS signing key retrieval (cached per jwks_uri)
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
const jwksClients = new Map();
|
|
190
|
+
function getJwksClient(uri) {
|
|
191
|
+
let client = jwksClients.get(uri);
|
|
192
|
+
if (!client) {
|
|
193
|
+
client = new jwks_rsa_1.default.JwksClient({
|
|
194
|
+
jwksUri: uri,
|
|
195
|
+
cache: true,
|
|
196
|
+
cacheMaxAge: 600000, // 10 min
|
|
197
|
+
rateLimit: true,
|
|
198
|
+
jwksRequestsPerMinute: 10,
|
|
199
|
+
});
|
|
200
|
+
jwksClients.set(uri, client);
|
|
201
|
+
}
|
|
202
|
+
return client;
|
|
203
|
+
}
|
|
204
|
+
function getJwksSigningKey(uri, header) {
|
|
205
|
+
const client = getJwksClient(uri);
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
client.getSigningKey(header.kid, (err, key) => {
|
|
208
|
+
if (err)
|
|
209
|
+
return reject(err);
|
|
210
|
+
if (!key)
|
|
211
|
+
return reject(new Error('No signing key found'));
|
|
212
|
+
const signingKey = key.getPublicKey();
|
|
213
|
+
resolve(signingKey);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// JWT verification
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
async function verifyJWT(token, config) {
|
|
221
|
+
const jwtConfig = config.jwt;
|
|
222
|
+
if (!jwtConfig || !jwtConfig.enabled) {
|
|
223
|
+
return { error: 'JWT authentication is not configured' };
|
|
224
|
+
}
|
|
225
|
+
// Determine the secret or key
|
|
226
|
+
// IMPORTANT: Prevent algorithm confusion attacks by restricting algorithms
|
|
227
|
+
// based on the verification method. Never allow symmetric (HS*) algorithms
|
|
228
|
+
// when using JWKS (asymmetric keys), and vice versa.
|
|
229
|
+
if (jwtConfig.jwks_uri) {
|
|
230
|
+
// JWKS-based: only asymmetric algorithms allowed
|
|
231
|
+
const ASYMMETRIC_ALGOS = ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'PS256', 'PS384', 'PS512'];
|
|
232
|
+
const userAlgos = jwtConfig.algorithms;
|
|
233
|
+
const algorithms = userAlgos
|
|
234
|
+
? userAlgos.filter(a => ASYMMETRIC_ALGOS.includes(a))
|
|
235
|
+
: ['RS256'];
|
|
236
|
+
if (algorithms.length === 0) {
|
|
237
|
+
return { error: 'No valid asymmetric algorithms configured for JWKS verification' };
|
|
238
|
+
}
|
|
239
|
+
try {
|
|
240
|
+
const decoded = jsonwebtoken_1.default.decode(token, { complete: true });
|
|
241
|
+
if (!decoded || typeof decoded === 'string') {
|
|
242
|
+
return { error: 'Invalid JWT format' };
|
|
243
|
+
}
|
|
244
|
+
const signingKey = await getJwksSigningKey(jwtConfig.jwks_uri, decoded.header);
|
|
245
|
+
const payload = jsonwebtoken_1.default.verify(token, signingKey, {
|
|
246
|
+
algorithms,
|
|
247
|
+
issuer: jwtConfig.issuer,
|
|
248
|
+
audience: jwtConfig.audience,
|
|
249
|
+
});
|
|
250
|
+
return { payload };
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
const msg = err instanceof Error ? err.message : 'JWT verification failed';
|
|
254
|
+
return { error: msg };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else if (jwtConfig.secret) {
|
|
258
|
+
// Static secret: only symmetric algorithms allowed
|
|
259
|
+
const SYMMETRIC_ALGOS = ['HS256', 'HS384', 'HS512'];
|
|
260
|
+
const userAlgos = jwtConfig.algorithms;
|
|
261
|
+
const algorithms = userAlgos
|
|
262
|
+
? userAlgos.filter(a => SYMMETRIC_ALGOS.includes(a))
|
|
263
|
+
: ['HS256'];
|
|
264
|
+
if (algorithms.length === 0) {
|
|
265
|
+
return { error: 'No valid symmetric algorithms configured for secret-based verification' };
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
const payload = jsonwebtoken_1.default.verify(token, jwtConfig.secret, {
|
|
269
|
+
algorithms,
|
|
270
|
+
issuer: jwtConfig.issuer,
|
|
271
|
+
audience: jwtConfig.audience,
|
|
272
|
+
});
|
|
273
|
+
return { payload };
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
const msg = err instanceof Error ? err.message : 'JWT verification failed';
|
|
277
|
+
return { error: msg };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return { error: 'JWT auth enabled but no secret or jwks_uri configured' };
|
|
281
|
+
}
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Hash-based API key lookup helpers (constant-time, prevents timing attacks)
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
/** SHA-256 hash a key for constant-time lookup (legacy unsalted) */
|
|
286
|
+
function hashKey(key) {
|
|
287
|
+
return crypto.createHash('sha256').update(key).digest('hex');
|
|
288
|
+
}
|
|
289
|
+
/** SHA-256 hash a key with a salt */
|
|
290
|
+
function hashKeyWithSalt(key, salt) {
|
|
291
|
+
return crypto.createHash('sha256').update(salt + key).digest('hex');
|
|
292
|
+
}
|
|
293
|
+
/** Generate a 16-byte random salt as hex string */
|
|
294
|
+
function generateSalt() {
|
|
295
|
+
return crypto.randomBytes(16).toString('hex');
|
|
296
|
+
}
|
|
297
|
+
/** Dummy buffer used for constant-time comparison when no match is found */
|
|
298
|
+
const DUMMY_HASH = crypto.createHash('sha256').update('dummy-no-match-sentinel').digest('hex');
|
|
299
|
+
/**
|
|
300
|
+
* Build a hash map from config API keys for O(1) lookup.
|
|
301
|
+
* Maps SHA-256(key) -> original key string.
|
|
302
|
+
*/
|
|
303
|
+
function buildKeyHashMap(apiKeys) {
|
|
304
|
+
const map = new Map();
|
|
305
|
+
for (const key of Object.keys(apiKeys)) {
|
|
306
|
+
map.set(hashKey(key), key);
|
|
307
|
+
}
|
|
308
|
+
return map;
|
|
309
|
+
}
|
|
310
|
+
/** Cache for buildKeyHashMap to avoid rebuilding per-request (e.g. in proxy auth) */
|
|
311
|
+
const keyHashMapCache = new WeakMap();
|
|
312
|
+
/** Get or build a cached key hash map for the given api_keys config object */
|
|
313
|
+
function getCachedKeyHashMap(apiKeys) {
|
|
314
|
+
let cached = keyHashMapCache.get(apiKeys);
|
|
315
|
+
if (!cached) {
|
|
316
|
+
cached = buildKeyHashMap(apiKeys);
|
|
317
|
+
keyHashMapCache.set(apiKeys, cached);
|
|
318
|
+
}
|
|
319
|
+
return cached;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Look up an API key by its hash. Uses timingSafeEqual for final verification
|
|
323
|
+
* and always performs a comparison (even on miss) to prevent timing leaks.
|
|
324
|
+
*/
|
|
325
|
+
function lookupApiKey(token, apiKeys, keyHashMap) {
|
|
326
|
+
const tokenHash = hashKey(token);
|
|
327
|
+
const candidateKey = keyHashMap.get(tokenHash);
|
|
328
|
+
// Always do a timingSafeEqual comparison regardless of match to prevent
|
|
329
|
+
// timing leaks between "no keys match" vs "a key matched"
|
|
330
|
+
const a = Buffer.from(tokenHash);
|
|
331
|
+
const b = Buffer.from(candidateKey ? hashKey(candidateKey) : DUMMY_HASH);
|
|
332
|
+
const isMatch = crypto.timingSafeEqual(a, b);
|
|
333
|
+
return isMatch && candidateKey ? candidateKey : undefined;
|
|
334
|
+
}
|
|
335
|
+
// ---------------------------------------------------------------------------
|
|
336
|
+
// Salted SaaS key lookup (backward compatible with unsalted hashes)
|
|
337
|
+
// ---------------------------------------------------------------------------
|
|
338
|
+
/**
|
|
339
|
+
* Compute the hash for a token given a stored key_hash value.
|
|
340
|
+
* Supports salted format "salt:hash" and legacy unsalted plain hex format.
|
|
341
|
+
* Returns true if the token matches the stored hash.
|
|
342
|
+
*/
|
|
343
|
+
function verifySaasKeyHash(token, storedKeyHash) {
|
|
344
|
+
if (storedKeyHash.includes(':')) {
|
|
345
|
+
// Salted format: "salt:hash"
|
|
346
|
+
const colonIndex = storedKeyHash.indexOf(':');
|
|
347
|
+
const salt = storedKeyHash.slice(0, colonIndex);
|
|
348
|
+
const expectedHash = storedKeyHash.slice(colonIndex + 1);
|
|
349
|
+
const computedHash = hashKeyWithSalt(token, salt);
|
|
350
|
+
// Constant-time comparison
|
|
351
|
+
const a = Buffer.from(computedHash, 'hex');
|
|
352
|
+
const b = Buffer.from(expectedHash, 'hex');
|
|
353
|
+
if (a.length !== b.length)
|
|
354
|
+
return false;
|
|
355
|
+
return crypto.timingSafeEqual(a, b);
|
|
356
|
+
}
|
|
357
|
+
// Legacy unsalted format: plain hex
|
|
358
|
+
const computedHash = hashKey(token);
|
|
359
|
+
const a = Buffer.from(computedHash, 'hex');
|
|
360
|
+
const b = Buffer.from(storedKeyHash, 'hex');
|
|
361
|
+
if (a.length !== b.length)
|
|
362
|
+
return false;
|
|
363
|
+
return crypto.timingSafeEqual(a, b);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Create a salted key hash for storage. Returns "salt:hash" format.
|
|
367
|
+
*/
|
|
368
|
+
function createSaltedKeyHash(key) {
|
|
369
|
+
const salt = generateSalt();
|
|
370
|
+
const hash = hashKeyWithSalt(key, salt);
|
|
371
|
+
return `${salt}:${hash}`;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Look up a SaaS-generated API key. Uses verifyToken if available (supports
|
|
375
|
+
* both salted and unsalted hashes), otherwise falls back to unsalted hash lookup.
|
|
376
|
+
*/
|
|
377
|
+
function lookupSaasKey(token, store) {
|
|
378
|
+
// Prefer verifyToken which handles both salted and unsalted formats
|
|
379
|
+
if (store.verifyToken) {
|
|
380
|
+
return store.verifyToken(token);
|
|
381
|
+
}
|
|
382
|
+
// Fallback: legacy unsalted hash lookup
|
|
383
|
+
const unsaltedHash = hashKey(token);
|
|
384
|
+
return store.getByKeyHash(unsaltedHash);
|
|
385
|
+
}
|
|
386
|
+
// ---------------------------------------------------------------------------
|
|
387
|
+
// Main auth middleware
|
|
388
|
+
// ---------------------------------------------------------------------------
|
|
389
|
+
function createAuthMiddleware(config, userApiKeyStore) {
|
|
390
|
+
// Pre-build hash map for O(1) key lookup
|
|
391
|
+
const keyHashMap = buildKeyHashMap(config.api_keys);
|
|
392
|
+
// Start brute-force cleanup timer
|
|
393
|
+
startBruteForceCleanup();
|
|
394
|
+
return async (req, res, next) => {
|
|
395
|
+
// -----------------------------------------------------------------------
|
|
396
|
+
// Session auth: if session middleware already set req.auth, pass through
|
|
397
|
+
// -----------------------------------------------------------------------
|
|
398
|
+
if (req.auth) {
|
|
399
|
+
return next();
|
|
400
|
+
}
|
|
401
|
+
// -----------------------------------------------------------------------
|
|
402
|
+
// Auth disabled: pass through with defaults
|
|
403
|
+
// -----------------------------------------------------------------------
|
|
404
|
+
if (!config.enabled) {
|
|
405
|
+
const authCtx = {
|
|
406
|
+
workspace_id: 'ws_default',
|
|
407
|
+
actor_id: 'anonymous',
|
|
408
|
+
roles: [],
|
|
409
|
+
permissions: [],
|
|
410
|
+
auth_method: 'none',
|
|
411
|
+
};
|
|
412
|
+
req.auth = authCtx;
|
|
413
|
+
// Backward compat
|
|
414
|
+
req.workspace_id = authCtx.workspace_id;
|
|
415
|
+
return next();
|
|
416
|
+
}
|
|
417
|
+
// -----------------------------------------------------------------------
|
|
418
|
+
// Brute-force protection: check if IP is locked
|
|
419
|
+
// -----------------------------------------------------------------------
|
|
420
|
+
const clientIp = getClientIp(req, config.trusted_proxies);
|
|
421
|
+
const bruteForceCheck = checkBruteForce(clientIp);
|
|
422
|
+
if (bruteForceCheck.locked) {
|
|
423
|
+
res.setHeader('Retry-After', String(bruteForceCheck.retryAfterSec));
|
|
424
|
+
(0, errors_1.sendError)(res, 429, errors_1.ErrorCode.RATE_LIMIT_EXCEEDED, 'Too many failed authentication attempts. Try again later.', {
|
|
425
|
+
hint: `Retry after ${bruteForceCheck.retryAfterSec} seconds`,
|
|
426
|
+
});
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
// -----------------------------------------------------------------------
|
|
430
|
+
// Extract token from headers
|
|
431
|
+
// -----------------------------------------------------------------------
|
|
432
|
+
const apiKeyHeader = req.headers['x-api-key'];
|
|
433
|
+
const authHeader = req.headers['authorization'];
|
|
434
|
+
const bearerToken = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : undefined;
|
|
435
|
+
const token = apiKeyHeader || bearerToken;
|
|
436
|
+
if (!token) {
|
|
437
|
+
recordAuthFailure(clientIp);
|
|
438
|
+
(0, errors_1.sendError)(res, 401, errors_1.ErrorCode.AUTH_REQUIRED, 'Missing API key. Provide X-API-Key header or Bearer token.', {
|
|
439
|
+
hint: 'Provide X-API-Key header or Authorization: Bearer <token>',
|
|
440
|
+
});
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
// -----------------------------------------------------------------------
|
|
444
|
+
// Try API key auth first (hash-based lookup to prevent timing attacks)
|
|
445
|
+
// -----------------------------------------------------------------------
|
|
446
|
+
const matchedKey = lookupApiKey(token, config.api_keys, keyHashMap);
|
|
447
|
+
const keyConfig = matchedKey ? config.api_keys[matchedKey] : undefined;
|
|
448
|
+
if (keyConfig) {
|
|
449
|
+
// Check revocation
|
|
450
|
+
if (keyConfig.revoked) {
|
|
451
|
+
recordAuthFailure(clientIp);
|
|
452
|
+
(0, errors_1.sendError)(res, 403, errors_1.ErrorCode.AUTH_KEY_REVOKED, 'API key has been revoked.', {
|
|
453
|
+
hint: 'Generate a new key via /admin/api-keys',
|
|
454
|
+
});
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
// Check expiration
|
|
458
|
+
if (keyConfig.expires_at) {
|
|
459
|
+
const expiresAt = new Date(keyConfig.expires_at);
|
|
460
|
+
if (expiresAt.getTime() <= Date.now()) {
|
|
461
|
+
recordAuthFailure(clientIp);
|
|
462
|
+
(0, errors_1.sendError)(res, 403, errors_1.ErrorCode.AUTH_KEY_EXPIRED, 'API key has expired.', {
|
|
463
|
+
hint: 'Renew the key or create a new one via /admin/api-keys',
|
|
464
|
+
});
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
// Auth success: clear brute-force entry
|
|
469
|
+
clearAuthFailures(clientIp);
|
|
470
|
+
// Fire-and-forget: update last_used_at
|
|
471
|
+
keyConfig.last_used_at = new Date().toISOString();
|
|
472
|
+
// Resolve roles and permissions
|
|
473
|
+
const roles = keyConfig.roles || [];
|
|
474
|
+
const permissions = resolvePermissions(roles, config.rbac);
|
|
475
|
+
const authCtx = {
|
|
476
|
+
workspace_id: keyConfig.workspace_id,
|
|
477
|
+
actor_id: `apikey:${crypto.createHash('sha256').update(token).digest('hex').slice(0, 12)}`,
|
|
478
|
+
roles,
|
|
479
|
+
permissions,
|
|
480
|
+
auth_method: 'api_key',
|
|
481
|
+
api_key_id: token,
|
|
482
|
+
};
|
|
483
|
+
req.auth = authCtx;
|
|
484
|
+
// Backward compat
|
|
485
|
+
req.workspace_id = keyConfig.workspace_id;
|
|
486
|
+
req.api_key_description = keyConfig.description;
|
|
487
|
+
return next();
|
|
488
|
+
}
|
|
489
|
+
// -----------------------------------------------------------------------
|
|
490
|
+
// Try SaaS-generated API key (UserApiKeyStore, salted or legacy hash lookup)
|
|
491
|
+
// -----------------------------------------------------------------------
|
|
492
|
+
if (userApiKeyStore) {
|
|
493
|
+
const saasKey = lookupSaasKey(token, userApiKeyStore);
|
|
494
|
+
if (saasKey) {
|
|
495
|
+
if (saasKey.revoked) {
|
|
496
|
+
recordAuthFailure(clientIp);
|
|
497
|
+
(0, errors_1.sendError)(res, 403, errors_1.ErrorCode.AUTH_KEY_REVOKED, 'API key has been revoked.', {
|
|
498
|
+
hint: 'Generate a new key via /admin/api-keys',
|
|
499
|
+
});
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
// Auth success: clear brute-force entry
|
|
503
|
+
clearAuthFailures(clientIp);
|
|
504
|
+
// Fire-and-forget: update last_used_at
|
|
505
|
+
userApiKeyStore.update(saasKey.id, { last_used_at: new Date().toISOString() });
|
|
506
|
+
const roles = saasKey.roles || [];
|
|
507
|
+
const permissions = resolvePermissions(roles, config.rbac);
|
|
508
|
+
const authCtx = {
|
|
509
|
+
workspace_id: saasKey.workspace_id,
|
|
510
|
+
actor_id: `apikey:${crypto.createHash('sha256').update(token).digest('hex').slice(0, 12)}`,
|
|
511
|
+
roles,
|
|
512
|
+
permissions,
|
|
513
|
+
auth_method: 'api_key',
|
|
514
|
+
api_key_id: saasKey.id,
|
|
515
|
+
user_id: saasKey.user_id,
|
|
516
|
+
api_key_tags: saasKey.tags && saasKey.tags.length > 0 ? saasKey.tags : undefined,
|
|
517
|
+
};
|
|
518
|
+
req.auth = authCtx;
|
|
519
|
+
req.workspace_id = saasKey.workspace_id;
|
|
520
|
+
return next();
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// -----------------------------------------------------------------------
|
|
524
|
+
// Try JWT auth (only if bearer token and JWT is configured)
|
|
525
|
+
// -----------------------------------------------------------------------
|
|
526
|
+
if (bearerToken && config.jwt?.enabled) {
|
|
527
|
+
const result = await verifyJWT(bearerToken, config);
|
|
528
|
+
if ('error' in result) {
|
|
529
|
+
recordAuthFailure(clientIp);
|
|
530
|
+
(0, errors_1.sendError)(res, 401, errors_1.ErrorCode.AUTH_INVALID_KEY, `JWT verification failed: ${result.error}`, {
|
|
531
|
+
hint: 'Check that the JWT is properly signed and not expired',
|
|
532
|
+
});
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
// Auth success: clear brute-force entry
|
|
536
|
+
clearAuthFailures(clientIp);
|
|
537
|
+
const payload = result.payload;
|
|
538
|
+
const workspaceClaim = config.jwt.workspace_claim || 'workspace_id';
|
|
539
|
+
const rolesClaim = config.jwt.roles_claim || 'roles';
|
|
540
|
+
const actorClaim = config.jwt.actor_claim || 'sub';
|
|
541
|
+
const workspaceId = payload[workspaceClaim] || 'ws_default';
|
|
542
|
+
const roles = Array.isArray(payload[rolesClaim]) ? payload[rolesClaim] : [];
|
|
543
|
+
const actorId = payload[actorClaim] || 'unknown';
|
|
544
|
+
const permissions = resolvePermissions(roles, config.rbac);
|
|
545
|
+
const authCtx = {
|
|
546
|
+
workspace_id: workspaceId,
|
|
547
|
+
actor_id: actorId,
|
|
548
|
+
roles,
|
|
549
|
+
permissions,
|
|
550
|
+
auth_method: 'jwt',
|
|
551
|
+
};
|
|
552
|
+
req.auth = authCtx;
|
|
553
|
+
// Backward compat
|
|
554
|
+
req.workspace_id = workspaceId;
|
|
555
|
+
return next();
|
|
556
|
+
}
|
|
557
|
+
// -----------------------------------------------------------------------
|
|
558
|
+
// No valid auth found
|
|
559
|
+
// -----------------------------------------------------------------------
|
|
560
|
+
recordAuthFailure(clientIp);
|
|
561
|
+
(0, errors_1.sendError)(res, 403, errors_1.ErrorCode.AUTH_INVALID_KEY, 'Invalid API key.', {
|
|
562
|
+
hint: 'Check that the API key is correct and has not been revoked',
|
|
563
|
+
});
|
|
564
|
+
return;
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Parse proxy auth from an HTTP request's Proxy-Authorization header.
|
|
569
|
+
* Format: `Proxy-Authorization: Basic base64(workspace_id:api_key)`
|
|
570
|
+
*
|
|
571
|
+
* Falls back to sidecar defaults (env vars) if no header is present.
|
|
572
|
+
* Also checks X-Palaryn-Workspace and X-Palaryn-Actor headers as overrides.
|
|
573
|
+
*/
|
|
574
|
+
function parseProxyAuth(headers, proxyConfig, authConfig) {
|
|
575
|
+
const proxyAuthHeader = headers['proxy-authorization'];
|
|
576
|
+
const workspaceHeader = headers['x-palaryn-workspace'];
|
|
577
|
+
const actorHeader = headers['x-palaryn-actor'];
|
|
578
|
+
// Try Proxy-Authorization: Basic <base64>
|
|
579
|
+
if (proxyAuthHeader) {
|
|
580
|
+
const match = proxyAuthHeader.match(/^Basic\s+(.+)$/i);
|
|
581
|
+
if (!match) {
|
|
582
|
+
return { authenticated: false, error: 'Invalid Proxy-Authorization format. Expected: Basic <base64>' };
|
|
583
|
+
}
|
|
584
|
+
const decoded = Buffer.from(match[1], 'base64').toString('utf-8');
|
|
585
|
+
const colonIndex = decoded.indexOf(':');
|
|
586
|
+
if (colonIndex === -1) {
|
|
587
|
+
return { authenticated: false, error: 'Invalid Proxy-Authorization credentials. Expected: workspace_id:api_key' };
|
|
588
|
+
}
|
|
589
|
+
const workspaceId = decoded.slice(0, colonIndex);
|
|
590
|
+
const apiKey = decoded.slice(colonIndex + 1);
|
|
591
|
+
if (!workspaceId || !apiKey) {
|
|
592
|
+
return { authenticated: false, error: 'Empty workspace_id or api_key in Proxy-Authorization' };
|
|
593
|
+
}
|
|
594
|
+
// Validate API key against config (hash-based lookup to prevent timing attacks)
|
|
595
|
+
if (authConfig.enabled) {
|
|
596
|
+
const proxyKeyHashMap = getCachedKeyHashMap(authConfig.api_keys);
|
|
597
|
+
const matchedKey = lookupApiKey(apiKey, authConfig.api_keys, proxyKeyHashMap);
|
|
598
|
+
const keyConfig = matchedKey ? authConfig.api_keys[matchedKey] : undefined;
|
|
599
|
+
if (!keyConfig) {
|
|
600
|
+
return { authenticated: false, error: 'Invalid API key in Proxy-Authorization' };
|
|
601
|
+
}
|
|
602
|
+
if (keyConfig.revoked) {
|
|
603
|
+
return { authenticated: false, error: 'API key has been revoked' };
|
|
604
|
+
}
|
|
605
|
+
if (keyConfig.expires_at) {
|
|
606
|
+
const expiresAt = new Date(keyConfig.expires_at);
|
|
607
|
+
if (expiresAt.getTime() <= Date.now()) {
|
|
608
|
+
return { authenticated: false, error: 'API key has expired' };
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
keyConfig.last_used_at = new Date().toISOString();
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
authenticated: true,
|
|
615
|
+
workspace_id: workspaceId,
|
|
616
|
+
api_key: apiKey,
|
|
617
|
+
actor_id: actorHeader || `proxy:${workspaceId}`,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
// Fall back to sidecar defaults from config/env vars
|
|
621
|
+
if (proxyConfig.default_workspace_id) {
|
|
622
|
+
return {
|
|
623
|
+
authenticated: true,
|
|
624
|
+
workspace_id: workspaceHeader || proxyConfig.default_workspace_id,
|
|
625
|
+
actor_id: actorHeader || proxyConfig.default_actor_id || 'proxy:sidecar',
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
// Header-only auth (X-Palaryn-Workspace without Proxy-Authorization).
|
|
629
|
+
// Only allowed when auth is not required (permissive/dev mode).
|
|
630
|
+
// When require_auth is true, headers alone are not sufficient — they can be
|
|
631
|
+
// spoofed by any client. Require Proxy-Authorization or sidecar defaults.
|
|
632
|
+
if (workspaceHeader && !proxyConfig.require_auth) {
|
|
633
|
+
logger_1.logger.warn('Unauthenticated request using header-provided identity — headers can be spoofed, enable require_auth in production', { component: 'proxy-auth', workspace: workspaceHeader, actor: actorHeader || 'default' });
|
|
634
|
+
return {
|
|
635
|
+
authenticated: true,
|
|
636
|
+
workspace_id: workspaceHeader,
|
|
637
|
+
actor_id: actorHeader || `proxy:${workspaceHeader}`,
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
// If auth is not required (permissive mode), use defaults
|
|
641
|
+
if (!proxyConfig.require_auth) {
|
|
642
|
+
logger_1.logger.warn('Unauthenticated proxy request using default identity (ws_default/proxy:anonymous) — enable require_auth in production', { component: 'proxy-auth' });
|
|
643
|
+
return {
|
|
644
|
+
authenticated: true,
|
|
645
|
+
workspace_id: 'ws_default',
|
|
646
|
+
actor_id: 'proxy:anonymous',
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
authenticated: false,
|
|
651
|
+
error: 'Proxy-Authorization required. Use Basic auth with workspace_id:api_key or set PALARYN_WORKSPACE_ID env var for sidecar mode',
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
// ---------------------------------------------------------------------------
|
|
655
|
+
// RBAC middleware factory
|
|
656
|
+
// ---------------------------------------------------------------------------
|
|
657
|
+
function createRBACMiddleware(config, requiredPermission) {
|
|
658
|
+
return (req, res, next) => {
|
|
659
|
+
// Even with RBAC disabled, admin endpoints require the key to have an admin role
|
|
660
|
+
if (!config.rbac?.enabled) {
|
|
661
|
+
if (requiredPermission === 'admin:full') {
|
|
662
|
+
const authCtx = req.auth;
|
|
663
|
+
// Allow if auth is disabled (no keys configured) or if the key has admin role
|
|
664
|
+
if (authCtx && authCtx.auth_method !== 'none') {
|
|
665
|
+
const keyRoles = authCtx.roles || [];
|
|
666
|
+
const hasAdmin = keyRoles.includes('admin');
|
|
667
|
+
if (!hasAdmin) {
|
|
668
|
+
// Check if the key has explicit admin:full permission via configured roles
|
|
669
|
+
const rbacForResolve = config.rbac?.roles
|
|
670
|
+
? { ...config.rbac, enabled: true }
|
|
671
|
+
: { enabled: true, roles: { admin: { description: 'Admin', permissions: ['admin:full'] } }, default_role: 'agent' };
|
|
672
|
+
const keyPerms = resolvePermissions(keyRoles, rbacForResolve);
|
|
673
|
+
if (!hasPermission(keyPerms, 'admin:full')) {
|
|
674
|
+
(0, errors_1.sendError)(res, 403, errors_1.ErrorCode.AUTH_INSUFFICIENT_PERMS, 'Admin access required', {
|
|
675
|
+
hint: 'Use an API key with the admin role to access this endpoint',
|
|
676
|
+
});
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return next();
|
|
683
|
+
}
|
|
684
|
+
const authCtx = req.auth;
|
|
685
|
+
if (!authCtx) {
|
|
686
|
+
(0, errors_1.sendError)(res, 401, errors_1.ErrorCode.AUTH_REQUIRED, 'Authentication required', {
|
|
687
|
+
hint: 'Provide X-API-Key header or Authorization: Bearer <token>',
|
|
688
|
+
});
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
// Auth method 'none' (auth disabled) - allow through
|
|
692
|
+
if (authCtx.auth_method === 'none') {
|
|
693
|
+
return next();
|
|
694
|
+
}
|
|
695
|
+
// If user has no roles and a default_role is configured, apply it
|
|
696
|
+
let permissions = authCtx.permissions;
|
|
697
|
+
if (authCtx.roles.length === 0 && config.rbac.default_role) {
|
|
698
|
+
// Prevent escalation: default_role must not be admin or operator
|
|
699
|
+
let effectiveDefaultRole = config.rbac.default_role;
|
|
700
|
+
if (effectiveDefaultRole === 'admin' || effectiveDefaultRole === 'operator') {
|
|
701
|
+
logger_1.logger.warn(`default_role '${effectiveDefaultRole}' is a privileged role — falling back to 'readonly'`, { component: 'auth' });
|
|
702
|
+
effectiveDefaultRole = 'readonly';
|
|
703
|
+
}
|
|
704
|
+
const defaultPerms = resolvePermissions([effectiveDefaultRole], config.rbac);
|
|
705
|
+
permissions = defaultPerms;
|
|
706
|
+
// Also update the auth context with the default role's permissions
|
|
707
|
+
authCtx.roles = [effectiveDefaultRole];
|
|
708
|
+
authCtx.permissions = defaultPerms;
|
|
709
|
+
}
|
|
710
|
+
if (!hasPermission(permissions, requiredPermission)) {
|
|
711
|
+
(0, errors_1.sendError)(res, 403, errors_1.ErrorCode.AUTH_INSUFFICIENT_PERMS, `Insufficient permissions. Required: ${requiredPermission}`, {
|
|
712
|
+
details: { required_permission: requiredPermission, roles: authCtx.roles },
|
|
713
|
+
hint: 'Assign a role with the required permission or use an admin key',
|
|
714
|
+
});
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
return next();
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
//# sourceMappingURL=auth.js.map
|