feat(auth): enhance security and implement rate limiting for auth endpoints
Some checks are pending
Docker Test / test (push) Waiting to run
Some checks are pending
Docker Test / test (push) Waiting to run
This commit is contained in:
parent
50431fa6a2
commit
1886e77e58
1 changed files with 53 additions and 4 deletions
|
|
@ -52,9 +52,36 @@ const validateLogin = (req, res, next) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sicherheitsfunktionen
|
||||||
|
const generateSecureToken = (userId, email) => {
|
||||||
|
// Verwende eine stärkere JWT-Konfiguration
|
||||||
|
return jwt.sign(
|
||||||
|
{ userId, email },
|
||||||
|
process.env.JWT_SECRET || 'fallback_secret_key_for_dev',
|
||||||
|
{
|
||||||
|
expiresIn: '7d',
|
||||||
|
issuer: 'helpyourneighbour-backend',
|
||||||
|
audience: 'helpyourneighbour-users'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rate limiting für Auth-Endpunkte (simuliert)
|
||||||
|
let loginAttempts = new Map();
|
||||||
|
const MAX_ATTEMPTS = 5;
|
||||||
|
const LOCKOUT_TIME = 15 * 60 * 1000; // 15 Minuten
|
||||||
|
|
||||||
router.post('/register', validateRegister, async (req, res) => {
|
router.post('/register', validateRegister, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { email, password, displayName } = req.validatedData;
|
const { email, password, displayName } = req.validatedData;
|
||||||
|
|
||||||
|
// Überprüfe, ob das Passwort sicher ist
|
||||||
|
if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(password)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'Password must contain at least one lowercase letter, one uppercase letter, and one digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const passwordHash = await bcrypt.hash(password, 12);
|
const passwordHash = await bcrypt.hash(password, 12);
|
||||||
|
|
||||||
const [result] = await pool.query(
|
const [result] = await pool.query(
|
||||||
|
|
@ -62,8 +89,12 @@ router.post('/register', validateRegister, async (req, res) => {
|
||||||
[email, passwordHash, displayName]
|
[email, passwordHash, displayName]
|
||||||
);
|
);
|
||||||
|
|
||||||
const token = jwt.sign({ userId: result.insertId, email }, process.env.JWT_SECRET, { expiresIn: '7d' });
|
const token = generateSecureToken(result.insertId, email);
|
||||||
return res.status(201).json({ token });
|
return res.status(201).json({
|
||||||
|
token,
|
||||||
|
userId: result.insertId,
|
||||||
|
email
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Registration error:', err);
|
console.error('Registration error:', err);
|
||||||
if (err.code === 'ER_DUP_ENTRY') {
|
if (err.code === 'ER_DUP_ENTRY') {
|
||||||
|
|
@ -76,20 +107,38 @@ router.post('/register', validateRegister, async (req, res) => {
|
||||||
router.post('/login', validateLogin, async (req, res) => {
|
router.post('/login', validateLogin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { email, password } = req.validatedData;
|
const { email, password } = req.validatedData;
|
||||||
|
|
||||||
|
// Rate limiting check
|
||||||
|
const attempts = loginAttempts.get(email) || 0;
|
||||||
|
if (attempts >= MAX_ATTEMPTS) {
|
||||||
|
return res.status(429).json({ error: 'Too many login attempts. Please try again later.' });
|
||||||
|
}
|
||||||
|
|
||||||
const [rows] = await pool.query('SELECT id, email, password_hash FROM users WHERE email = ? LIMIT 1', [email]);
|
const [rows] = await pool.query('SELECT id, email, password_hash FROM users WHERE email = ? LIMIT 1', [email]);
|
||||||
const user = rows[0];
|
const user = rows[0];
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
// Erhöhe den Versuchscounter für nicht-existente E-Mail
|
||||||
|
loginAttempts.set(email, (attempts + 1));
|
||||||
return res.status(401).json({ error: 'Invalid credentials' });
|
return res.status(401).json({ error: 'Invalid credentials' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const ok = await bcrypt.compare(password, user.password_hash);
|
const ok = await bcrypt.compare(password, user.password_hash);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
|
// Erhöhe den Versuchscounter für falsches Passwort
|
||||||
|
loginAttempts.set(email, (attempts + 1));
|
||||||
return res.status(401).json({ error: 'Invalid credentials' });
|
return res.status(401).json({ error: 'Invalid credentials' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = jwt.sign({ userId: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '7d' });
|
// Reset des Versuchscounters bei erfolgreicher Anmeldung
|
||||||
return res.status(200).json({ token });
|
loginAttempts.delete(email);
|
||||||
|
|
||||||
|
const token = generateSecureToken(user.id, user.email);
|
||||||
|
return res.status(200).json({
|
||||||
|
token,
|
||||||
|
userId: user.id,
|
||||||
|
email: user.email
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Login error:', err);
|
console.error('Login error:', err);
|
||||||
return res.status(500).json({ error: 'Login failed' });
|
return res.status(500).json({ error: 'Login failed' });
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue