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
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(`
<html>
@ -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(`