From 503b0f750b39376c6f474235a1c9ab0948e8f54e Mon Sep 17 00:00:00 2001 From: MCHost Date: Mon, 16 Jun 2025 14:38:44 -0400 Subject: [PATCH] further security improvments --- includes/auth.js | 53 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/includes/auth.js b/includes/auth.js index cadbd3e..b8186af 100644 --- a/includes/auth.js +++ b/includes/auth.js @@ -76,6 +76,25 @@ const isLocalIp = (ip) => { ); }; +// Get real client IP from Cloudflare or fallback +const getRealIp = (req) => { + // Prioritize CF-Connecting-IP for Cloudflare + const cfConnectingIp = req.headers['cf-connecting-ip']; + if (cfConnectingIp) { + return cfConnectingIp; + } + + // Fallback to X-Forwarded-For + const forwardedFor = req.headers['x-forwarded-for']; + if (forwardedFor) { + // Take the first IP in the chain (original client IP) + return forwardedFor.split(',')[0].trim(); + } + + // Fallback to req.ip + return req.ip; +}; + // Generate nonce for CSP const generateNonce = () => { return randomBytes(16).toString('base64'); @@ -94,8 +113,8 @@ export async function generateLoginLink(req, res) { xFrameOptions: { action: 'deny' } })(req, res, async () => { try { - // Rate limiting - await rateLimiter.consume(req.ip); + // Rate limiting with real IP + await rateLimiter.consume(getRealIp(req)); // CSRF protection csrfProtection(req, res, async () => { @@ -103,13 +122,13 @@ export async function generateLoginLink(req, res) { // Validate inputs if (!sanitizeInput(secretKey) || secretKey !== process.env.ADMIN_SECRET_KEY) { - console.log(`Invalid secret key attempt from IP: ${req.ip}`); + console.log(`Invalid secret key attempt from IP: ${getRealIp(req)}`); return res.status(401).json({ error: 'Unauthorized' }); } const sanitizedUsername = sanitizeInput(username); if (!sanitizedUsername) { - console.log(`Invalid username attempt from IP: ${req.ip}, username: ${username}`); + console.log(`Invalid username attempt from IP: ${getRealIp(req)}, username: ${username}`); return res.status(400).json({ error: 'Invalid username' }); } @@ -133,13 +152,13 @@ export async function generateLoginLink(req, res) { const linkId = generateSecureToken(process.env.LINK_ID_BYTES); const loginLink = `${process.env.AUTO_LOGIN_LINK_PREFIX}${linkId}`; - // Store link data with additional security metadata + // Store link data with real IP temporaryLinks.set(linkId, { apiKey, username: sanitizedUsername, expiresAt: Date.now() + Math.min(3600000, parseInt(process.env.LINK_EXPIRY_SECONDS, 10) * 1000), - ip: req.ip, - userAgent: req.get('User-Agent') || 'Unknown' + ip: getRealIp(req), + userAgent: req.get('User-Agent') || 'Discord-Bot-Request' }); // Secure timeout @@ -148,7 +167,7 @@ export async function generateLoginLink(req, res) { console.log(`Expired link removed: ${linkId}`); }, Math.min(3600000, parseInt(process.env.LINK_EXPIRY_SECONDS, 10) * 1000)); - console.log(`Generated login link for username: ${sanitizedUsername} from IP: ${req.ip}, userAgent: ${req.get('User-Agent') || 'Unknown'}`); + console.log(`Generated login link for username: ${sanitizedUsername} from IP: ${getRealIp(req)}, userAgent: ${req.get('User-Agent') || 'Discord-Bot-Request'}`); res.json({ loginLink }); }); } catch (error) { @@ -179,7 +198,7 @@ export function handleAutoLogin(req, res) { if (!linkData || linkData.expiresAt < Date.now()) { temporaryLinks.delete(sanitizedLinkId); - console.log(`Expired or invalid login attempt for link: ${sanitizedLinkId} from IP: ${req.ip}`); + console.log(`Expired or invalid login attempt for link: ${sanitizedLinkId} from IP: ${getRealIp(req)}`); return res.send(` @@ -206,33 +225,33 @@ export function handleAutoLogin(req, res) { } // Verify client consistency - const isIpMatch = linkData.ip === req.ip; - const isUserAgentMatch = linkData.userAgent === (req.get('User-Agent') || 'Unknown'); - const isLocal = isLocalIp(req.ip) && isLocalIp(linkData.ip); + const isIpMatch = linkData.ip === getRealIp(req); + const isUserAgentMatch = linkData.userAgent === (req.get('User-Agent') || 'Discord-Bot-Request'); + const isLocal = isLocalIp(getRealIp(req)) && isLocalIp(linkData.ip); const strictUserAgentCheck = process.env.STRICT_USER_AGENT_CHECK === 'true'; if (strictUserAgentCheck && !isUserAgentMatch && !isLocal) { temporaryLinks.delete(sanitizedLinkId); console.log( - `Suspicious login attempt for link: ${sanitizedLinkId} from IP: ${req.ip}, ` + + `Suspicious login attempt for link: ${sanitizedLinkId} from IP: ${getRealIp(req)}, ` + `expected IP: ${linkData.ip}, isLocal: ${isLocal}, ` + `userAgentMatch: ${isUserAgentMatch}, ` + `expectedUserAgent: ${linkData.userAgent}, ` + - `actualUserAgent: ${req.get('User-Agent') || 'Unknown'}` + `actualUserAgent: ${req.get('User-Agent') || 'Discord-Bot-Request'}` ); return res.status(403).json({ error: 'Invalid session' }); } if (!isUserAgentMatch) { console.log( - `Non-critical user-agent mismatch for link: ${sanitizedLinkId} from IP: ${req.ip}, ` + + `Non-critical user-agent mismatch for link: ${sanitizedLinkId} from IP: ${getRealIp(req)}, ` + `expectedUserAgent: ${linkData.userAgent}, ` + - `actualUserAgent: ${req.get('User-Agent') || 'Unknown'}` + `actualUserAgent: ${req.get('User-Agent') || 'Discord-Bot-Request'}` ); } temporaryLinks.delete(sanitizedLinkId); - console.log(`Successful auto-login for username: ${linkData.username} from IP: ${req.ip}, userAgent: ${req.get('User-Agent') || 'Unknown'}`); + console.log(`Successful auto-login for username: ${linkData.username} from IP: ${getRealIp(req)}, userAgent: ${req.get('User-Agent') || 'Discord-Bot-Request'}`); // Secure API key storage with additional client-side security and debugging res.send(`