Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
AuthenticatedOnlyMiddleware
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
3 / 3
7
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%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 handleGuestAccess
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\User\Presentation\Middleware;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use Psr\Http\Message\ResponseFactoryInterface;
12use Psr\Http\Message\ResponseInterface;
13use Psr\Http\Message\ServerRequestInterface;
14use Psr\Http\Server\MiddlewareInterface;
15use Psr\Http\Server\RequestHandlerInterface;
16use Yiisoft\User\CurrentUser;
17
18/**
19 * Access Control Middleware for Authenticated Only Routes.
20 *
21 * Redirects guest users to /login when attempting to access protected routes.
22 *
23 * @package App\Modules\User\Presentation\Middleware
24 */
25final readonly class AuthenticatedOnlyMiddleware implements MiddlewareInterface
26{
27    private const string MIME_JSON = 'application/json; charset=UTF-8';
28    private const string ERR_AUTH_REQUIRED = '{"success":false,"error":"Authentication required."}';
29
30    /**
31     * AuthenticatedOnlyMiddleware constructor.
32     *
33     * @param CurrentUser $currentUser Current identity user service instance.
34     * @param ResponseFactoryInterface $responseFactory PSR-7 Response factory.
35     */
36    public function __construct(
37        private CurrentUser $currentUser,
38        private ResponseFactoryInterface $responseFactory
39    ) {
40    }
41
42    /**
43     * {@inheritdoc}
44     */
45    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
46    {
47        if ($this->currentUser->isGuest()) {
48            return $this->handleGuestAccess($request);
49        }
50
51        return $handler->handle($request);
52    }
53
54    /**
55     * Generates appropriate unauthorized response based on request protocol headers.
56     *
57     * @param ServerRequestInterface $request PSR-7 server request.
58     * @return ResponseInterface 302, 401 JSON, or 401 with HX-Redirect header.
59     */
60    private function handleGuestAccess(ServerRequestInterface $request): ResponseInterface
61    {
62        if ($request->hasHeader('HX-Request')) {
63            return $this->responseFactory->createResponse(401)
64                ->withHeader('HX-Redirect', '/login')
65                ->withHeader('Content-Type', 'text/plain');
66        }
67
68        $accept = $request->getHeaderLine('Accept');
69        $isJson = str_contains($accept, 'application/json');
70        $isXhr  = strtolower($request->getHeaderLine('X-Requested-With')) === 'xmlhttprequest';
71
72        if ($isJson || $isXhr) {
73            $response = $this->responseFactory->createResponse(401);
74            $response->getBody()->write(self::ERR_AUTH_REQUIRED);
75            return $response->withHeader('Content-Type', self::MIME_JSON);
76        }
77
78        return $this->responseFactory->createResponse(302)->withHeader('Location', '/login');
79    }
80}