feat: 支持禁用webui 速率配置 禁用外网访问 禁用webui

This commit is contained in:
手瓜一十雪
2025-09-06 11:53:04 +08:00
parent e97f3e1283
commit 58332dad24
10 changed files with 493 additions and 2 deletions

View File

@@ -61,7 +61,15 @@ async function checkCertificates(logger: LogWrapper): Promise<{ key: string, cer
export async function InitWebUi(logger: LogWrapper, pathWrapper: NapCatPathWrapper) {
webUiPathWrapper = pathWrapper;
WebUiConfig = new WebUiConfigWrapper();
const [host, port, token] = await InitPort(await WebUiConfig.GetWebUIConfig());
const config = await WebUiConfig.GetWebUIConfig();
// 检查是否禁用WebUI
if (config.disableWebUI) {
logger.log('[NapCat] [WebUi] WebUI is disabled by configuration.');
return;
}
const [host, port, token] = await InitPort(config);
webUiRuntimePort = port;
if (port == 0) {
logger.log('[NapCat] [WebUi] Current WebUi is not run.');

View File

@@ -0,0 +1,127 @@
import { RequestHandler } from 'express';
import { WebUiConfig } from '@/webui';
import { sendError, sendSuccess } from '@webapi/utils/response';
import { isEmpty } from '@webapi/utils/check';
// 获取WebUI基础配置
export const GetWebUIConfigHandler: RequestHandler = async (_, res) => {
try {
const config = await WebUiConfig.GetWebUIConfig();
return sendSuccess(res, {
host: config.host,
port: config.port,
loginRate: config.loginRate,
disableWebUI: config.disableWebUI,
disableNonLANAccess: config.disableNonLANAccess
});
} catch (error) {
const msg = (error as Error).message;
return sendError(res, `获取WebUI配置失败: ${msg}`);
}
};
// 获取是否禁用WebUI
export const GetDisableWebUIHandler: RequestHandler = async (_, res) => {
try {
const disable = await WebUiConfig.GetDisableWebUI();
return sendSuccess(res, disable);
} catch (error) {
const msg = (error as Error).message;
return sendError(res, `获取WebUI禁用状态失败: ${msg}`);
}
};
// 更新是否禁用WebUI
export const UpdateDisableWebUIHandler: RequestHandler = async (req, res) => {
try {
const { disable } = req.body;
if (typeof disable !== 'boolean') {
return sendError(res, 'disable参数必须是布尔值');
}
await WebUiConfig.UpdateDisableWebUI(disable);
return sendSuccess(res, null);
} catch (error) {
const msg = (error as Error).message;
return sendError(res, `更新WebUI禁用状态失败: ${msg}`);
}
};
// 获取是否禁用非局域网访问
export const GetDisableNonLANAccessHandler: RequestHandler = async (_, res) => {
try {
const disable = await WebUiConfig.GetDisableNonLANAccess();
return sendSuccess(res, disable);
} catch (error) {
const msg = (error as Error).message;
return sendError(res, `获取非局域网访问禁用状态失败: ${msg}`);
}
};
// 更新是否禁用非局域网访问
export const UpdateDisableNonLANAccessHandler: RequestHandler = async (req, res) => {
try {
const { disable } = req.body;
if (typeof disable !== 'boolean') {
return sendError(res, 'disable参数必须是布尔值');
}
await WebUiConfig.UpdateDisableNonLANAccess(disable);
return sendSuccess(res, null);
} catch (error) {
const msg = (error as Error).message;
return sendError(res, `更新非局域网访问禁用状态失败: ${msg}`);
}
};
// 更新WebUI基础配置
export const UpdateWebUIConfigHandler: RequestHandler = async (req, res) => {
try {
const { host, port, loginRate, disableWebUI, disableNonLANAccess } = req.body;
const updateConfig: any = {};
if (host !== undefined) {
if (isEmpty(host)) {
return sendError(res, 'host不能为空');
}
updateConfig.host = host;
}
if (port !== undefined) {
if (!Number.isInteger(port) || port < 1 || port > 65535) {
return sendError(res, 'port必须是1-65535之间的整数');
}
updateConfig.port = port;
}
if (loginRate !== undefined) {
if (!Number.isInteger(loginRate) || loginRate < 1) {
return sendError(res, 'loginRate必须是大于0的整数');
}
updateConfig.loginRate = loginRate;
}
if (disableWebUI !== undefined) {
if (typeof disableWebUI !== 'boolean') {
return sendError(res, 'disableWebUI必须是布尔值');
}
updateConfig.disableWebUI = disableWebUI;
}
if (disableNonLANAccess !== undefined) {
if (typeof disableNonLANAccess !== 'boolean') {
return sendError(res, 'disableNonLANAccess必须是布尔值');
}
updateConfig.disableNonLANAccess = disableNonLANAccess;
}
await WebUiConfig.UpdateWebUIConfig(updateConfig);
return sendSuccess(res, null);
} catch (error) {
const msg = (error as Error).message;
return sendError(res, `更新WebUI配置失败: ${msg}`);
}
};

View File

@@ -19,6 +19,10 @@ const WebUiConfigSchema = Type.Object({
autoLoginAccount: Type.String({ default: '' }),
theme: themeType,
defaultToken: Type.Boolean({ default: true }),
// 是否关闭WebUI
disableWebUI: Type.Boolean({ default: false }),
// 是否关闭非局域网访问
disableNonLANAccess: Type.Boolean({ default: false }),
});
export type WebUiConfigType = Static<typeof WebUiConfigSchema>;
@@ -177,4 +181,26 @@ export class WebUiConfigWrapper {
async UpdateTheme(theme: WebUiConfigType['theme']): Promise<void> {
await this.UpdateWebUIConfig({ theme: theme });
}
// 获取是否禁用WebUI
async GetDisableWebUI(): Promise<boolean> {
const config = await this.GetWebUIConfig();
return config.disableWebUI;
}
// 更新是否禁用WebUI
async UpdateDisableWebUI(disable: boolean): Promise<void> {
await this.UpdateWebUIConfig({ disableWebUI: disable });
}
// 获取是否禁用非局域网访问
async GetDisableNonLANAccess(): Promise<boolean> {
const config = await this.GetWebUIConfig();
return config.disableNonLANAccess;
}
// 更新是否禁用非局域网访问
async UpdateDisableNonLANAccess(disable: boolean): Promise<void> {
await this.UpdateWebUIConfig({ disableNonLANAccess: disable });
}
}

View File

@@ -1,7 +1,53 @@
import type { RequestHandler } from 'express';
import { WebUiConfig } from '@/webui';
// 检查是否为局域网IP地址
function isLANIP(ip: string): boolean {
if (!ip) return false;
// 移除IPv6的前缀如果存在
const cleanIP = ip.replace(/^::ffff:/, '');
// 本地回环地址
if (cleanIP === '127.0.0.1' || cleanIP === 'localhost' || cleanIP === '::1') {
return true;
}
// 检查IPv4私有网络地址
const ipv4Regex = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;
const match = cleanIP.match(ipv4Regex);
if (match) {
const [, a, b] = match.map(Number);
// 10.0.0.0/8
if (a === 10) return true;
// 172.16.0.0/12
if (a === 172 && b !== undefined && b >= 16 && b <= 31) return true;
// 192.168.0.0/16
if (a === 192 && b === 168) return true;
// 169.254.0.0/16 (链路本地地址)
if (a === 169 && b === 254) return true;
}
return false;
}
// CORS 中间件,跨域用
export const cors: RequestHandler = (req, res, next) => {
export const cors: RequestHandler = async (req, res, next) => {
// 检查是否禁用非局域网访问
const config = await WebUiConfig.GetWebUIConfig();
if (config.disableNonLANAccess) {
const clientIP = req.ip || req.socket.remoteAddress || '';
if (!isLANIP(clientIP)) {
res.status(403).json({ error: '非局域网访问被禁止' });
return;
}
}
const origin = req.headers.origin || '*';
res.header('Access-Control-Allow-Origin', origin);
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');

View File

@@ -0,0 +1,31 @@
import { Router } from 'express';
import {
GetWebUIConfigHandler,
GetDisableWebUIHandler,
UpdateDisableWebUIHandler,
GetDisableNonLANAccessHandler,
UpdateDisableNonLANAccessHandler,
UpdateWebUIConfigHandler
} from '@webapi/api/WebUIConfig';
const router = Router();
// 获取WebUI基础配置
router.get('/GetConfig', GetWebUIConfigHandler);
// 更新WebUI基础配置
router.post('/UpdateConfig', UpdateWebUIConfigHandler);
// 获取是否禁用WebUI
router.get('/GetDisableWebUI', GetDisableWebUIHandler);
// 更新是否禁用WebUI
router.post('/UpdateDisableWebUI', UpdateDisableWebUIHandler);
// 获取是否禁用非局域网访问
router.get('/GetDisableNonLANAccess', GetDisableNonLANAccessHandler);
// 更新是否禁用非局域网访问
router.post('/UpdateDisableNonLANAccess', UpdateDisableNonLANAccessHandler);
export { router as WebUIConfigRouter };

View File

@@ -13,6 +13,7 @@ import { AuthRouter } from '@webapi/router/auth';
import { LogRouter } from '@webapi/router/Log';
import { BaseRouter } from '@webapi/router/Base';
import { FileRouter } from './File';
import { WebUIConfigRouter } from './WebUIConfig';
const router = Router();
@@ -35,5 +36,7 @@ router.use('/OB11Config', OB11ConfigRouter);
router.use('/Log', LogRouter);
// file:文件相关路由
router.use('/File', FileRouter);
// router:WebUI配置相关路由
router.use('/WebUIConfig', WebUIConfigRouter);
export { router as ALLRouter };