fix: clarify New-API authentication failures
This commit is contained in:
@@ -176,6 +176,12 @@ def _clean_auth_header_value(value: Any, field_name: str) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def _is_dashboard_jwt(value: Any) -> bool:
|
||||
"""识别 New-API 面板登录 JWT,避免将其当作长期 API Token 使用。"""
|
||||
token = str(value or "").strip()
|
||||
return len(token) > 512 and token.count(".") == 2
|
||||
|
||||
|
||||
def _find_user_id(value: Any) -> str:
|
||||
if isinstance(value, dict):
|
||||
for key in ("id", "user_id", "userId"):
|
||||
@@ -537,6 +543,11 @@ class UpstreamClient:
|
||||
headers["Nox-Api-User"] = user_id
|
||||
elif self.auth_type == "new_api_token":
|
||||
token = _clean_auth_header_value(self.auth_config.get("token", ""), "New-API access token")
|
||||
if _is_dashboard_jwt(token):
|
||||
raise UpstreamError(
|
||||
"New-API token 是面板登录 JWT,不是用户 API Token;"
|
||||
"请在上游 Token 页面重新生成 Access Token"
|
||||
)
|
||||
user_id = self._user_header_value()
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
@@ -1095,6 +1106,15 @@ class UpstreamClient:
|
||||
if not email or not password:
|
||||
raise UpstreamError("login_password auth requires email and password in auth_config")
|
||||
resp = self._request("POST", login_path, {username_field: email, "password": password}, auth=False)
|
||||
data = _unwrap_data(resp)
|
||||
if isinstance(data, dict) and data.get("require_2fa"):
|
||||
raise UpstreamError(
|
||||
"New-API 账号启用了二次验证,SmartUp 暂不支持密码登录 2FA;"
|
||||
"请改用 Access Token 或 Cookie"
|
||||
)
|
||||
if not _is_success_response(resp):
|
||||
message = _response_message(resp, "New-API 登录失败")
|
||||
raise UpstreamError(message)
|
||||
token = _find_token(resp)
|
||||
if token:
|
||||
self._token = token
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from app.services.upstream_client import UpstreamClient
|
||||
import pytest
|
||||
|
||||
from app.services.upstream_client import UpstreamClient, UpstreamError
|
||||
|
||||
|
||||
def test_new_api_token_headers_include_authorization_and_new_api_user():
|
||||
@@ -16,6 +18,18 @@ def test_new_api_token_headers_include_authorization_and_new_api_user():
|
||||
assert "Nox-Api-User" not in headers
|
||||
|
||||
|
||||
def test_new_api_token_rejects_dashboard_jwt():
|
||||
client = UpstreamClient(
|
||||
base_url="http://newapi.local",
|
||||
api_prefix="",
|
||||
auth_type="new_api_token",
|
||||
auth_config={"token": "a" * 1200 + ".payload.signature", "user_id": "7"},
|
||||
)
|
||||
|
||||
with pytest.raises(UpstreamError, match="面板登录 JWT"):
|
||||
client._headers()
|
||||
|
||||
|
||||
def test_nox_token_headers_include_authorization_and_nox_api_user():
|
||||
client = UpstreamClient(
|
||||
base_url="http://nox.local",
|
||||
|
||||
@@ -8,7 +8,7 @@ import httpx
|
||||
import pytest
|
||||
|
||||
from app.services import upstream_client
|
||||
from app.services.upstream_client import UpstreamClient
|
||||
from app.services.upstream_client import UpstreamClient, UpstreamError
|
||||
|
||||
|
||||
class FakeHttpClient:
|
||||
@@ -412,3 +412,41 @@ def test_login_password_empty_prefix_defaults_to_user_api_login(monkeypatch):
|
||||
assert headers["Authorization"] == "Bearer nox-access"
|
||||
assert headers["Nox-Api-User"] == "9"
|
||||
assert headers["New-Api-User"] == "9"
|
||||
|
||||
|
||||
def test_login_password_surfaces_business_error_response(monkeypatch):
|
||||
def handler(request: httpx.Request, kwargs):
|
||||
return _response(request, 200, {
|
||||
"success": False,
|
||||
"message": "用户名或密码错误",
|
||||
})
|
||||
|
||||
_install_fake_client(monkeypatch, handler)
|
||||
client = UpstreamClient(
|
||||
base_url="http://newapi.local",
|
||||
api_prefix="",
|
||||
auth_type="login_password",
|
||||
auth_config={"email": "admin", "password": "wrong"},
|
||||
)
|
||||
|
||||
with pytest.raises(UpstreamError, match="用户名或密码错误"):
|
||||
client.login()
|
||||
|
||||
|
||||
def test_login_password_reports_unsupported_two_factor(monkeypatch):
|
||||
def handler(request: httpx.Request, kwargs):
|
||||
return _response(request, 200, {
|
||||
"success": True,
|
||||
"data": {"require_2fa": True, "flow_token": "flow"},
|
||||
})
|
||||
|
||||
_install_fake_client(monkeypatch, handler)
|
||||
client = UpstreamClient(
|
||||
base_url="http://newapi.local",
|
||||
api_prefix="",
|
||||
auth_type="login_password",
|
||||
auth_config={"email": "admin", "password": "secret"},
|
||||
)
|
||||
|
||||
with pytest.raises(UpstreamError, match="二次验证"):
|
||||
client.login()
|
||||
|
||||
@@ -698,6 +698,17 @@ function openAuthCapture() {
|
||||
authCaptureVisible.value = true
|
||||
}
|
||||
|
||||
function isDashboardJwt(value: string): boolean {
|
||||
const token = value.trim()
|
||||
return token.length > 512 && token.split('.').length === 3
|
||||
}
|
||||
|
||||
function rejectNewApiDashboardJwt(value: string): boolean {
|
||||
if (quickPlatform.value !== 'new-api-user' || !isDashboardJwt(value)) return false
|
||||
ElMessage.warning('提取到的是 New-API 面板登录 JWT,不是长期 Access Token;请在上游 Token 页面生成 API Token')
|
||||
return true
|
||||
}
|
||||
|
||||
function handleAuthCaptureSelect(candidate: {
|
||||
type: string
|
||||
value: string
|
||||
@@ -720,6 +731,7 @@ function handleAuthCaptureSelect(candidate: {
|
||||
}
|
||||
ElMessage.success('已填入 Nox Access Token')
|
||||
} else if (quickPlatform.value === 'new-api-user') {
|
||||
if (rejectNewApiDashboardJwt(candidate.value)) return
|
||||
form.value.auth_type = 'new_api_token'
|
||||
form.value.auth_config.token = candidate.value
|
||||
form.value.auth_config.provider = 'new-api'
|
||||
@@ -793,6 +805,7 @@ function handleAuthCaptureSelect(candidate: {
|
||||
ElMessage.success('已填入 API Key')
|
||||
} else if (candidate.type === 'credential') {
|
||||
// Try to guess — if value starts with 'sk-', treat as bearer
|
||||
if (rejectNewApiDashboardJwt(candidate.value)) return
|
||||
if (candidate.value.startsWith('sk-')) {
|
||||
form.value.auth_type = quickPlatform.value === 'nox-api' ? 'nox_token' : quickPlatform.value === 'new-api-user' ? 'new_api_token' : 'bearer'
|
||||
form.value.auth_config.token = candidate.value
|
||||
@@ -1164,6 +1177,10 @@ function openEdit(row: UpstreamData) {
|
||||
async function handleSave() {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
if (form.value.auth_type === 'new_api_token' && isDashboardJwt(String(form.value.auth_config.token || ''))) {
|
||||
ElMessage.warning('New-API 面板登录 JWT 不能作为长期 Access Token;请在上游 Token 页面生成 API Token')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = {
|
||||
|
||||
Reference in New Issue
Block a user