session->isLoggedIn()) { return $this->redirect('/dashboard'); } return $this->render('auth/login'); } public function login(): Response { $email = $this->request->post('email'); $password = $this->request->post('password'); // Check if account is locked if ($this->session->isLockedOut()) { $this->flash('error', 'Account ist gesperrt. Bitte warten Sie 15 Minuten.'); return $this->redirect('/login'); } // Validate input $errors = $this->validate([ 'email' => 'required|email', 'password' => 'required' ]); if (!empty($errors)) { foreach ($errors as $field => $fieldErrors) { foreach ($fieldErrors as $error) { $this->flash('error', $error); } } return $this->redirect('/login'); } // Find user $user = (new User($this->database))->findByEmail($email); if (!$user || !$user['active']) { $this->incrementLoginAttempts(); $this->flash('error', 'Ungültige Anmeldedaten oder Account inaktiv.'); return $this->redirect('/login'); } // Verify password if (!password_verify($password, $user['passhash'])) { $this->incrementLoginAttempts(); $this->flash('error', 'Ungültige Anmeldedaten.'); return $this->redirect('/login'); } // Check if password needs rehash if (password_needs_rehash($user['passhash'], PASSWORD_ARGON2ID)) { $newHash = password_hash($password, PASSWORD_ARGON2ID); (new User($this->database))->update($user['id'], ['passhash' => $newHash]); } // Reset login attempts $this->session->setLoginAttempts(0); // Set user session $this->session->setUser($user); $this->session->setLastActivity(); // Log audit $this->logAudit('login', 'users', $user['id']); $this->flash('success', 'Erfolgreich angemeldet.'); return $this->redirect('/dashboard'); } public function logout(): Response { if ($this->session->isLoggedIn()) { $userId = $this->session->getUserId(); $this->logAudit('logout', 'users', $userId); } $this->session->logout(); $this->flash('success', 'Erfolgreich abgemeldet.'); return $this->redirect('/login'); } public function showForgotPassword(): string { return $this->render('auth/forgot-password'); } public function forgotPassword(): Response { $email = $this->request->post('email'); $errors = $this->validate([ 'email' => 'required|email' ]); if (!empty($errors)) { foreach ($errors as $field => $fieldErrors) { foreach ($fieldErrors as $error) { $this->flash('error', $error); } } return $this->redirect('/password/forgot'); } $user = (new User($this->database))->findByEmail($email); if ($user && $user['active']) { $token = bin2hex(random_bytes(32)); $expiresAt = date('Y-m-d H:i:s', strtotime('+1 hour')); $passwordReset = new PasswordReset($this->database); $passwordReset->create([ 'user_id' => $user['id'], 'token' => $token, 'expires_at' => $expiresAt ]); // Send email (in production, implement email service) // $this->sendPasswordResetEmail($user['email'], $token); } // Always show success message for security $this->flash('success', 'Falls die E-Mail-Adresse existiert, wurde ein Reset-Link gesendet.'); return $this->redirect('/login'); } public function showResetPassword(): string { $token = $this->request->get('token'); if (!$token) { $this->flash('error', 'Ungültiger Reset-Link.'); return $this->redirect('/login'); } $passwordReset = new PasswordReset($this->database); $reset = $passwordReset->findByToken($token); if (!$reset || strtotime($reset['expires_at']) < time()) { $this->flash('error', 'Reset-Link ist ungültig oder abgelaufen.'); return $this->redirect('/login'); } return $this->render('auth/reset-password', ['token' => $token]); } public function resetPassword(): Response { $token = $this->request->post('token'); $password = $this->request->post('password'); $passwordConfirm = $this->request->post('password_confirm'); $errors = $this->validate([ 'password' => 'required|min:8', 'password_confirm' => 'required' ]); if ($password !== $passwordConfirm) { $this->flash('error', 'Passwörter stimmen nicht überein.'); return $this->redirect('/password/reset?token=' . $token); } if (!empty($errors)) { foreach ($errors as $field => $fieldErrors) { foreach ($fieldErrors as $error) { $this->flash('error', $error); } } return $this->redirect('/password/reset?token=' . $token); } $passwordReset = new PasswordReset($this->database); $reset = $passwordReset->findByToken($token); if (!$reset || strtotime($reset['expires_at']) < time()) { $this->flash('error', 'Reset-Link ist ungültig oder abgelaufen.'); return $this->redirect('/login'); } // Update user password $userModel = new User($this->database); $userModel->update($reset['user_id'], [ 'passhash' => password_hash($password, PASSWORD_ARGON2ID) ]); // Delete used reset token $passwordReset->delete($reset['id']); $this->flash('success', 'Passwort erfolgreich geändert. Sie können sich jetzt anmelden.'); return $this->redirect('/login'); } private function incrementLoginAttempts(): void { $attempts = $this->session->getLoginAttempts(); $this->session->setLoginAttempts($attempts + 1); } }