mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-29 18:30:15 +01:00
117 lines
4.4 KiB
JavaScript
117 lines
4.4 KiB
JavaScript
import path from 'node:path';
|
|
import fs from 'node:fs';
|
|
import process from 'node:process';
|
|
import Handlebars from 'handlebars';
|
|
import ipMatching from 'ip-matching';
|
|
|
|
import { getIpFromRequest } from '../express-common.js';
|
|
import { color, getConfigValue, safeReadFileSync } from '../util.js';
|
|
|
|
const whitelistPath = path.join(process.cwd(), './whitelist.txt');
|
|
const enableForwardedWhitelist = getConfigValue('enableForwardedWhitelist', false);
|
|
let whitelist = getConfigValue('whitelist', []);
|
|
let knownIPs = new Set();
|
|
|
|
export const getAccessLogPath = () => path.join(globalThis.DATA_ROOT, 'access.log');
|
|
|
|
if (fs.existsSync(whitelistPath)) {
|
|
try {
|
|
let whitelistTxt = fs.readFileSync(whitelistPath, 'utf-8');
|
|
whitelist = whitelistTxt.split('\n').filter(ip => ip).map(ip => ip.trim());
|
|
} catch (e) {
|
|
// Ignore errors that may occur when reading the whitelist (e.g. permissions)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the client IP address from the request headers.
|
|
* @param {import('express').Request} req Express request object
|
|
* @returns {string|undefined} The client IP address
|
|
*/
|
|
function getForwardedIp(req) {
|
|
if (!enableForwardedWhitelist) {
|
|
return undefined;
|
|
}
|
|
|
|
// Check if X-Real-IP is available
|
|
if (req.headers['x-real-ip']) {
|
|
return req.headers['x-real-ip'].toString();
|
|
}
|
|
|
|
// Check for X-Forwarded-For and parse if available
|
|
if (req.headers['x-forwarded-for']) {
|
|
const ipList = req.headers['x-forwarded-for'].toString().split(',').map(ip => ip.trim());
|
|
return ipList[0];
|
|
}
|
|
|
|
// If none of the headers are available, return undefined
|
|
return undefined;
|
|
}
|
|
|
|
export function migrateAccessLog() {
|
|
try {
|
|
if (!fs.existsSync('access.log')) {
|
|
return;
|
|
}
|
|
const logPath = getAccessLogPath();
|
|
if (fs.existsSync(logPath)) {
|
|
return;
|
|
}
|
|
fs.renameSync('access.log', logPath);
|
|
console.log(color.yellow('Migrated access.log to new location:'), logPath);
|
|
} catch (e) {
|
|
console.error('Failed to migrate access log:', e);
|
|
console.info('Please move access.log to the data directory manually.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a middleware function that checks if the client IP is in the whitelist.
|
|
* @param {boolean} whitelistMode If whitelist mode is enabled via config or command line
|
|
* @param {boolean} listen If listen mode is enabled via config or command line
|
|
* @returns {import('express').RequestHandler} The middleware function
|
|
*/
|
|
export default function whitelistMiddleware(whitelistMode, listen) {
|
|
const forbiddenWebpage = Handlebars.compile(
|
|
safeReadFileSync('./public/error/forbidden-by-whitelist.html') ?? '',
|
|
);
|
|
|
|
return function (req, res, next) {
|
|
const clientIp = getIpFromRequest(req);
|
|
const forwardedIp = getForwardedIp(req);
|
|
const userAgent = req.headers['user-agent'];
|
|
|
|
if (listen && !knownIPs.has(clientIp)) {
|
|
console.info(color.yellow(`New connection from ${clientIp}; User Agent: ${userAgent}\n`));
|
|
knownIPs.add(clientIp);
|
|
|
|
// Write access log
|
|
const logPath = getAccessLogPath();
|
|
const timestamp = new Date().toISOString();
|
|
const log = `${timestamp} ${clientIp} ${userAgent}\n`;
|
|
fs.appendFile(logPath, log, (err) => {
|
|
if (err) {
|
|
console.error('Failed to write access log:', err);
|
|
}
|
|
});
|
|
}
|
|
|
|
//clientIp = req.connection.remoteAddress.split(':').pop();
|
|
if (whitelistMode === true && !whitelist.some(x => ipMatching.matches(clientIp, ipMatching.getMatch(x)))
|
|
|| forwardedIp && whitelistMode === true && !whitelist.some(x => ipMatching.matches(forwardedIp, ipMatching.getMatch(x)))
|
|
) {
|
|
// Log the connection attempt with real IP address
|
|
const ipDetails = forwardedIp
|
|
? `${clientIp} (forwarded from ${forwardedIp})`
|
|
: clientIp;
|
|
console.warn(
|
|
color.red(
|
|
`Blocked connection from ${clientIp}; User Agent: ${userAgent}\n\tTo allow this connection, add its IP address to the whitelist or disable whitelist mode by editing config.yaml in the root directory of your SillyTavern installation.\n`,
|
|
),
|
|
);
|
|
return res.status(403).send(forbiddenWebpage({ ipDetails }));
|
|
}
|
|
next();
|
|
};
|
|
}
|