238 lines
6.5 KiB
JavaScript
238 lines
6.5 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const crypto = require('crypto');
|
|
const Admin = require('../models/Admin');
|
|
const ResetToken = require('../models/ResetToken');
|
|
const config = require('../config/env');
|
|
const logger = require('../utils/logger');
|
|
const { auditAuth } = require('../middleware/auditLogger');
|
|
|
|
const login = async (req, res) => {
|
|
try {
|
|
const { username, password } = req.body;
|
|
|
|
if (!username || !password) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Benutzername und Passwort sind erforderlich'
|
|
});
|
|
}
|
|
|
|
// Find admin (case-insensitive username match)
|
|
const admin = await Admin.findOne({ username: { $regex: new RegExp(`^${username.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'i') } });
|
|
if (!admin) {
|
|
await auditAuth(req, false, username, 'Benutzer nicht gefunden');
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Benutzername oder Passwort falsch'
|
|
});
|
|
}
|
|
|
|
// Check password
|
|
const isPasswordValid = await admin.comparePassword(password);
|
|
if (!isPasswordValid) {
|
|
await auditAuth(req, false, username, 'Falsches Passwort');
|
|
return res.status(401).json({
|
|
success: false,
|
|
message: 'Benutzername oder Passwort falsch'
|
|
});
|
|
}
|
|
|
|
// Generate token
|
|
const token = jwt.sign(
|
|
{ id: admin._id, username: admin.username },
|
|
config.jwtSecret,
|
|
{ expiresIn: config.jwtExpiresIn }
|
|
);
|
|
|
|
// Set token in httpOnly cookie (XSS protection)
|
|
res.cookie('token', token, {
|
|
httpOnly: true, // Not accessible via JavaScript (XSS protection)
|
|
secure: false, // Allow over HTTP in development (localhost)
|
|
sameSite: 'lax', // Relaxed CSRF protection (allows cross-port cookies on localhost)
|
|
path: '/', // Ensure cookie is sent for all app routes, including /stoeberhunde/api
|
|
maxAge: 24 * 60 * 60 * 1000 // 24 hours in milliseconds
|
|
});
|
|
|
|
// Log successful login
|
|
await auditAuth(req, true, username);
|
|
|
|
res.json({
|
|
success: true,
|
|
user: {
|
|
id: admin._id,
|
|
username: admin.username
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logger.error('Login-Fehler:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Serverfehler beim Anmelden'
|
|
});
|
|
}
|
|
};
|
|
|
|
const logout = async (req, res) => {
|
|
try {
|
|
// Log logout (get username from token if available)
|
|
const username = req.user?.username || 'unknown';
|
|
await auditAuth(req, true, username, null);
|
|
|
|
// Clear the token cookie
|
|
res.clearCookie('token', {
|
|
httpOnly: true,
|
|
secure: config.nodeEnv === 'production',
|
|
sameSite: 'lax',
|
|
path: '/'
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Erfolgreich abgemeldet'
|
|
});
|
|
} catch (error) {
|
|
logger.error('Logout-Fehler:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Serverfehler beim Abmelden'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Request password reset - generates reset token
|
|
*/
|
|
const forgotPassword = async (req, res) => {
|
|
try {
|
|
const { username } = req.body;
|
|
|
|
if (!username) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Benutzername ist erforderlich'
|
|
});
|
|
}
|
|
|
|
// Find admin
|
|
const admin = await Admin.findOne({ username });
|
|
|
|
// Don't reveal if user exists (security best practice)
|
|
if (!admin) {
|
|
logger.warn(`Password reset requested for non-existent user: ${username}`);
|
|
return res.json({
|
|
success: true,
|
|
message: 'Falls der Benutzer existiert, wurde ein Reset-Token generiert. Bitte kontaktieren Sie den Administrator.'
|
|
});
|
|
}
|
|
|
|
// Generate secure random token
|
|
const token = crypto.randomBytes(32).toString('hex');
|
|
const expiresAt = new Date(Date.now() + 3600000); // 1 hour from now
|
|
|
|
// Delete any existing tokens for this admin
|
|
await ResetToken.deleteMany({ adminId: admin._id });
|
|
|
|
// Create new reset token
|
|
await ResetToken.create({
|
|
adminId: admin._id,
|
|
token,
|
|
expiresAt
|
|
});
|
|
|
|
// In production, send email here
|
|
// For development, log the token
|
|
if (config.nodeEnv !== 'production') {
|
|
logger.info(`Password reset token for ${username}: ${token} (expires in 1 hour)`);
|
|
} else {
|
|
logger.info(`Password reset token generiert für Benutzer: ${username}`);
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Reset-Token wurde generiert. In der Entwicklungsumgebung finden Sie den Token in den Logs.',
|
|
...(config.nodeEnv === 'development' && { token }) // Only in dev!
|
|
});
|
|
} catch (error) {
|
|
logger.error('Fehler bei Passwort-Reset-Anfrage:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Serverfehler bei Passwort-Reset-Anfrage'
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reset password with token
|
|
*/
|
|
const resetPassword = async (req, res) => {
|
|
try {
|
|
const { token, newPassword } = req.body;
|
|
|
|
if (!token || !newPassword) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Token und neues Passwort sind erforderlich'
|
|
});
|
|
}
|
|
|
|
// Validate password length
|
|
if (newPassword.length < 6) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Passwort muss mindestens 6 Zeichen lang sein'
|
|
});
|
|
}
|
|
|
|
// Find valid, unused token
|
|
const resetToken = await ResetToken.findOne({
|
|
token,
|
|
expiresAt: { $gt: new Date() },
|
|
used: false
|
|
});
|
|
|
|
if (!resetToken) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Ungültiger oder abgelaufener Reset-Token'
|
|
});
|
|
}
|
|
|
|
// Update admin password
|
|
const admin = await Admin.findById(resetToken.adminId);
|
|
if (!admin) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Admin nicht gefunden'
|
|
});
|
|
}
|
|
|
|
admin.password = newPassword;
|
|
await admin.save();
|
|
|
|
// Mark token as used
|
|
resetToken.used = true;
|
|
await resetToken.save();
|
|
|
|
// Delete all other tokens for this admin
|
|
await ResetToken.deleteMany({
|
|
adminId: admin._id,
|
|
_id: { $ne: resetToken._id }
|
|
});
|
|
|
|
logger.info(`Password successfully reset for admin: ${admin.username}`);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Passwort erfolgreich zurückgesetzt'
|
|
});
|
|
} catch (error) {
|
|
logger.error('Fehler beim Zurücksetzen des Passworts:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Serverfehler beim Zurücksetzen des Passworts'
|
|
});
|
|
}
|
|
};
|
|
|
|
module.exports = { login, logout, forgotPassword, resetPassword };
|