Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.83% covered (success)
95.83%
23 / 24
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
PanelAuth
95.83% covered (success)
95.83%
23 / 24
75.00% covered (warning)
75.00%
3 / 4
9
0.00% covered (danger)
0.00%
0 / 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%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 unauthorized
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 loginUrl
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Middleware;
6
7use Celema\Core\Factory\Factory;
8use Cosray\Auth;
9use Cosray\Config;
10use Cosray\Security\Policy;
11use Cosray\Users;
12use Override;
13use Psr\Http\Message\ResponseInterface as Response;
14use Psr\Http\Message\ServerRequestInterface as Request;
15use Psr\Http\Server\MiddlewareInterface as Middleware;
16use Psr\Http\Server\RequestHandlerInterface as Handler;
17
18class PanelAuth implements Middleware
19{
20    public function __construct(
21        private readonly Config $config,
22        private readonly Users $users,
23        private readonly Factory $factory,
24        private readonly Policy $policy,
25    ) {}
26
27    #[Override]
28    public function process(Request $request, Handler $handler): Response
29    {
30        $session = $request->getAttribute('session', null);
31        $auth = new Auth($request, $this->users, $this->config, $session);
32        $user = $auth->user();
33
34        if ($this->policy->permits($user, 'panel')) {
35            // Expose the resolved user downstream (chrome gating, panel
36            // locale) for token requests too, which never touch the
37            // session middleware's attribute.
38            return $handler->handle($request->withAttribute('user', $user));
39        }
40
41        return $this->unauthorized($request, $user !== null);
42    }
43
44    private function unauthorized(Request $request, bool $authenticated): Response
45    {
46        $url = $this->loginUrl($request);
47
48        if ($request->hasHeader('HX-Request')) {
49            $status = $authenticated ? 403 : 401;
50
51            return $this->factory
52                ->response($status)
53                ->withHeader('HX-Redirect', $url);
54        }
55
56        return $this->factory
57            ->response(303)
58            ->withHeader('Location', $url);
59    }
60
61    private function loginUrl(Request $request): string
62    {
63        $panelPath = $this->config->panel->path;
64        $path = $request->getUri()->getPath();
65
66        if ($path === '') {
67            $path = '/';
68        }
69
70        $query = $request->getUri()->getQuery();
71        $next = $query === '' ? $path : $path . '?' . $query;
72        $params = http_build_query(['next' => $next]);
73
74        return $panelPath . '/login?' . $params;
75    }
76}