Add local bypass feature

This commit is contained in:
Peter Steinberger 2025-06-24 03:26:04 +02:00
parent 85ac380095
commit 801438d867
2 changed files with 66 additions and 1 deletions

View file

@ -11,14 +11,42 @@ interface AuthConfig {
isHQMode: boolean;
bearerToken?: string; // Token that HQ must use to authenticate with this remote
authService?: AuthService; // Enhanced auth service for JWT tokens
allowLocalBypass?: boolean; // Allow localhost connections to bypass auth
localAuthToken?: string; // Token for localhost authentication
}
interface AuthenticatedRequest extends Request {
userId?: string;
authMethod?: 'ssh-key' | 'password' | 'hq-bearer' | 'no-auth';
authMethod?: 'ssh-key' | 'password' | 'hq-bearer' | 'no-auth' | 'local-bypass';
isHQRequest?: boolean;
}
// Helper function to check if request is from localhost
function isLocalRequest(req: Request): boolean {
// Get the real client IP
const clientIp = req.ip || req.socket.remoteAddress || '';
// Check for localhost IPs
const localIPs = ['127.0.0.1', '::1', '::ffff:127.0.0.1', 'localhost'];
const ipIsLocal = localIPs.includes(clientIp);
// Additional security checks to prevent spoofing
const noForwardedFor = !req.headers['x-forwarded-for'];
const noRealIP = !req.headers['x-real-ip'];
const noForwardedHost = !req.headers['x-forwarded-host'];
// Check hostname
const hostIsLocal =
req.hostname === 'localhost' || req.hostname === '127.0.0.1' || req.hostname === '[::1]';
logger.debug(
`Local request check - IP: ${clientIp}, Host: ${req.hostname}, ` +
`Forwarded headers: ${!noForwardedFor || !noRealIP || !noForwardedHost}`
);
return ipIsLocal && noForwardedFor && noRealIP && noForwardedHost && hostIsLocal;
}
export function createAuthMiddleware(config: AuthConfig) {
return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
// Skip auth for health check endpoint, auth endpoints, client logging, and push notifications
@ -37,6 +65,28 @@ export function createAuthMiddleware(config: AuthConfig) {
return next();
}
// Check for local bypass if enabled
if (config.allowLocalBypass && isLocalRequest(req)) {
// If a local auth token is configured, check for it
if (config.localAuthToken) {
const providedToken = req.headers['x-vibetunnel-local'] as string;
if (providedToken === config.localAuthToken) {
logger.debug('Local request authenticated with token');
req.authMethod = 'local-bypass';
req.userId = 'local-user';
return next();
} else {
logger.debug('Local request missing or invalid token');
}
} else {
// No token required for local bypass
logger.debug('Local request authenticated without token');
req.authMethod = 'local-bypass';
req.userId = 'local-user';
return next();
}
}
// Only log auth requests that might be problematic (no header or failures)
// Remove verbose logging for successful token auth to reduce spam

View file

@ -61,6 +61,9 @@ interface Config {
vapidEmail: string | null;
generateVapidKeys: boolean;
bellNotificationsEnabled: boolean;
// Local bypass configuration
allowLocalBypass: boolean;
localAuthToken: string | null;
}
// Show help message
@ -78,6 +81,8 @@ Options:
--enable-ssh-keys Enable SSH key authentication UI and functionality
--disallow-user-password Disable password auth, SSH keys only (auto-enables --enable-ssh-keys)
--no-auth Disable authentication (auto-login as current user)
--allow-local-bypass Allow localhost connections to bypass authentication
--local-auth-token <token> Token for localhost authentication bypass
--debug Enable debug logging
Push Notification Options:
@ -141,6 +146,9 @@ function parseArgs(): Config {
vapidEmail: null as string | null,
generateVapidKeys: true, // Generate keys automatically
bellNotificationsEnabled: true, // Enable bell notifications by default
// Local bypass configuration
allowLocalBypass: false,
localAuthToken: null as string | null,
};
// Check for help flag first
@ -197,6 +205,11 @@ function parseArgs(): Config {
i++; // Skip the email value in next iteration
} else if (args[i] === '--generate-vapid-keys') {
config.generateVapidKeys = true;
} else if (args[i] === '--allow-local-bypass') {
config.allowLocalBypass = true;
} else if (args[i] === '--local-auth-token' && i + 1 < args.length) {
config.localAuthToken = args[i + 1];
i++; // Skip the token value in next iteration
} else if (args[i].startsWith('--')) {
// Unknown argument
logger.error(`Unknown argument: ${args[i]}`);
@ -428,6 +441,8 @@ export async function createApp(): Promise<AppInstance> {
isHQMode: config.isHQMode,
bearerToken: remoteBearerToken || undefined, // Token that HQ must use to auth with us
authService, // Add enhanced auth service for JWT tokens
allowLocalBypass: config.allowLocalBypass,
localAuthToken: config.localAuthToken || undefined,
});
// Serve static files with .html extension handling