/** * Common utilities and state for terminal routes */ import { randomBytes } from 'crypto'; import { createLogger } from '@automaker/utils'; import type { Request, Response, NextFunction } from 'express'; import { getTerminalService } from '../../services/terminal-service.js'; const logger = createLogger('Terminal'); // Read env variables lazily to ensure dotenv has loaded them function getTerminalPassword(): string | undefined { return process.env.TERMINAL_PASSWORD; } function getTerminalEnabledConfig(): boolean { return process.env.TERMINAL_ENABLED !== 'false'; // Enabled by default } // In-memory session tokens (would use Redis in production) - private const validTokens: Map = new Map(); const TOKEN_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours /** * Add a token to the valid tokens map */ export function addToken(token: string, data: { createdAt: Date; expiresAt: Date }): void { validTokens.set(token, data); } /** * Delete a token from the valid tokens map */ export function deleteToken(token: string): void { validTokens.delete(token); } /** * Get token data for a given token */ export function getTokenData(token: string): { createdAt: Date; expiresAt: Date } | undefined { return validTokens.get(token); } /** * Generate a cryptographically secure random token */ export function generateToken(): string { return `term-${randomBytes(32).toString('base64url')}`; } /** * Clean up expired tokens */ export function cleanupExpiredTokens(): void { const now = new Date(); validTokens.forEach((data, token) => { if (data.expiresAt < now) { validTokens.delete(token); } }); } // Clean up expired tokens every 5 minutes setInterval(cleanupExpiredTokens, 5 * 60 * 1000); /** * Validate a terminal session token */ export function validateTerminalToken(token: string | undefined): boolean { if (!token) return false; const tokenData = validTokens.get(token); if (!tokenData) return false; if (tokenData.expiresAt < new Date()) { validTokens.delete(token); return false; } return true; } /** * Check if terminal requires password */ export function isTerminalPasswordRequired(): boolean { return !!getTerminalPassword(); } /** * Check if terminal is enabled */ export function isTerminalEnabled(): boolean { return getTerminalEnabledConfig(); } /** * Terminal authentication middleware * Checks for valid session token if password is configured */ export function terminalAuthMiddleware(req: Request, res: Response, next: NextFunction): void { // Check if terminal is enabled if (!getTerminalEnabledConfig()) { res.status(403).json({ success: false, error: 'Terminal access is disabled', }); return; } // If no password configured, allow all requests if (!getTerminalPassword()) { next(); return; } // Check for session token const token = (req.headers['x-terminal-token'] as string) || (req.query.token as string); if (!validateTerminalToken(token)) { res.status(401).json({ success: false, error: 'Terminal authentication required', passwordRequired: true, }); return; } next(); } export function getTerminalPasswordConfig(): string | undefined { return getTerminalPassword(); } export function getTerminalEnabledConfigValue(): boolean { return getTerminalEnabledConfig(); } export function getTokenExpiryMs(): number { return TOKEN_EXPIRY_MS; } import { getErrorMessage as getErrorMessageShared, createLogError } from '../common.js'; // Re-export shared utilities export { getErrorMessageShared as getErrorMessage }; export const logError = createLogError(logger);