diff --git a/CHANGELOG.md b/CHANGELOG.md index 170ecf4..48943f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,33 @@ Dieses Projekt verwendet [Semantic Versioning](https://semver.org/lang/de/). ## [Unreleased] +--- + +## [0.3.0] - 2026-06-27 + ### Added -- Laravel-Projekt aufgesetzt mit MariaDB-Anbindung -- Authentifizierung via Laravel Breeze (Login, Registrierung, Passwort-Reset) -- Rollen-basierte Zugriffskontrolle (RBAC) via Spatie Laravel Permission +- Admin-Modul: komplette Benutzerverwaltung unter `/admin/users` +- Benutzer anlegen, bearbeiten, löschen über Web-Oberfläche +- Rollenzuweisung direkt im Formular (admin / manager / user) +- Navigationslink „Benutzerverwaltung" nur für Admins sichtbar (`@role('admin')`) +- Gefahrenzone im Bearbeiten-Formular für sicheres Löschen +- Schutz: eigener Account kann nicht gelöscht werden + +### Security +- Admin-Routen mit Middleware `role:admin` geschützt + +--- + +## [0.2.0] - 2026-06-27 + +### Added +- Laravel 13 Projektstruktur +- Authentifizierung via Laravel Breeze (Blade) +- RBAC via Spatie Permission v8 (admin/manager/user) +- MariaDB-Anbindung konfiguriert - Rollen: `admin`, `manager`, `user` - Permissions: `user.*`, `role.*`, `network.*` -- Standard-Admin-Account beim Seeden angelegt +- Standard-Admin-Account: admin@mms-systemservice.de - Docker-Umgebung: Gitea, MariaDB, phpMyAdmin --- @@ -26,5 +46,7 @@ Dieses Projekt verwendet [Semantic Versioning](https://semver.org/lang/de/). - Grundlegende PHP-Projektstruktur (public/, src/, config/) - composer.json, .gitignore, README.md -[Unreleased]: http://localhost:3000/admin/Network-MGMT/compare/v0.1.0...HEAD +[Unreleased]: http://localhost:3000/admin/Network-MGMT/compare/v0.3.0...HEAD +[0.3.0]: http://localhost:3000/admin/Network-MGMT/compare/v0.2.0...v0.3.0 +[0.2.0]: http://localhost:3000/admin/Network-MGMT/compare/v0.1.0...v0.2.0 [0.1.0]: http://localhost:3000/admin/Network-MGMT/releases/tag/v0.1.0 diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php new file mode 100644 index 0000000..9cfcc13 --- /dev/null +++ b/app/Http/Controllers/Admin/UserController.php @@ -0,0 +1,100 @@ +orderBy('name')->paginate(20); + + return view('admin.users.index', compact('users')); + } + + public function create(): View + { + $roles = Role::orderBy('name')->get(); + + return view('admin.users.create', compact('roles')); + } + + public function store(Request $request): RedirectResponse + { + $validated = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], + 'password' => ['required', Password::min(8)->mixedCase()->numbers()], + 'role' => ['required', 'string', Rule::exists('roles', 'name')], + ]); + + $user = User::create([ + 'name' => $validated['name'], + 'email' => $validated['email'], + 'password' => Hash::make($validated['password']), + ]); + + $user->assignRole($validated['role']); + + return redirect() + ->route('admin.users.index') + ->with('success', "Benutzer „{$user->name}" wurde angelegt."); + } + + public function edit(User $user): View + { + $roles = Role::orderBy('name')->get(); + + return view('admin.users.edit', compact('user', 'roles')); + } + + public function update(Request $request, User $user): RedirectResponse + { + $validated = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], + 'password' => ['nullable', Password::min(8)->mixedCase()->numbers()], + 'role' => ['required', 'string', Rule::exists('roles', 'name')], + ]); + + $user->update([ + 'name' => $validated['name'], + 'email' => $validated['email'], + ]); + + if (!empty($validated['password'])) { + $user->update(['password' => Hash::make($validated['password'])]); + } + + $user->syncRoles([$validated['role']]); + + return redirect() + ->route('admin.users.index') + ->with('success', "Benutzer „{$user->name}" wurde aktualisiert."); + } + + public function destroy(User $user): RedirectResponse + { + if ($user->id === auth()->id()) { + return redirect() + ->route('admin.users.index') + ->with('error', 'Du kannst deinen eigenen Account nicht löschen.'); + } + + $name = $user->name; + $user->delete(); + + return redirect() + ->route('admin.users.index') + ->with('success', "Benutzer „{$name}" wurde gelöscht."); + } +} diff --git a/resources/views/admin/users/create.blade.php b/resources/views/admin/users/create.blade.php new file mode 100644 index 0000000..edff5f5 --- /dev/null +++ b/resources/views/admin/users/create.blade.php @@ -0,0 +1,67 @@ + + +
+ Benutzerverwaltung + / +

Neuer Benutzer

+
+
+ +
+
+
+ +
+ @csrf + + {{-- Name --}} +
+ + + +
+ + {{-- E-Mail --}} +
+ + + +
+ + {{-- Passwort --}} +
+ + +

Mindestens 8 Zeichen, Groß- und Kleinbuchstaben, eine Zahl.

+ +
+ + {{-- Rolle --}} +
+ + + +
+ + {{-- Buttons --}} +
+ Abbrechen + Benutzer anlegen +
+
+ +
+
+
+
diff --git a/resources/views/admin/users/edit.blade.php b/resources/views/admin/users/edit.blade.php new file mode 100644 index 0000000..05175e9 --- /dev/null +++ b/resources/views/admin/users/edit.blade.php @@ -0,0 +1,90 @@ + + +
+ Benutzerverwaltung + / +

{{ $user->name }} bearbeiten

+
+
+ +
+
+ + {{-- Stammdaten & Rolle --}} +
+

Stammdaten

+ +
+ @csrf + @method('PUT') + + {{-- Name --}} +
+ + + +
+ + {{-- E-Mail --}} +
+ + + +
+ + {{-- Neues Passwort --}} +
+ + +

Mindestens 8 Zeichen, Groß- und Kleinbuchstaben, eine Zahl.

+ +
+ + {{-- Rolle --}} +
+ + + +
+ + {{-- Buttons --}} +
+ Abbrechen + Änderungen speichern +
+
+
+ + {{-- Gefahrenzone --}} + @if($user->id !== auth()->id()) +
+

Gefahrenzone

+

+ Dieser Benutzer wird dauerhaft gelöscht und kann nicht wiederhergestellt werden. +

+
+ @csrf + @method('DELETE') + +
+
+ @endif + +
+
+
diff --git a/resources/views/admin/users/index.blade.php b/resources/views/admin/users/index.blade.php new file mode 100644 index 0000000..03d58ea --- /dev/null +++ b/resources/views/admin/users/index.blade.php @@ -0,0 +1,93 @@ + + +
+

+ Benutzerverwaltung +

+ + + Neuer Benutzer + +
+
+ +
+
+ + {{-- Flash-Meldungen --}} + @if(session('success')) +
+ {{ session('success') }} +
+ @endif + @if(session('error')) +
+ {{ session('error') }} +
+ @endif + +
+ + + + + + + + + + + + @forelse($users as $user) + + + + + + + + @empty + + + + @endforelse + +
NameE-MailRolleErstelltAktionen
+
{{ $user->name }}
+
+ {{ $user->email }} + + @foreach($user->roles as $role) + + {{ $role->name }} + + @endforeach + + {{ $user->created_at->format('d.m.Y') }} + + Bearbeiten + + @if($user->id !== auth()->id()) +
+ @csrf + @method('DELETE') + +
+ @endif +
+ Noch keine Benutzer vorhanden. +
+ + @if($users->hasPages()) +
+ {{ $users->links() }} +
+ @endif +
+
+
+
diff --git a/resources/views/layouts/navigation.blade.php b/resources/views/layouts/navigation.blade.php index c2d3a65..dbe7983 100644 --- a/resources/views/layouts/navigation.blade.php +++ b/resources/views/layouts/navigation.blade.php @@ -15,6 +15,11 @@ {{ __('Dashboard') }} + @role('admin') + + Benutzerverwaltung + + @endrole @@ -70,6 +75,11 @@ {{ __('Dashboard') }} + @role('admin') + + Benutzerverwaltung + + @endrole diff --git a/routes/web.php b/routes/web.php index 74bb7ca..056fc97 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,6 +1,7 @@ group(function () { Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); }); +// Admin-Bereich – nur für Admins +Route::prefix('admin') + ->name('admin.') + ->middleware(['auth', 'verified', 'role:admin']) + ->group(function () { + Route::get('/', fn() => redirect()->route('admin.users.index')); + Route::resource('users', AdminUserController::class); + }); + require __DIR__.'/auth.php';