Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
ChangePasswordHandler
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
2 / 2
4
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
 handle
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace App\Identity\Application\Command;
6
7use App\Identity\Application\AuthenticatedUserFinder;
8use App\Identity\Application\Service\PasswordHasher;
9use App\Identity\Application\Service\PasswordPolicy;
10use App\Identity\Domain\Exceptions\InvalidCurrentPassword;
11use App\Identity\Domain\UserRepository;
12use App\Identity\Domain\ValueObject\UserId;
13
14final readonly class ChangePasswordHandler
15{
16    public function __construct(
17        private AuthenticatedUserFinder $finder,
18        private UserRepository $users,
19        private PasswordHasher $passwordHasher,
20        private PasswordPolicy $passwordPolicy,
21    ) {
22    }
23
24    public function handle(ChangePasswordCommand $command): void
25    {
26        $user = $this->finder->getOrFail(new UserId($command->userId));
27
28        $current = $user->password();
29
30        // A provider-only account has no current password to match, so it can
31        // never pass this check — same 400 as a wrong one.
32        if ($current === null || !$this->passwordHasher->verify($current, $command->currentPassword)) {
33            throw InvalidCurrentPassword::create();
34        }
35
36        $this->passwordPolicy->assert($command->newPassword);
37
38        $user->changePassword($this->passwordHasher->hash($command->newPassword));
39
40        $this->users->save($user);
41    }
42}