diff --git a/default/config.yaml b/default/config.yaml index d730bfbdb..3dce817cd 100644 --- a/default/config.yaml +++ b/default/config.yaml @@ -85,6 +85,11 @@ cookieSecret: '' disableCsrfProtection: false # Disable startup security checks - NOT RECOMMENDED securityOverride: false +# -- LOGGING CONFIGURATION -- +logging: + # Enable access logging to access.log file + # Records new connections with timestamp, IP address and user agent + enableAccessLog: true # -- RATE LIMITING CONFIGURATION -- rateLimiting: # Use X-Real-IP header instead of socket IP for rate limiting diff --git a/server.js b/server.js index b83a79235..e020cbbdf 100644 --- a/server.js +++ b/server.js @@ -57,7 +57,8 @@ import { import getWebpackServeMiddleware from './src/middleware/webpack-serve.js'; import basicAuthMiddleware from './src/middleware/basicAuth.js'; -import whitelistMiddleware, { getAccessLogPath, migrateAccessLog } from './src/middleware/whitelist.js'; +import whitelistMiddleware from './src/middleware/whitelist.js'; +import accessLoggerMiddleware, { getAccessLogPath, migrateAccessLog } from './src/middleware/accessLogger.js'; import multerMonkeyPatch from './src/middleware/multerMonkeyPatch.js'; import initRequestProxy from './src/request-proxy.js'; import getCacheBusterMiddleware from './src/middleware/cacheBuster.js'; @@ -342,7 +343,8 @@ app.use(CORS); if (listen && basicAuthMode) app.use(basicAuthMiddleware); -app.use(whitelistMiddleware(enableWhitelist, listen)); +app.use(whitelistMiddleware(enableWhitelist)); +app.use(accessLoggerMiddleware()); if (enableCorsProxy) { app.use(bodyParser.json({ diff --git a/src/middleware/accessLogger.js b/src/middleware/accessLogger.js new file mode 100644 index 000000000..26a2b49aa --- /dev/null +++ b/src/middleware/accessLogger.js @@ -0,0 +1,59 @@ +import path from 'node:path'; +import fs from 'node:fs'; +import { getRealIpFromHeader } from '../express-common.js'; +import { color, getConfigValue } from '../util.js'; + +const enableAccessLog = getConfigValue('logging.enableAccessLog', true); + +const knownIPs = new Set(); + +export const getAccessLogPath = () => path.join(globalThis.DATA_ROOT, 'access.log'); + +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.'); + } +} + +/** + * Creates middleware for logging access and new connections + * @returns {import('express').RequestHandler} + */ +export default function accessLoggerMiddleware() { + return function (req, res, next) { + const clientIp = getRealIpFromHeader(req); + const userAgent = req.headers['user-agent']; + + if (!knownIPs.has(clientIp)) { + // Log new connection + console.info(color.yellow(`New connection from ${clientIp}; User Agent: ${userAgent}\n`)); + knownIPs.add(clientIp); + + // Write to access log if enabled + if (enableAccessLog) { + 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); + } + }); + } + } + + next(); + }; +} diff --git a/src/middleware/whitelist.js b/src/middleware/whitelist.js index cb90328ee..146f38110 100644 --- a/src/middleware/whitelist.js +++ b/src/middleware/whitelist.js @@ -10,9 +10,6 @@ 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 { @@ -48,30 +45,12 @@ function getForwardedIp(req) { 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) { +export default function whitelistMiddleware(whitelistMode) { const forbiddenWebpage = Handlebars.compile( safeReadFileSync('./public/error/forbidden-by-whitelist.html') ?? '', ); @@ -81,21 +60,6 @@ export default function whitelistMiddleware(whitelistMode, listen) { 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)))