Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
PasswordResetController
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
7 / 7
10
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
 reset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 postReset
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
2
 resetPassword
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 postResetPassword
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 showView
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 requireToken
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Controllers;
6
7use Engelsystem\Http\Exceptions\HttpNotFound;
8use Engelsystem\Http\Request;
9use Engelsystem\Http\Response;
10use Engelsystem\Mail\EngelsystemMailer;
11use Engelsystem\Models\User\PasswordReset;
12use Engelsystem\Models\User\User;
13use Psr\Log\LoggerInterface;
14use Symfony\Component\HttpFoundation\Session\SessionInterface;
15
16class PasswordResetController extends BaseController
17{
18    use HasUserNotifications;
19
20    /** @var array<string, string> */
21    protected array $permissions = [
22        'reset'             => 'login',
23        'postReset'         => 'login',
24        'resetPassword'     => 'login',
25        'postResetPassword' => 'login',
26    ];
27
28    public function __construct(
29        protected Response $response,
30        protected SessionInterface $session,
31        protected EngelsystemMailer $mail,
32        protected LoggerInterface $log
33    ) {
34    }
35
36    public function reset(): Response
37    {
38        return $this->showView('pages/password/reset');
39    }
40
41    public function postReset(Request $request): Response
42    {
43        $data = $this->validate($request, [
44            'email' => 'required|email',
45        ]);
46
47        /** @var User $user */
48        $user = User::whereEmail($data['email'])->first();
49        if ($user) {
50            $reset = (new PasswordReset())->findOrNew($user->id);
51            $reset->user_id = $user->id;
52            $reset->token = bin2hex(random_bytes(16));
53            $reset->save();
54
55            $this->log->info(
56                sprintf('Password recovery for %s (%u)', $user->name, $user->id),
57                ['user' => $user->toJson()]
58            );
59
60            $this->mail->sendViewTranslated(
61                $user,
62                'Password recovery',
63                'emails/password-reset',
64                ['username' => $user->displayName, 'reset' => $reset]
65            );
66        }
67
68        return $this->showView('pages/password/reset-success', ['type' => 'email']);
69    }
70
71    public function resetPassword(Request $request): Response
72    {
73        $this->requireToken($request);
74
75        return $this->showView(
76            'pages/password/reset-form',
77            ['min_length' => config('password_min_length')]
78        );
79    }
80
81    public function postResetPassword(Request $request): Response
82    {
83        $reset = $this->requireToken($request);
84
85        $data = $this->validate($request, [
86            'password'              => 'required|min:' . config('password_min_length'),
87            'password_confirmation' => 'required',
88        ]);
89
90        if ($data['password'] !== $data['password_confirmation']) {
91            $this->addNotification('validation.password.confirmed', NotificationType::ERROR);
92
93            return $this->showView('pages/password/reset-form');
94        }
95
96        auth()->setPassword($reset->user, $data['password']);
97        $reset->delete();
98
99        $reset->user->sessions()->getQuery()->delete();
100
101        return $this->showView('pages/password/reset-success', ['type' => 'reset']);
102    }
103
104    protected function showView(string $view = 'pages/password/reset', array $data = []): Response
105    {
106        return $this->response->withView($view, $data);
107    }
108
109    protected function requireToken(Request $request): PasswordReset
110    {
111        $token = $request->getAttribute('token');
112
113        /** @var PasswordReset|null $reset */
114        $reset = PasswordReset::whereToken($token)->first();
115
116        if (!$reset) {
117            throw new HttpNotFound();
118        }
119
120        return $reset;
121    }
122}