LibreChat/api/server/services/domains.js
Danny Avila 69bd8e3644
🔐 feat: Implement Allowed Action Domains (#4964)
* chore: RequestExecutor typing

* feat: allowed action domains

* fix: rename TAgentsEndpoint to TAssistantEndpoint in typedefs

* chore: update librechat-data-provider version to 0.7.62
2024-12-12 12:52:42 -05:00

109 lines
2.7 KiB
JavaScript

const { getCustomConfig } = require('~/server/services/Config');
/**
* @param {string} email
* @returns {Promise<boolean>}
*/
async function isEmailDomainAllowed(email) {
if (!email) {
return false;
}
const domain = email.split('@')[1];
if (!domain) {
return false;
}
const customConfig = await getCustomConfig();
if (!customConfig) {
return true;
} else if (!customConfig?.registration?.allowedDomains) {
return true;
}
return customConfig.registration.allowedDomains.includes(domain);
}
/**
* Normalizes a domain string
* @param {string} domain
* @returns {string|null}
*/
/**
* Normalizes a domain string. If the domain is invalid, returns null.
* Normalized === lowercase, trimmed, and protocol added if missing.
* @param {string} domain
* @returns {string|null}
*/
function normalizeDomain(domain) {
try {
let normalizedDomain = domain.toLowerCase().trim();
// Early return for obviously invalid formats
if (normalizedDomain === 'http://' || normalizedDomain === 'https://') {
return null;
}
// If it's not already a URL, make it one
if (!normalizedDomain.startsWith('http://') && !normalizedDomain.startsWith('https://')) {
normalizedDomain = `https://${normalizedDomain}`;
}
const url = new URL(normalizedDomain);
// Additional validation that hostname isn't just protocol
if (!url.hostname || url.hostname === 'http:' || url.hostname === 'https:') {
return null;
}
return url.hostname.replace(/^www\./i, '');
} catch {
return null;
}
}
/**
* Checks if the given domain is allowed. If no restrictions are set, allows all domains.
* @param {string} [domain]
* @returns {Promise<boolean>}
*/
async function isActionDomainAllowed(domain) {
if (!domain || typeof domain !== 'string') {
return false;
}
const customConfig = await getCustomConfig();
const allowedDomains = customConfig?.actions?.allowedDomains;
if (!Array.isArray(allowedDomains) || !allowedDomains.length) {
return true;
}
const normalizedInputDomain = normalizeDomain(domain);
if (!normalizedInputDomain) {
return false;
}
for (const allowedDomain of allowedDomains) {
const normalizedAllowedDomain = normalizeDomain(allowedDomain);
if (!normalizedAllowedDomain) {
continue;
}
if (normalizedAllowedDomain.startsWith('*.')) {
const baseDomain = normalizedAllowedDomain.slice(2);
if (
normalizedInputDomain === baseDomain ||
normalizedInputDomain.endsWith(`.${baseDomain}`)
) {
return true;
}
} else if (normalizedInputDomain === normalizedAllowedDomain) {
return true;
}
}
return false;
}
module.exports = { isEmailDomainAllowed, isActionDomainAllowed };