import express, { Request, Response } from 'express'; import { promises as fs } from 'fs'; import path from 'path'; import rateLimit from 'express-rate-limit'; import logger from '../logger.js'; import process from 'process'; const router = express.Router(); // Rate limiting middleware for logs routes const logsRateLimit = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 20, // Limit each IP to 20 requests per windowMs message: { error: 'Too many log requests from this IP, please try again later.' }, standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers legacyHeaders: false, // Disable the `X-RateLimit-*` headers }); // Apply rate limiting to all routes in this router router.use(logsRateLimit); // Get list of available log files router.get('/', (_req: Request, res: Response) => { void (async () => { // [ ] add an auth mechanism. Below fcn is based on // the CouchDB auth mechanism, forwarded from the web-app (not direct-access of server). // // const auth = await requestIsAdminAuthenticated(req); // if (!auth) { // res.status(401).json({ error: 'Unauthorized' }); // return; // } try { const logsDir = path.join(process.cwd(), 'logs'); const files = await fs.readdir(logsDir); // Create HTML content const html = ` Log Files

Available Log Files

`; res.type('html').send(html); } catch (error) { logger.error('Error reading logs directory:', error); res.status(500).json({ error: 'Failed to retrieve log files' }); } })(); }); router.get('/:filename', (req: Request, res: Response) => { void (async () => { try { const filename = req.params.filename; // Sanitize filename to prevent directory traversal if (filename.includes('..') || !filename.match(/^[a-zA-Z0-9-_.]+$/)) { res.status(400).send('

Error

Invalid filename

'); return; } const logPath = path.join(process.cwd(), 'logs', filename); const content = (await fs.readFile(logPath, 'utf-8')).toString(); // Parse log lines const lines = content .split('\n') .filter((line) => line.trim()) .map((line) => { try { return JSON.parse(line); } catch { return line; } }); const html = ` Log Viewer - ${filename}

Log Viewer - ${filename}

${lines .map((line) => { let levelClass = ''; if (typeof line === 'object' && line.level) { levelClass = line.level.toLowerCase(); } return `
${ typeof line === 'object' ? JSON.stringify(line, null, 2) : line }
`; }) .join('')}
`; res.type('html').send(html); } catch (error) { logger.error(`Error reading log file ${req.params.filename}:`, error); res.status(500).send('

Error

Failed to retrieve log file

'); } })(); }); // Get contents of specific log file router.get('/:filename/raw', (req: Request, res: Response) => { void (async () => { // const auth = await requestIsAdminAuthenticated(req); // if (!auth) { // res.status(401).json({ error: 'Unauthorized' }); // return; // } try { const filename = req.params.filename; // Sanitize filename to prevent directory traversal if (filename.includes('..') || !filename.match(/^[a-zA-Z0-9-_.]+$/)) { res.status(400).json({ error: 'Invalid filename' }); return; } const logPath = path.join(process.cwd(), 'logs', filename); const content = (await fs.readFile(logPath, 'utf-8')).toString(); // If the client requests JSON format if (req.headers.accept === 'application/json') { const lines = content .split('\n') .filter((line) => line.trim()) .map((line) => { try { return JSON.parse(line); } catch { return line; } }); res.json(lines); } else { // Return plain text by default res.type('text/plain').send(content); } } catch (error) { logger.error(`Error reading log file ${req.params.filename}:`, error); res.status(500).json({ error: 'Failed to retrieve log file' }); } })(); }); export default router;