fix(auth): return HTTP 401 for token-auth failures (#13420)

Follow-up to #12488 #13386

### What problem does this PR solve?

Previously, token authentication failures returned HTTP 200 with an
error code in the response body.

This PR updates `token_required` to raise `Unauthorized` and relies on
the global error handler to return a structured JSON response with HTTP
401 status.

The response body structure (`code`, `message`, `data`) remains
unchanged to preserve compatibility with the official SDK.

Frontend logic has been updated to handle HTTP 401 responses in addition
to checking `data.code`.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
OliverW
2026-03-06 18:18:14 +08:00
committed by GitHub
parent 51be1f1442
commit 3ed91345aa
7 changed files with 106 additions and 49 deletions

View File

@@ -33,7 +33,7 @@ from quart import (
request,
has_app_context,
)
from werkzeug.exceptions import BadRequest as WerkzeugBadRequest
from werkzeug.exceptions import BadRequest as WerkzeugBadRequest, Unauthorized as WerkzeugUnauthorized
try:
from quart.exceptions import BadRequest as QuartBadRequest
@@ -270,39 +270,41 @@ def construct_json_result(code: RetCode = RetCode.SUCCESS, message="success", da
def token_required(func):
def get_tenant_id(**kwargs):
@wraps(func)
async def wrapper(*args, **kwargs):
# Validate the token (API Key)
if os.environ.get("DISABLE_SDK"):
return False, get_json_result(data=False, message="`Authorization` can't be empty")
err = WerkzeugUnauthorized(description="`Authorization` can't be empty")
err.code = RetCode.SUCCESS
raise err
authorization_str = request.headers.get("Authorization")
if not authorization_str:
return False, get_json_result(data=False, message="`Authorization` can't be empty")
err = WerkzeugUnauthorized(description="`Authorization` can't be empty")
err.code = RetCode.SUCCESS
raise err
authorization_list = authorization_str.split()
if len(authorization_list) < 2:
return False, get_json_result(data=False, message="Please check your authorization format.")
err = WerkzeugUnauthorized(description="Please check your authorization format.")
err.code = RetCode.AUTHENTICATION_ERROR
raise err
token = authorization_list[1]
objs = APIToken.query(token=token)
if not objs:
return False, get_json_result(data=False, message="Authentication error: API key is invalid!", code=RetCode.AUTHENTICATION_ERROR)
err = WerkzeugUnauthorized(description="Authentication error: API key is invalid!")
err.code = RetCode.AUTHENTICATION_ERROR
raise err
# On success, inject tenant_id into the route function's kwargs
kwargs["tenant_id"] = objs[0].tenant_id
return True, kwargs
result = func(*args, **kwargs)
if inspect.iscoroutine(result):
return await result
return result
@wraps(func)
def decorated_function(*args, **kwargs):
e, kwargs = get_tenant_id(**kwargs)
if not e:
return kwargs
return func(*args, **kwargs)
@wraps(func)
async def adecorated_function(*args, **kwargs):
e, kwargs = get_tenant_id(**kwargs)
if not e:
return kwargs
return await func(*args, **kwargs)
if inspect.iscoroutinefunction(func):
return adecorated_function
return decorated_function
return wrapper
def get_result(code=RetCode.SUCCESS, message="", data=None, total=None):