From a099f2a4dc53b86e20cc1bc3ac144ffb17cb709c Mon Sep 17 00:00:00 2001 From: Boban Blaskovic Date: Fri, 22 May 2026 01:41:10 +0200 Subject: [PATCH] feat(auth): HasRole middleware with OR-semantics Co-Authored-By: Claude Sonnet 4.6 --- app/Http/Middleware/HasRole.php | 27 +++++++++++++++++++++++ bootstrap/app.php | 7 +++++- routes/web.php | 5 +++++ tests/Feature/Auth/RoleMiddlewareTest.php | 27 +++++++++++++++++++++++ 4 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 app/Http/Middleware/HasRole.php create mode 100644 tests/Feature/Auth/RoleMiddlewareTest.php diff --git a/app/Http/Middleware/HasRole.php b/app/Http/Middleware/HasRole.php new file mode 100644 index 0000000..b849ad9 --- /dev/null +++ b/app/Http/Middleware/HasRole.php @@ -0,0 +1,27 @@ +user(); + + if (!$user) { + return redirect('/login'); + } + + // OR semantics: comma-separated roles = ANY of them grants access + foreach ($roles as $role) { + if ($user->hasRole($role)) { + return $next($request); + } + } + + abort(403); + } +} diff --git a/bootstrap/app.php b/bootstrap/app.php index c183276..4719bae 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -11,7 +11,12 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { - // + $middleware->redirectTo(guests: '/login'); + + $middleware->alias([ + 'role' => \App\Http\Middleware\HasRole::class, + 'license' => \App\Http\Middleware\EnsureActiveLicense::class, + ]); }) ->withExceptions(function (Exceptions $exceptions): void { // diff --git a/routes/web.php b/routes/web.php index 86a06c5..5cdd3ce 100644 --- a/routes/web.php +++ b/routes/web.php @@ -5,3 +5,8 @@ use Illuminate\Support\Facades\Route; Route::get('/', function () { return view('welcome'); }); + +// Test route for middleware test — only reachable with school-admin or teacher role +Route::middleware(['auth', 'role:school-admin,teacher'])->group(function () { + Route::get('/school/dashboard', fn() => response('ok'))->name('school.dashboard'); +}); diff --git a/tests/Feature/Auth/RoleMiddlewareTest.php b/tests/Feature/Auth/RoleMiddlewareTest.php new file mode 100644 index 0000000..e963dd9 --- /dev/null +++ b/tests/Feature/Auth/RoleMiddlewareTest.php @@ -0,0 +1,27 @@ +withRole('child')->create(); + actingAs($child)->get('/school/dashboard')->assertForbidden(); +}); + +it('erlaubt school-admin-Route für teacher (OR-Semantik)', function () { + $teacher = User::factory()->withRole('teacher')->create(); + actingAs($teacher)->get('/school/dashboard')->assertOk(); +}); + +it('erlaubt school-admin-Route für school-admin (OR-Semantik)', function () { + $admin = User::factory()->withRole('school-admin')->create(); + actingAs($admin)->get('/school/dashboard')->assertOk(); +}); + +it('redirected Unauthenticated to login', function () { + get('/school/dashboard')->assertRedirect('/login'); +});