Initial commit of the Asset Management System, including project structure, Docker configuration, database migrations, and core application files. Added user authentication, asset management features, and basic UI components.

This commit is contained in:
2025-08-22 21:41:02 +02:00
parent b43a98f0ec
commit 677f70a19c
52 changed files with 5186 additions and 2 deletions

View File

@@ -0,0 +1,209 @@
<?php
namespace App\Controllers;
use App\Core\BaseController;
use App\Models\User;
use App\Models\PasswordReset;
class AuthController extends BaseController
{
public function showLogin(): string
{
if ($this->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);
}
}