mirror of
https://github.com/whyour/qinglong.git
synced 2026-08-16 14:20:51 +08:00
143 lines
4.0 KiB
TypeScript
143 lines
4.0 KiB
TypeScript
import express, { Application, Request } from 'express';
|
|
import { AuthDataType, SystemModel } from '../data/system';
|
|
import { normalizeClientIp } from './clientIp';
|
|
|
|
const DEFAULT_TRUST_PROXY = 'loopback';
|
|
|
|
type TrustProxyValue = boolean | number | string;
|
|
|
|
let activeApp: Application | undefined;
|
|
|
|
function getEnvironmentSetting(): string {
|
|
return process.env.QL_TRUST_PROXY?.trim() || '';
|
|
}
|
|
|
|
function normalizeSetting(value?: string): string {
|
|
return value?.trim() || DEFAULT_TRUST_PROXY;
|
|
}
|
|
|
|
export function resolveTrustProxy(value?: string): TrustProxyValue {
|
|
const setting = normalizeSetting(value);
|
|
if (setting === 'true' || setting === 'false') {
|
|
return setting === 'true';
|
|
}
|
|
if (/^\d+$/.test(setting)) {
|
|
return Number(setting);
|
|
}
|
|
return setting;
|
|
}
|
|
|
|
function validateTrustProxy(value: string): string {
|
|
const setting = normalizeSetting(value);
|
|
if (setting.length > 500 || /[\r\n]/.test(setting)) {
|
|
throw new Error('trust proxy 配置格式无效');
|
|
}
|
|
if (/^\d+$/.test(setting) && Number(setting) > 20) {
|
|
throw new Error('代理层数不能超过 20');
|
|
}
|
|
|
|
const probe = express();
|
|
probe.set('trust proxy', resolveTrustProxy(setting));
|
|
return setting;
|
|
}
|
|
|
|
async function getStoredSetting(): Promise<string> {
|
|
const doc = await SystemModel.findOne({
|
|
where: { type: AuthDataType.systemConfig },
|
|
});
|
|
const info = (doc?.get('info') || {}) as Record<string, unknown>;
|
|
return typeof info.trustProxy === 'string' ? info.trustProxy : '';
|
|
}
|
|
|
|
export async function getTrustProxyConfig() {
|
|
const environmentSetting = getEnvironmentSetting();
|
|
const storedSetting = await getStoredSetting();
|
|
const trustProxy = normalizeSetting(environmentSetting || storedSetting);
|
|
|
|
return {
|
|
trustProxy,
|
|
source: environmentSetting
|
|
? 'environment'
|
|
: storedSetting
|
|
? 'system'
|
|
: 'default',
|
|
editable: !environmentSetting,
|
|
};
|
|
}
|
|
|
|
export async function initializeTrustProxy(app: Application) {
|
|
activeApp = app;
|
|
const { trustProxy } = await getTrustProxyConfig();
|
|
app.set('trust proxy', resolveTrustProxy(trustProxy));
|
|
}
|
|
|
|
export async function updateTrustProxy(value: string) {
|
|
if (getEnvironmentSetting()) {
|
|
throw new Error('环境变量 QL_TRUST_PROXY 已生效,系统设置不可覆盖');
|
|
}
|
|
|
|
const trustProxy = validateTrustProxy(value);
|
|
const doc = await SystemModel.findOne({
|
|
where: { type: AuthDataType.systemConfig },
|
|
});
|
|
if (!doc) {
|
|
throw new Error('系统配置不存在');
|
|
}
|
|
|
|
const plain = doc.get({ plain: true });
|
|
await SystemModel.update(
|
|
{ info: { ...(plain.info || {}), trustProxy } as any },
|
|
{ where: { id: plain.id } },
|
|
);
|
|
activeApp?.set('trust proxy', resolveTrustProxy(trustProxy));
|
|
|
|
return getTrustProxyConfig();
|
|
}
|
|
|
|
function parseForwardedFor(value: string | string[] | undefined): string[] {
|
|
const values = Array.isArray(value) ? value : value ? [value] : [];
|
|
return values
|
|
.flatMap((item) => item.split(','))
|
|
.map((item) => normalizeClientIp(item))
|
|
.filter(Boolean);
|
|
}
|
|
|
|
export async function diagnoseClientIp(req: Request) {
|
|
const remoteAddress = normalizeClientIp(req.socket.remoteAddress);
|
|
const forwardedFor = parseForwardedFor(req.headers['x-forwarded-for']);
|
|
const hopsFromApp = [remoteAddress, ...forwardedFor.slice().reverse()].filter(
|
|
Boolean,
|
|
);
|
|
const trust = req.app.get('trust proxy fn') as
|
|
| ((ip: string, hop: number) => boolean)
|
|
| undefined;
|
|
|
|
let selectedIndex = Math.max(hopsFromApp.length - 1, 0);
|
|
for (let index = 0; index < hopsFromApp.length - 1; index += 1) {
|
|
if (!trust?.(hopsFromApp[index], index)) {
|
|
selectedIndex = index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
const hops = hopsFromApp.map((ip, index) => ({
|
|
ip,
|
|
hop: index,
|
|
status:
|
|
index < selectedIndex
|
|
? 'trusted'
|
|
: index === selectedIndex
|
|
? 'client'
|
|
: 'not_checked',
|
|
}));
|
|
|
|
return {
|
|
...(await getTrustProxyConfig()),
|
|
remoteAddress,
|
|
forwardedFor,
|
|
expressIps: req.ips.map(normalizeClientIp),
|
|
clientIp: normalizeClientIp(req.ip || req.socket.remoteAddress),
|
|
hops,
|
|
};
|
|
}
|