🔍 feat: Fetch Google Service Key and Consolidate Key Loading Logic (#8179)

This commit is contained in:
Danny Avila 2025-07-01 22:37:29 -04:00 committed by GitHub
parent 738d04fac4
commit 59d00e99f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 89 additions and 34 deletions

View file

@ -1,5 +1,5 @@
const fs = require('fs');
const path = require('path');
const { loadServiceKey } = require('@librechat/api');
const { EModelEndpoint } = require('librechat-data-provider');
const { isUserProvided } = require('~/server/utils');
const { config } = require('./EndpointService');
@ -18,15 +18,7 @@ async function loadAsyncEndpoints(req) {
path.join(__dirname, '../../..', 'data', 'auth.json');
try {
if (process.env.GOOGLE_SERVICE_KEY_FILE_PATH) {
const absolutePath = path.isAbsolute(serviceKeyPath)
? serviceKeyPath
: path.resolve(serviceKeyPath);
const fileContent = fs.readFileSync(absolutePath, 'utf8');
serviceKey = JSON.parse(fileContent);
} else {
serviceKey = require('~/data/auth.json');
}
serviceKey = await loadServiceKey(serviceKeyPath);
} catch {
if (i === 0) {
i++;

View file

@ -1,7 +1,6 @@
const fs = require('fs');
const path = require('path');
const { getGoogleConfig, isEnabled } = require('@librechat/api');
const { EModelEndpoint, AuthKeys } = require('librechat-data-provider');
const { getGoogleConfig, isEnabled, loadServiceKey } = require('@librechat/api');
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
const { GoogleClient } = require('~/app');
@ -19,17 +18,12 @@ const initializeClient = async ({ req, res, endpointOption, overrideModel, optio
let serviceKey = {};
try {
if (process.env.GOOGLE_SERVICE_KEY_FILE_PATH) {
const serviceKeyPath =
process.env.GOOGLE_SERVICE_KEY_FILE_PATH ||
path.join(__dirname, '../../../../..', 'data', 'auth.json');
const absolutePath = path.isAbsolute(serviceKeyPath)
? serviceKeyPath
: path.resolve(serviceKeyPath);
const fileContent = fs.readFileSync(absolutePath, 'utf8');
serviceKey = JSON.parse(fileContent);
} else {
serviceKey = require('~/data/auth.json');
const serviceKeyPath =
process.env.GOOGLE_SERVICE_KEY_FILE_PATH ||
path.join(__dirname, '../../../..', 'data', 'auth.json');
serviceKey = await loadServiceKey(serviceKeyPath);
if (!serviceKey) {
serviceKey = {};
}
} catch (_e) {
// Do nothing

View file

@ -21,6 +21,7 @@ import type {
OCRImage,
} from '~/types';
import { logAxiosError, createAxiosInstance } from '~/utils/axios';
import { loadServiceKey } from '~/utils/key';
const axios = createAxiosInstance();
const DEFAULT_MISTRAL_BASE_URL = 'https://api.mistral.ai/v1';
@ -443,27 +444,24 @@ async function loadGoogleAuthConfig(): Promise<{
const serviceKeyPath =
process.env.GOOGLE_SERVICE_KEY_FILE_PATH ||
path.join(__dirname, '..', '..', '..', 'api', 'data', 'auth.json');
const absolutePath = path.isAbsolute(serviceKeyPath)
? serviceKeyPath
: path.resolve(serviceKeyPath);
let serviceKey: GoogleServiceAccount;
try {
const authJsonContent = fs.readFileSync(absolutePath, 'utf8');
serviceKey = JSON.parse(authJsonContent) as GoogleServiceAccount;
} catch {
throw new Error(`Google service account not found at ${absolutePath}`);
const serviceKey = await loadServiceKey(serviceKeyPath);
if (!serviceKey) {
throw new Error(
`Google service account not found or could not be loaded from ${serviceKeyPath}`,
);
}
if (!serviceKey.client_email || !serviceKey.private_key || !serviceKey.project_id) {
throw new Error('Invalid Google service account configuration');
}
const jwt = await createJWT(serviceKey);
const jwt = await createJWT(serviceKey as GoogleServiceAccount);
const accessToken = await exchangeJWTForAccessToken(jwt);
return {
serviceAccount: serviceKey,
serviceAccount: serviceKey as GoogleServiceAccount,
accessToken,
};
}

View file

@ -5,6 +5,7 @@ export * from './env';
export * from './events';
export * from './files';
export * from './generators';
export * from './key';
export * from './llm';
export * from './math';
export * from './openid';

View file

@ -0,0 +1,70 @@
import fs from 'fs';
import path from 'path';
import axios from 'axios';
import { logger } from '@librechat/data-schemas';
export interface GoogleServiceKey {
type?: string;
project_id?: string;
private_key_id?: string;
private_key?: string;
client_email?: string;
client_id?: string;
auth_uri?: string;
token_uri?: string;
auth_provider_x509_cert_url?: string;
client_x509_cert_url?: string;
[key: string]: unknown;
}
/**
* Load Google service key from file path or URL
* @param keyPath - The path or URL to the service key file
* @returns The parsed service key object or null if failed
*/
export async function loadServiceKey(keyPath: string): Promise<GoogleServiceKey | null> {
if (!keyPath) {
return null;
}
let serviceKey: unknown;
// Check if it's a URL
if (/^https?:\/\//.test(keyPath)) {
try {
const response = await axios.get(keyPath);
serviceKey = response.data;
} catch (error) {
logger.error(`Failed to fetch the service key from URL: ${keyPath}`, error);
return null;
}
} else {
// It's a file path
try {
const absolutePath = path.isAbsolute(keyPath) ? keyPath : path.resolve(keyPath);
const fileContent = fs.readFileSync(absolutePath, 'utf8');
serviceKey = JSON.parse(fileContent);
} catch (error) {
logger.error(`Failed to load service key from file: ${keyPath}`, error);
return null;
}
}
// If the response is a string (e.g., from a URL that returns JSON as text), parse it
if (typeof serviceKey === 'string') {
try {
serviceKey = JSON.parse(serviceKey);
} catch (parseError) {
logger.error(`Failed to parse service key JSON from ${keyPath}`, parseError);
return null;
}
}
// Validate the service key has required fields
if (!serviceKey || typeof serviceKey !== 'object') {
logger.error(`Invalid service key format from ${keyPath}`);
return null;
}
return serviceKey as GoogleServiceKey;
}