KiroProxy User commited on
Commit
2286d62
·
1 Parent(s): deeaf30

feat: 添加Hugging Face部署支持和管理员认证系统

Browse files

- 添加远程SQL数据库支持(PostgreSQL/MySQL/SQLite)
- 实现管理员密码认证和会话管理系统
- 创建Dockerfile用于Hugging Face Space部署
- 添加数据库抽象层,支持环境变量配置
- 为敏感管理员操作添加认证保护
- 更新README.md包含HF Space配置
- 添加.env.example环境变量示例文件

.env.example ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # KiroProxy 环境变量配置示例
2
+ # 复制此文件为 .env 并根据需要修改配置
3
+
4
+ # ==================== 数据库配置 ====================
5
+ # 数据库连接URL(可选)
6
+ # 如果不设置,将使用文件系统存储(默认:~/.kiro-proxy/)
7
+
8
+ # PostgreSQL 示例
9
+ # DATABASE_URL=postgresql://username:password@hostname:5432/database_name
10
+
11
+ # MySQL 示例
12
+ # DATABASE_URL=mysql://username:password@hostname:3306/database_name
13
+
14
+ # SQLite 示例(本地文件)
15
+ # DATABASE_URL=sqlite:///path/to/database.db
16
+
17
+ # ==================== 管理员认证配置 ====================
18
+ # 管理员面板密码(强烈推荐设置)
19
+ # 如果不设置,系统将自动生成随机密码并在启动时显示
20
+ ADMIN_PASSWORD=your_secure_admin_password_here
21
+
22
+ # ==================== 服务器配置 ====================
23
+ # 服务器端口(默认:8080)
24
+ PORT=7860
25
+
26
+ # 服务器主机地址(默认:0.0.0.0)
27
+ HOST=0.0.0.0
28
+
29
+ # ==================== Kiro API 配置 ====================
30
+ # Kiro API 端点(通常不需要修改)
31
+ # KIRO_API_URL=https://q.us-east-1.amazonaws.com/generateAssistantResponse
32
+ # MODELS_URL=https://q.us-east-1.amazonaws.com/ListAvailableModels
33
+
34
+ # ==================== 高级配置 ====================
35
+ # 配额冷却时间(秒,默认:300)
36
+ # QUOTA_COOLDOWN_SECONDS=300
37
+
38
+ # 数据目录(默认:~/.kiro-proxy)
39
+ # DATA_DIR=/app/data
40
+
41
+ # 日志级别(DEBUG, INFO, WARNING, ERROR)
42
+ # LOG_LEVEL=INFO
43
+
44
+ # ==================== Hugging Face Space 专用配置 ====================
45
+ # 在 Hugging Face Space 中,以下环境变量会自动设置:
46
+ # SPACE_ID - Space 的唯一标识符
47
+ # SPACE_AUTHOR_NAME - Space 作者名称
48
+ # SPACE_REPO_NAME - Space 仓库名称
49
+
50
+ # 建议在 Hugging Face Space 设置中配置:
51
+ # 1. DATABASE_URL - 连接到远程数据库(如 PostgreSQL)
52
+ # 2. ADMIN_PASSWORD - 设置管理员密码
53
+ # 3. 其他自定义配置根据需要添加
Dockerfile ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Docker部署配置
2
+ FROM python:3.11-slim
3
+
4
+ # 设置工作目录
5
+ WORKDIR /app
6
+
7
+ # 设置环境变量
8
+ ENV PYTHONPATH=/app
9
+ ENV PYTHONUNBUFFERED=1
10
+ ENV PYTHONDONTWRITEBYTECODE=1
11
+
12
+ # 安装系统依赖
13
+ RUN apt-get update && apt-get install -y \
14
+ gcc \
15
+ g++ \
16
+ curl \
17
+ && rm -rf /var/lib/apt/lists/*
18
+
19
+ # 复制requirements文件
20
+ COPY requirements.txt .
21
+
22
+ # 安装Python依赖
23
+ RUN pip install --no-cache-dir -r requirements.txt
24
+
25
+ # 复制项目文件
26
+ COPY KiroProxy/ ./KiroProxy/
27
+ COPY run.py .
28
+
29
+ # 创建数据目录
30
+ RUN mkdir -p /app/data
31
+
32
+ # 设置权限
33
+ RUN chmod +x run.py
34
+
35
+ # 暴露端口
36
+ EXPOSE 7860
37
+
38
+ # 健康检查
39
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
40
+ CMD curl -f http://localhost:7860/api/status || exit 1
41
+
42
+ # 启动命令
43
+ CMD ["python", "run.py", "7860"]
KiroProxy/kiro_proxy/core/admin_auth.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """管理员认证系统"""
2
+ import os
3
+ import secrets
4
+ import hashlib
5
+ import hmac
6
+ import time
7
+ import logging
8
+ from typing import Optional, Dict, Any
9
+ from datetime import datetime, timedelta
10
+ from .persistence import save_admin_config, load_admin_config
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class AdminAuth:
15
+ """管理员认证管理器"""
16
+
17
+ def __init__(self):
18
+ self.secret_key = self._get_or_create_secret_key()
19
+ self.session_timeout = 24 * 60 * 60 # 24小时
20
+ self._sessions: Dict[str, Dict[str, Any]] = {}
21
+
22
+ def _get_or_create_secret_key(self) -> str:
23
+ """获取或创建密钥"""
24
+ admin_config = load_admin_config()
25
+ secret_key = admin_config.get("secret_key")
26
+
27
+ if not secret_key:
28
+ secret_key = secrets.token_urlsafe(32)
29
+ admin_config["secret_key"] = secret_key
30
+ save_admin_config(admin_config)
31
+ logger.info("生成新的管理员密钥")
32
+
33
+ return secret_key
34
+
35
+ def get_admin_password(self) -> str:
36
+ """获取管理员密码"""
37
+ # 1. 优先使用环境变量
38
+ env_password = os.getenv("ADMIN_PASSWORD")
39
+ if env_password:
40
+ logger.info("使用环境变量中的管理员密码")
41
+ return env_password
42
+
43
+ # 2. 从配置文件获取
44
+ admin_config = load_admin_config()
45
+ stored_password = admin_config.get("password")
46
+
47
+ if stored_password:
48
+ logger.info("使用配置文件中的管理员密码")
49
+ return stored_password
50
+
51
+ # 3. 生成随机密码
52
+ random_password = secrets.token_urlsafe(12)
53
+ admin_config["password"] = random_password
54
+ save_admin_config(admin_config)
55
+
56
+ logger.warning("=" * 60)
57
+ logger.warning("🔐 管理员密码已自动生成")
58
+ logger.warning(f"密码: {random_password}")
59
+ logger.warning("请妥善保存此密码,或设置环境变量 ADMIN_PASSWORD")
60
+ logger.warning("=" * 60)
61
+
62
+ return random_password
63
+
64
+ def hash_password(self, password: str) -> str:
65
+ """哈希密码"""
66
+ salt = secrets.token_bytes(32)
67
+ pwdhash = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000)
68
+ return salt.hex() + pwdhash.hex()
69
+
70
+ def verify_password(self, password: str, hashed: str) -> bool:
71
+ """验证密码"""
72
+ try:
73
+ salt = bytes.fromhex(hashed[:64])
74
+ stored_hash = bytes.fromhex(hashed[64:])
75
+ pwdhash = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000)
76
+ return hmac.compare_digest(stored_hash, pwdhash)
77
+ except Exception:
78
+ return False
79
+
80
+ def authenticate(self, password: str) -> bool:
81
+ """验证管理员密码"""
82
+ admin_password = self.get_admin_password()
83
+ return hmac.compare_digest(password, admin_password)
84
+
85
+ def create_session(self, user_id: str = "admin") -> str:
86
+ """创建会话"""
87
+ session_id = secrets.token_urlsafe(32)
88
+ expires_at = time.time() + self.session_timeout
89
+
90
+ self._sessions[session_id] = {
91
+ "user_id": user_id,
92
+ "created_at": time.time(),
93
+ "expires_at": expires_at,
94
+ "last_activity": time.time()
95
+ }
96
+
97
+ # 清理过期会话
98
+ self._cleanup_expired_sessions()
99
+
100
+ logger.info(f"创建管理员会话: {session_id[:8]}...")
101
+ return session_id
102
+
103
+ def validate_session(self, session_id: str) -> bool:
104
+ """验证会话"""
105
+ if not session_id or session_id not in self._sessions:
106
+ return False
107
+
108
+ session = self._sessions[session_id]
109
+ current_time = time.time()
110
+
111
+ # 检查是否过期
112
+ if current_time > session["expires_at"]:
113
+ del self._sessions[session_id]
114
+ return False
115
+
116
+ # 更新最后活动时间
117
+ session["last_activity"] = current_time
118
+ return True
119
+
120
+ def revoke_session(self, session_id: str) -> bool:
121
+ """撤销会话"""
122
+ if session_id in self._sessions:
123
+ del self._sessions[session_id]
124
+ logger.info(f"撤销管理员会话: {session_id[:8]}...")
125
+ return True
126
+ return False
127
+
128
+ def get_session_info(self, session_id: str) -> Optional[Dict[str, Any]]:
129
+ """获取会话信息"""
130
+ if session_id in self._sessions:
131
+ session = self._sessions[session_id].copy()
132
+ session["created_at"] = datetime.fromtimestamp(session["created_at"]).isoformat()
133
+ session["expires_at"] = datetime.fromtimestamp(session["expires_at"]).isoformat()
134
+ session["last_activity"] = datetime.fromtimestamp(session["last_activity"]).isoformat()
135
+ return session
136
+ return None
137
+
138
+ def _cleanup_expired_sessions(self):
139
+ """清理过期会话"""
140
+ current_time = time.time()
141
+ expired_sessions = [
142
+ sid for sid, session in self._sessions.items()
143
+ if current_time > session["expires_at"]
144
+ ]
145
+
146
+ for sid in expired_sessions:
147
+ del self._sessions[sid]
148
+
149
+ if expired_sessions:
150
+ logger.info(f"清理了 {len(expired_sessions)} 个过期会话")
151
+
152
+ def get_active_sessions(self) -> Dict[str, Dict[str, Any]]:
153
+ """获取所有活跃会话"""
154
+ self._cleanup_expired_sessions()
155
+ return {
156
+ sid: self.get_session_info(sid)
157
+ for sid in self._sessions.keys()
158
+ }
159
+
160
+
161
+ # 全局认证实例
162
+ _auth_instance: Optional[AdminAuth] = None
163
+
164
+ def get_admin_auth() -> AdminAuth:
165
+ """获取管理员认证实例(单例模式)"""
166
+ global _auth_instance
167
+ if _auth_instance is None:
168
+ _auth_instance = AdminAuth()
169
+ return _auth_instance
170
+
171
+
172
+ # 便捷函数
173
+ def authenticate_admin(password: str) -> bool:
174
+ """验证管理员密码"""
175
+ return get_admin_auth().authenticate(password)
176
+
177
+ def create_admin_session() -> str:
178
+ """创建管理员会话"""
179
+ return get_admin_auth().create_session()
180
+
181
+ def validate_admin_session(session_id: str) -> bool:
182
+ """验证管理员会话"""
183
+ return get_admin_auth().validate_session(session_id)
184
+
185
+ def revoke_admin_session(session_id: str) -> bool:
186
+ """撤销管理员会话"""
187
+ return get_admin_auth().revoke_session(session_id)
188
+
189
+ def get_admin_password() -> str:
190
+ """获取管理员密码"""
191
+ return get_admin_auth().get_admin_password()
KiroProxy/kiro_proxy/core/auth_middleware.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """管理员认证中间件和装饰器"""
2
+ from functools import wraps
3
+ from typing import Optional
4
+ from fastapi import HTTPException, Request, Depends
5
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
6
+ from .admin_auth import get_admin_auth, validate_admin_session
7
+
8
+ security = HTTPBearer(auto_error=False)
9
+
10
+ class AdminAuthMiddleware:
11
+ """管理员认证中间件"""
12
+
13
+ def __init__(self):
14
+ self.auth = get_admin_auth()
15
+
16
+ def get_session_from_request(self, request: Request) -> Optional[str]:
17
+ """从请求中提取会话ID"""
18
+ # 1. 从Authorization头获取
19
+ auth_header = request.headers.get("Authorization")
20
+ if auth_header and auth_header.startswith("Bearer "):
21
+ return auth_header[7:]
22
+
23
+ # 2. 从Cookie获取
24
+ session_id = request.cookies.get("admin_session")
25
+ if session_id:
26
+ return session_id
27
+
28
+ # 3. 从查询参数获取(用于WebSocket等场景)
29
+ session_id = request.query_params.get("session")
30
+ if session_id:
31
+ return session_id
32
+
33
+ return None
34
+
35
+ async def authenticate_request(self, request: Request) -> bool:
36
+ """验证请求是否已认证"""
37
+ session_id = self.get_session_from_request(request)
38
+ if not session_id:
39
+ return False
40
+
41
+ return validate_admin_session(session_id)
42
+
43
+
44
+ # 全局中间件实例
45
+ _middleware_instance: Optional[AdminAuthMiddleware] = None
46
+
47
+ def get_auth_middleware() -> AdminAuthMiddleware:
48
+ """获取认证中间件实例"""
49
+ global _middleware_instance
50
+ if _middleware_instance is None:
51
+ _middleware_instance = AdminAuthMiddleware()
52
+ return _middleware_instance
53
+
54
+
55
+ # FastAPI依赖项
56
+ async def require_admin_auth(
57
+ request: Request,
58
+ credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)
59
+ ) -> str:
60
+ """要求管理员认证的依赖项"""
61
+ middleware = get_auth_middleware()
62
+
63
+ # 检查认证
64
+ if not await middleware.authenticate_request(request):
65
+ raise HTTPException(
66
+ status_code=401,
67
+ detail="需要管理员认证",
68
+ headers={"WWW-Authenticate": "Bearer"}
69
+ )
70
+
71
+ # 返回会话ID
72
+ session_id = middleware.get_session_from_request(request)
73
+ return session_id
74
+
75
+
76
+ async def optional_admin_auth(
77
+ request: Request,
78
+ credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)
79
+ ) -> Optional[str]:
80
+ """可选的管理员认证依赖项"""
81
+ middleware = get_auth_middleware()
82
+
83
+ if await middleware.authenticate_request(request):
84
+ return middleware.get_session_from_request(request)
85
+
86
+ return None
87
+
88
+
89
+ # 装饰器版本(用于非FastAPI函数)
90
+ def require_admin_session(func):
91
+ """要求管理员会话的装饰器"""
92
+ @wraps(func)
93
+ async def wrapper(*args, **kwargs):
94
+ # 查找Request参数
95
+ request = None
96
+ for arg in args:
97
+ if isinstance(arg, Request):
98
+ request = arg
99
+ break
100
+
101
+ if not request:
102
+ # 从kwargs查找
103
+ request = kwargs.get('request')
104
+
105
+ if not request:
106
+ raise HTTPException(status_code=500, detail="无法获取请求对象")
107
+
108
+ middleware = get_auth_middleware()
109
+ if not await middleware.authenticate_request(request):
110
+ raise HTTPException(
111
+ status_code=401,
112
+ detail="需要管理员认证",
113
+ headers={"WWW-Authenticate": "Bearer"}
114
+ )
115
+
116
+ return await func(*args, **kwargs)
117
+
118
+ return wrapper
KiroProxy/kiro_proxy/core/database.py ADDED
@@ -0,0 +1,397 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """数据库抽象层 - 支持文件系统和远程SQL数据库"""
2
+ import os
3
+ import json
4
+ import asyncio
5
+ from abc import ABC, abstractmethod
6
+ from typing import List, Dict, Any, Optional
7
+ from pathlib import Path
8
+ import logging
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class DatabaseInterface(ABC):
13
+ """数据库接口抽象类"""
14
+
15
+ @abstractmethod
16
+ async def save_accounts(self, accounts: List[Dict[str, Any]]) -> bool:
17
+ """保存账号配置"""
18
+ pass
19
+
20
+ @abstractmethod
21
+ async def load_accounts(self) -> List[Dict[str, Any]]:
22
+ """加载账号配置"""
23
+ pass
24
+
25
+ @abstractmethod
26
+ async def save_config(self, config: Dict[str, Any]) -> bool:
27
+ """保存完整配置"""
28
+ pass
29
+
30
+ @abstractmethod
31
+ async def load_config(self) -> Dict[str, Any]:
32
+ """加载完整配置"""
33
+ pass
34
+
35
+ @abstractmethod
36
+ async def save_admin_config(self, admin_config: Dict[str, Any]) -> bool:
37
+ """保存管理员配置"""
38
+ pass
39
+
40
+ @abstractmethod
41
+ async def load_admin_config(self) -> Dict[str, Any]:
42
+ """加载管理员配置"""
43
+ pass
44
+
45
+ @abstractmethod
46
+ async def initialize(self) -> bool:
47
+ """初始化数据库"""
48
+ pass
49
+
50
+
51
+ class FileSystemDatabase(DatabaseInterface):
52
+ """文件系统数据库实现(原有逻辑)"""
53
+
54
+ def __init__(self, data_dir: Path):
55
+ self.data_dir = data_dir
56
+ self.config_file = data_dir / "config.json"
57
+ self.admin_config_file = data_dir / "admin.json"
58
+
59
+ def _ensure_dir(self):
60
+ """确保目录存在"""
61
+ self.data_dir.mkdir(parents=True, exist_ok=True)
62
+
63
+ async def initialize(self) -> bool:
64
+ """初始化文件系统"""
65
+ try:
66
+ self._ensure_dir()
67
+ return True
68
+ except Exception as e:
69
+ logger.error(f"文件系统初始化失败: {e}")
70
+ return False
71
+
72
+ async def save_accounts(self, accounts: List[Dict[str, Any]]) -> bool:
73
+ """保存账号配置"""
74
+ try:
75
+ self._ensure_dir()
76
+ config = await self.load_config()
77
+ config["accounts"] = accounts
78
+ return await self.save_config(config)
79
+ except Exception as e:
80
+ logger.error(f"保存账号配置失败: {e}")
81
+ return False
82
+
83
+ async def load_accounts(self) -> List[Dict[str, Any]]:
84
+ """加载账号配置"""
85
+ config = await self.load_config()
86
+ return config.get("accounts", [])
87
+
88
+ async def save_config(self, config: Dict[str, Any]) -> bool:
89
+ """保存完整配置"""
90
+ try:
91
+ self._ensure_dir()
92
+ with open(self.config_file, "w", encoding="utf-8") as f:
93
+ json.dump(config, f, indent=2, ensure_ascii=False)
94
+ return True
95
+ except Exception as e:
96
+ logger.error(f"保存配置失败: {e}")
97
+ return False
98
+
99
+ async def load_config(self) -> Dict[str, Any]:
100
+ """加载完整配置"""
101
+ try:
102
+ if self.config_file.exists():
103
+ with open(self.config_file, "r", encoding="utf-8") as f:
104
+ return json.load(f)
105
+ except Exception as e:
106
+ logger.error(f"加载配置失败: {e}")
107
+ return {}
108
+
109
+ async def save_admin_config(self, admin_config: Dict[str, Any]) -> bool:
110
+ """保存管理员配置"""
111
+ try:
112
+ self._ensure_dir()
113
+ with open(self.admin_config_file, "w", encoding="utf-8") as f:
114
+ json.dump(admin_config, f, indent=2, ensure_ascii=False)
115
+ return True
116
+ except Exception as e:
117
+ logger.error(f"保存管理员配置失败: {e}")
118
+ return False
119
+
120
+ async def load_admin_config(self) -> Dict[str, Any]:
121
+ """加载管理员配置"""
122
+ try:
123
+ if self.admin_config_file.exists():
124
+ with open(self.admin_config_file, "r", encoding="utf-8") as f:
125
+ return json.load(f)
126
+ except Exception as e:
127
+ logger.error(f"加载管理员配置失败: {e}")
128
+ return {}
129
+
130
+
131
+ class SQLDatabase(DatabaseInterface):
132
+ """SQL数据库实现"""
133
+
134
+ def __init__(self, database_url: str):
135
+ self.database_url = database_url
136
+ self.pool = None
137
+ self._db_type = self._detect_db_type(database_url)
138
+
139
+ def _detect_db_type(self, url: str) -> str:
140
+ """检测数据库类型"""
141
+ if url.startswith("postgresql://") or url.startswith("postgres://"):
142
+ return "postgresql"
143
+ elif url.startswith("mysql://") or url.startswith("mysql+"):
144
+ return "mysql"
145
+ elif url.startswith("sqlite://"):
146
+ return "sqlite"
147
+ else:
148
+ return "unknown"
149
+
150
+ async def initialize(self) -> bool:
151
+ """初始化SQL数据库连接和表结构"""
152
+ try:
153
+ if self._db_type == "postgresql":
154
+ await self._init_postgresql()
155
+ elif self._db_type == "mysql":
156
+ await self._init_mysql()
157
+ elif self._db_type == "sqlite":
158
+ await self._init_sqlite()
159
+ else:
160
+ logger.error(f"不支持的数据库类型: {self._db_type}")
161
+ return False
162
+
163
+ await self._create_tables()
164
+ return True
165
+ except Exception as e:
166
+ logger.error(f"SQL数据库初始化失败: {e}")
167
+ return False
168
+
169
+ async def _init_postgresql(self):
170
+ """初始化PostgreSQL连接"""
171
+ try:
172
+ import asyncpg
173
+ self.pool = await asyncpg.create_pool(self.database_url)
174
+ except ImportError:
175
+ raise ImportError("请安装 asyncpg: pip install asyncpg")
176
+
177
+ async def _init_mysql(self):
178
+ """初始化MySQL连接"""
179
+ try:
180
+ import aiomysql
181
+ # 解析连接URL
182
+ from urllib.parse import urlparse
183
+ parsed = urlparse(self.database_url)
184
+ self.pool = await aiomysql.create_pool(
185
+ host=parsed.hostname,
186
+ port=parsed.port or 3306,
187
+ user=parsed.username,
188
+ password=parsed.password,
189
+ db=parsed.path.lstrip('/'),
190
+ charset='utf8mb4'
191
+ )
192
+ except ImportError:
193
+ raise ImportError("请安装 aiomysql: pip install aiomysql")
194
+
195
+ async def _init_sqlite(self):
196
+ """初始化SQLite连接"""
197
+ try:
198
+ import aiosqlite
199
+ db_path = self.database_url.replace("sqlite://", "")
200
+ self.db_path = db_path
201
+ except ImportError:
202
+ raise ImportError("请安装 aiosqlite: pip install aiosqlite")
203
+
204
+ async def _create_tables(self):
205
+ """创建数据表"""
206
+ if self._db_type == "postgresql":
207
+ await self._create_tables_postgresql()
208
+ elif self._db_type == "mysql":
209
+ await self._create_tables_mysql()
210
+ elif self._db_type == "sqlite":
211
+ await self._create_tables_sqlite()
212
+
213
+ async def _create_tables_postgresql(self):
214
+ """创建PostgreSQL表"""
215
+ async with self.pool.acquire() as conn:
216
+ await conn.execute("""
217
+ CREATE TABLE IF NOT EXISTS kiro_config (
218
+ id SERIAL PRIMARY KEY,
219
+ key VARCHAR(255) UNIQUE NOT NULL,
220
+ value JSONB NOT NULL,
221
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
222
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
223
+ )
224
+ """)
225
+
226
+ await conn.execute("""
227
+ CREATE TABLE IF NOT EXISTS kiro_admin (
228
+ id SERIAL PRIMARY KEY,
229
+ key VARCHAR(255) UNIQUE NOT NULL,
230
+ value JSONB NOT NULL,
231
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
232
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
233
+ )
234
+ """)
235
+
236
+ async def _create_tables_mysql(self):
237
+ """创建MySQL表"""
238
+ async with self.pool.acquire() as conn:
239
+ async with conn.cursor() as cursor:
240
+ await cursor.execute("""
241
+ CREATE TABLE IF NOT EXISTS kiro_config (
242
+ id INT AUTO_INCREMENT PRIMARY KEY,
243
+ `key` VARCHAR(255) UNIQUE NOT NULL,
244
+ value JSON NOT NULL,
245
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
246
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
247
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
248
+ """)
249
+
250
+ await cursor.execute("""
251
+ CREATE TABLE IF NOT EXISTS kiro_admin (
252
+ id INT AUTO_INCREMENT PRIMARY KEY,
253
+ `key` VARCHAR(255) UNIQUE NOT NULL,
254
+ value JSON NOT NULL,
255
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
256
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
257
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
258
+ """)
259
+
260
+ async def _create_tables_sqlite(self):
261
+ """创建SQLite表"""
262
+ import aiosqlite
263
+ async with aiosqlite.connect(self.db_path) as db:
264
+ await db.execute("""
265
+ CREATE TABLE IF NOT EXISTS kiro_config (
266
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
267
+ key TEXT UNIQUE NOT NULL,
268
+ value TEXT NOT NULL,
269
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
270
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
271
+ )
272
+ """)
273
+
274
+ await db.execute("""
275
+ CREATE TABLE IF NOT EXISTS kiro_admin (
276
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
277
+ key TEXT UNIQUE NOT NULL,
278
+ value TEXT NOT NULL,
279
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
280
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
281
+ )
282
+ """)
283
+ await db.commit()
284
+
285
+ async def _get_config_value(self, table: str, key: str) -> Optional[Dict[str, Any]]:
286
+ """从指定表获取配置值"""
287
+ try:
288
+ if self._db_type == "postgresql":
289
+ async with self.pool.acquire() as conn:
290
+ row = await conn.fetchrow(f"SELECT value FROM {table} WHERE key = $1", key)
291
+ return row['value'] if row else None
292
+
293
+ elif self._db_type == "mysql":
294
+ async with self.pool.acquire() as conn:
295
+ async with conn.cursor() as cursor:
296
+ await cursor.execute(f"SELECT value FROM {table} WHERE `key` = %s", (key,))
297
+ row = await cursor.fetchone()
298
+ return json.loads(row[0]) if row else None
299
+
300
+ elif self._db_type == "sqlite":
301
+ import aiosqlite
302
+ async with aiosqlite.connect(self.db_path) as db:
303
+ cursor = await db.execute(f"SELECT value FROM {table} WHERE key = ?", (key,))
304
+ row = await cursor.fetchone()
305
+ return json.loads(row[0]) if row else None
306
+
307
+ except Exception as e:
308
+ logger.error(f"获取配置失败 {table}.{key}: {e}")
309
+ return None
310
+
311
+ async def _set_config_value(self, table: str, key: str, value: Dict[str, Any]) -> bool:
312
+ """设置配置值到指定表"""
313
+ try:
314
+ if self._db_type == "postgresql":
315
+ async with self.pool.acquire() as conn:
316
+ await conn.execute(f"""
317
+ INSERT INTO {table} (key, value) VALUES ($1, $2)
318
+ ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = CURRENT_TIMESTAMP
319
+ """, key, json.dumps(value))
320
+
321
+ elif self._db_type == "mysql":
322
+ async with self.pool.acquire() as conn:
323
+ async with conn.cursor() as cursor:
324
+ await cursor.execute(f"""
325
+ INSERT INTO {table} (`key`, value) VALUES (%s, %s)
326
+ ON DUPLICATE KEY UPDATE value = %s, updated_at = CURRENT_TIMESTAMP
327
+ """, (key, json.dumps(value), json.dumps(value)))
328
+ await conn.commit()
329
+
330
+ elif self._db_type == "sqlite":
331
+ import aiosqlite
332
+ async with aiosqlite.connect(self.db_path) as db:
333
+ await db.execute(f"""
334
+ INSERT OR REPLACE INTO {table} (key, value, updated_at)
335
+ VALUES (?, ?, CURRENT_TIMESTAMP)
336
+ """, (key, json.dumps(value)))
337
+ await db.commit()
338
+
339
+ return True
340
+ except Exception as e:
341
+ logger.error(f"设置配置失败 {table}.{key}: {e}")
342
+ return False
343
+
344
+ async def save_accounts(self, accounts: List[Dict[str, Any]]) -> bool:
345
+ """保存账号配置"""
346
+ config = await self.load_config()
347
+ config["accounts"] = accounts
348
+ return await self.save_config(config)
349
+
350
+ async def load_accounts(self) -> List[Dict[str, Any]]:
351
+ """加载账号配置"""
352
+ config = await self.load_config()
353
+ return config.get("accounts", [])
354
+
355
+ async def save_config(self, config: Dict[str, Any]) -> bool:
356
+ """保存完整配置"""
357
+ return await self._set_config_value("kiro_config", "main", config)
358
+
359
+ async def load_config(self) -> Dict[str, Any]:
360
+ """加载完整配置"""
361
+ config = await self._get_config_value("kiro_config", "main")
362
+ return config or {}
363
+
364
+ async def save_admin_config(self, admin_config: Dict[str, Any]) -> bool:
365
+ """保存管理员配置"""
366
+ return await self._set_config_value("kiro_admin", "main", admin_config)
367
+
368
+ async def load_admin_config(self) -> Dict[str, Any]:
369
+ """加载管理员配置"""
370
+ config = await self._get_config_value("kiro_admin", "main")
371
+ return config or {}
372
+
373
+
374
+ # 数据库工厂
375
+ def create_database() -> DatabaseInterface:
376
+ """根据环境变量创建数据库实例"""
377
+ database_url = os.getenv("DATABASE_URL")
378
+
379
+ if database_url:
380
+ logger.info(f"使用远程数据库: {database_url.split('@')[0]}@***")
381
+ return SQLDatabase(database_url)
382
+ else:
383
+ from ..config import DATA_DIR
384
+ logger.info(f"使用文件系统数据库: {DATA_DIR}")
385
+ return FileSystemDatabase(DATA_DIR)
386
+
387
+
388
+ # 全局数据库实例
389
+ _db_instance: Optional[DatabaseInterface] = None
390
+
391
+ async def get_database() -> DatabaseInterface:
392
+ """获取数据库实例(单例模式)"""
393
+ global _db_instance
394
+ if _db_instance is None:
395
+ _db_instance = create_database()
396
+ await _db_instance.initialize()
397
+ return _db_instance
KiroProxy/kiro_proxy/core/persistence.py CHANGED
@@ -1,69 +1,98 @@
1
- """配置持久化"""
2
- import json
3
- from pathlib import Path
4
  from typing import List, Dict, Any
 
5
 
6
- # 统一使用 config.py 中 DATA_DIR
7
- from ..config import DATA_DIR
 
 
8
 
9
- # 配置文件路径
10
- CONFIG_DIR = DATA_DIR
11
- CONFIG_FILE = CONFIG_DIR / "config.json"
12
 
 
 
 
13
 
14
- def ensure_config_dir():
15
- """确保配置目录存在"""
16
- CONFIG_DIR.mkdir(parents=True, exist_ok=True)
17
 
 
 
 
18
 
19
- def save_accounts(accounts: List[Dict[str, Any]]) -> bool:
20
- """保存账号配置"""
 
 
 
 
 
21
  try:
22
- ensure_config_dir()
23
- config = load_config()
24
- config["accounts"] = accounts
25
- with open(CONFIG_FILE, "w", encoding="utf-8") as f:
26
- json.dump(config, f, indent=2, ensure_ascii=False)
27
- return True
28
  except Exception as e:
29
- print(f"[Persistence] 保存配置失败: {e}")
30
  return False
31
 
 
 
 
 
 
 
 
 
32
 
33
- def load_accounts() -> List[Dict[str, Any]]:
34
- """加载账号配置"""
35
- config = load_config()
36
- return config.get("accounts", [])
37
-
 
 
 
38
 
39
- def load_config() -> Dict[str, Any]:
40
- """加载完整配置"""
41
  try:
42
- if CONFIG_FILE.exists():
43
- with open(CONFIG_FILE, "r", encoding="utf-8") as f:
44
- return json.load(f)
45
  except Exception as e:
46
  print(f"[Persistence] 加载配置失败: {e}")
47
- return {}
48
 
49
-
50
- def save_config(config: Dict[str, Any]) -> bool:
51
- """保存完整配置"""
52
  try:
53
- ensure_config_dir()
54
- with open(CONFIG_FILE, "w", encoding="utf-8") as f:
55
- json.dump(config, f, indent=2, ensure_ascii=False)
56
- return True
57
  except Exception as e:
58
- print(f"[Persistence] 保存配置失败: {e}")
59
  return False
60
 
 
 
 
 
 
 
 
 
61
 
62
- def export_config() -> Dict[str, Any]:
63
- """导出配置(用于备份)"""
64
- return load_config()
65
 
 
 
 
66
 
67
- def import_config(config: Dict[str, Any]) -> bool:
68
- """导入配置(用于恢复)"""
69
- return save_config(config)
 
 
1
+ """配置持久化 - 支持文件系统和远程数据库"""
2
+ import asyncio
 
3
  from typing import List, Dict, Any
4
+ from .database import get_database
5
 
6
+ # 向后兼容同步接口
7
+ def save_accounts(accounts: List[Dict[str, Any]]) -> bool:
8
+ """保存账号配置(同步接口)"""
9
+ return asyncio.run(_save_accounts_async(accounts))
10
 
11
+ def load_accounts() -> List[Dict[str, Any]]:
12
+ """加载账号配置(同步接口)"""
13
+ return asyncio.run(_load_accounts_async())
14
 
15
+ def save_config(config: Dict[str, Any]) -> bool:
16
+ """保存完整配置(同步接口)"""
17
+ return asyncio.run(_save_config_async(config))
18
 
19
+ def load_config() -> Dict[str, Any]:
20
+ """加载完整配置(同步接口)"""
21
+ return asyncio.run(_load_config_async())
22
 
23
+ def export_config() -> Dict[str, Any]:
24
+ """导出配置(用于备份)"""
25
+ return load_config()
26
 
27
+ def import_config(config: Dict[str, Any]) -> bool:
28
+ """导入配置(用于恢复)"""
29
+ return save_config(config)
30
+
31
+ # 异步接口
32
+ async def _save_accounts_async(accounts: List[Dict[str, Any]]) -> bool:
33
+ """保存账号配置(异步)"""
34
  try:
35
+ db = await get_database()
36
+ return await db.save_accounts(accounts)
 
 
 
 
37
  except Exception as e:
38
+ print(f"[Persistence] 保存账号配置失败: {e}")
39
  return False
40
 
41
+ async def _load_accounts_async() -> List[Dict[str, Any]]:
42
+ """加载账号配置(异步)"""
43
+ try:
44
+ db = await get_database()
45
+ return await db.load_accounts()
46
+ except Exception as e:
47
+ print(f"[Persistence] 加载账号配置失败: {e}")
48
+ return []
49
 
50
+ async def _save_config_async(config: Dict[str, Any]) -> bool:
51
+ """保存完整配置(异步)"""
52
+ try:
53
+ db = await get_database()
54
+ return await db.save_config(config)
55
+ except Exception as e:
56
+ print(f"[Persistence] 保存配置失败: {e}")
57
+ return False
58
 
59
+ async def _load_config_async() -> Dict[str, Any]:
60
+ """加载完整配置(异步)"""
61
  try:
62
+ db = await get_database()
63
+ return await db.load_config()
 
64
  except Exception as e:
65
  print(f"[Persistence] 加载配置失败: {e}")
66
+ return {}
67
 
68
+ # 新增:管理员配置接口
69
+ async def save_admin_config_async(admin_config: Dict[str, Any]) -> bool:
70
+ """保存管理员配置(异步)"""
71
  try:
72
+ db = await get_database()
73
+ return await db.save_admin_config(admin_config)
 
 
74
  except Exception as e:
75
+ print(f"[Persistence] 保存管理员配置失败: {e}")
76
  return False
77
 
78
+ async def load_admin_config_async() -> Dict[str, Any]:
79
+ """加载管理员配置(异步)"""
80
+ try:
81
+ db = await get_database()
82
+ return await db.load_admin_config()
83
+ except Exception as e:
84
+ print(f"[Persistence] 加载管理员配置失败: {e}")
85
+ return {}
86
 
87
+ def save_admin_config(admin_config: Dict[str, Any]) -> bool:
88
+ """保存管理员配置(同步接口)"""
89
+ return asyncio.run(save_admin_config_async(admin_config))
90
 
91
+ def load_admin_config() -> Dict[str, Any]:
92
+ """加载管理员配置(同步接口)"""
93
+ return asyncio.run(load_admin_config_async())
94
 
95
+ # 向后兼容:保留原有函数名
96
+ def ensure_config_dir():
97
+ """确保配置目录存在(向后兼容,现在由数据库层处理)"""
98
+ pass
KiroProxy/kiro_proxy/routers/admin.py CHANGED
@@ -1,12 +1,126 @@
1
- from fastapi import APIRouter, Request, HTTPException
 
2
 
3
  from ..core import get_history_config, get_rate_limiter, update_history_config
 
 
4
  from ..handlers import admin as admin_handler
5
  from ..resources import get_resource_path
6
 
7
  router = APIRouter(prefix="/api")
8
 
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  @router.get("/status")
11
  async def api_status():
12
  return await admin_handler.get_status()
@@ -27,28 +141,30 @@ async def api_logs(limit: int = 100):
27
  return await admin_handler.get_logs(limit)
28
 
29
 
 
 
30
  @router.get("/accounts/export")
31
- async def api_export_accounts():
32
  return await admin_handler.export_accounts()
33
 
34
 
35
  @router.post("/accounts/import")
36
- async def api_import_accounts(request: Request):
37
  return await admin_handler.import_accounts(request)
38
 
39
 
40
  @router.post("/accounts/manual")
41
- async def api_add_manual_token(request: Request):
42
  return await admin_handler.add_manual_token(request)
43
 
44
 
45
  @router.post("/accounts/batch")
46
- async def api_batch_import_accounts(request: Request):
47
  return await admin_handler.batch_import_accounts(request)
48
 
49
 
50
  @router.post("/accounts/refresh-all")
51
- async def api_refresh_all():
52
  return await admin_handler.refresh_all_tokens()
53
 
54
 
@@ -63,7 +179,7 @@ async def api_accounts_summary():
63
 
64
 
65
  @router.post("/accounts/refresh-all-quotas")
66
- async def api_refresh_all_quotas():
67
  return await admin_handler.refresh_all_quotas()
68
 
69
 
@@ -73,7 +189,7 @@ async def api_refresh_progress():
73
 
74
 
75
  @router.post("/refresh/all")
76
- async def api_refresh_all_with_progress():
77
  return await admin_handler.refresh_all_with_progress()
78
 
79
 
@@ -83,7 +199,7 @@ async def api_get_refresh_config():
83
 
84
 
85
  @router.put("/refresh/config")
86
- async def api_update_refresh_config(request: Request):
87
  return await admin_handler.update_refresh_config(request)
88
 
89
 
@@ -98,22 +214,22 @@ async def api_accounts():
98
 
99
 
100
  @router.post("/accounts")
101
- async def api_add_account(request: Request):
102
  return await admin_handler.add_account(request)
103
 
104
 
105
  @router.delete("/accounts/{account_id}")
106
- async def api_delete_account(account_id: str):
107
  return await admin_handler.delete_account(account_id)
108
 
109
 
110
  @router.put("/accounts/{account_id}")
111
- async def api_update_account(account_id: str, request: Request):
112
  return await admin_handler.update_account(account_id, request)
113
 
114
 
115
  @router.post("/accounts/{account_id}/toggle")
116
- async def api_toggle_account(account_id: str):
117
  return await admin_handler.toggle_account(account_id)
118
 
119
 
 
1
+ from fastapi import APIRouter, Request, HTTPException, Depends, Response
2
+ from fastapi.responses import JSONResponse
3
 
4
  from ..core import get_history_config, get_rate_limiter, update_history_config
5
+ from ..core.auth_middleware import require_admin_auth, optional_admin_auth
6
+ from ..core.admin_auth import get_admin_auth, authenticate_admin, create_admin_session, revoke_admin_session
7
  from ..handlers import admin as admin_handler
8
  from ..resources import get_resource_path
9
 
10
  router = APIRouter(prefix="/api")
11
 
12
 
13
+ # ==================== 认证相关端点 ====================
14
+
15
+ @router.post("/auth/login")
16
+ async def api_admin_login(request: Request, response: Response):
17
+ """管理员登录"""
18
+ try:
19
+ data = await request.json()
20
+ password = data.get("password")
21
+
22
+ if not password:
23
+ raise HTTPException(status_code=400, detail="密码不能为空")
24
+
25
+ if not authenticate_admin(password):
26
+ raise HTTPException(status_code=401, detail="密码错误")
27
+
28
+ # 创建会话
29
+ session_id = create_admin_session()
30
+
31
+ # 设置Cookie(可选,主要用Bearer Token)
32
+ response.set_cookie(
33
+ key="admin_session",
34
+ value=session_id,
35
+ max_age=24 * 60 * 60, # 24小时
36
+ httponly=True,
37
+ secure=False, # 在生产环境中应该设为True
38
+ samesite="lax"
39
+ )
40
+
41
+ return {
42
+ "success": True,
43
+ "session_id": session_id,
44
+ "message": "登录成功"
45
+ }
46
+
47
+ except HTTPException:
48
+ raise
49
+ except Exception as e:
50
+ raise HTTPException(status_code=500, detail=f"登录失败: {str(e)}")
51
+
52
+
53
+ @router.post("/auth/logout")
54
+ async def api_admin_logout(
55
+ response: Response,
56
+ session_id: str = Depends(require_admin_auth)
57
+ ):
58
+ """管理员登出"""
59
+ try:
60
+ # 撤销会话
61
+ revoke_admin_session(session_id)
62
+
63
+ # 清除Cookie
64
+ response.delete_cookie(key="admin_session")
65
+
66
+ return {
67
+ "success": True,
68
+ "message": "登出成功"
69
+ }
70
+
71
+ except Exception as e:
72
+ raise HTTPException(status_code=500, detail=f"登出失败: {str(e)}")
73
+
74
+
75
+ @router.get("/auth/session")
76
+ async def api_admin_session_info(session_id: str = Depends(require_admin_auth)):
77
+ """获取当前会话信息"""
78
+ try:
79
+ auth = get_admin_auth()
80
+ session_info = auth.get_session_info(session_id)
81
+
82
+ if not session_info:
83
+ raise HTTPException(status_code=401, detail="会话无效")
84
+
85
+ return {
86
+ "success": True,
87
+ "session": session_info
88
+ }
89
+
90
+ except HTTPException:
91
+ raise
92
+ except Exception as e:
93
+ raise HTTPException(status_code=500, detail=f"获取会话信息失败: {str(e)}")
94
+
95
+
96
+ @router.get("/auth/sessions")
97
+ async def api_admin_sessions(session_id: str = Depends(require_admin_auth)):
98
+ """获取所有活跃会话"""
99
+ try:
100
+ auth = get_admin_auth()
101
+ sessions = auth.get_active_sessions()
102
+
103
+ return {
104
+ "success": True,
105
+ "sessions": sessions
106
+ }
107
+
108
+ except Exception as e:
109
+ raise HTTPException(status_code=500, detail=f"获取会话列表失败: {str(e)}")
110
+
111
+
112
+ @router.post("/auth/check")
113
+ async def api_admin_auth_check(session_id: str = Depends(optional_admin_auth)):
114
+ """检查认证状态"""
115
+ return {
116
+ "authenticated": session_id is not None,
117
+ "session_id": session_id
118
+ }
119
+
120
+
121
+ # ==================== 公开API(无需认证) ====================
122
+
123
+
124
  @router.get("/status")
125
  async def api_status():
126
  return await admin_handler.get_status()
 
141
  return await admin_handler.get_logs(limit)
142
 
143
 
144
+ # ==================== 需要认证的管理员API ====================
145
+
146
  @router.get("/accounts/export")
147
+ async def api_export_accounts(session_id: str = Depends(require_admin_auth)):
148
  return await admin_handler.export_accounts()
149
 
150
 
151
  @router.post("/accounts/import")
152
+ async def api_import_accounts(request: Request, session_id: str = Depends(require_admin_auth)):
153
  return await admin_handler.import_accounts(request)
154
 
155
 
156
  @router.post("/accounts/manual")
157
+ async def api_add_manual_token(request: Request, session_id: str = Depends(require_admin_auth)):
158
  return await admin_handler.add_manual_token(request)
159
 
160
 
161
  @router.post("/accounts/batch")
162
+ async def api_batch_import_accounts(request: Request, session_id: str = Depends(require_admin_auth)):
163
  return await admin_handler.batch_import_accounts(request)
164
 
165
 
166
  @router.post("/accounts/refresh-all")
167
+ async def api_refresh_all(session_id: str = Depends(require_admin_auth)):
168
  return await admin_handler.refresh_all_tokens()
169
 
170
 
 
179
 
180
 
181
  @router.post("/accounts/refresh-all-quotas")
182
+ async def api_refresh_all_quotas(session_id: str = Depends(require_admin_auth)):
183
  return await admin_handler.refresh_all_quotas()
184
 
185
 
 
189
 
190
 
191
  @router.post("/refresh/all")
192
+ async def api_refresh_all_with_progress(session_id: str = Depends(require_admin_auth)):
193
  return await admin_handler.refresh_all_with_progress()
194
 
195
 
 
199
 
200
 
201
  @router.put("/refresh/config")
202
+ async def api_update_refresh_config(request: Request, session_id: str = Depends(require_admin_auth)):
203
  return await admin_handler.update_refresh_config(request)
204
 
205
 
 
214
 
215
 
216
  @router.post("/accounts")
217
+ async def api_add_account(request: Request, session_id: str = Depends(require_admin_auth)):
218
  return await admin_handler.add_account(request)
219
 
220
 
221
  @router.delete("/accounts/{account_id}")
222
+ async def api_delete_account(account_id: str, session_id: str = Depends(require_admin_auth)):
223
  return await admin_handler.delete_account(account_id)
224
 
225
 
226
  @router.put("/accounts/{account_id}")
227
+ async def api_update_account(account_id: str, request: Request, session_id: str = Depends(require_admin_auth)):
228
  return await admin_handler.update_account(account_id, request)
229
 
230
 
231
  @router.post("/accounts/{account_id}/toggle")
232
+ async def api_toggle_account(account_id: str, session_id: str = Depends(require_admin_auth)):
233
  return await admin_handler.toggle_account(account_id)
234
 
235
 
KiroProxy/requirements.txt CHANGED
@@ -6,3 +6,8 @@ pytest>=7.0.0
6
  hypothesis>=6.0.0
7
  tiktoken>=0.5.0
8
  cbor2>=5.4.0
 
 
 
 
 
 
6
  hypothesis>=6.0.0
7
  tiktoken>=0.5.0
8
  cbor2>=5.4.0
9
+
10
+ # 数据库驱动(可选,根据DATABASE_URL自动选择)
11
+ asyncpg>=0.28.0 # PostgreSQL
12
+ aiomysql>=0.2.0 # MySQL
13
+ aiosqlite>=0.19.0 # SQLite
README.md ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: KiroProxy
3
+ emoji: 🚀
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ short_description: Kiro IDE API 反向代理服务器 - 支持多账号管理和多协议转换
10
+ app_port: 7860
11
+ ---
12
+
13
+ # KiroProxy
14
+
15
+ 🚀 **Kiro IDE API 反向代理服务器** - 支持多账号管理和多协议转换
16
+
17
+ ## 功能特性
18
+
19
+ - **多协议支持** - 兼容 OpenAI、Anthropic、Gemini 三种 API 协议
20
+ - **多账号轮询** - 支持随机、轮询、会话粘性等选择策略
21
+ - **自动 Token 管理** - 后台自动刷新即将过期的 Token
22
+ - **配额管理** - 智能配额调度和超限冷却
23
+ - **Web UI** - 内置管理界面,支持账号管理、监控、日志
24
+ - **数据库支持** - 支持文件系统和远程SQL数据库存储
25
+ - **管理员认证** - 安全的密码认证和会话管理系统
26
+
27
+ ## 快速开始
28
+
29
+ ### 本地运行
30
+
31
+ ```bash
32
+ # 克隆项目
33
+ git clone <repository-url>
34
+ cd KiroProxy
35
+
36
+ # 安装依赖
37
+ pip install -r requirements.txt
38
+
39
+ # 启动服务
40
+ python run.py 8080
41
+ ```
42
+
43
+ ### Docker 部署
44
+
45
+ ```bash
46
+ # 构建镜像
47
+ docker build -t kiroproxy .
48
+
49
+ # 运行容器
50
+ docker run -p 7860:7860 \
51
+ -e ADMIN_PASSWORD=your_password \
52
+ -e DATABASE_URL=postgresql://user:pass@host:5432/db \
53
+ kiroproxy
54
+ ```
55
+
56
+ ### Hugging Face Space 部署
57
+
58
+ 1. Fork 此项目到你的 Hugging Face Space
59
+ 2. 在 Space 设置中配置环境变量:
60
+ - `ADMIN_PASSWORD`: 管理员密码
61
+ - `DATABASE_URL`: 远程数据库连接(可选)
62
+ 3. Space 将自动构建和部署
63
+
64
+ ## 环境变量配置
65
+
66
+ 参考 `.env.example` 文件了解所有可用的环境变量配置选项。
67
+
68
+ ### 核心配置
69
+
70
+ - `ADMIN_PASSWORD`: 管理员面板密码(强烈推荐设置)
71
+ - `DATABASE_URL`: 数据库连接URL(可选,支持PostgreSQL/MySQL/SQLite)
72
+ - `PORT`: 服务器端口(默认:8080,Hugging Face Space使用7860)
73
+
74
+ ## 管理员面板
75
+
76
+ 访问 `http://localhost:8080` 进入管理界面。
77
+
78
+ 首次启动时:
79
+ - 如果设置了 `ADMIN_PASSWORD` 环境变量,使用该密码登录
80
+ - 如果未设置,系统会自动生成随机密码并在启动日志中显示
81
+
82
+ ## 数据存储
83
+
84
+ ### 文件系统存储(默认)
85
+ - 配置存储在 `~/.kiro-proxy/` 目录
86
+ - 适合本地开发和小规模部署
87
+
88
+ ### 远程数据库存储
89
+ - 支持 PostgreSQL、MySQL、SQLite
90
+ - 适合生产环境和多实例部署
91
+ - 通过 `DATABASE_URL` 环境变量配置
92
+
93
+ ## API 协议支持
94
+
95
+ ### OpenAI 兼容
96
+ ```
97
+ POST /v1/chat/completions
98
+ ```
99
+
100
+ ### Anthropic 兼容
101
+ ```
102
+ POST /v1/messages
103
+ ```
104
+
105
+ ### Gemini 兼容
106
+ ```
107
+ POST /v1beta/models/{model}:generateContent
108
+ ```
109
+
110
+ ## 许可证
111
+
112
+ MIT License
113
+
114
+ ## 贡献
115
+
116
+ 欢迎提交 Issue 和 Pull Request!
README_HF.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ title: KiroProxy
2
+ emoji: 🚀
3
+ colorFrom: blue
4
+ colorTo: purple
5
+ sdk: docker
6
+ pinned: false
7
+ license: mit
8
+ short_description: Kiro IDE API 反向代理服务器 - 支持多账号管理和多协议转换
9
+ app_port: 7860
requirements.txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi>=0.100.0
2
+ uvicorn>=0.23.0
3
+ httpx>=0.24.0
4
+ requests>=2.31.0
5
+ pytest>=7.0.0
6
+ hypothesis>=6.0.0
7
+ tiktoken>=0.5.0
8
+ cbor2>=5.4.0
9
+
10
+ # 数据库驱动(可选,根据DATABASE_URL自动选择)
11
+ asyncpg>=0.28.0 # PostgreSQL
12
+ aiomysql>=0.2.0 # MySQL
13
+ aiosqlite>=0.19.0 # SQLite