refactor: update domain validation to use appConfig for allowed domains

This commit is contained in:
Danny Avila 2025-08-18 00:23:45 -04:00
parent 677481dde6
commit 50bd6d3a02
No known key found for this signature in database
GPG key ID: BF31EEB2C5CA0956
8 changed files with 43 additions and 61 deletions

View file

@ -1,5 +1,6 @@
const { logger } = require('@librechat/data-schemas');
const { isEmailDomainAllowed } = require('~/server/services/domains');
const { logger } = require('~/config');
const { getAppConfig } = require('~/server/services/Config');
/**
* Checks the domain's social login is allowed
@ -14,7 +15,10 @@ const { logger } = require('~/config');
*/
const checkDomainAllowed = async (req, res, next = () => {}) => {
const email = req?.user?.email;
if (email && !(await isEmailDomainAllowed(email))) {
const appConfig = getAppConfig({
role: req?.user?.role,
});
if (email && !(await isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains))) {
logger.error(`[Social Login] [Social Login not allowed] [Email: ${email}]`);
return res.redirect('/login');
} else {

View file

@ -16,6 +16,7 @@ const { getAgent, updateAgent, getListAgentsByAccess } = require('~/models/Agent
const { updateAction, getActions, deleteAction } = require('~/models/Action');
const { isActionDomainAllowed } = require('~/server/services/domains');
const { canAccessAgentResource } = require('~/server/middleware');
const { getAppConfig } = require('~/server/services/Config/app');
const { getRoleByName } = require('~/models/Role');
const router = express.Router();
@ -83,7 +84,11 @@ router.post(
}
let metadata = await encryptMetadata(removeNullishValues(_metadata, true));
const isDomainAllowed = await isActionDomainAllowed(metadata.domain);
const appConfig = await getAppConfig({ role: req.user.role });
const isDomainAllowed = await isActionDomainAllowed(
metadata.domain,
appConfig?.registration?.allowedDomains,
);
if (!isDomainAllowed) {
return res.status(400).json({ message: 'Domain not allowed' });
}

View file

@ -21,8 +21,8 @@ const router = express.Router();
* @returns {Object} 200 - success response - application/json
*/
router.post('/:assistant_id', async (req, res) => {
const appConfig = await getAppConfig({ role: req.user?.role });
try {
const appConfig = await getAppConfig({ role: req.user?.role });
const { assistant_id } = req.params;
/** @type {{ functions: FunctionTool[], action_id: string, metadata: ActionMetadata }} */
@ -32,7 +32,10 @@ router.post('/:assistant_id', async (req, res) => {
}
let metadata = await encryptMetadata(removeNullishValues(_metadata, true));
const isDomainAllowed = await isActionDomainAllowed(metadata.domain);
const isDomainAllowed = await isActionDomainAllowed(
metadata.domain,
appConfig?.registration?.allowedDomains,
);
if (!isDomainAllowed) {
return res.status(400).json({ message: 'Domain not allowed' });
}

View file

@ -84,33 +84,32 @@ const AppService = async () => {
// Store MCP config for later initialization
const mcpConfig = config.mcpServers || null;
const socialLogins =
config?.registration?.socialLogins ?? configDefaults?.registration?.socialLogins;
const registration = config.registration ?? configDefaults.registration;
const interfaceConfig = await loadDefaultInterface(config, configDefaults);
const turnstileConfig = loadTurnstileConfig(config, configDefaults);
const defaultLocals = {
config,
const defaultConfig = {
ocr,
paths,
config,
memory,
balance,
mcpConfig,
webSearch,
fileStrategy,
socialLogins,
registration,
filteredTools,
includedTools,
imageOutputType,
interfaceConfig,
turnstileConfig,
balance,
mcpConfig,
};
const agentsDefaults = agentsConfigSetup(config);
if (!Object.keys(config).length) {
const appConfig = {
...defaultLocals,
...defaultConfig,
[EModelEndpoint.agents]: agentsDefaults,
};
await setAppConfig(appConfig);
@ -169,7 +168,7 @@ const AppService = async () => {
}
const appConfig = {
...defaultLocals,
...defaultConfig,
fileConfig: config?.fileConfig,
secureImageLinks: config?.secureImageLinks,
modelSpecs: processModelSpecs(endpoints, config.modelSpecs, interfaceConfig),

View file

@ -20,9 +20,9 @@ const {
deleteUserById,
generateRefreshToken,
} = require('~/models');
const { getBalanceConfig, getAppConfig } = require('~/server/services/Config');
const { isEmailDomainAllowed } = require('~/server/services/domains');
const { checkEmailConfig, sendEmail } = require('~/server/utils');
const { getBalanceConfig } = require('~/server/services/Config');
const { registerSchema } = require('~/strategies/validators');
const domains = {
@ -195,7 +195,8 @@ const registerUser = async (user, additionalData = {}) => {
return { status: 200, message: genericVerificationMessage };
}
if (!(await isEmailDomainAllowed(email))) {
const appConfig = await getAppConfig({ role: user.role });
if (!(await isEmailDomainAllowed(email, appConfig?.registration?.allowedDomains))) {
const errorMessage =
'The email address provided cannot be used. Please use a different email address.';
logger.error(`[registerUser] [Registration not allowed] [Email: ${user.email}]`);

View file

@ -2,37 +2,6 @@ const { logger } = require('@librechat/data-schemas');
const { CacheKeys } = require('librechat-data-provider');
const getLogStores = require('~/cache/getLogStores');
/**
* @typedef {Object} AppConfig
* @property {import('librechat-data-provider').TCustomConfig} config - The main custom configuration
* @property {import('librechat-data-provider').TCustomConfig['ocr']} ocr - OCR configuration
* @property {Object} paths - File paths configuration
* @property {import('librechat-data-provider').TMemoryConfig | undefined} memory - Memory configuration
* @property {import('librechat-data-provider').TCustomConfig['webSearch']} webSearch - Web search configuration
* @property {string} fileStrategy - File storage strategy ('local', 's3', 'firebase', 'azure_blob')
* @property {Array} socialLogins - Social login configurations
* @property {string[]} [filteredTools] - Admin-filtered tools
* @property {string[]} [includedTools] - Admin-included tools
* @property {string} imageOutputType - Image output type configuration
* @property {import('librechat-data-provider').TCustomConfig['interface']} interfaceConfig - Interface configuration
* @property {import('librechat-data-provider').TCustomConfig['registration']} turnstileConfig - Turnstile configuration
* @property {import('librechat-data-provider').TCustomConfig['balance']} balance - Balance configuration
* @property {import('librechat-data-provider').TCustomConfig['mcpServers'] | null} mcpConfig - MCP server configuration
* @property {import('librechat-data-provider').TCustomConfig['fileConfig']} [fileConfig] - File configuration
* @property {import('librechat-data-provider').TCustomConfig['secureImageLinks']} [secureImageLinks] - Secure image links configuration
* @property {import('librechat-data-provider').TCustomConfig['modelSpecs'] | undefined} [modelSpecs] - Processed model specifications
* @property {import('librechat-data-provider').TEndpoint} [openAI] - OpenAI endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [google] - Google endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [bedrock] - Bedrock endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [anthropic] - Anthropic endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [gptPlugins] - GPT plugins endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [azureOpenAI] - Azure OpenAI endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [assistants] - Assistants endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [azureAssistants] - Azure assistants endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [agents] - Agents endpoint configuration
* @property {import('librechat-data-provider').TEndpoint} [all] - Global endpoint configuration
*/
/**
* Get the app configuration based on user context
* @param {Object} [options]

View file

@ -364,8 +364,10 @@ async function processRequiredActions(client, requiredActions) {
const domain = await domainParser(action.metadata.domain, true);
domainMap.set(domain, action);
// Check if domain is allowed
const isDomainAllowed = await isActionDomainAllowed(action.metadata.domain);
const isDomainAllowed = await isActionDomainAllowed(
action.metadata.domain,
appConfig?.registration?.allowedDomains,
);
if (!isDomainAllowed) {
continue;
}
@ -631,7 +633,10 @@ async function loadAgentTools({ req, res, agent, tool_resources, openAIApiKey })
domainMap.set(domain, action);
// Check if domain is allowed (do this once per action set)
const isDomainAllowed = await isActionDomainAllowed(action.metadata.domain);
const isDomainAllowed = await isActionDomainAllowed(
action.metadata.domain,
appConfig?.registration?.allowedDomains,
);
if (!isDomainAllowed) {
continue;
}

View file

@ -1,10 +1,9 @@
const { getCustomConfig } = require('~/server/services/Config');
/**
* @param {string} email
* @param {string[]} [allowedDomains]
* @returns {Promise<boolean>}
*/
async function isEmailDomainAllowed(email) {
async function isEmailDomainAllowed(email, allowedDomains) {
if (!email) {
return false;
}
@ -15,14 +14,13 @@ async function isEmailDomainAllowed(email) {
return false;
}
const customConfig = await getCustomConfig();
if (!customConfig) {
if (!allowedDomains) {
return true;
} else if (!customConfig?.registration?.allowedDomains) {
} else if (!Array.isArray(allowedDomains) || !allowedDomains.length) {
return true;
}
return customConfig.registration.allowedDomains.includes(domain);
return allowedDomains.includes(domain);
}
/**
@ -65,16 +63,14 @@ function normalizeDomain(domain) {
/**
* Checks if the given domain is allowed. If no restrictions are set, allows all domains.
* @param {string} [domain]
* @param {string[]} [allowedDomains]
* @returns {Promise<boolean>}
*/
async function isActionDomainAllowed(domain) {
async function isActionDomainAllowed(domain, allowedDomains) {
if (!domain || typeof domain !== 'string') {
return false;
}
const customConfig = await getCustomConfig();
const allowedDomains = customConfig?.actions?.allowedDomains;
if (!Array.isArray(allowedDomains) || !allowedDomains.length) {
return true;
}