| | import { extractEnvVariable } from 'librechat-data-provider'; |
| | import type { MCPOptions } from 'librechat-data-provider'; |
| | import type { IUser } from '@librechat/data-schemas'; |
| | import type { RequestBody } from '~/types'; |
| | import { extractOpenIDTokenInfo, processOpenIDPlaceholders, isOpenIDTokenValid } from './oidc'; |
| |
|
| | |
| | |
| | |
| | |
| | const ALLOWED_USER_FIELDS = [ |
| | 'id', |
| | 'name', |
| | 'username', |
| | 'email', |
| | 'provider', |
| | 'role', |
| | 'googleId', |
| | 'facebookId', |
| | 'openidId', |
| | 'samlId', |
| | 'ldapId', |
| | 'githubId', |
| | 'discordId', |
| | 'appleId', |
| | 'emailVerified', |
| | 'twoFactorEnabled', |
| | 'termsAccepted', |
| | ] as const; |
| |
|
| | type AllowedUserField = (typeof ALLOWED_USER_FIELDS)[number]; |
| | type SafeUser = Pick<IUser, AllowedUserField>; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export function createSafeUser( |
| | user: IUser | null | undefined, |
| | ): Partial<SafeUser> & { federatedTokens?: unknown } { |
| | if (!user) { |
| | return {}; |
| | } |
| |
|
| | const safeUser: Partial<SafeUser> & { federatedTokens?: unknown } = {}; |
| | for (const field of ALLOWED_USER_FIELDS) { |
| | if (field in user) { |
| | safeUser[field] = user[field]; |
| | } |
| | } |
| |
|
| | if ('federatedTokens' in user) { |
| | safeUser.federatedTokens = user.federatedTokens; |
| | } |
| |
|
| | return safeUser; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | const ALLOWED_BODY_FIELDS = ['conversationId', 'parentMessageId', 'messageId'] as const; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | function processUserPlaceholders(value: string, user?: IUser): string { |
| | if (!user || typeof value !== 'string') { |
| | return value; |
| | } |
| |
|
| | for (const field of ALLOWED_USER_FIELDS) { |
| | const placeholder = `{{LIBRECHAT_USER_${field.toUpperCase()}}}`; |
| |
|
| | if (typeof value !== 'string' || !value.includes(placeholder)) { |
| | continue; |
| | } |
| |
|
| | const fieldValue = user[field as keyof IUser]; |
| |
|
| | |
| | if (!(field in user)) { |
| | continue; |
| | } |
| |
|
| | |
| | if (field === 'id' && (fieldValue === undefined || fieldValue === '')) { |
| | continue; |
| | } |
| |
|
| | const replacementValue = fieldValue == null ? '' : String(fieldValue); |
| | value = value.replace(new RegExp(placeholder, 'g'), replacementValue); |
| | } |
| |
|
| | return value; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | function processBodyPlaceholders(value: string, body: RequestBody): string { |
| | |
| | if (typeof value !== 'string') { |
| | return value; |
| | } |
| |
|
| | for (const field of ALLOWED_BODY_FIELDS) { |
| | const placeholder = `{{LIBRECHAT_BODY_${field.toUpperCase()}}}`; |
| | if (!value.includes(placeholder)) { |
| | continue; |
| | } |
| |
|
| | const fieldValue = body[field]; |
| | const replacementValue = fieldValue == null ? '' : String(fieldValue); |
| | value = value.replace(new RegExp(placeholder, 'g'), replacementValue); |
| | } |
| |
|
| | return value; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | function processSingleValue({ |
| | originalValue, |
| | customUserVars, |
| | user, |
| | body = undefined, |
| | }: { |
| | originalValue: string; |
| | customUserVars?: Record<string, string>; |
| | user?: IUser; |
| | body?: RequestBody; |
| | }): string { |
| | |
| | if (typeof originalValue !== 'string') { |
| | return String(originalValue); |
| | } |
| |
|
| | let value = originalValue; |
| |
|
| | if (customUserVars) { |
| | for (const [varName, varVal] of Object.entries(customUserVars)) { |
| | |
| | const escapedVarName = varName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| | const placeholderRegex = new RegExp(`\\{\\{${escapedVarName}\\}\\}`, 'g'); |
| | value = value.replace(placeholderRegex, varVal); |
| | } |
| | } |
| |
|
| | value = processUserPlaceholders(value, user); |
| |
|
| | const openidTokenInfo = extractOpenIDTokenInfo(user); |
| | if (openidTokenInfo && isOpenIDTokenValid(openidTokenInfo)) { |
| | value = processOpenIDPlaceholders(value, openidTokenInfo); |
| | } |
| |
|
| | if (body) { |
| | value = processBodyPlaceholders(value, body); |
| | } |
| |
|
| | value = extractEnvVariable(value); |
| |
|
| | return value; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export function processMCPEnv(params: { |
| | options: Readonly<MCPOptions>; |
| | user?: IUser; |
| | customUserVars?: Record<string, string>; |
| | body?: RequestBody; |
| | }): MCPOptions { |
| | const { options, user, customUserVars, body } = params; |
| |
|
| | if (options === null || options === undefined) { |
| | return options; |
| | } |
| |
|
| | const newObj: MCPOptions = structuredClone(options); |
| |
|
| | if ('env' in newObj && newObj.env) { |
| | const processedEnv: Record<string, string> = {}; |
| | for (const [key, originalValue] of Object.entries(newObj.env)) { |
| | processedEnv[key] = processSingleValue({ originalValue, customUserVars, user, body }); |
| | } |
| | newObj.env = processedEnv; |
| | } |
| |
|
| | if ('args' in newObj && newObj.args) { |
| | const processedArgs: string[] = []; |
| | for (const originalValue of newObj.args) { |
| | processedArgs.push(processSingleValue({ originalValue, customUserVars, user, body })); |
| | } |
| | newObj.args = processedArgs; |
| | } |
| |
|
| | |
| | |
| | if ('headers' in newObj && newObj.headers) { |
| | const processedHeaders: Record<string, string> = {}; |
| | for (const [key, originalValue] of Object.entries(newObj.headers)) { |
| | processedHeaders[key] = processSingleValue({ originalValue, customUserVars, user, body }); |
| | } |
| | newObj.headers = processedHeaders; |
| | } |
| |
|
| | |
| | if ('url' in newObj && newObj.url) { |
| | newObj.url = processSingleValue({ originalValue: newObj.url, customUserVars, user, body }); |
| | } |
| |
|
| | |
| | if ('oauth' in newObj && newObj.oauth) { |
| | const processedOAuth: Record<string, boolean | string | string[] | undefined> = {}; |
| | for (const [key, originalValue] of Object.entries(newObj.oauth)) { |
| | |
| | |
| | if (typeof originalValue === 'string') { |
| | processedOAuth[key] = processSingleValue({ originalValue, customUserVars, user, body }); |
| | } else { |
| | processedOAuth[key] = originalValue; |
| | } |
| | } |
| | newObj.oauth = processedOAuth; |
| | } |
| |
|
| | return newObj; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | function processValue( |
| | value: unknown, |
| | options: { |
| | customUserVars?: Record<string, string>; |
| | user?: IUser; |
| | body?: RequestBody; |
| | }, |
| | ): unknown { |
| | if (typeof value === 'string') { |
| | return processSingleValue({ |
| | originalValue: value, |
| | customUserVars: options.customUserVars, |
| | user: options.user, |
| | body: options.body, |
| | }); |
| | } |
| |
|
| | if (Array.isArray(value)) { |
| | return value.map((item) => processValue(item, options)); |
| | } |
| |
|
| | if (value !== null && typeof value === 'object') { |
| | const processed: Record<string, unknown> = {}; |
| | for (const [key, val] of Object.entries(value)) { |
| | processed[key] = processValue(val, options); |
| | } |
| | return processed; |
| | } |
| |
|
| | return value; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export function resolveNestedObject<T = unknown>(options?: { |
| | obj: T | undefined; |
| | user?: Partial<IUser> | { id: string }; |
| | body?: RequestBody; |
| | customUserVars?: Record<string, string>; |
| | }): T { |
| | const { obj, user, body, customUserVars } = options ?? {}; |
| |
|
| | if (!obj) { |
| | return obj as T; |
| | } |
| |
|
| | return processValue(obj, { |
| | customUserVars, |
| | user: user as IUser, |
| | body, |
| | }) as T; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export function resolveHeaders(options?: { |
| | headers: Record<string, string> | undefined; |
| | user?: Partial<IUser> | { id: string }; |
| | body?: RequestBody; |
| | customUserVars?: Record<string, string>; |
| | }) { |
| | const { headers, user, body, customUserVars } = options ?? {}; |
| | const inputHeaders = headers ?? {}; |
| |
|
| | const resolvedHeaders: Record<string, string> = { ...inputHeaders }; |
| |
|
| | if (inputHeaders && typeof inputHeaders === 'object' && !Array.isArray(inputHeaders)) { |
| | Object.keys(inputHeaders).forEach((key) => { |
| | resolvedHeaders[key] = processSingleValue({ |
| | originalValue: inputHeaders[key], |
| | customUserVars, |
| | user: user as IUser, |
| | body, |
| | }); |
| | }); |
| | } |
| |
|
| | return resolvedHeaders; |
| | } |
| |
|