feat: 为token支持独立client_id绑定

This commit is contained in:
TheSmallHanCat
2025-12-04 17:33:03 +08:00
parent 0a09cd5b00
commit e3819a4805
5 changed files with 58 additions and 16 deletions

View File

@@ -62,6 +62,7 @@ class AddTokenRequest(BaseModel):
token: str # Access Token (AT)
st: Optional[str] = None # Session Token (optional, for storage)
rt: Optional[str] = None # Refresh Token (optional, for storage)
client_id: Optional[str] = None # Client ID (optional)
remark: Optional[str] = None
image_enabled: bool = True # Enable image generation
video_enabled: bool = True # Enable video generation
@@ -81,6 +82,7 @@ class UpdateTokenRequest(BaseModel):
token: Optional[str] = None # Access Token
st: Optional[str] = None
rt: Optional[str] = None
client_id: Optional[str] = None # Client ID
remark: Optional[str] = None
image_enabled: Optional[bool] = None # Enable image generation
video_enabled: Optional[bool] = None # Enable video generation
@@ -169,6 +171,7 @@ async def get_tokens(token: str = Depends(verify_admin_token)) -> List[dict]:
"token": token.token, # 完整的Access Token
"st": token.st, # 完整的Session Token
"rt": token.rt, # 完整的Refresh Token
"client_id": token.client_id, # Client ID
"email": token.email,
"name": token.name,
"remark": token.remark,
@@ -210,6 +213,7 @@ async def add_token(request: AddTokenRequest, token: str = Depends(verify_admin_
token_value=request.token,
st=request.st,
rt=request.rt,
client_id=request.client_id,
remark=request.remark,
update_if_exists=False,
image_enabled=request.image_enabled,
@@ -412,6 +416,7 @@ async def update_token(
token=request.token,
st=request.st,
rt=request.rt,
client_id=request.client_id,
remark=request.remark,
image_enabled=request.image_enabled,
video_enabled=request.video_enabled,

View File

@@ -194,6 +194,7 @@ class Database:
("video_enabled", "BOOLEAN DEFAULT 1"),
("image_concurrency", "INTEGER DEFAULT -1"),
("video_concurrency", "INTEGER DEFAULT -1"),
("client_id", "TEXT"),
]
for col_name, col_type in columns_to_add:
@@ -269,6 +270,7 @@ class Database:
name TEXT NOT NULL,
st TEXT,
rt TEXT,
client_id TEXT,
remark TEXT,
expiry_time TIMESTAMP,
is_active BOOLEAN DEFAULT 1,
@@ -561,12 +563,12 @@ class Database:
"""Add a new token"""
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("""
INSERT INTO tokens (token, email, username, name, st, rt, remark, expiry_time, is_active,
INSERT INTO tokens (token, email, username, name, st, rt, client_id, remark, expiry_time, is_active,
plan_type, plan_title, subscription_end, sora2_supported, sora2_invite_code,
sora2_redeemed_count, sora2_total_count, sora2_remaining_count, sora2_cooldown_until,
image_enabled, video_enabled, image_concurrency, video_concurrency)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (token.token, token.email, "", token.name, token.st, token.rt,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (token.token, token.email, "", token.name, token.st, token.rt, token.client_id,
token.remark, token.expiry_time, token.is_active,
token.plan_type, token.plan_title, token.subscription_end,
token.sora2_supported, token.sora2_invite_code,
@@ -701,6 +703,7 @@ class Database:
token: Optional[str] = None,
st: Optional[str] = None,
rt: Optional[str] = None,
client_id: Optional[str] = None,
remark: Optional[str] = None,
expiry_time: Optional[datetime] = None,
plan_type: Optional[str] = None,
@@ -710,7 +713,7 @@ class Database:
video_enabled: Optional[bool] = None,
image_concurrency: Optional[int] = None,
video_concurrency: Optional[int] = None):
"""Update token (AT, ST, RT, remark, expiry_time, subscription info, image_enabled, video_enabled)"""
"""Update token (AT, ST, RT, client_id, remark, expiry_time, subscription info, image_enabled, video_enabled)"""
async with aiosqlite.connect(self.db_path) as db:
# Build dynamic update query
updates = []
@@ -728,6 +731,10 @@ class Database:
updates.append("rt = ?")
params.append(rt)
if client_id is not None:
updates.append("client_id = ?")
params.append(client_id)
if remark is not None:
updates.append("remark = ?")
params.append(remark)

View File

@@ -11,6 +11,7 @@ class Token(BaseModel):
name: Optional[str] = ""
st: Optional[str] = None
rt: Optional[str] = None
client_id: Optional[str] = None
remark: Optional[str] = None
expiry_time: Optional[datetime] = None
is_active: bool = True

View File

@@ -513,9 +513,18 @@ class TokenManager:
debug_logger.log_info(f"[ST_TO_AT] 🔴 异常: {str(e)}")
raise
async def rt_to_at(self, refresh_token: str) -> dict:
"""Convert Refresh Token to Access Token"""
async def rt_to_at(self, refresh_token: str, client_id: Optional[str] = None) -> dict:
"""Convert Refresh Token to Access Token
Args:
refresh_token: Refresh Token
client_id: Client ID (optional, uses default if not provided)
"""
# Use provided client_id or default
effective_client_id = client_id or "app_LlGpXReQgckcGGUo2JrYvtJK"
debug_logger.log_info(f"[RT_TO_AT] 开始转换 Refresh Token 为 Access Token...")
debug_logger.log_info(f"[RT_TO_AT] 使用 Client ID: {effective_client_id[:20]}...")
proxy_url = await self.proxy_manager.get_proxy_url()
async with AsyncSession() as session:
@@ -527,7 +536,7 @@ class TokenManager:
kwargs = {
"headers": headers,
"json": {
"client_id": "app_LlGpXReQgckcGGUo2JrYvtJK",
"client_id": effective_client_id,
"grant_type": "refresh_token",
"redirect_uri": "com.openai.chat://auth0.openai.com/ios/com.openai.chat/callback",
"refresh_token": refresh_token
@@ -600,6 +609,7 @@ class TokenManager:
async def add_token(self, token_value: str,
st: Optional[str] = None,
rt: Optional[str] = None,
client_id: Optional[str] = None,
remark: Optional[str] = None,
update_if_exists: bool = False,
image_enabled: bool = True,
@@ -612,6 +622,7 @@ class TokenManager:
token_value: Access Token
st: Session Token (optional)
rt: Refresh Token (optional)
client_id: Client ID (optional)
remark: Remark (optional)
update_if_exists: If True, update existing token instead of raising error
image_enabled: Enable image generation (default: True)
@@ -747,6 +758,7 @@ class TokenManager:
name=name,
st=st,
rt=rt,
client_id=client_id,
remark=remark,
expiry_time=expiry_time,
is_active=True,
@@ -831,12 +843,13 @@ class TokenManager:
token: Optional[str] = None,
st: Optional[str] = None,
rt: Optional[str] = None,
client_id: Optional[str] = None,
remark: Optional[str] = None,
image_enabled: Optional[bool] = None,
video_enabled: Optional[bool] = None,
image_concurrency: Optional[int] = None,
video_concurrency: Optional[int] = None):
"""Update token (AT, ST, RT, remark, image_enabled, video_enabled, concurrency limits)"""
"""Update token (AT, ST, RT, client_id, remark, image_enabled, video_enabled, concurrency limits)"""
# If token (AT) is updated, decode JWT to get new expiry time
expiry_time = None
if token:
@@ -846,7 +859,7 @@ class TokenManager:
except Exception:
pass # If JWT decode fails, keep expiry_time as None
await self.db.update_token(token_id, token=token, st=st, rt=rt, remark=remark, expiry_time=expiry_time,
await self.db.update_token(token_id, token=token, st=st, rt=rt, client_id=client_id, remark=remark, expiry_time=expiry_time,
image_enabled=image_enabled, video_enabled=video_enabled,
image_concurrency=image_concurrency, video_concurrency=video_concurrency)
@@ -1063,7 +1076,7 @@ class TokenManager:
if not new_at and token_data.rt:
try:
debug_logger.log_info(f"[AUTO_REFRESH] 📝 Token {token_id}: 尝试使用 RT 刷新...")
result = await self.rt_to_at(token_data.rt)
result = await self.rt_to_at(token_data.rt, client_id=token_data.client_id)
new_at = result.get("access_token")
new_rt = result.get("refresh_token", token_data.rt) # RT might be updated
refresh_method = "RT"