further security improvments
This commit is contained in:
@ -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(`
|
||||
|
Reference in New Issue
Block a user