api-key-manager 2.1.0__tar.gz → 2.1.2__tar.gz
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.
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/PKG-INFO +23 -3
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/README.md +22 -2
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/api_key_manager.egg-info/PKG-INFO +23 -3
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/api_key_manager.egg-info/SOURCES.txt +7 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/__init__.py +3 -2
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/api_models.py +9 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/core.py +10 -4
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/proxy.py +0 -13
- api_key_manager-2.1.2/key_manager/py.typed +1 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/storage.py +10 -10
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/validator.py +1 -1
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/web.py +194 -149
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/pyproject.toml +1 -1
- api_key_manager-2.1.2/tests/test_bug_fixes.py +246 -0
- api_key_manager-2.1.2/tests/test_core.py +265 -0
- api_key_manager-2.1.2/tests/test_logger.py +273 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_providers.py +1 -1
- api_key_manager-2.1.2/tests/test_proxy.py +122 -0
- api_key_manager-2.1.2/tests/test_tester.py +300 -0
- api_key_manager-2.1.2/tests/test_web_fixes.py +161 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/api_key_manager.egg-info/dependency_links.txt +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/api_key_manager.egg-info/entry_points.txt +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/api_key_manager.egg-info/requires.txt +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/api_key_manager.egg-info/top_level.txt +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/__main__.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/checker.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/cli.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/config.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/detector.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/errors.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/i18n.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/logger.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/model_capabilities.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/parser.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/__init__.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/ai302.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/anthropic.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/baichuan.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/base.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/cerebras.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/cohere.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/cstcloud.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/dashscope.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/dashscope_coding.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/deepseek.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/dmxapi.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/doubao.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/fireworks.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/google.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/grok.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/groq.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/huggingface.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/hyperbolic.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/infini.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/infini_coding.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/kimi.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/kimi_coding.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/longcat.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/mimo.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/mimo_plan.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/minimax.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/minimax_plan.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/mistral.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/models_registry.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/modelscope.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/nvidia.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/ocoolai.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/openai.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/openrouter.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/perplexity.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/poe.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/ppio.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/replicate.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/siliconflow.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/stepfun.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/tencent_hunyuan.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/together.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/yi.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/zai.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/zhipu.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/providers/zhipu_coding.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/ssrf.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/tester.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/url_override.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/key_manager/webhook.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/setup.cfg +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_checker.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_detector.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_e2e.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_errors.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_i18n.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_openapi.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_parser.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_provider_detection.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_security.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_storage.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_validator.py +0 -0
- {api_key_manager-2.1.0 → api_key_manager-2.1.2}/tests/test_webhook.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: api-key-manager
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Batch manage API keys for 44+ AI providers with CLI and Web interfaces
|
|
5
5
|
Author: Townrain
|
|
6
6
|
License: MIT
|
|
@@ -192,10 +192,11 @@ python web.py
|
|
|
192
192
|
### 安全防护
|
|
193
193
|
|
|
194
194
|
- **路径遍历防护** - 导入端点验证路径在允许目录内
|
|
195
|
-
- **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP
|
|
195
|
+
- **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP,已接入 `check/single` 和 `balance` 端点
|
|
196
196
|
- **时序安全认证** - 使用 `hmac.compare_digest()` 防止时序攻击
|
|
197
197
|
- **认证警告** - 未配置 API Key 时启动警告
|
|
198
198
|
- **密钥掩码** - API 响应中只返回 `key_masked`,不暴露完整密钥
|
|
199
|
+
- **Webhook 安全** - Webhook 端点使用正确的 API 方法,防止运行时错误
|
|
199
200
|
|
|
200
201
|
### API 认证
|
|
201
202
|
|
|
@@ -609,7 +610,7 @@ python -m pytest tests/ --cov=key_manager --cov-report=term-missing
|
|
|
609
610
|
| 密钥解析 | `test_parser.py` | 12 |
|
|
610
611
|
| 验证器 | `test_validator.py` | 5 |
|
|
611
612
|
| 检查器 | `test_checker.py` | 4 |
|
|
612
|
-
| 提供商合约 | `test_providers.py` |
|
|
613
|
+
| 提供商合约 | `test_providers.py` | 220 |
|
|
613
614
|
| 安全回归 | `test_security.py` | 12 |
|
|
614
615
|
| 加密存储 | `test_storage.py` | 26 |
|
|
615
616
|
| 错误系统 | `test_errors.py` | 28 |
|
|
@@ -617,7 +618,12 @@ python -m pytest tests/ --cov=key_manager --cov-report=term-missing
|
|
|
617
618
|
| 端到端 | `test_e2e.py` | 17 |
|
|
618
619
|
| Webhook | `test_webhook.py` | 35 |
|
|
619
620
|
| OpenAPI | `test_openapi.py` | 26 |
|
|
621
|
+
| 代理检测 | `test_proxy.py` | 19 |
|
|
622
|
+
| 日志系统 | `test_logger.py` | 21 |
|
|
623
|
+
| 能力测试 | `test_tester.py` | 11 |
|
|
624
|
+
| 核心门面 | `test_core.py` | 22 |
|
|
620
625
|
|
|
626
|
+
**总测试数**: 583+ | **覆盖率**: 88%
|
|
621
627
|
## SDK 使用
|
|
622
628
|
|
|
623
629
|
### Python SDK
|
|
@@ -704,6 +710,20 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
|
|
|
704
710
|
|
|
705
711
|
模型列表从 Cherry Studio 同步,每日更新一次。新发布的模型可能需要等待同步后才能被检测到。
|
|
706
712
|
|
|
713
|
+
|
|
714
|
+
## 更新日志
|
|
715
|
+
|
|
716
|
+
### v2.1.2 (2026-06-11)
|
|
717
|
+
|
|
718
|
+
- **Bug 修复**: 修复 `KeyManager.detect_provider()` 调用异步函数未 await 的问题
|
|
719
|
+
- **测试扩展**: 提供商合约测试扩展至全部 44 个服务商
|
|
720
|
+
- **测试新增**: 新增 `test_proxy.py`、`test_logger.py`、`test_tester.py`、`test_core.py`
|
|
721
|
+
- **覆盖率提升**: 测试覆盖率从 74% 提升至 88%
|
|
722
|
+
|
|
723
|
+
### v2.1.1
|
|
724
|
+
|
|
725
|
+
- 初始发布版本
|
|
726
|
+
|
|
707
727
|
## 许可证
|
|
708
728
|
|
|
709
729
|
MIT License
|
|
@@ -159,10 +159,11 @@ python web.py
|
|
|
159
159
|
### 安全防护
|
|
160
160
|
|
|
161
161
|
- **路径遍历防护** - 导入端点验证路径在允许目录内
|
|
162
|
-
- **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP
|
|
162
|
+
- **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP,已接入 `check/single` 和 `balance` 端点
|
|
163
163
|
- **时序安全认证** - 使用 `hmac.compare_digest()` 防止时序攻击
|
|
164
164
|
- **认证警告** - 未配置 API Key 时启动警告
|
|
165
165
|
- **密钥掩码** - API 响应中只返回 `key_masked`,不暴露完整密钥
|
|
166
|
+
- **Webhook 安全** - Webhook 端点使用正确的 API 方法,防止运行时错误
|
|
166
167
|
|
|
167
168
|
### API 认证
|
|
168
169
|
|
|
@@ -576,7 +577,7 @@ python -m pytest tests/ --cov=key_manager --cov-report=term-missing
|
|
|
576
577
|
| 密钥解析 | `test_parser.py` | 12 |
|
|
577
578
|
| 验证器 | `test_validator.py` | 5 |
|
|
578
579
|
| 检查器 | `test_checker.py` | 4 |
|
|
579
|
-
| 提供商合约 | `test_providers.py` |
|
|
580
|
+
| 提供商合约 | `test_providers.py` | 220 |
|
|
580
581
|
| 安全回归 | `test_security.py` | 12 |
|
|
581
582
|
| 加密存储 | `test_storage.py` | 26 |
|
|
582
583
|
| 错误系统 | `test_errors.py` | 28 |
|
|
@@ -584,7 +585,12 @@ python -m pytest tests/ --cov=key_manager --cov-report=term-missing
|
|
|
584
585
|
| 端到端 | `test_e2e.py` | 17 |
|
|
585
586
|
| Webhook | `test_webhook.py` | 35 |
|
|
586
587
|
| OpenAPI | `test_openapi.py` | 26 |
|
|
588
|
+
| 代理检测 | `test_proxy.py` | 19 |
|
|
589
|
+
| 日志系统 | `test_logger.py` | 21 |
|
|
590
|
+
| 能力测试 | `test_tester.py` | 11 |
|
|
591
|
+
| 核心门面 | `test_core.py` | 22 |
|
|
587
592
|
|
|
593
|
+
**总测试数**: 583+ | **覆盖率**: 88%
|
|
588
594
|
## SDK 使用
|
|
589
595
|
|
|
590
596
|
### Python SDK
|
|
@@ -671,6 +677,20 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
|
|
|
671
677
|
|
|
672
678
|
模型列表从 Cherry Studio 同步,每日更新一次。新发布的模型可能需要等待同步后才能被检测到。
|
|
673
679
|
|
|
680
|
+
|
|
681
|
+
## 更新日志
|
|
682
|
+
|
|
683
|
+
### v2.1.2 (2026-06-11)
|
|
684
|
+
|
|
685
|
+
- **Bug 修复**: 修复 `KeyManager.detect_provider()` 调用异步函数未 await 的问题
|
|
686
|
+
- **测试扩展**: 提供商合约测试扩展至全部 44 个服务商
|
|
687
|
+
- **测试新增**: 新增 `test_proxy.py`、`test_logger.py`、`test_tester.py`、`test_core.py`
|
|
688
|
+
- **覆盖率提升**: 测试覆盖率从 74% 提升至 88%
|
|
689
|
+
|
|
690
|
+
### v2.1.1
|
|
691
|
+
|
|
692
|
+
- 初始发布版本
|
|
693
|
+
|
|
674
694
|
## 许可证
|
|
675
695
|
|
|
676
696
|
MIT License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: api-key-manager
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.2
|
|
4
4
|
Summary: Batch manage API keys for 44+ AI providers with CLI and Web interfaces
|
|
5
5
|
Author: Townrain
|
|
6
6
|
License: MIT
|
|
@@ -192,10 +192,11 @@ python web.py
|
|
|
192
192
|
### 安全防护
|
|
193
193
|
|
|
194
194
|
- **路径遍历防护** - 导入端点验证路径在允许目录内
|
|
195
|
-
- **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP
|
|
195
|
+
- **SSRF 防护** - `custom_base_url` 验证域名白名单,阻止私有 IP,已接入 `check/single` 和 `balance` 端点
|
|
196
196
|
- **时序安全认证** - 使用 `hmac.compare_digest()` 防止时序攻击
|
|
197
197
|
- **认证警告** - 未配置 API Key 时启动警告
|
|
198
198
|
- **密钥掩码** - API 响应中只返回 `key_masked`,不暴露完整密钥
|
|
199
|
+
- **Webhook 安全** - Webhook 端点使用正确的 API 方法,防止运行时错误
|
|
199
200
|
|
|
200
201
|
### API 认证
|
|
201
202
|
|
|
@@ -609,7 +610,7 @@ python -m pytest tests/ --cov=key_manager --cov-report=term-missing
|
|
|
609
610
|
| 密钥解析 | `test_parser.py` | 12 |
|
|
610
611
|
| 验证器 | `test_validator.py` | 5 |
|
|
611
612
|
| 检查器 | `test_checker.py` | 4 |
|
|
612
|
-
| 提供商合约 | `test_providers.py` |
|
|
613
|
+
| 提供商合约 | `test_providers.py` | 220 |
|
|
613
614
|
| 安全回归 | `test_security.py` | 12 |
|
|
614
615
|
| 加密存储 | `test_storage.py` | 26 |
|
|
615
616
|
| 错误系统 | `test_errors.py` | 28 |
|
|
@@ -617,7 +618,12 @@ python -m pytest tests/ --cov=key_manager --cov-report=term-missing
|
|
|
617
618
|
| 端到端 | `test_e2e.py` | 17 |
|
|
618
619
|
| Webhook | `test_webhook.py` | 35 |
|
|
619
620
|
| OpenAPI | `test_openapi.py` | 26 |
|
|
621
|
+
| 代理检测 | `test_proxy.py` | 19 |
|
|
622
|
+
| 日志系统 | `test_logger.py` | 21 |
|
|
623
|
+
| 能力测试 | `test_tester.py` | 11 |
|
|
624
|
+
| 核心门面 | `test_core.py` | 22 |
|
|
620
625
|
|
|
626
|
+
**总测试数**: 583+ | **覆盖率**: 88%
|
|
621
627
|
## SDK 使用
|
|
622
628
|
|
|
623
629
|
### Python SDK
|
|
@@ -704,6 +710,20 @@ const result = await client.checkSingleKey({ key: 'sk-xxx', provider: 'openai' }
|
|
|
704
710
|
|
|
705
711
|
模型列表从 Cherry Studio 同步,每日更新一次。新发布的模型可能需要等待同步后才能被检测到。
|
|
706
712
|
|
|
713
|
+
|
|
714
|
+
## 更新日志
|
|
715
|
+
|
|
716
|
+
### v2.1.2 (2026-06-11)
|
|
717
|
+
|
|
718
|
+
- **Bug 修复**: 修复 `KeyManager.detect_provider()` 调用异步函数未 await 的问题
|
|
719
|
+
- **测试扩展**: 提供商合约测试扩展至全部 44 个服务商
|
|
720
|
+
- **测试新增**: 新增 `test_proxy.py`、`test_logger.py`、`test_tester.py`、`test_core.py`
|
|
721
|
+
- **覆盖率提升**: 测试覆盖率从 74% 提升至 88%
|
|
722
|
+
|
|
723
|
+
### v2.1.1
|
|
724
|
+
|
|
725
|
+
- 初始发布版本
|
|
726
|
+
|
|
707
727
|
## 许可证
|
|
708
728
|
|
|
709
729
|
MIT License
|
|
@@ -20,6 +20,7 @@ key_manager/logger.py
|
|
|
20
20
|
key_manager/model_capabilities.py
|
|
21
21
|
key_manager/parser.py
|
|
22
22
|
key_manager/proxy.py
|
|
23
|
+
key_manager/py.typed
|
|
23
24
|
key_manager/ssrf.py
|
|
24
25
|
key_manager/storage.py
|
|
25
26
|
key_manager/tester.py
|
|
@@ -74,16 +75,22 @@ key_manager/providers/yi.py
|
|
|
74
75
|
key_manager/providers/zai.py
|
|
75
76
|
key_manager/providers/zhipu.py
|
|
76
77
|
key_manager/providers/zhipu_coding.py
|
|
78
|
+
tests/test_bug_fixes.py
|
|
77
79
|
tests/test_checker.py
|
|
80
|
+
tests/test_core.py
|
|
78
81
|
tests/test_detector.py
|
|
79
82
|
tests/test_e2e.py
|
|
80
83
|
tests/test_errors.py
|
|
81
84
|
tests/test_i18n.py
|
|
85
|
+
tests/test_logger.py
|
|
82
86
|
tests/test_openapi.py
|
|
83
87
|
tests/test_parser.py
|
|
84
88
|
tests/test_provider_detection.py
|
|
85
89
|
tests/test_providers.py
|
|
90
|
+
tests/test_proxy.py
|
|
86
91
|
tests/test_security.py
|
|
87
92
|
tests/test_storage.py
|
|
93
|
+
tests/test_tester.py
|
|
88
94
|
tests/test_validator.py
|
|
95
|
+
tests/test_web_fixes.py
|
|
89
96
|
tests/test_webhook.py
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Key Manager - Batch API key management for 44+ AI providers."""
|
|
1
|
+
"""Key Manager - Batch API key management for 44+ AI providers."""
|
|
2
2
|
from key_manager.core import KeyManager
|
|
3
3
|
from key_manager.providers import PROVIDERS, get_display_name
|
|
4
4
|
from key_manager.errors import KeyManagerError, ErrorCode
|
|
@@ -13,4 +13,5 @@ __all__ = [
|
|
|
13
13
|
"KeyStore",
|
|
14
14
|
]
|
|
15
15
|
|
|
16
|
-
__version__ = "2.1.
|
|
16
|
+
__version__ = "2.1.2"
|
|
17
|
+
|
|
@@ -42,6 +42,7 @@ __all__ = [
|
|
|
42
42
|
"OperationEntry",
|
|
43
43
|
"OperationsResponse",
|
|
44
44
|
"ProgressResponse",
|
|
45
|
+
"ProxyResponse",
|
|
45
46
|
]
|
|
46
47
|
|
|
47
48
|
|
|
@@ -356,3 +357,11 @@ class ProgressResponse(BaseModel):
|
|
|
356
357
|
total: int = Field(0, description="Total items to process")
|
|
357
358
|
status: str = Field("", description="Task status: loading / done / error")
|
|
358
359
|
results: dict[str, Any] | None = Field(None, description="Final results when complete")
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# ── Proxy ─────────────────────────────────────────────────────────────────────
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class ProxyResponse(BaseModel):
|
|
366
|
+
proxy: str | None = Field(None, description="Detected proxy URL")
|
|
367
|
+
source: str = Field("", description="Proxy source: config / env / auto / none")
|
|
@@ -62,10 +62,16 @@ class KeyManager:
|
|
|
62
62
|
"""Save keys to encrypted storage."""
|
|
63
63
|
self._store.save(data)
|
|
64
64
|
|
|
65
|
-
def detect_provider(self, key: str) -> list[str]:
|
|
65
|
+
async def detect_provider(self, key: str) -> list[str]:
|
|
66
66
|
"""Auto-detect provider from key prefix."""
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
import httpx
|
|
68
|
+
from key_manager.detector import detect_provider as _detect_provider
|
|
69
|
+
|
|
70
|
+
timeout = self.config.get("check", {}).get("timeout_seconds", 30)
|
|
71
|
+
proxy = self.config.get("proxy") or None
|
|
72
|
+
async with httpx.AsyncClient(timeout=timeout, proxy=proxy) as client:
|
|
73
|
+
result = await _detect_provider(client, key)
|
|
74
|
+
return [result] if result else []
|
|
69
75
|
def list_keys(
|
|
70
76
|
self,
|
|
71
77
|
provider: str | None = None,
|
|
@@ -139,7 +145,7 @@ class KeyManager:
|
|
|
139
145
|
from key_manager.providers import PROVIDERS
|
|
140
146
|
|
|
141
147
|
if not provider:
|
|
142
|
-
providers = self.detect_provider(key)
|
|
148
|
+
providers = await self.detect_provider(key)
|
|
143
149
|
if not providers:
|
|
144
150
|
return {"valid": False, "error": "Could not detect provider"}
|
|
145
151
|
provider = providers[0]
|
|
@@ -55,16 +55,3 @@ def get_proxy(config_proxy: str = None) -> str:
|
|
|
55
55
|
return ""
|
|
56
56
|
# Otherwise use the configured proxy
|
|
57
57
|
return config_proxy
|
|
58
|
-
"""Get proxy from config or auto-detect."""
|
|
59
|
-
# 空字符串表示禁用代理
|
|
60
|
-
if config_proxy == "":
|
|
61
|
-
return ""
|
|
62
|
-
# 有配置则使用配置
|
|
63
|
-
if config_proxy:
|
|
64
|
-
return config_proxy
|
|
65
|
-
# 没有配置则自动检测
|
|
66
|
-
return detect_system_proxy()
|
|
67
|
-
"""Get proxy from config or auto-detect."""
|
|
68
|
-
if config_proxy:
|
|
69
|
-
return config_proxy
|
|
70
|
-
return detect_system_proxy()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -6,6 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
7
7
|
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
|
8
8
|
from cryptography.hazmat.primitives import hashes
|
|
9
|
+
from key_manager.errors import ErrorCode, StorageError
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
_ITERATIONS = 100_000
|
|
@@ -33,7 +34,8 @@ def _get_passphrase(config: dict | None = None) -> str:
|
|
|
33
34
|
if config and config.get("encryption", {}).get("passphrase"):
|
|
34
35
|
return config["encryption"]["passphrase"]
|
|
35
36
|
raise StorageError(
|
|
36
|
-
|
|
37
|
+
code=ErrorCode.STORAGE_ENCRYPTION_ERROR,
|
|
38
|
+
message="No passphrase found. Set KEY_MANAGER_SECRET env var or "
|
|
37
39
|
"configure encryption.passphrase in config.yaml"
|
|
38
40
|
)
|
|
39
41
|
|
|
@@ -46,8 +48,6 @@ def _b64d(s: str) -> bytes:
|
|
|
46
48
|
return base64.b64decode(s)
|
|
47
49
|
|
|
48
50
|
|
|
49
|
-
class StorageError(Exception):
|
|
50
|
-
pass
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
class KeyStore:
|
|
@@ -58,12 +58,12 @@ class KeyStore:
|
|
|
58
58
|
|
|
59
59
|
def load(self) -> dict:
|
|
60
60
|
if not self.path.exists():
|
|
61
|
-
raise StorageError(f"File not found: {self.path}")
|
|
61
|
+
raise StorageError(code=ErrorCode.STORAGE_READ_ERROR, message=f"File not found: {self.path}")
|
|
62
62
|
raw = self.path.read_text(encoding="utf-8")
|
|
63
63
|
try:
|
|
64
64
|
data = json.loads(raw)
|
|
65
65
|
except json.JSONDecodeError as e:
|
|
66
|
-
raise StorageError(f"Invalid JSON in {self.path}: {e}") from e
|
|
66
|
+
raise StorageError(code=ErrorCode.STORAGE_READ_ERROR, message=f"Invalid JSON in {self.path}: {e}") from e
|
|
67
67
|
if isinstance(data, dict) and data.get("encrypted") is True:
|
|
68
68
|
return self._decrypt(data)
|
|
69
69
|
return data
|
|
@@ -83,12 +83,12 @@ class KeyStore:
|
|
|
83
83
|
|
|
84
84
|
def rotate_key(self, new_passphrase: str) -> dict:
|
|
85
85
|
if not self.path.exists():
|
|
86
|
-
raise StorageError(f"File not found: {self.path}")
|
|
86
|
+
raise StorageError(code=ErrorCode.STORAGE_READ_ERROR, message=f"File not found: {self.path}")
|
|
87
87
|
raw = self.path.read_text(encoding="utf-8")
|
|
88
88
|
try:
|
|
89
89
|
envelope = json.loads(raw)
|
|
90
90
|
except json.JSONDecodeError as e:
|
|
91
|
-
raise StorageError(f"Invalid JSON in {self.path}: {e}") from e
|
|
91
|
+
raise StorageError(code=ErrorCode.STORAGE_READ_ERROR, message=f"Invalid JSON in {self.path}: {e}") from e
|
|
92
92
|
if isinstance(envelope, dict) and envelope.get("encrypted") is True:
|
|
93
93
|
data = self._decrypt(envelope)
|
|
94
94
|
else:
|
|
@@ -120,15 +120,15 @@ class KeyStore:
|
|
|
120
120
|
nonce = _b64d(envelope["nonce"])
|
|
121
121
|
ciphertext = _b64d(envelope["data"])
|
|
122
122
|
except (KeyError, ValueError) as e:
|
|
123
|
-
raise StorageError(f"Malformed encrypted envelope: {e}") from e
|
|
123
|
+
raise StorageError(code=ErrorCode.STORAGE_ENCRYPTION_ERROR, message=f"Malformed encrypted envelope: {e}") from e
|
|
124
124
|
passphrase = _get_passphrase(self.config)
|
|
125
125
|
key = _derive_key(passphrase, salt)
|
|
126
126
|
aes = AESGCM(key)
|
|
127
127
|
try:
|
|
128
128
|
plaintext = aes.decrypt(nonce, ciphertext, None)
|
|
129
129
|
except Exception as e:
|
|
130
|
-
raise StorageError(f"Decryption failed (wrong key or tampered data): {e}") from e
|
|
130
|
+
raise StorageError(code=ErrorCode.STORAGE_ENCRYPTION_ERROR, message=f"Decryption failed (wrong key or tampered data): {e}") from e
|
|
131
131
|
try:
|
|
132
132
|
return json.loads(plaintext.decode("utf-8"))
|
|
133
133
|
except json.JSONDecodeError as e:
|
|
134
|
-
raise StorageError(f"Decrypted data is not valid JSON: {e}") from e
|
|
134
|
+
raise StorageError(code=ErrorCode.STORAGE_ENCRYPTION_ERROR, message=f"Decrypted data is not valid JSON: {e}") from e
|
|
@@ -60,7 +60,7 @@ async def validate_keys(keys_file: str = "./data/keys.json",
|
|
|
60
60
|
provider_name = info["provider"].lower()
|
|
61
61
|
provider = PROVIDERS.get(provider_name)
|
|
62
62
|
if not provider:
|
|
63
|
-
from
|
|
63
|
+
from key_manager.providers.base import CheckResult
|
|
64
64
|
result = CheckResult(valid=False, status_code=None, latency_ms=0, error=f"unknown provider: {provider_name}")
|
|
65
65
|
else:
|
|
66
66
|
result = await provider.check(client, key)
|