2024-10-11 00:28:17 +03:00
import path from 'node:path' ;
import fs from 'node:fs' ;
2024-10-11 10:43:29 +03:00
import process from 'node:process' ;
2025-01-24 03:35:56 +00:00
import Handlebars from 'handlebars' ;
2024-10-10 22:37:22 +03:00
import ipMatching from 'ip-matching' ;
2023-12-14 17:36:41 -05:00
2024-10-10 22:37:22 +03:00
import { getIpFromRequest } from '../express-common.js' ;
import { color , getConfigValue } from '../util.js' ;
2023-12-14 17:36:41 -05:00
const whitelistPath = path . join ( process . cwd ( ) , './whitelist.txt' ) ;
2024-04-22 15:52:59 +03:00
const enableForwardedWhitelist = getConfigValue ( 'enableForwardedWhitelist' , false ) ;
2023-12-14 17:36:41 -05:00
let whitelist = getConfigValue ( 'whitelist' , [ ] ) ;
2023-12-15 18:43:00 +02:00
let knownIPs = new Set ( ) ;
2023-12-14 17:36:41 -05:00
2025-01-24 03:35:56 +00:00
const DEFAULT _WHITELIST _ERROR _MESSAGE =
'<h1>Forbidden</h1><p>If you are the system administrator, add your IP address to the whitelist or disable whitelist mode by editing <code>config.yaml</code> in the root directory of your installation.</p><hr /><p><em>Connection from {{ipDetails}} has been blocked. This attempt has been logged.</em></p>' ;
2023-12-14 17:36:41 -05:00
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)
}
}
2024-04-13 15:26:48 +03:00
/ * *
* Get the client IP address from the request headers .
* @ param { import ( 'express' ) . Request } req Express request object
* @ returns { string | undefined } The client IP address
* /
2024-04-12 22:03:36 -07:00
function getForwardedIp ( req ) {
2024-04-22 15:52:59 +03:00
if ( ! enableForwardedWhitelist ) {
return undefined ;
}
2024-04-12 22:03:36 -07:00
// Check if X-Real-IP is available
if ( req . headers [ 'x-real-ip' ] ) {
2024-04-22 15:52:59 +03:00
return req . headers [ 'x-real-ip' ] . toString ( ) ;
2024-04-12 22:03:36 -07:00
}
// Check for X-Forwarded-For and parse if available
if ( req . headers [ 'x-forwarded-for' ] ) {
2024-04-22 15:52:59 +03:00
const ipList = req . headers [ 'x-forwarded-for' ] . toString ( ) . split ( ',' ) . map ( ip => ip . trim ( ) ) ;
2024-04-12 22:03:36 -07:00
return ipList [ 0 ] ;
}
// If none of the headers are available, return undefined
return undefined ;
}
2024-03-30 22:42:51 +02:00
/ * *
* Returns a middleware function that checks if the client IP is in the whitelist .
2024-04-12 01:33:39 +03:00
* @ param { boolean } whitelistMode If whitelist mode is enabled via config or command line
2024-03-30 22:42:51 +02:00
* @ param { boolean } listen If listen mode is enabled via config or command line
* @ returns { import ( 'express' ) . RequestHandler } The middleware function
* /
2024-10-10 22:37:22 +03:00
export default function whitelistMiddleware ( whitelistMode , listen ) {
2024-03-30 22:42:51 +02:00
return function ( req , res , next ) {
const clientIp = getIpFromRequest ( req ) ;
2024-04-12 22:03:36 -07:00
const forwardedIp = getForwardedIp ( req ) ;
2025-01-24 03:35:56 +00:00
const userAgent = req . headers [ 'user-agent' ] ;
2023-12-14 17:36:41 -05:00
2024-03-30 22:42:51 +02:00
if ( listen && ! knownIPs . has ( clientIp ) ) {
console . log ( color . yellow ( ` New connection from ${ clientIp } ; User Agent: ${ userAgent } \n ` ) ) ;
knownIPs . add ( clientIp ) ;
// Write access log
const timestamp = new Date ( ) . toISOString ( ) ;
const log = ` ${ timestamp } ${ clientIp } ${ userAgent } \n ` ;
fs . appendFile ( 'access.log' , log , ( err ) => {
if ( err ) {
console . error ( 'Failed to write access log:' , err ) ;
}
} ) ;
}
//clientIp = req.connection.remoteAddress.split(':').pop();
2024-04-12 22:03:36 -07:00
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
2025-01-24 03:35:56 +00:00
const ipDetails = forwardedIp
? ` ${ clientIp } (forwarded from ${ forwardedIp } ) `
: clientIp ;
const errorMessage = Handlebars . compile (
getConfigValue (
'whitelistErrorMessage' ,
DEFAULT _WHITELIST _ERROR _MESSAGE ,
) ,
) ;
console . log (
color . red (
` Blocked connection from ${ clientIp } ; User Agent: ${ userAgent } \n \t To 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 ( errorMessage ( { ipDetails } ) ) ;
2024-03-30 22:42:51 +02:00
}
next ( ) ;
} ;
}