mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-21 21:50:49 +02:00
🔃 fix: Token Refresh in Browser Only, Redirect on Refresh Failure (#9583)
* 🔃 fix: Token Refresh in Browser Only, Redirect on Refresh Failure
* chore: Update import for SearchResultData and fix FormattedToolResponse type build warning
This commit is contained in:
parent
180046a3c5
commit
e3a645e8fb
2 changed files with 63 additions and 61 deletions
|
@ -8,7 +8,7 @@ import {
|
||||||
StreamableHTTPOptionsSchema,
|
StreamableHTTPOptionsSchema,
|
||||||
Tools,
|
Tools,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { UIResource, TPlugin, TUser } from 'librechat-data-provider';
|
import type { SearchResultData, UIResource, TPlugin, TUser } from 'librechat-data-provider';
|
||||||
import type * as t from '@modelcontextprotocol/sdk/types.js';
|
import type * as t from '@modelcontextprotocol/sdk/types.js';
|
||||||
import type { TokenMethods } from '@librechat/data-schemas';
|
import type { TokenMethods } from '@librechat/data-schemas';
|
||||||
import type { FlowStateManager } from '~/flow/manager';
|
import type { FlowStateManager } from '~/flow/manager';
|
||||||
|
@ -133,7 +133,7 @@ export type Artifacts =
|
||||||
sources: FileSearchSource[];
|
sources: FileSearchSource[];
|
||||||
fileCitations?: boolean;
|
fileCitations?: boolean;
|
||||||
};
|
};
|
||||||
[Tools.web_search]?: import('librechat-data-provider').SearchResultData;
|
[Tools.web_search]?: SearchResultData;
|
||||||
files?: Array<{ id: string; name: string }>;
|
files?: Array<{ id: string; name: string }>;
|
||||||
session_id?: string;
|
session_id?: string;
|
||||||
file_ids?: string[];
|
file_ids?: string[];
|
||||||
|
@ -144,10 +144,7 @@ export type FormattedContentResult = [string | FormattedContent[], undefined | A
|
||||||
|
|
||||||
export type ImageFormatter = (item: ImageContent) => FormattedContent;
|
export type ImageFormatter = (item: ImageContent) => FormattedContent;
|
||||||
|
|
||||||
export type FormattedToolResponse = [
|
export type FormattedToolResponse = FormattedContentResult;
|
||||||
string | FormattedContent[],
|
|
||||||
{ content: FormattedContent[] } | undefined,
|
|
||||||
];
|
|
||||||
|
|
||||||
export type ParsedServerConfig = MCPOptions & {
|
export type ParsedServerConfig = MCPOptions & {
|
||||||
url?: string;
|
url?: string;
|
||||||
|
|
|
@ -83,70 +83,75 @@ const processQueue = (error: AxiosError | null, token: string | null = null) =>
|
||||||
failedQueue = [];
|
failedQueue = [];
|
||||||
};
|
};
|
||||||
|
|
||||||
axios.interceptors.response.use(
|
if (typeof window !== 'undefined') {
|
||||||
(response) => response,
|
axios.interceptors.response.use(
|
||||||
async (error) => {
|
(response) => response,
|
||||||
const originalRequest = error.config;
|
async (error) => {
|
||||||
if (!error.response) {
|
const originalRequest = error.config;
|
||||||
return Promise.reject(error);
|
if (!error.response) {
|
||||||
}
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
if (originalRequest.url?.includes('/api/auth/2fa') === true) {
|
if (originalRequest.url?.includes('/api/auth/2fa') === true) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
if (originalRequest.url?.includes('/api/auth/logout') === true) {
|
if (originalRequest.url?.includes('/api/auth/logout') === true) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
if (originalRequest.url?.includes('/api/auth/refresh') === true) {
|
||||||
|
// Refresh token itself failed - redirect to login
|
||||||
|
console.log('Refresh token request failed, redirecting to login...');
|
||||||
|
window.location.href = '/login';
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
if (error.response.status === 401 && !originalRequest._retry) {
|
if (error.response.status === 401 && !originalRequest._retry) {
|
||||||
console.warn('401 error, refreshing token');
|
console.warn('401 error, refreshing token');
|
||||||
originalRequest._retry = true;
|
originalRequest._retry = true;
|
||||||
|
|
||||||
|
if (isRefreshing) {
|
||||||
|
try {
|
||||||
|
const token = await new Promise((resolve, reject) => {
|
||||||
|
failedQueue.push({ resolve, reject });
|
||||||
|
});
|
||||||
|
originalRequest.headers['Authorization'] = 'Bearer ' + token;
|
||||||
|
return await axios(originalRequest);
|
||||||
|
} catch (err) {
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isRefreshing = true;
|
||||||
|
|
||||||
if (isRefreshing) {
|
|
||||||
try {
|
try {
|
||||||
const token = await new Promise((resolve, reject) => {
|
const response = await refreshToken();
|
||||||
failedQueue.push({ resolve, reject });
|
|
||||||
});
|
const token = response?.token ?? '';
|
||||||
originalRequest.headers['Authorization'] = 'Bearer ' + token;
|
|
||||||
return await axios(originalRequest);
|
if (token) {
|
||||||
|
originalRequest.headers['Authorization'] = 'Bearer ' + token;
|
||||||
|
dispatchTokenUpdatedEvent(token);
|
||||||
|
processQueue(null, token);
|
||||||
|
return await axios(originalRequest);
|
||||||
|
} else if (window.location.href.includes('share/')) {
|
||||||
|
console.log(
|
||||||
|
`Refresh token failed from shared link, attempting request to ${originalRequest.url}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
window.location.href = '/login';
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
processQueue(err as AxiosError, null);
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
|
} finally {
|
||||||
|
isRefreshing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isRefreshing = true;
|
return Promise.reject(error);
|
||||||
|
},
|
||||||
try {
|
);
|
||||||
const response = await refreshToken(
|
}
|
||||||
// Handle edge case where we get a blank screen if the initial 401 error is from a refresh token request
|
|
||||||
originalRequest.url?.includes('api/auth/refresh') === true ? true : false,
|
|
||||||
);
|
|
||||||
|
|
||||||
const token = response?.token ?? '';
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
originalRequest.headers['Authorization'] = 'Bearer ' + token;
|
|
||||||
dispatchTokenUpdatedEvent(token);
|
|
||||||
processQueue(null, token);
|
|
||||||
return await axios(originalRequest);
|
|
||||||
} else if (window.location.href.includes('share/')) {
|
|
||||||
console.log(
|
|
||||||
`Refresh token failed from shared link, attempting request to ${originalRequest.url}`,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
window.location.href = '/login';
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
processQueue(err as AxiosError, null);
|
|
||||||
return Promise.reject(err);
|
|
||||||
} finally {
|
|
||||||
isRefreshing = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(error);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
get: _get,
|
get: _get,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue