mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-16 16:30:15 +01:00
🐛 fix: RAG API failing with OPENID_REUSE_TOKENS Enabled (#8090)
* feat: Implement Short-Lived JWT Token Generation for RAG API * fix: Update import paths * fix: Correct environment variable names for OpenID on behalf flow * fix: Remove unnecessary spaces in OpenID on behalf flow userinfo scope --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com>
This commit is contained in:
parent
33b4a97b42
commit
452151e408
7 changed files with 33 additions and 14 deletions
|
|
@ -453,8 +453,8 @@ OPENID_REUSE_TOKENS=
|
||||||
OPENID_JWKS_URL_CACHE_ENABLED=
|
OPENID_JWKS_URL_CACHE_ENABLED=
|
||||||
OPENID_JWKS_URL_CACHE_TIME= # 600000 ms eq to 10 minutes leave empty to disable caching
|
OPENID_JWKS_URL_CACHE_TIME= # 600000 ms eq to 10 minutes leave empty to disable caching
|
||||||
#Set to true to trigger token exchange flow to acquire access token for the userinfo endpoint.
|
#Set to true to trigger token exchange flow to acquire access token for the userinfo endpoint.
|
||||||
OPENID_ON_BEHALF_FLOW_FOR_USERINFRO_REQUIRED=
|
OPENID_ON_BEHALF_FLOW_FOR_USERINFO_REQUIRED=
|
||||||
OPENID_ON_BEHALF_FLOW_USERINFRO_SCOPE = "user.read" # example for Scope Needed for Microsoft Graph API
|
OPENID_ON_BEHALF_FLOW_USERINFO_SCOPE="user.read" # example for Scope Needed for Microsoft Graph API
|
||||||
# Set to true to use the OpenID Connect end session endpoint for logout
|
# Set to true to use the OpenID Connect end session endpoint for logout
|
||||||
OPENID_USE_END_SESSION_ENDPOINT=
|
OPENID_USE_END_SESSION_ENDPOINT=
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { isEnabled } = require('@librechat/api');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
|
const { generateShortLivedToken } = require('~/server/services/AuthService');
|
||||||
|
|
||||||
const footer = `Use the context as your learned knowledge to better answer the user.
|
const footer = `Use the context as your learned knowledge to better answer the user.
|
||||||
|
|
||||||
|
|
@ -18,7 +19,7 @@ function createContextHandlers(req, userMessageContent) {
|
||||||
const queryPromises = [];
|
const queryPromises = [];
|
||||||
const processedFiles = [];
|
const processedFiles = [];
|
||||||
const processedIds = new Set();
|
const processedIds = new Set();
|
||||||
const jwtToken = req.headers.authorization.split(' ')[1];
|
const jwtToken = generateShortLivedToken(req.user.id);
|
||||||
const useFullContext = isEnabled(process.env.RAG_USE_FULL_CONTEXT);
|
const useFullContext = isEnabled(process.env.RAG_USE_FULL_CONTEXT);
|
||||||
|
|
||||||
const query = async (file) => {
|
const query = async (file) => {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
const { z } = require('zod');
|
const { z } = require('zod');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const { tool } = require('@langchain/core/tools');
|
const { tool } = require('@langchain/core/tools');
|
||||||
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { Tools, EToolResources } = require('librechat-data-provider');
|
const { Tools, EToolResources } = require('librechat-data-provider');
|
||||||
|
const { generateShortLivedToken } = require('~/server/services/AuthService');
|
||||||
const { getFiles } = require('~/models/File');
|
const { getFiles } = require('~/models/File');
|
||||||
const { logger } = require('~/config');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -59,7 +60,7 @@ const createFileSearchTool = async ({ req, files, entity_id }) => {
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
return 'No files to search. Instruct the user to add files for the search.';
|
return 'No files to search. Instruct the user to add files for the search.';
|
||||||
}
|
}
|
||||||
const jwtToken = req.headers.authorization.split(' ')[1];
|
const jwtToken = generateShortLivedToken(req.user.id);
|
||||||
if (!jwtToken) {
|
if (!jwtToken) {
|
||||||
return 'There was an error authenticating the file search request.';
|
return 'There was an error authenticating the file search request.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
const bcrypt = require('bcryptjs');
|
const bcrypt = require('bcryptjs');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
const { webcrypto } = require('node:crypto');
|
const { webcrypto } = require('node:crypto');
|
||||||
const { isEnabled } = require('@librechat/api');
|
const { isEnabled } = require('@librechat/api');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
|
|
@ -499,6 +500,18 @@ const resendVerificationEmail = async (req) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Generate a short-lived JWT token
|
||||||
|
* @param {String} userId - The ID of the user
|
||||||
|
* @param {String} [expireIn='5m'] - The expiration time for the token (default is 5 minutes)
|
||||||
|
* @returns {String} - The generated JWT token
|
||||||
|
*/
|
||||||
|
const generateShortLivedToken = (userId, expireIn = '5m') => {
|
||||||
|
return jwt.sign({ id: userId }, process.env.JWT_SECRET, {
|
||||||
|
expiresIn: expireIn,
|
||||||
|
algorithm: 'HS256',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
logoutUser,
|
logoutUser,
|
||||||
|
|
@ -506,7 +519,8 @@ module.exports = {
|
||||||
registerUser,
|
registerUser,
|
||||||
setAuthTokens,
|
setAuthTokens,
|
||||||
resetPassword,
|
resetPassword,
|
||||||
|
setOpenIDAuthTokens,
|
||||||
requestPasswordReset,
|
requestPasswordReset,
|
||||||
resendVerificationEmail,
|
resendVerificationEmail,
|
||||||
setOpenIDAuthTokens,
|
generateShortLivedToken,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { EModelEndpoint } = require('librechat-data-provider');
|
const { EModelEndpoint } = require('librechat-data-provider');
|
||||||
|
const { generateShortLivedToken } = require('~/server/services/AuthService');
|
||||||
const { getBufferMetadata } = require('~/server/utils');
|
const { getBufferMetadata } = require('~/server/utils');
|
||||||
const paths = require('~/config/paths');
|
const paths = require('~/config/paths');
|
||||||
const { logger } = require('~/config');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves a file to a specified output path with a new filename.
|
* Saves a file to a specified output path with a new filename.
|
||||||
|
|
@ -206,7 +207,7 @@ const deleteLocalFile = async (req, file) => {
|
||||||
const cleanFilepath = file.filepath.split('?')[0];
|
const cleanFilepath = file.filepath.split('?')[0];
|
||||||
|
|
||||||
if (file.embedded && process.env.RAG_API_URL) {
|
if (file.embedded && process.env.RAG_API_URL) {
|
||||||
const jwtToken = req.headers.authorization.split(' ')[1];
|
const jwtToken = generateShortLivedToken(req.user.id);
|
||||||
axios.delete(`${process.env.RAG_API_URL}/documents`, {
|
axios.delete(`${process.env.RAG_API_URL}/documents`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${jwtToken}`,
|
Authorization: `Bearer ${jwtToken}`,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ const FormData = require('form-data');
|
||||||
const { logAxiosError } = require('@librechat/api');
|
const { logAxiosError } = require('@librechat/api');
|
||||||
const { logger } = require('@librechat/data-schemas');
|
const { logger } = require('@librechat/data-schemas');
|
||||||
const { FileSources } = require('librechat-data-provider');
|
const { FileSources } = require('librechat-data-provider');
|
||||||
|
const { generateShortLivedToken } = require('~/server/services/AuthService');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a file from the vector database. This function takes a file object, constructs the full path, and
|
* Deletes a file from the vector database. This function takes a file object, constructs the full path, and
|
||||||
|
|
@ -23,7 +24,8 @@ const deleteVectors = async (req, file) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const jwtToken = req.headers.authorization.split(' ')[1];
|
const jwtToken = generateShortLivedToken(req.user.id);
|
||||||
|
|
||||||
return await axios.delete(`${process.env.RAG_API_URL}/documents`, {
|
return await axios.delete(`${process.env.RAG_API_URL}/documents`, {
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${jwtToken}`,
|
Authorization: `Bearer ${jwtToken}`,
|
||||||
|
|
@ -70,7 +72,7 @@ async function uploadVectors({ req, file, file_id, entity_id }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const jwtToken = req.headers.authorization.split(' ')[1];
|
const jwtToken = generateShortLivedToken(req.user.id);
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file_id', file_id);
|
formData.append('file_id', file_id);
|
||||||
formData.append('file', fs.createReadStream(file.path));
|
formData.append('file', fs.createReadStream(file.path));
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ class CustomOpenIDStrategy extends OpenIDStrategy {
|
||||||
*/
|
*/
|
||||||
const exchangeAccessTokenIfNeeded = async (config, accessToken, sub, fromCache = false) => {
|
const exchangeAccessTokenIfNeeded = async (config, accessToken, sub, fromCache = false) => {
|
||||||
const tokensCache = getLogStores(CacheKeys.OPENID_EXCHANGED_TOKENS);
|
const tokensCache = getLogStores(CacheKeys.OPENID_EXCHANGED_TOKENS);
|
||||||
const onBehalfFlowRequired = isEnabled(process.env.OPENID_ON_BEHALF_FLOW_FOR_USERINFRO_REQUIRED);
|
const onBehalfFlowRequired = isEnabled(process.env.OPENID_ON_BEHALF_FLOW_FOR_USERINFO_REQUIRED);
|
||||||
if (onBehalfFlowRequired) {
|
if (onBehalfFlowRequired) {
|
||||||
if (fromCache) {
|
if (fromCache) {
|
||||||
const cachedToken = await tokensCache.get(sub);
|
const cachedToken = await tokensCache.get(sub);
|
||||||
|
|
@ -130,7 +130,7 @@ const exchangeAccessTokenIfNeeded = async (config, accessToken, sub, fromCache =
|
||||||
config,
|
config,
|
||||||
'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
||||||
{
|
{
|
||||||
scope: process.env.OPENID_ON_BEHALF_FLOW_USERINFRO_SCOPE || 'user.read',
|
scope: process.env.OPENID_ON_BEHALF_FLOW_USERINFO_SCOPE || 'user.read',
|
||||||
assertion: accessToken,
|
assertion: accessToken,
|
||||||
requested_token_use: 'on_behalf_of',
|
requested_token_use: 'on_behalf_of',
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue