feat(auth): HasRole middleware with OR-semantics

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
Boban Blaskovic 2026-05-22 01:41:10 +02:00
parent 986f2afcff
commit a099f2a4dc
4 changed files with 65 additions and 1 deletions

View File

@ -0,0 +1,27 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class HasRole
{
public function handle(Request $request, Closure $next, string ...$roles): mixed
{
$user = $request->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);
}
}

View File

@ -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 {
//

View File

@ -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');
});

View File

@ -0,0 +1,27 @@
<?php
use App\Models\Tenant;
use App\Models\User;
use function Pest\Laravel\actingAs;
use function Pest\Laravel\get;
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
it('blockt school-admin-Route für child-Rolle', function () {
$child = User::factory()->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');
});