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

@ -73,6 +73,9 @@ const errorHandler = (error: {
return response ?? { data: { code: 1999 } };
};
// avoid duplicate 401 redirects
let isRedirecting = false;
const request = axios.create({
// errorHandler,
timeout: 300000,
@ -123,13 +126,16 @@ request.interceptors.response.use(
if (data?.code === 100) {
message.error(data?.message);
} else if (data?.code === 401) {
notification.error({
message: data?.message,
description: data?.message,
duration: 3,
});
authorizationUtil.removeAll();
redirectToLogin();
if (!isRedirecting) {
isRedirecting = true;
notification.error({
message: data?.message,
description: data?.message,
duration: 3,
});
authorizationUtil.removeAll();
redirectToLogin();
}
} else if (data?.code !== 0) {
notification.error({
message: `${i18n.t('message.hint')} : ${data?.code}`,
@ -141,6 +147,26 @@ request.interceptors.response.use(
},
function (error) {
console.log('🚀 ~ error:', error);
// Handle HTTP 401 (token expired / invalid)
const status = error?.response?.status;
if (status === 401) {
if (!isRedirecting) {
isRedirecting = true;
const messageText =
error?.response?.data?.message || RetcodeMessage[401];
notification.error({
message: messageText,
description: messageText,
duration: 3,
});
authorizationUtil.removeAll();
redirectToLogin();
}
return Promise.reject(error);
}
errorHandler(error);
return Promise.reject(error);
},

View File

@ -80,6 +80,9 @@ const request: RequestMethod = extend({
getResponse: true,
});
// avoid duplicate 401 redirects
let isRedirecting = false;
request.interceptors.request.use((url: string, options: any) => {
const data = convertTheKeysOfTheObjectToSnake(options.data);
const params = convertTheKeysOfTheObjectToSnake(options.params);
@ -109,6 +112,27 @@ request.interceptors.response.use(async (response: Response, options) => {
message.error(RetcodeMessage[response?.status as ResultCode]);
}
// Handle HTTP 401
if (response?.status === 401) {
if (!isRedirecting) {
isRedirecting = true;
const data = await response.clone().json().catch(() => ({}));
const messageText =
data?.message || RetcodeMessage[401];
notification.error({
message: messageText,
description: messageText,
duration: 3,
});
authorizationUtil.removeAll();
redirectToLogin();
}
return response;
}
if (options.responseType === 'blob') {
return response;
}
@ -126,11 +150,16 @@ request.interceptors.response.use(async (response: Response, options) => {
if (data?.code === 100) {
message.error(data?.message);
} else if (data?.code === 401) {
notification.error({
message: data?.message,
description: data?.message,
duration: 3,
});
if (!isRedirecting) {
isRedirecting = true;
notification.error({
message: data?.message,
description: data?.message,
duration: 3,
});
authorizationUtil.removeAll();
redirectToLogin();
}
authorizationUtil.removeAll();
redirectToLogin();
} else if (data?.code !== 0) {