mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
- Added rate limiting to the authentication middleware to prevent brute-force attacks. - Introduced a secure comparison function to mitigate timing attacks during API key validation. - Created a new rate limiter class to track failed authentication attempts and block requests after exceeding the maximum allowed failures. - Updated the authentication middleware to handle rate limiting and secure key comparison. - Enhanced error handling for rate-limited requests, providing appropriate responses to clients.
164 lines
4.1 KiB
TypeScript
164 lines
4.1 KiB
TypeScript
/**
|
|
* 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<string, { createdAt: Date; expiresAt: Date }> = 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);
|
|
|
|
/**
|
|
* Extract Bearer token from Authorization header
|
|
* Returns undefined if header is missing or malformed
|
|
*/
|
|
export function extractBearerToken(req: Request): string | undefined {
|
|
const authHeader = req.headers.authorization;
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
return undefined;
|
|
}
|
|
return authHeader.slice(7); // Remove 'Bearer ' prefix
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
// Extract token from Authorization header only (Bearer token format)
|
|
// Query string tokens are not supported due to security risks (URL logging, referrer leakage)
|
|
const token = extractBearerToken(req);
|
|
|
|
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);
|