Add flexible IP access control to WebUI config

Replaces the 'disableNonLANAccess' option with a more flexible access control system supporting 'none', 'whitelist', and 'blacklist' modes, along with IP list and X-Forwarded-For support. Updates backend API, config schema, middleware, and frontend UI to allow configuration of access control mode, IP whitelist/blacklist, and X-Forwarded-For handling. Removes legacy LAN-only access logic and updates types accordingly.
This commit is contained in:
手瓜一十雪
2026-01-26 19:46:15 +08:00
parent 0d4ff2abe4
commit 5bc8d5ad68
9 changed files with 489 additions and 107 deletions

View File

@@ -12,7 +12,10 @@ export const GetWebUIConfigHandler: RequestHandler = async (_, res) => {
port: config.port,
loginRate: config.loginRate,
disableWebUI: config.disableWebUI,
disableNonLANAccess: config.disableNonLANAccess,
accessControlMode: config.accessControlMode || 'none',
ipWhitelist: config.ipWhitelist || [],
ipBlacklist: config.ipBlacklist || [],
enableXForwardedFor: config.enableXForwardedFor || false,
});
} catch (error) {
const msg = (error as Error).message;
@@ -48,38 +51,47 @@ export const UpdateDisableWebUIHandler: RequestHandler = async (req, res) => {
}
};
// 获取是否禁用非局域网访问
export const GetDisableNonLANAccessHandler: RequestHandler = async (_, res) => {
// 获取当前客户端IP
export const GetClientIPHandler: RequestHandler = async (req, res) => {
try {
const disable = await WebUiConfig.GetDisableNonLANAccess();
return sendSuccess(res, disable);
} catch (error) {
const msg = (error as Error).message;
return sendError(res, `获取非局域网访问禁用状态失败: ${msg}`);
}
};
const config = await WebUiConfig.GetWebUIConfig();
// 更新是否禁用非局域网访问
export const UpdateDisableNonLANAccessHandler: RequestHandler = async (req, res) => {
try {
const { disable } = req.body;
if (typeof disable !== 'boolean') {
return sendError(res, 'disable参数必须是布尔值');
// 根据配置决定如何获取客户端IP与 CORS 中间件逻辑一致)
let clientIP: string;
if (config.enableXForwardedFor) {
const forwardedFor = req.headers['x-forwarded-for'];
if (typeof forwardedFor === 'string') {
clientIP = forwardedFor.split(',')[0]?.trim() || '';
} else if (Array.isArray(forwardedFor) && forwardedFor.length > 0) {
clientIP = forwardedFor[0] || '';
} else {
clientIP = req.ip || req.socket.remoteAddress || '';
}
} else {
clientIP = req.ip || req.socket.remoteAddress || '';
}
await WebUiConfig.UpdateDisableNonLANAccess(disable);
return sendSuccess(res, null);
// 标准化 IP移除 IPv4-mapped IPv6 前缀,但保留纯 IPv6
let normalizedIP = clientIP;
if (clientIP.startsWith('::ffff:')) {
const ipv4 = clientIP.substring(7);
// 检查是否是有效的 IPv4
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(ipv4)) {
normalizedIP = ipv4;
}
}
return sendSuccess(res, { ip: normalizedIP });
} catch (error) {
const msg = (error as Error).message;
return sendError(res, `更新非局域网访问禁用状态失败: ${msg}`);
return sendError(res, `获取客户端IP失败: ${msg}`);
}
};
// 更新WebUI基础配置
export const UpdateWebUIConfigHandler: RequestHandler = async (req, res) => {
try {
const { host, port, loginRate, disableWebUI, disableNonLANAccess } = req.body;
const { host, port, loginRate, disableWebUI, accessControlMode, ipWhitelist, ipBlacklist, enableXForwardedFor } = req.body;
const updateConfig: any = {};
@@ -111,11 +123,32 @@ export const UpdateWebUIConfigHandler: RequestHandler = async (req, res) => {
updateConfig.disableWebUI = disableWebUI;
}
if (disableNonLANAccess !== undefined) {
if (typeof disableNonLANAccess !== 'boolean') {
return sendError(res, 'disableNonLANAccess必须是布尔值');
if (accessControlMode !== undefined) {
if (!['none', 'whitelist', 'blacklist'].includes(accessControlMode)) {
return sendError(res, 'accessControlMode必须是none、whitelist或blacklist');
}
updateConfig.disableNonLANAccess = disableNonLANAccess;
updateConfig.accessControlMode = accessControlMode;
}
if (ipWhitelist !== undefined) {
if (!Array.isArray(ipWhitelist)) {
return sendError(res, 'ipWhitelist必须是数组');
}
updateConfig.ipWhitelist = ipWhitelist;
}
if (ipBlacklist !== undefined) {
if (!Array.isArray(ipBlacklist)) {
return sendError(res, 'ipBlacklist必须是数组');
}
updateConfig.ipBlacklist = ipBlacklist;
}
if (enableXForwardedFor !== undefined) {
if (typeof enableXForwardedFor !== 'boolean') {
return sendError(res, 'enableXForwardedFor必须是布尔值');
}
updateConfig.enableXForwardedFor = enableXForwardedFor;
}
await WebUiConfig.UpdateWebUIConfig(updateConfig);