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
|
// 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(`
|
||||||
|
Reference in New Issue
Block a user