ccproxy-api 0.1.7__py3-none-any.whl → 0.2.0__py3-none-any.whl
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.
- ccproxy/api/__init__.py +1 -15
- ccproxy/api/app.py +434 -219
- ccproxy/api/bootstrap.py +30 -0
- ccproxy/api/decorators.py +85 -0
- ccproxy/api/dependencies.py +144 -168
- ccproxy/api/format_validation.py +54 -0
- ccproxy/api/middleware/cors.py +6 -3
- ccproxy/api/middleware/errors.py +388 -524
- ccproxy/api/middleware/hooks.py +563 -0
- ccproxy/api/middleware/normalize_headers.py +59 -0
- ccproxy/api/middleware/request_id.py +35 -16
- ccproxy/api/middleware/streaming_hooks.py +292 -0
- ccproxy/api/routes/__init__.py +5 -14
- ccproxy/api/routes/health.py +39 -672
- ccproxy/api/routes/plugins.py +277 -0
- ccproxy/auth/__init__.py +2 -19
- ccproxy/auth/bearer.py +25 -15
- ccproxy/auth/dependencies.py +123 -157
- ccproxy/auth/exceptions.py +0 -12
- ccproxy/auth/manager.py +35 -49
- ccproxy/auth/managers/__init__.py +10 -0
- ccproxy/auth/managers/base.py +523 -0
- ccproxy/auth/managers/base_enhanced.py +63 -0
- ccproxy/auth/managers/token_snapshot.py +77 -0
- ccproxy/auth/models/base.py +65 -0
- ccproxy/auth/models/credentials.py +40 -0
- ccproxy/auth/oauth/__init__.py +4 -18
- ccproxy/auth/oauth/base.py +533 -0
- ccproxy/auth/oauth/cli_errors.py +37 -0
- ccproxy/auth/oauth/flows.py +430 -0
- ccproxy/auth/oauth/protocol.py +366 -0
- ccproxy/auth/oauth/registry.py +408 -0
- ccproxy/auth/oauth/router.py +396 -0
- ccproxy/auth/oauth/routes.py +186 -113
- ccproxy/auth/oauth/session.py +151 -0
- ccproxy/auth/oauth/templates.py +342 -0
- ccproxy/auth/storage/__init__.py +2 -5
- ccproxy/auth/storage/base.py +279 -5
- ccproxy/auth/storage/generic.py +134 -0
- ccproxy/cli/__init__.py +1 -2
- ccproxy/cli/_settings_help.py +351 -0
- ccproxy/cli/commands/auth.py +1519 -793
- ccproxy/cli/commands/config/commands.py +209 -276
- ccproxy/cli/commands/plugins.py +669 -0
- ccproxy/cli/commands/serve.py +75 -810
- ccproxy/cli/commands/status.py +254 -0
- ccproxy/cli/decorators.py +83 -0
- ccproxy/cli/helpers.py +22 -60
- ccproxy/cli/main.py +359 -10
- ccproxy/cli/options/claude_options.py +0 -25
- ccproxy/config/__init__.py +7 -11
- ccproxy/config/core.py +227 -0
- ccproxy/config/env_generator.py +232 -0
- ccproxy/config/runtime.py +67 -0
- ccproxy/config/security.py +36 -3
- ccproxy/config/settings.py +382 -441
- ccproxy/config/toml_generator.py +299 -0
- ccproxy/config/utils.py +452 -0
- ccproxy/core/__init__.py +7 -271
- ccproxy/{_version.py → core/_version.py} +16 -3
- ccproxy/core/async_task_manager.py +516 -0
- ccproxy/core/async_utils.py +47 -14
- ccproxy/core/auth/__init__.py +6 -0
- ccproxy/core/constants.py +16 -50
- ccproxy/core/errors.py +53 -0
- ccproxy/core/id_utils.py +20 -0
- ccproxy/core/interfaces.py +16 -123
- ccproxy/core/logging.py +473 -18
- ccproxy/core/plugins/__init__.py +77 -0
- ccproxy/core/plugins/cli_discovery.py +211 -0
- ccproxy/core/plugins/declaration.py +455 -0
- ccproxy/core/plugins/discovery.py +604 -0
- ccproxy/core/plugins/factories.py +967 -0
- ccproxy/core/plugins/hooks/__init__.py +30 -0
- ccproxy/core/plugins/hooks/base.py +58 -0
- ccproxy/core/plugins/hooks/events.py +46 -0
- ccproxy/core/plugins/hooks/implementations/__init__.py +16 -0
- ccproxy/core/plugins/hooks/implementations/formatters/__init__.py +11 -0
- ccproxy/core/plugins/hooks/implementations/formatters/json.py +552 -0
- ccproxy/core/plugins/hooks/implementations/formatters/raw.py +370 -0
- ccproxy/core/plugins/hooks/implementations/http_tracer.py +431 -0
- ccproxy/core/plugins/hooks/layers.py +44 -0
- ccproxy/core/plugins/hooks/manager.py +186 -0
- ccproxy/core/plugins/hooks/registry.py +139 -0
- ccproxy/core/plugins/hooks/thread_manager.py +203 -0
- ccproxy/core/plugins/hooks/types.py +22 -0
- ccproxy/core/plugins/interfaces.py +416 -0
- ccproxy/core/plugins/loader.py +166 -0
- ccproxy/core/plugins/middleware.py +233 -0
- ccproxy/core/plugins/models.py +59 -0
- ccproxy/core/plugins/protocol.py +180 -0
- ccproxy/core/plugins/runtime.py +519 -0
- ccproxy/{observability/context.py → core/request_context.py} +137 -94
- ccproxy/core/status_report.py +211 -0
- ccproxy/core/transformers.py +13 -8
- ccproxy/data/claude_headers_fallback.json +540 -19
- ccproxy/data/codex_headers_fallback.json +114 -7
- ccproxy/http/__init__.py +30 -0
- ccproxy/http/base.py +95 -0
- ccproxy/http/client.py +323 -0
- ccproxy/http/hooks.py +642 -0
- ccproxy/http/pool.py +279 -0
- ccproxy/llms/formatters/__init__.py +7 -0
- ccproxy/llms/formatters/anthropic_to_openai/__init__.py +55 -0
- ccproxy/llms/formatters/anthropic_to_openai/errors.py +65 -0
- ccproxy/llms/formatters/anthropic_to_openai/requests.py +356 -0
- ccproxy/llms/formatters/anthropic_to_openai/responses.py +153 -0
- ccproxy/llms/formatters/anthropic_to_openai/streams.py +1546 -0
- ccproxy/llms/formatters/base.py +140 -0
- ccproxy/llms/formatters/base_model.py +33 -0
- ccproxy/llms/formatters/common/__init__.py +51 -0
- ccproxy/llms/formatters/common/identifiers.py +48 -0
- ccproxy/llms/formatters/common/streams.py +254 -0
- ccproxy/llms/formatters/common/thinking.py +74 -0
- ccproxy/llms/formatters/common/usage.py +135 -0
- ccproxy/llms/formatters/constants.py +55 -0
- ccproxy/llms/formatters/context.py +116 -0
- ccproxy/llms/formatters/mapping.py +33 -0
- ccproxy/llms/formatters/openai_to_anthropic/__init__.py +55 -0
- ccproxy/llms/formatters/openai_to_anthropic/_helpers.py +141 -0
- ccproxy/llms/formatters/openai_to_anthropic/errors.py +53 -0
- ccproxy/llms/formatters/openai_to_anthropic/requests.py +674 -0
- ccproxy/llms/formatters/openai_to_anthropic/responses.py +285 -0
- ccproxy/llms/formatters/openai_to_anthropic/streams.py +530 -0
- ccproxy/llms/formatters/openai_to_openai/__init__.py +53 -0
- ccproxy/llms/formatters/openai_to_openai/_helpers.py +325 -0
- ccproxy/llms/formatters/openai_to_openai/errors.py +6 -0
- ccproxy/llms/formatters/openai_to_openai/requests.py +388 -0
- ccproxy/llms/formatters/openai_to_openai/responses.py +594 -0
- ccproxy/llms/formatters/openai_to_openai/streams.py +1832 -0
- ccproxy/llms/formatters/utils.py +306 -0
- ccproxy/llms/models/__init__.py +9 -0
- ccproxy/llms/models/anthropic.py +619 -0
- ccproxy/llms/models/openai.py +844 -0
- ccproxy/llms/streaming/__init__.py +26 -0
- ccproxy/llms/streaming/accumulators.py +1074 -0
- ccproxy/llms/streaming/formatters.py +251 -0
- ccproxy/{adapters/openai/streaming.py → llms/streaming/processors.py} +193 -240
- ccproxy/models/__init__.py +8 -159
- ccproxy/models/detection.py +92 -193
- ccproxy/models/provider.py +75 -0
- ccproxy/plugins/access_log/README.md +32 -0
- ccproxy/plugins/access_log/__init__.py +20 -0
- ccproxy/plugins/access_log/config.py +33 -0
- ccproxy/plugins/access_log/formatter.py +126 -0
- ccproxy/plugins/access_log/hook.py +763 -0
- ccproxy/plugins/access_log/logger.py +254 -0
- ccproxy/plugins/access_log/plugin.py +137 -0
- ccproxy/plugins/access_log/writer.py +109 -0
- ccproxy/plugins/analytics/README.md +24 -0
- ccproxy/plugins/analytics/__init__.py +1 -0
- ccproxy/plugins/analytics/config.py +5 -0
- ccproxy/plugins/analytics/ingest.py +85 -0
- ccproxy/plugins/analytics/models.py +97 -0
- ccproxy/plugins/analytics/plugin.py +121 -0
- ccproxy/plugins/analytics/routes.py +163 -0
- ccproxy/plugins/analytics/service.py +284 -0
- ccproxy/plugins/claude_api/README.md +29 -0
- ccproxy/plugins/claude_api/__init__.py +10 -0
- ccproxy/plugins/claude_api/adapter.py +829 -0
- ccproxy/plugins/claude_api/config.py +52 -0
- ccproxy/plugins/claude_api/detection_service.py +461 -0
- ccproxy/plugins/claude_api/health.py +175 -0
- ccproxy/plugins/claude_api/hooks.py +284 -0
- ccproxy/plugins/claude_api/models.py +256 -0
- ccproxy/plugins/claude_api/plugin.py +298 -0
- ccproxy/plugins/claude_api/routes.py +118 -0
- ccproxy/plugins/claude_api/streaming_metrics.py +68 -0
- ccproxy/plugins/claude_api/tasks.py +84 -0
- ccproxy/plugins/claude_sdk/README.md +35 -0
- ccproxy/plugins/claude_sdk/__init__.py +80 -0
- ccproxy/plugins/claude_sdk/adapter.py +749 -0
- ccproxy/plugins/claude_sdk/auth.py +57 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/client.py +63 -39
- ccproxy/plugins/claude_sdk/config.py +210 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/converter.py +6 -6
- ccproxy/plugins/claude_sdk/detection_service.py +163 -0
- ccproxy/{services/claude_sdk_service.py → plugins/claude_sdk/handler.py} +123 -304
- ccproxy/plugins/claude_sdk/health.py +113 -0
- ccproxy/plugins/claude_sdk/hooks.py +115 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/manager.py +42 -32
- ccproxy/{claude_sdk → plugins/claude_sdk}/message_queue.py +8 -8
- ccproxy/{models/claude_sdk.py → plugins/claude_sdk/models.py} +64 -16
- ccproxy/plugins/claude_sdk/options.py +154 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/parser.py +23 -5
- ccproxy/plugins/claude_sdk/plugin.py +269 -0
- ccproxy/plugins/claude_sdk/routes.py +104 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/session_client.py +124 -12
- ccproxy/plugins/claude_sdk/session_pool.py +700 -0
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_handle.py +48 -43
- ccproxy/{claude_sdk → plugins/claude_sdk}/stream_worker.py +22 -18
- ccproxy/{claude_sdk → plugins/claude_sdk}/streaming.py +50 -16
- ccproxy/plugins/claude_sdk/tasks.py +97 -0
- ccproxy/plugins/claude_shared/README.md +18 -0
- ccproxy/plugins/claude_shared/__init__.py +12 -0
- ccproxy/plugins/claude_shared/model_defaults.py +171 -0
- ccproxy/plugins/codex/README.md +35 -0
- ccproxy/plugins/codex/__init__.py +6 -0
- ccproxy/plugins/codex/adapter.py +635 -0
- ccproxy/{config/codex.py → plugins/codex/config.py} +78 -12
- ccproxy/plugins/codex/detection_service.py +544 -0
- ccproxy/plugins/codex/health.py +162 -0
- ccproxy/plugins/codex/hooks.py +263 -0
- ccproxy/plugins/codex/model_defaults.py +39 -0
- ccproxy/plugins/codex/models.py +263 -0
- ccproxy/plugins/codex/plugin.py +275 -0
- ccproxy/plugins/codex/routes.py +129 -0
- ccproxy/plugins/codex/streaming_metrics.py +324 -0
- ccproxy/plugins/codex/tasks.py +106 -0
- ccproxy/plugins/codex/utils/__init__.py +1 -0
- ccproxy/plugins/codex/utils/sse_parser.py +106 -0
- ccproxy/plugins/command_replay/README.md +34 -0
- ccproxy/plugins/command_replay/__init__.py +17 -0
- ccproxy/plugins/command_replay/config.py +133 -0
- ccproxy/plugins/command_replay/formatter.py +432 -0
- ccproxy/plugins/command_replay/hook.py +294 -0
- ccproxy/plugins/command_replay/plugin.py +161 -0
- ccproxy/plugins/copilot/README.md +39 -0
- ccproxy/plugins/copilot/__init__.py +11 -0
- ccproxy/plugins/copilot/adapter.py +465 -0
- ccproxy/plugins/copilot/config.py +155 -0
- ccproxy/plugins/copilot/data/copilot_fallback.json +41 -0
- ccproxy/plugins/copilot/detection_service.py +255 -0
- ccproxy/plugins/copilot/manager.py +275 -0
- ccproxy/plugins/copilot/model_defaults.py +284 -0
- ccproxy/plugins/copilot/models.py +148 -0
- ccproxy/plugins/copilot/oauth/__init__.py +16 -0
- ccproxy/plugins/copilot/oauth/client.py +494 -0
- ccproxy/plugins/copilot/oauth/models.py +385 -0
- ccproxy/plugins/copilot/oauth/provider.py +602 -0
- ccproxy/plugins/copilot/oauth/storage.py +170 -0
- ccproxy/plugins/copilot/plugin.py +360 -0
- ccproxy/plugins/copilot/routes.py +294 -0
- ccproxy/plugins/credential_balancer/README.md +124 -0
- ccproxy/plugins/credential_balancer/__init__.py +6 -0
- ccproxy/plugins/credential_balancer/config.py +270 -0
- ccproxy/plugins/credential_balancer/factory.py +415 -0
- ccproxy/plugins/credential_balancer/hook.py +51 -0
- ccproxy/plugins/credential_balancer/manager.py +587 -0
- ccproxy/plugins/credential_balancer/plugin.py +146 -0
- ccproxy/plugins/dashboard/README.md +25 -0
- ccproxy/plugins/dashboard/__init__.py +1 -0
- ccproxy/plugins/dashboard/config.py +8 -0
- ccproxy/plugins/dashboard/plugin.py +71 -0
- ccproxy/plugins/dashboard/routes.py +67 -0
- ccproxy/plugins/docker/README.md +32 -0
- ccproxy/{docker → plugins/docker}/__init__.py +3 -0
- ccproxy/{docker → plugins/docker}/adapter.py +108 -10
- ccproxy/plugins/docker/config.py +82 -0
- ccproxy/{docker → plugins/docker}/docker_path.py +4 -3
- ccproxy/{docker → plugins/docker}/middleware.py +2 -2
- ccproxy/plugins/docker/plugin.py +198 -0
- ccproxy/{docker → plugins/docker}/stream_process.py +3 -3
- ccproxy/plugins/duckdb_storage/README.md +26 -0
- ccproxy/plugins/duckdb_storage/__init__.py +1 -0
- ccproxy/plugins/duckdb_storage/config.py +22 -0
- ccproxy/plugins/duckdb_storage/plugin.py +128 -0
- ccproxy/plugins/duckdb_storage/routes.py +51 -0
- ccproxy/plugins/duckdb_storage/storage.py +633 -0
- ccproxy/plugins/max_tokens/README.md +38 -0
- ccproxy/plugins/max_tokens/__init__.py +12 -0
- ccproxy/plugins/max_tokens/adapter.py +235 -0
- ccproxy/plugins/max_tokens/config.py +86 -0
- ccproxy/plugins/max_tokens/models.py +53 -0
- ccproxy/plugins/max_tokens/plugin.py +200 -0
- ccproxy/plugins/max_tokens/service.py +271 -0
- ccproxy/plugins/max_tokens/token_limits.json +54 -0
- ccproxy/plugins/metrics/README.md +35 -0
- ccproxy/plugins/metrics/__init__.py +10 -0
- ccproxy/{observability/metrics.py → plugins/metrics/collector.py} +20 -153
- ccproxy/plugins/metrics/config.py +85 -0
- ccproxy/plugins/metrics/grafana/dashboards/ccproxy-dashboard.json +1720 -0
- ccproxy/plugins/metrics/hook.py +403 -0
- ccproxy/plugins/metrics/plugin.py +268 -0
- ccproxy/{observability → plugins/metrics}/pushgateway.py +57 -59
- ccproxy/plugins/metrics/routes.py +107 -0
- ccproxy/plugins/metrics/tasks.py +117 -0
- ccproxy/plugins/oauth_claude/README.md +35 -0
- ccproxy/plugins/oauth_claude/__init__.py +14 -0
- ccproxy/plugins/oauth_claude/client.py +270 -0
- ccproxy/plugins/oauth_claude/config.py +84 -0
- ccproxy/plugins/oauth_claude/manager.py +482 -0
- ccproxy/plugins/oauth_claude/models.py +266 -0
- ccproxy/plugins/oauth_claude/plugin.py +149 -0
- ccproxy/plugins/oauth_claude/provider.py +571 -0
- ccproxy/plugins/oauth_claude/storage.py +212 -0
- ccproxy/plugins/oauth_codex/README.md +38 -0
- ccproxy/plugins/oauth_codex/__init__.py +14 -0
- ccproxy/plugins/oauth_codex/client.py +224 -0
- ccproxy/plugins/oauth_codex/config.py +95 -0
- ccproxy/plugins/oauth_codex/manager.py +256 -0
- ccproxy/plugins/oauth_codex/models.py +239 -0
- ccproxy/plugins/oauth_codex/plugin.py +146 -0
- ccproxy/plugins/oauth_codex/provider.py +574 -0
- ccproxy/plugins/oauth_codex/storage.py +92 -0
- ccproxy/plugins/permissions/README.md +28 -0
- ccproxy/plugins/permissions/__init__.py +22 -0
- ccproxy/plugins/permissions/config.py +28 -0
- ccproxy/{cli/commands/permission_handler.py → plugins/permissions/handlers/cli.py} +49 -25
- ccproxy/plugins/permissions/handlers/protocol.py +33 -0
- ccproxy/plugins/permissions/handlers/terminal.py +675 -0
- ccproxy/{api/routes → plugins/permissions}/mcp.py +34 -7
- ccproxy/{models/permissions.py → plugins/permissions/models.py} +65 -1
- ccproxy/plugins/permissions/plugin.py +153 -0
- ccproxy/{api/routes/permissions.py → plugins/permissions/routes.py} +20 -16
- ccproxy/{api/services/permission_service.py → plugins/permissions/service.py} +65 -11
- ccproxy/{api → plugins/permissions}/ui/permission_handler_protocol.py +1 -1
- ccproxy/{api → plugins/permissions}/ui/terminal_permission_handler.py +66 -10
- ccproxy/plugins/pricing/README.md +34 -0
- ccproxy/plugins/pricing/__init__.py +6 -0
- ccproxy/{pricing → plugins/pricing}/cache.py +7 -6
- ccproxy/{config/pricing.py → plugins/pricing/config.py} +32 -6
- ccproxy/plugins/pricing/exceptions.py +35 -0
- ccproxy/plugins/pricing/loader.py +440 -0
- ccproxy/{pricing → plugins/pricing}/models.py +13 -23
- ccproxy/plugins/pricing/plugin.py +169 -0
- ccproxy/plugins/pricing/service.py +191 -0
- ccproxy/plugins/pricing/tasks.py +300 -0
- ccproxy/{pricing → plugins/pricing}/updater.py +86 -72
- ccproxy/plugins/pricing/utils.py +99 -0
- ccproxy/plugins/request_tracer/README.md +40 -0
- ccproxy/plugins/request_tracer/__init__.py +7 -0
- ccproxy/plugins/request_tracer/config.py +120 -0
- ccproxy/plugins/request_tracer/hook.py +415 -0
- ccproxy/plugins/request_tracer/plugin.py +255 -0
- ccproxy/scheduler/__init__.py +2 -14
- ccproxy/scheduler/core.py +26 -41
- ccproxy/scheduler/manager.py +61 -105
- ccproxy/scheduler/registry.py +6 -32
- ccproxy/scheduler/tasks.py +268 -276
- ccproxy/services/__init__.py +0 -1
- ccproxy/services/adapters/__init__.py +11 -0
- ccproxy/services/adapters/base.py +123 -0
- ccproxy/services/adapters/chain_composer.py +88 -0
- ccproxy/services/adapters/chain_validation.py +44 -0
- ccproxy/services/adapters/chat_accumulator.py +200 -0
- ccproxy/services/adapters/delta_utils.py +142 -0
- ccproxy/services/adapters/format_adapter.py +136 -0
- ccproxy/services/adapters/format_context.py +11 -0
- ccproxy/services/adapters/format_registry.py +158 -0
- ccproxy/services/adapters/http_adapter.py +1045 -0
- ccproxy/services/adapters/mock_adapter.py +118 -0
- ccproxy/services/adapters/protocols.py +35 -0
- ccproxy/services/adapters/simple_converters.py +571 -0
- ccproxy/services/auth_registry.py +180 -0
- ccproxy/services/cache/__init__.py +6 -0
- ccproxy/services/cache/response_cache.py +261 -0
- ccproxy/services/cli_detection.py +437 -0
- ccproxy/services/config/__init__.py +6 -0
- ccproxy/services/config/proxy_configuration.py +111 -0
- ccproxy/services/container.py +256 -0
- ccproxy/services/factories.py +380 -0
- ccproxy/services/handler_config.py +76 -0
- ccproxy/services/interfaces.py +298 -0
- ccproxy/services/mocking/__init__.py +6 -0
- ccproxy/services/mocking/mock_handler.py +291 -0
- ccproxy/services/tracing/__init__.py +7 -0
- ccproxy/services/tracing/interfaces.py +61 -0
- ccproxy/services/tracing/null_tracer.py +57 -0
- ccproxy/streaming/__init__.py +23 -0
- ccproxy/streaming/buffer.py +1056 -0
- ccproxy/streaming/deferred.py +897 -0
- ccproxy/streaming/handler.py +117 -0
- ccproxy/streaming/interfaces.py +77 -0
- ccproxy/streaming/simple_adapter.py +39 -0
- ccproxy/streaming/sse.py +109 -0
- ccproxy/streaming/sse_parser.py +127 -0
- ccproxy/templates/__init__.py +6 -0
- ccproxy/templates/plugin_scaffold.py +695 -0
- ccproxy/testing/endpoints/__init__.py +33 -0
- ccproxy/testing/endpoints/cli.py +215 -0
- ccproxy/testing/endpoints/config.py +874 -0
- ccproxy/testing/endpoints/console.py +57 -0
- ccproxy/testing/endpoints/models.py +100 -0
- ccproxy/testing/endpoints/runner.py +1903 -0
- ccproxy/testing/endpoints/tools.py +308 -0
- ccproxy/testing/mock_responses.py +70 -1
- ccproxy/testing/response_handlers.py +20 -0
- ccproxy/utils/__init__.py +0 -6
- ccproxy/utils/binary_resolver.py +476 -0
- ccproxy/utils/caching.py +327 -0
- ccproxy/utils/cli_logging.py +101 -0
- ccproxy/utils/command_line.py +251 -0
- ccproxy/utils/headers.py +228 -0
- ccproxy/utils/model_mapper.py +120 -0
- ccproxy/utils/startup_helpers.py +68 -446
- ccproxy/utils/version_checker.py +273 -6
- ccproxy_api-0.2.0.dist-info/METADATA +212 -0
- ccproxy_api-0.2.0.dist-info/RECORD +417 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/WHEEL +1 -1
- ccproxy_api-0.2.0.dist-info/entry_points.txt +24 -0
- ccproxy/__init__.py +0 -4
- ccproxy/adapters/__init__.py +0 -11
- ccproxy/adapters/base.py +0 -80
- ccproxy/adapters/codex/__init__.py +0 -11
- ccproxy/adapters/openai/__init__.py +0 -42
- ccproxy/adapters/openai/adapter.py +0 -953
- ccproxy/adapters/openai/models.py +0 -412
- ccproxy/adapters/openai/response_adapter.py +0 -355
- ccproxy/adapters/openai/response_models.py +0 -178
- ccproxy/api/middleware/headers.py +0 -49
- ccproxy/api/middleware/logging.py +0 -180
- ccproxy/api/middleware/request_content_logging.py +0 -297
- ccproxy/api/middleware/server_header.py +0 -58
- ccproxy/api/responses.py +0 -89
- ccproxy/api/routes/claude.py +0 -371
- ccproxy/api/routes/codex.py +0 -1251
- ccproxy/api/routes/metrics.py +0 -1029
- ccproxy/api/routes/proxy.py +0 -211
- ccproxy/api/services/__init__.py +0 -6
- ccproxy/auth/conditional.py +0 -84
- ccproxy/auth/credentials_adapter.py +0 -93
- ccproxy/auth/models.py +0 -118
- ccproxy/auth/oauth/models.py +0 -48
- ccproxy/auth/openai/__init__.py +0 -13
- ccproxy/auth/openai/credentials.py +0 -166
- ccproxy/auth/openai/oauth_client.py +0 -334
- ccproxy/auth/openai/storage.py +0 -184
- ccproxy/auth/storage/json_file.py +0 -158
- ccproxy/auth/storage/keyring.py +0 -189
- ccproxy/claude_sdk/__init__.py +0 -18
- ccproxy/claude_sdk/options.py +0 -194
- ccproxy/claude_sdk/session_pool.py +0 -550
- ccproxy/cli/docker/__init__.py +0 -34
- ccproxy/cli/docker/adapter_factory.py +0 -157
- ccproxy/cli/docker/params.py +0 -274
- ccproxy/config/auth.py +0 -153
- ccproxy/config/claude.py +0 -348
- ccproxy/config/cors.py +0 -79
- ccproxy/config/discovery.py +0 -95
- ccproxy/config/docker_settings.py +0 -264
- ccproxy/config/observability.py +0 -158
- ccproxy/config/reverse_proxy.py +0 -31
- ccproxy/config/scheduler.py +0 -108
- ccproxy/config/server.py +0 -86
- ccproxy/config/validators.py +0 -231
- ccproxy/core/codex_transformers.py +0 -389
- ccproxy/core/http.py +0 -328
- ccproxy/core/http_transformers.py +0 -812
- ccproxy/core/proxy.py +0 -143
- ccproxy/core/validators.py +0 -288
- ccproxy/models/errors.py +0 -42
- ccproxy/models/messages.py +0 -269
- ccproxy/models/requests.py +0 -107
- ccproxy/models/responses.py +0 -270
- ccproxy/models/types.py +0 -102
- ccproxy/observability/__init__.py +0 -51
- ccproxy/observability/access_logger.py +0 -457
- ccproxy/observability/sse_events.py +0 -303
- ccproxy/observability/stats_printer.py +0 -753
- ccproxy/observability/storage/__init__.py +0 -1
- ccproxy/observability/storage/duckdb_simple.py +0 -677
- ccproxy/observability/storage/models.py +0 -70
- ccproxy/observability/streaming_response.py +0 -107
- ccproxy/pricing/__init__.py +0 -19
- ccproxy/pricing/loader.py +0 -251
- ccproxy/services/claude_detection_service.py +0 -243
- ccproxy/services/codex_detection_service.py +0 -252
- ccproxy/services/credentials/__init__.py +0 -55
- ccproxy/services/credentials/config.py +0 -105
- ccproxy/services/credentials/manager.py +0 -561
- ccproxy/services/credentials/oauth_client.py +0 -481
- ccproxy/services/proxy_service.py +0 -1827
- ccproxy/static/.keep +0 -0
- ccproxy/utils/cost_calculator.py +0 -210
- ccproxy/utils/disconnection_monitor.py +0 -83
- ccproxy/utils/model_mapping.py +0 -199
- ccproxy/utils/models_provider.py +0 -150
- ccproxy/utils/simple_request_logger.py +0 -284
- ccproxy/utils/streaming_metrics.py +0 -199
- ccproxy_api-0.1.7.dist-info/METADATA +0 -615
- ccproxy_api-0.1.7.dist-info/RECORD +0 -191
- ccproxy_api-0.1.7.dist-info/entry_points.txt +0 -4
- /ccproxy/{api/middleware/auth.py → auth/models/__init__.py} +0 -0
- /ccproxy/{claude_sdk → plugins/claude_sdk}/exceptions.py +0 -0
- /ccproxy/{docker → plugins/docker}/models.py +0 -0
- /ccproxy/{docker → plugins/docker}/protocol.py +0 -0
- /ccproxy/{docker → plugins/docker}/validators.py +0 -0
- /ccproxy/{auth/oauth/storage.py → plugins/permissions/handlers/__init__.py} +0 -0
- /ccproxy/{api → plugins/permissions}/ui/__init__.py +0 -0
- {ccproxy_api-0.1.7.dist-info → ccproxy_api-0.2.0.dist-info}/licenses/LICENSE +0 -0
ccproxy/auth/oauth/routes.py
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
+
import httpx
|
|
6
7
|
from fastapi import APIRouter, Query, Request
|
|
7
8
|
from fastapi.responses import HTMLResponse
|
|
8
|
-
from
|
|
9
|
+
from pydantic import ValidationError
|
|
9
10
|
|
|
10
|
-
from ccproxy.auth.
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
from ccproxy.auth.exceptions import (
|
|
12
|
+
CredentialsStorageError,
|
|
13
|
+
OAuthError,
|
|
14
|
+
OAuthTokenRefreshError,
|
|
13
15
|
)
|
|
14
|
-
from ccproxy.auth.
|
|
15
|
-
|
|
16
|
-
# Import CredentialsManager locally to avoid circular import
|
|
17
|
-
from ccproxy.services.credentials.config import OAuthConfig
|
|
16
|
+
from ccproxy.auth.oauth.registry import OAuthRegistry
|
|
17
|
+
from ccproxy.core.logging import get_logger
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
logger = get_logger(__name__)
|
|
@@ -36,7 +36,12 @@ def register_oauth_flow(
|
|
|
36
36
|
"success": False,
|
|
37
37
|
"error": None,
|
|
38
38
|
}
|
|
39
|
-
logger.debug(
|
|
39
|
+
logger.debug(
|
|
40
|
+
"Registered OAuth flow",
|
|
41
|
+
state=state,
|
|
42
|
+
operation="register_oauth_flow",
|
|
43
|
+
category="auth",
|
|
44
|
+
)
|
|
40
45
|
|
|
41
46
|
|
|
42
47
|
def get_oauth_flow_result(state: str) -> dict[str, Any] | None:
|
|
@@ -68,6 +73,7 @@ async def oauth_callback(
|
|
|
68
73
|
oauth_error_description=error_description,
|
|
69
74
|
state=state,
|
|
70
75
|
operation="oauth_callback",
|
|
76
|
+
category="auth",
|
|
71
77
|
)
|
|
72
78
|
|
|
73
79
|
# Update pending flow if state is provided
|
|
@@ -102,6 +108,7 @@ async def oauth_callback(
|
|
|
102
108
|
error_message=error_msg,
|
|
103
109
|
state=state,
|
|
104
110
|
operation="oauth_callback",
|
|
111
|
+
category="auth",
|
|
105
112
|
)
|
|
106
113
|
|
|
107
114
|
if state and state in _pending_flows:
|
|
@@ -134,6 +141,7 @@ async def oauth_callback(
|
|
|
134
141
|
error_type="missing_state",
|
|
135
142
|
error_message=error_msg,
|
|
136
143
|
operation="oauth_callback",
|
|
144
|
+
category="auth",
|
|
137
145
|
)
|
|
138
146
|
return HTMLResponse(
|
|
139
147
|
content=f"""
|
|
@@ -158,6 +166,7 @@ async def oauth_callback(
|
|
|
158
166
|
error_message="Invalid or expired state parameter",
|
|
159
167
|
state=state,
|
|
160
168
|
operation="oauth_callback",
|
|
169
|
+
category="auth",
|
|
161
170
|
)
|
|
162
171
|
return HTMLResponse(
|
|
163
172
|
content=f"""
|
|
@@ -178,8 +187,13 @@ async def oauth_callback(
|
|
|
178
187
|
code_verifier = flow["code_verifier"]
|
|
179
188
|
custom_paths = flow["custom_paths"]
|
|
180
189
|
|
|
181
|
-
# Exchange authorization code for tokens
|
|
182
|
-
|
|
190
|
+
# Exchange authorization code for tokens using app-scoped registry
|
|
191
|
+
registry: OAuthRegistry | None = getattr(
|
|
192
|
+
request.app.state, "oauth_registry", None
|
|
193
|
+
)
|
|
194
|
+
success = await _exchange_code_for_tokens(
|
|
195
|
+
code, code_verifier, state, custom_paths, registry
|
|
196
|
+
)
|
|
183
197
|
|
|
184
198
|
# Update flow result
|
|
185
199
|
_pending_flows[state].update(
|
|
@@ -192,7 +206,10 @@ async def oauth_callback(
|
|
|
192
206
|
|
|
193
207
|
if success:
|
|
194
208
|
logger.info(
|
|
195
|
-
"OAuth login successful",
|
|
209
|
+
"OAuth login successful",
|
|
210
|
+
state=state,
|
|
211
|
+
operation="oauth_callback",
|
|
212
|
+
category="auth",
|
|
196
213
|
)
|
|
197
214
|
return HTMLResponse(
|
|
198
215
|
content="""
|
|
@@ -220,6 +237,7 @@ async def oauth_callback(
|
|
|
220
237
|
error_message=error_msg,
|
|
221
238
|
state=state,
|
|
222
239
|
operation="oauth_callback",
|
|
240
|
+
category="auth",
|
|
223
241
|
)
|
|
224
242
|
return HTMLResponse(
|
|
225
243
|
content=f"""
|
|
@@ -235,14 +253,108 @@ async def oauth_callback(
|
|
|
235
253
|
status_code=500,
|
|
236
254
|
)
|
|
237
255
|
|
|
256
|
+
except (OAuthError, OAuthTokenRefreshError, CredentialsStorageError) as e:
|
|
257
|
+
logger.error(
|
|
258
|
+
"oauth_callback_error",
|
|
259
|
+
error_type="auth_error",
|
|
260
|
+
error=str(e),
|
|
261
|
+
state=state,
|
|
262
|
+
operation="oauth_callback",
|
|
263
|
+
exc_info=e,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
if state and state in _pending_flows:
|
|
267
|
+
_pending_flows[state].update(
|
|
268
|
+
{
|
|
269
|
+
"completed": True,
|
|
270
|
+
"success": False,
|
|
271
|
+
"error": str(e),
|
|
272
|
+
}
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
return HTMLResponse(
|
|
276
|
+
content=f"""
|
|
277
|
+
<html>
|
|
278
|
+
<head><title>Login Error</title></head>
|
|
279
|
+
<body>
|
|
280
|
+
<h1>Login Error</h1>
|
|
281
|
+
<p>Authentication error: {str(e)}</p>
|
|
282
|
+
<p>You can close this window and try again.</p>
|
|
283
|
+
</body>
|
|
284
|
+
</html>
|
|
285
|
+
""",
|
|
286
|
+
status_code=500,
|
|
287
|
+
)
|
|
288
|
+
except httpx.HTTPError as e:
|
|
289
|
+
logger.error(
|
|
290
|
+
"oauth_callback_http_error",
|
|
291
|
+
error=str(e),
|
|
292
|
+
status=e.response.status_code if hasattr(e, "response") else None,
|
|
293
|
+
state=state,
|
|
294
|
+
operation="oauth_callback",
|
|
295
|
+
exc_info=e,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
if state and state in _pending_flows:
|
|
299
|
+
_pending_flows[state].update(
|
|
300
|
+
{
|
|
301
|
+
"completed": True,
|
|
302
|
+
"success": False,
|
|
303
|
+
"error": f"HTTP error: {str(e)}",
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return HTMLResponse(
|
|
308
|
+
content=f"""
|
|
309
|
+
<html>
|
|
310
|
+
<head><title>Login Error</title></head>
|
|
311
|
+
<body>
|
|
312
|
+
<h1>Login Error</h1>
|
|
313
|
+
<p>Network error occurred: {str(e)}</p>
|
|
314
|
+
<p>You can close this window and try again.</p>
|
|
315
|
+
</body>
|
|
316
|
+
</html>
|
|
317
|
+
""",
|
|
318
|
+
status_code=500,
|
|
319
|
+
)
|
|
320
|
+
except ValidationError as e:
|
|
321
|
+
logger.error(
|
|
322
|
+
"oauth_callback_validation_error",
|
|
323
|
+
error=str(e),
|
|
324
|
+
state=state,
|
|
325
|
+
operation="oauth_callback",
|
|
326
|
+
exc_info=e,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
if state and state in _pending_flows:
|
|
330
|
+
_pending_flows[state].update(
|
|
331
|
+
{
|
|
332
|
+
"completed": True,
|
|
333
|
+
"success": False,
|
|
334
|
+
"error": f"Validation error: {str(e)}",
|
|
335
|
+
}
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
return HTMLResponse(
|
|
339
|
+
content="""
|
|
340
|
+
<html>
|
|
341
|
+
<head><title>Login Error</title></head>
|
|
342
|
+
<body>
|
|
343
|
+
<h1>Login Error</h1>
|
|
344
|
+
<p>Data validation error occurred</p>
|
|
345
|
+
<p>You can close this window and try again.</p>
|
|
346
|
+
</body>
|
|
347
|
+
</html>
|
|
348
|
+
""",
|
|
349
|
+
status_code=500,
|
|
350
|
+
)
|
|
238
351
|
except Exception as e:
|
|
239
352
|
logger.error(
|
|
240
|
-
"
|
|
241
|
-
|
|
242
|
-
error_message=str(e),
|
|
353
|
+
"oauth_callback_unexpected_error",
|
|
354
|
+
error=str(e),
|
|
243
355
|
state=state,
|
|
244
356
|
operation="oauth_callback",
|
|
245
|
-
exc_info=
|
|
357
|
+
exc_info=e,
|
|
246
358
|
)
|
|
247
359
|
|
|
248
360
|
if state and state in _pending_flows:
|
|
@@ -270,125 +382,86 @@ async def oauth_callback(
|
|
|
270
382
|
|
|
271
383
|
|
|
272
384
|
async def _exchange_code_for_tokens(
|
|
273
|
-
authorization_code: str,
|
|
385
|
+
authorization_code: str,
|
|
386
|
+
code_verifier: str,
|
|
387
|
+
state: str,
|
|
388
|
+
custom_paths: list[Path] | None = None,
|
|
389
|
+
registry: OAuthRegistry | None = None,
|
|
274
390
|
) -> bool:
|
|
275
391
|
"""Exchange authorization code for access tokens."""
|
|
276
392
|
try:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
headers = {
|
|
294
|
-
"Content-Type": "application/json",
|
|
295
|
-
"anthropic-beta": oauth_config.beta_version,
|
|
296
|
-
"User-Agent": oauth_config.user_agent,
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async with httpx.AsyncClient() as client:
|
|
300
|
-
response = await client.post(
|
|
301
|
-
oauth_config.token_url,
|
|
302
|
-
headers=headers,
|
|
303
|
-
json=token_data,
|
|
304
|
-
timeout=30.0,
|
|
393
|
+
# Get OAuth provider from provided registry
|
|
394
|
+
if registry is None:
|
|
395
|
+
logger.error(
|
|
396
|
+
"oauth_registry_not_available", operation="exchange_code_for_tokens"
|
|
397
|
+
)
|
|
398
|
+
return False
|
|
399
|
+
oauth_provider = registry.get("claude-api")
|
|
400
|
+
if not oauth_provider:
|
|
401
|
+
logger.error("claude_oauth_provider_not_found", category="auth")
|
|
402
|
+
return False
|
|
403
|
+
|
|
404
|
+
# Use OAuth provider to handle the callback
|
|
405
|
+
try:
|
|
406
|
+
credentials = await oauth_provider.handle_callback(
|
|
407
|
+
authorization_code, state, code_verifier
|
|
305
408
|
)
|
|
306
409
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if
|
|
314
|
-
|
|
315
|
-
|
|
410
|
+
# Save credentials using provider's storage mechanism
|
|
411
|
+
if custom_paths:
|
|
412
|
+
# Let the provider handle storage with custom path
|
|
413
|
+
success = await oauth_provider.save_credentials(
|
|
414
|
+
credentials, custom_path=custom_paths[0] if custom_paths else None
|
|
415
|
+
)
|
|
416
|
+
if success:
|
|
417
|
+
logger.info(
|
|
418
|
+
"Successfully saved OAuth credentials to custom path",
|
|
419
|
+
operation="exchange_code_for_tokens",
|
|
420
|
+
path=str(custom_paths[0]),
|
|
316
421
|
)
|
|
317
|
-
|
|
318
|
-
# Create credentials object
|
|
319
|
-
oauth_data = {
|
|
320
|
-
"accessToken": result.get("access_token"),
|
|
321
|
-
"refreshToken": result.get("refresh_token"),
|
|
322
|
-
"expiresAt": expires_at,
|
|
323
|
-
"scopes": result.get("scope", "").split()
|
|
324
|
-
if result.get("scope")
|
|
325
|
-
else oauth_config.scopes,
|
|
326
|
-
"subscriptionType": result.get("subscription_type", "unknown"),
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
credentials = ClaudeCredentials(claudeAiOauth=OAuthToken(**oauth_data))
|
|
330
|
-
|
|
331
|
-
# Save credentials using CredentialsManager (lazy import to avoid circular import)
|
|
332
|
-
from ccproxy.services.credentials.manager import CredentialsManager
|
|
333
|
-
|
|
334
|
-
if custom_paths:
|
|
335
|
-
# Use the first custom path for storage
|
|
336
|
-
storage = JsonFileStorage(custom_paths[0])
|
|
337
|
-
manager = CredentialsManager(storage=storage)
|
|
338
422
|
else:
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
423
|
+
logger.error(
|
|
424
|
+
"Failed to save OAuth credentials to custom path",
|
|
425
|
+
error_type="save_credentials_failed",
|
|
426
|
+
operation="exchange_code_for_tokens",
|
|
427
|
+
path=str(custom_paths[0]),
|
|
428
|
+
)
|
|
429
|
+
else:
|
|
430
|
+
# Save using provider's default storage
|
|
431
|
+
success = await oauth_provider.save_credentials(credentials)
|
|
432
|
+
if success:
|
|
342
433
|
logger.info(
|
|
343
434
|
"Successfully saved OAuth credentials",
|
|
344
|
-
subscription_type=oauth_data["subscriptionType"],
|
|
345
|
-
scopes=oauth_data["scopes"],
|
|
346
435
|
operation="exchange_code_for_tokens",
|
|
347
436
|
)
|
|
348
|
-
return True
|
|
349
437
|
else:
|
|
350
438
|
logger.error(
|
|
351
439
|
"Failed to save OAuth credentials",
|
|
352
440
|
error_type="save_credentials_failed",
|
|
353
441
|
operation="exchange_code_for_tokens",
|
|
354
442
|
)
|
|
355
|
-
return False
|
|
356
443
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
os.environ.get("CCPROXY_VERBOSE_API", "false").lower() == "true"
|
|
363
|
-
)
|
|
444
|
+
logger.info(
|
|
445
|
+
"OAuth flow completed successfully",
|
|
446
|
+
operation="exchange_code_for_tokens",
|
|
447
|
+
)
|
|
448
|
+
return True
|
|
364
449
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
error_detail = response_text
|
|
375
|
-
|
|
376
|
-
logger.error(
|
|
377
|
-
"Token exchange failed",
|
|
378
|
-
error_type="token_exchange_failed",
|
|
379
|
-
status_code=response.status_code,
|
|
380
|
-
error_detail=error_detail,
|
|
381
|
-
verbose_api_enabled=verbose_api,
|
|
382
|
-
operation="exchange_code_for_tokens",
|
|
383
|
-
)
|
|
384
|
-
return False
|
|
450
|
+
except Exception as e:
|
|
451
|
+
logger.error(
|
|
452
|
+
"oauth_provider_callback_error",
|
|
453
|
+
error=str(e),
|
|
454
|
+
error_type=type(e).__name__,
|
|
455
|
+
operation="exchange_code_for_tokens",
|
|
456
|
+
exc_info=e,
|
|
457
|
+
)
|
|
458
|
+
return False
|
|
385
459
|
|
|
386
460
|
except Exception as e:
|
|
387
461
|
logger.error(
|
|
388
|
-
"
|
|
389
|
-
|
|
390
|
-
error_message=str(e),
|
|
462
|
+
"oauth_exchange_error",
|
|
463
|
+
error=str(e),
|
|
391
464
|
operation="exchange_code_for_tokens",
|
|
392
|
-
exc_info=
|
|
465
|
+
exc_info=e,
|
|
393
466
|
)
|
|
394
467
|
return False
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""OAuth session management for handling OAuth state and PKCE.
|
|
2
|
+
|
|
3
|
+
This module provides session management for OAuth flows, storing
|
|
4
|
+
state, PKCE verifiers, and other session data during the OAuth process.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import time
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import structlog
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = structlog.get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OAuthSessionManager:
|
|
17
|
+
"""Manages OAuth session data during authentication flows.
|
|
18
|
+
|
|
19
|
+
This is a simple in-memory implementation. In production,
|
|
20
|
+
consider using Redis or another persistent store.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self, ttl_seconds: int = 600) -> None:
|
|
24
|
+
"""Initialize the session manager.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
ttl_seconds: Time-to-live for sessions in seconds (default: 10 minutes)
|
|
28
|
+
"""
|
|
29
|
+
self._sessions: dict[str, dict[str, Any]] = {}
|
|
30
|
+
self._ttl_seconds = ttl_seconds
|
|
31
|
+
logger.info(
|
|
32
|
+
"oauth_session_manager_initialized",
|
|
33
|
+
ttl_seconds=ttl_seconds,
|
|
34
|
+
category="auth",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
async def create_session(self, state: str, data: dict[str, Any]) -> None:
|
|
38
|
+
"""Create a new OAuth session.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
state: OAuth state parameter (session key)
|
|
42
|
+
data: Session data to store
|
|
43
|
+
"""
|
|
44
|
+
self._sessions[state] = {
|
|
45
|
+
**data,
|
|
46
|
+
"created_at": time.time(),
|
|
47
|
+
}
|
|
48
|
+
logger.debug(
|
|
49
|
+
"oauth_session_created",
|
|
50
|
+
state=state,
|
|
51
|
+
provider=data.get("provider"),
|
|
52
|
+
has_pkce=bool(data.get("code_verifier")),
|
|
53
|
+
category="auth",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Clean up expired sessions
|
|
57
|
+
await self._cleanup_expired()
|
|
58
|
+
|
|
59
|
+
async def get_session(self, state: str) -> dict[str, Any] | None:
|
|
60
|
+
"""Retrieve session data by state.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
state: OAuth state parameter
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Session data or None if not found/expired
|
|
67
|
+
"""
|
|
68
|
+
session = self._sessions.get(state)
|
|
69
|
+
|
|
70
|
+
if not session:
|
|
71
|
+
logger.debug("oauth_session_not_found", state=state, category="auth")
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
# Check if session expired
|
|
75
|
+
created_at = session.get("created_at", 0)
|
|
76
|
+
if time.time() - created_at > self._ttl_seconds:
|
|
77
|
+
logger.debug("oauth_session_expired", state=state, category="auth")
|
|
78
|
+
await self.delete_session(state)
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
logger.debug(
|
|
82
|
+
"oauth_session_retrieved",
|
|
83
|
+
state=state,
|
|
84
|
+
provider=session.get("provider"),
|
|
85
|
+
category="auth",
|
|
86
|
+
)
|
|
87
|
+
return session
|
|
88
|
+
|
|
89
|
+
async def delete_session(self, state: str) -> None:
|
|
90
|
+
"""Delete a session.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
state: OAuth state parameter
|
|
94
|
+
"""
|
|
95
|
+
if state in self._sessions:
|
|
96
|
+
provider = self._sessions[state].get("provider")
|
|
97
|
+
del self._sessions[state]
|
|
98
|
+
logger.debug(
|
|
99
|
+
"oauth_session_deleted", state=state, provider=provider, category="auth"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
async def _cleanup_expired(self) -> None:
|
|
103
|
+
"""Remove expired sessions."""
|
|
104
|
+
current_time = time.time()
|
|
105
|
+
expired_states = [
|
|
106
|
+
state
|
|
107
|
+
for state, session in self._sessions.items()
|
|
108
|
+
if current_time - session.get("created_at", 0) > self._ttl_seconds
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
for state in expired_states:
|
|
112
|
+
await self.delete_session(state)
|
|
113
|
+
|
|
114
|
+
if expired_states:
|
|
115
|
+
logger.debug(
|
|
116
|
+
"oauth_sessions_cleaned", count=len(expired_states), category="auth"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def clear_all(self) -> None:
|
|
120
|
+
"""Clear all sessions (mainly for testing)."""
|
|
121
|
+
count = len(self._sessions)
|
|
122
|
+
self._sessions.clear()
|
|
123
|
+
logger.info("oauth_sessions_cleared", count=count, category="auth")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# Global session manager instance
|
|
127
|
+
_session_manager: OAuthSessionManager | None = None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_oauth_session_manager() -> OAuthSessionManager:
|
|
131
|
+
"""Get the global OAuth session manager instance.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Global OAuth session manager
|
|
135
|
+
"""
|
|
136
|
+
global _session_manager
|
|
137
|
+
if _session_manager is None:
|
|
138
|
+
_session_manager = OAuthSessionManager()
|
|
139
|
+
return _session_manager
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def reset_oauth_session_manager() -> None:
|
|
143
|
+
"""Reset the global OAuth session manager.
|
|
144
|
+
|
|
145
|
+
This clears all sessions and creates a new manager.
|
|
146
|
+
Mainly useful for testing.
|
|
147
|
+
"""
|
|
148
|
+
global _session_manager
|
|
149
|
+
if _session_manager:
|
|
150
|
+
_session_manager.clear_all()
|
|
151
|
+
_session_manager = OAuthSessionManager()
|