mirror of
https://github.com/cnlimiter/codex-register.git
synced 2026-06-27 02:01:38 +08:00
Merge pull request #109 from MisonL/feature/newapi-token-validation
fix: validate NEWAPI token input across UI and API
This commit is contained in:
@@ -23,9 +23,42 @@ def _normalize_base(api_url: str) -> str:
|
||||
return (api_url or "").strip().rstrip("/")
|
||||
|
||||
|
||||
def normalize_authorization_token(header_value: str, header_name: str = "Authorization Token") -> str:
|
||||
normalized_value = (header_value or "").strip()
|
||||
if not normalized_value:
|
||||
raise ValueError(f"{header_name} 不能为空")
|
||||
try:
|
||||
normalized_value.encode("ascii")
|
||||
except UnicodeEncodeError as exc:
|
||||
raise ValueError(f"{header_name} 包含非 ASCII 字符,请确认填写的是实际令牌而不是中文说明") from exc
|
||||
if any(ord(ch) < 32 or ord(ch) == 127 for ch in normalized_value):
|
||||
raise ValueError(f"{header_name} 包含非法控制字符")
|
||||
return normalized_value
|
||||
|
||||
|
||||
def _mask_header_value(header_value: str, keep: int = 4) -> str:
|
||||
"""
|
||||
Mask a sensitive header value for safe logging.
|
||||
|
||||
The strategy is:
|
||||
- If the value is empty, return an empty string.
|
||||
- If the length is <= keep, fully mask it (no characters revealed).
|
||||
- Otherwise, reveal only the last `keep` characters and mask the rest.
|
||||
"""
|
||||
if not header_value:
|
||||
return ""
|
||||
|
||||
length = len(header_value)
|
||||
if length <= keep:
|
||||
return "*" * length
|
||||
|
||||
masked_prefix = "*" * (length - keep)
|
||||
visible_suffix = header_value[-keep:]
|
||||
return masked_prefix + visible_suffix
|
||||
def _build_headers(api_key: str) -> dict:
|
||||
safe_api_key = normalize_authorization_token(api_key)
|
||||
return {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Authorization": f"Bearer {safe_api_key}",
|
||||
"New-Api-User": "1",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
@@ -68,7 +101,7 @@ def upload_to_newapi(
|
||||
"auto_ban": 1,
|
||||
"name": account.email or "",
|
||||
"type": resolved_channel_type,
|
||||
"key": json.dumps({"access_token": account.access_token or "", "account_id": account_name}, ensure_ascii=False),
|
||||
"key": json.dumps({"access_token": account.access_token or "", "account_id": account_name}, ensure_ascii=True),
|
||||
"base_url": resolved_channel_base_url,
|
||||
"models": resolved_channel_models,
|
||||
"multi_key_mode": "random",
|
||||
@@ -79,10 +112,20 @@ def upload_to_newapi(
|
||||
}
|
||||
|
||||
try:
|
||||
payload = json.dumps({"mode": "single", "channel": channel}, ensure_ascii=True)
|
||||
headers = _build_headers(api_key)
|
||||
headers["Content-Type"] = "application/json; charset=utf-8"
|
||||
|
||||
logger.info("NEWAPI 上传 URL: %s", url)
|
||||
safe_headers = dict(headers)
|
||||
if "Authorization" in safe_headers:
|
||||
safe_headers["Authorization"] = "REDACTED"
|
||||
logger.debug("NEWAPI 请求头: %s", safe_headers)
|
||||
|
||||
resp = cffi_requests.post(
|
||||
url,
|
||||
headers=_build_headers(api_key),
|
||||
json={"mode": "single", "channel": channel},
|
||||
headers=headers,
|
||||
data=payload.encode("utf-8"),
|
||||
proxies=None,
|
||||
timeout=30,
|
||||
impersonate="chrome110",
|
||||
|
||||
@@ -8,6 +8,7 @@ from pydantic import BaseModel
|
||||
|
||||
from ....database import crud
|
||||
from ....database.session import get_db
|
||||
from ....core.upload.newapi_upload import normalize_authorization_token
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -67,6 +68,13 @@ def _to_response(svc) -> NewapiServiceResponse:
|
||||
)
|
||||
|
||||
|
||||
def _validated_newapi_api_key(api_key: str) -> str:
|
||||
try:
|
||||
return normalize_authorization_token(api_key, header_name="Root Token / API Key")
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.get("", response_model=List[NewapiServiceResponse])
|
||||
async def list_newapi_services(enabled: Optional[bool] = None):
|
||||
with get_db() as db:
|
||||
@@ -81,7 +89,7 @@ async def create_newapi_service(request: NewapiServiceCreate):
|
||||
db,
|
||||
name=request.name,
|
||||
api_url=request.api_url,
|
||||
api_key=request.api_key,
|
||||
api_key=_validated_newapi_api_key(request.api_key),
|
||||
channel_type=request.channel_type,
|
||||
channel_base_url=request.channel_base_url,
|
||||
channel_models=request.channel_models,
|
||||
@@ -113,7 +121,7 @@ async def update_newapi_service(service_id: int, request: NewapiServiceUpdate):
|
||||
if request.api_url is not None:
|
||||
update_data["api_url"] = request.api_url
|
||||
if request.api_key:
|
||||
update_data["api_key"] = request.api_key
|
||||
update_data["api_key"] = _validated_newapi_api_key(request.api_key)
|
||||
if request.enabled is not None:
|
||||
update_data["enabled"] = request.enabled
|
||||
if request.priority is not None:
|
||||
|
||||
Reference in New Issue
Block a user