Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
RoleMiddleware
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
3 / 3
5
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 process
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
3
 forbiddenResponse
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace App\Middleware;
6
7use App\Domain\Auth\Data\UserAuthData;
8use Nyholm\Psr7\Response;
9use Psr\Http\Message\ResponseInterface;
10use Psr\Http\Message\ServerRequestInterface;
11use Psr\Http\Server\MiddlewareInterface;
12use Psr\Http\Server\RequestHandlerInterface;
13
14use function in_array;
15use function sprintf;
16
17/**
18 * Role-based Access Control Middleware.
19 *
20 * Checks if authenticated user has required role(s).
21 * Must be used AFTER JwtAuthMiddleware.
22 */
23final class RoleMiddleware implements MiddlewareInterface
24{
25    /**
26     * @param array<int, string> $allowedRoles
27     */
28    public function __construct(
29        private readonly array $allowedRoles,
30    ) {}
31
32    public function process(
33        ServerRequestInterface $request,
34        RequestHandlerInterface $handler,
35    ): ResponseInterface {
36        // Get user from request (added by JwtAuthMiddleware)
37        $user = $request->getAttribute('user');
38
39        // User should exist (JwtAuthMiddleware runs first)
40        if (!$user instanceof UserAuthData) {
41            return $this->forbiddenResponse('Authentication required');
42        }
43
44        // Check if user has require role
45        if (!in_array($user->role, $this->allowedRoles, true)) {
46            return $this->forbiddenResponse(
47                sprintf(
48                    'Insufficient permissions. Required role: %s',
49                    implode(' or ', $this->allowedRoles),
50                ),
51            );
52        }
53
54        // User has required role, continue
55        return $handler->handle($request);
56    }
57
58    private function forbiddenResponse(string $message): ResponseInterface
59    {
60        $response = new Response();
61        $response->getBody()->write((string)json_encode([
62            'error' => 'Forbidden',
63            'message' => $message,
64        ]));
65
66        return $response
67            ->withHeader('Content-Type', 'application/json')
68            ->withStatus(403);
69    }
70}