further security improvments

This commit is contained in:
MCHost
2025-06-16 14:38:44 -04:00
parent e92790383d
commit 503b0f750b

View File

@ -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 // Generate nonce for CSP
const generateNonce = () => { const generateNonce = () => {
return randomBytes(16).toString('base64'); return randomBytes(16).toString('base64');
@ -94,8 +113,8 @@ export async function generateLoginLink(req, res) {
xFrameOptions: { action: 'deny' } xFrameOptions: { action: 'deny' }
})(req, res, async () => { })(req, res, async () => {
try { try {
// Rate limiting // Rate limiting with real IP
await rateLimiter.consume(req.ip); await rateLimiter.consume(getRealIp(req));
// CSRF protection // CSRF protection
csrfProtection(req, res, async () => { csrfProtection(req, res, async () => {
@ -103,13 +122,13 @@ export async function generateLoginLink(req, res) {
// Validate inputs // Validate inputs
if (!sanitizeInput(secretKey) || secretKey !== process.env.ADMIN_SECRET_KEY) { 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' }); return res.status(401).json({ error: 'Unauthorized' });
} }
const sanitizedUsername = sanitizeInput(username); const sanitizedUsername = sanitizeInput(username);
if (!sanitizedUsername) { 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' }); 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 linkId = generateSecureToken(process.env.LINK_ID_BYTES);
const loginLink = `${process.env.AUTO_LOGIN_LINK_PREFIX}${linkId}`; 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, { temporaryLinks.set(linkId, {
apiKey, apiKey,
username: sanitizedUsername, username: sanitizedUsername,
expiresAt: Date.now() + Math.min(3600000, parseInt(process.env.LINK_EXPIRY_SECONDS, 10) * 1000), expiresAt: Date.now() + Math.min(3600000, parseInt(process.env.LINK_EXPIRY_SECONDS, 10) * 1000),
ip: req.ip, ip: getRealIp(req),
userAgent: req.get('User-Agent') || 'Unknown' userAgent: req.get('User-Agent') || 'Discord-Bot-Request'
}); });
// Secure timeout // Secure timeout
@ -148,7 +167,7 @@ export async function generateLoginLink(req, res) {
console.log(`Expired link removed: ${linkId}`); console.log(`Expired link removed: ${linkId}`);
}, Math.min(3600000, parseInt(process.env.LINK_EXPIRY_SECONDS, 10) * 1000)); }, 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 }); res.json({ loginLink });
}); });
} catch (error) { } catch (error) {
@ -179,7 +198,7 @@ export function handleAutoLogin(req, res) {
if (!linkData || linkData.expiresAt < Date.now()) { if (!linkData || linkData.expiresAt < Date.now()) {
temporaryLinks.delete(sanitizedLinkId); 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(` return res.send(`
<html> <html>
@ -206,33 +225,33 @@ export function handleAutoLogin(req, res) {
} }
// Verify client consistency // Verify client consistency
const isIpMatch = linkData.ip === req.ip; const isIpMatch = linkData.ip === getRealIp(req);
const isUserAgentMatch = linkData.userAgent === (req.get('User-Agent') || 'Unknown'); const isUserAgentMatch = linkData.userAgent === (req.get('User-Agent') || 'Discord-Bot-Request');
const isLocal = isLocalIp(req.ip) && isLocalIp(linkData.ip); const isLocal = isLocalIp(getRealIp(req)) && isLocalIp(linkData.ip);
const strictUserAgentCheck = process.env.STRICT_USER_AGENT_CHECK === 'true'; const strictUserAgentCheck = process.env.STRICT_USER_AGENT_CHECK === 'true';
if (strictUserAgentCheck && !isUserAgentMatch && !isLocal) { if (strictUserAgentCheck && !isUserAgentMatch && !isLocal) {
temporaryLinks.delete(sanitizedLinkId); temporaryLinks.delete(sanitizedLinkId);
console.log( 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}, ` + `expected IP: ${linkData.ip}, isLocal: ${isLocal}, ` +
`userAgentMatch: ${isUserAgentMatch}, ` + `userAgentMatch: ${isUserAgentMatch}, ` +
`expectedUserAgent: ${linkData.userAgent}, ` + `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' }); return res.status(403).json({ error: 'Invalid session' });
} }
if (!isUserAgentMatch) { if (!isUserAgentMatch) {
console.log( 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}, ` + `expectedUserAgent: ${linkData.userAgent}, ` +
`actualUserAgent: ${req.get('User-Agent') || 'Unknown'}` `actualUserAgent: ${req.get('User-Agent') || 'Discord-Bot-Request'}`
); );
} }
temporaryLinks.delete(sanitizedLinkId); 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 // Secure API key storage with additional client-side security and debugging
res.send(` res.send(`