const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const crypto = require('crypto'); const User = require('../models/User'); const config = require('../config/env'); const logger = require('../utils/logger'); // POST /api/drohnenfuehrer/login — email + password const drohnenfuehrerLogin = async (req, res) => { try { const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ success: false, message: 'E-Mail und Passwort erforderlich' }); } const user = await User.findOne({ email: email.trim().toLowerCase(), deleted: false }); if (!user || !user.passwordHash) { return res.status(401).json({ success: false, message: 'Ungültige Anmeldedaten oder kein Passwort hinterlegt' }); } const valid = await bcrypt.compare(password, user.passwordHash); if (!valid) { return res.status(401).json({ success: false, message: 'Ungültige Anmeldedaten' }); } const token = jwt.sign( { id: user._id.toString(), role: 'drohnenfuehrer' }, config.jwtSecret, { expiresIn: '12h' } ); res.json({ success: true, token, user: { id: user._id, name: user.name, email: user.email, available: user.available, type: user.type, phone: user.phone, landline: user.landline, address: user.address } }); } catch (error) { logger.error('Drohnenführer-Login Fehler:', error); res.status(500).json({ success: false, message: 'Serverfehler beim Login' }); } }; // POST /api/drohnenfuehrer/set-password — set first password using a one-time invite token // Admin must first generate an invite token; the Drohnenführer then uses it here. const setDrohnenfuehrerPassword = async (req, res) => { try { const { email, inviteToken, newPassword } = req.body; if (!email || !inviteToken || !newPassword || newPassword.length < 8) { return res.status(400).json({ success: false, message: 'E-Mail, Einladungs-Token und Passwort (min. 8 Zeichen) erforderlich' }); } const user = await User.findOne({ email: email.trim().toLowerCase(), deleted: false }) .select('+inviteToken +inviteTokenExpiry'); // Always return the same error to prevent user enumeration const invalidMsg = 'Ungültiger oder abgelaufener Einladungs-Token'; if (!user || !user.inviteToken || !user.inviteTokenExpiry) { return res.status(401).json({ success: false, message: invalidMsg }); } if (user.inviteTokenExpiry < new Date()) { user.inviteToken = null; user.inviteTokenExpiry = null; await user.save(); return res.status(401).json({ success: false, message: invalidMsg }); } // Timing-safe comparison to prevent timing attacks const incoming = Buffer.from(inviteToken.trim()); const stored = Buffer.from(user.inviteToken); if (incoming.length !== stored.length || !crypto.timingSafeEqual(incoming, stored)) { return res.status(401).json({ success: false, message: invalidMsg }); } user.passwordHash = await bcrypt.hash(newPassword, 12); user.inviteToken = null; user.inviteTokenExpiry = null; await user.save(); res.json({ success: true, message: 'Passwort erfolgreich gesetzt' }); } catch (error) { logger.error('Passwort setzen Fehler:', error); res.status(500).json({ success: false, message: 'Serverfehler' }); } }; // PUT /api/drohnenfuehrer/me — Drohnenführer updates own availability + contact data const updateDrohnenfuehrerSelf = async (req, res) => { try { const userId = req.drohnenfuehrerUser.id; const { available, phone, landline, address } = req.body; const update = {}; if (typeof available === 'boolean') update.available = available; if (phone && phone.trim()) update.phone = phone.trim(); if (landline !== undefined) update.landline = landline ? landline.trim() : null; if (address && address.trim()) update.address = address.trim(); const user = await User.findByIdAndUpdate( userId, update, { new: true, select: '-passwordHash' } ); if (!user) { return res.status(404).json({ success: false, message: 'Drohnenführer nicht gefunden' }); } res.json({ success: true, data: user }); } catch (error) { logger.error('Drohnenführer-Update Fehler:', error); res.status(500).json({ success: false, message: 'Fehler beim Aktualisieren' }); } }; // GET /api/drohnenfuehrer/me — get own profile const getDrohnenfuehrerSelf = async (req, res) => { try { const user = await User.findById(req.drohnenfuehrerUser.id).select('-passwordHash'); if (!user) return res.status(404).json({ success: false, message: 'Nicht gefunden' }); res.json({ success: true, data: user }); } catch (error) { res.status(500).json({ success: false, message: 'Serverfehler' }); } }; // POST /api/drohnenfuehrer/:id/invite-token — Admin generates one-time invite token const generateInviteToken = async (req, res) => { try { const user = await User.findOne({ _id: req.params.id, deleted: false }) .select('+inviteToken +inviteTokenExpiry'); if (!user) { return res.status(404).json({ success: false, message: 'Benutzer nicht gefunden' }); } if (!user.email) { return res.status(400).json({ success: false, message: 'Kein E-Mail für diesen Benutzer hinterlegt' }); } const token = crypto.randomBytes(32).toString('hex'); user.inviteToken = token; user.inviteTokenExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days await user.save(); res.json({ success: true, message: 'Einladungs-Token generiert (gültig 7 Tage)', data: { inviteToken: token, expiresAt: user.inviteTokenExpiry, email: user.email } }); } catch (error) { logger.error('Fehler beim Generieren des Einladungs-Tokens:', error); res.status(500).json({ success: false, message: 'Serverfehler' }); } }; module.exports = { drohnenfuehrerLogin, setDrohnenfuehrerPassword, generateInviteToken, updateDrohnenfuehrerSelf, getDrohnenfuehrerSelf };