Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
ResetPasswordAction
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 2
90
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __invoke
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
72
1<?php
2
3declare(strict_types=1);
4
5namespace App\Action\Auth;
6
7use App\Domain\Auth\Repository\AuthRepository;
8use App\Domain\Auth\Service\PasswordService;
9use App\Domain\Exception\BadRequestException;
10use App\Renderer\JsonRenderer;
11use App\Support\Row;
12use InvalidArgumentException;
13use Psr\Http\Message\ResponseInterface;
14use Psr\Http\Message\ServerRequestInterface;
15
16/**
17 * POST /api/auth/reset-password
18 *
19 * Accepts { token, password } and resets the user's password.
20 */
21final readonly class ResetPasswordAction
22{
23    public function __construct(
24        private AuthRepository $authRepository,
25        private PasswordService $passwordService,
26        private JsonRenderer $renderer,
27    ) {}
28
29    public function __invoke(
30        ServerRequestInterface $request,
31        ResponseInterface $response,
32    ): ResponseInterface {
33        $data = (array)$request->getParsedBody();
34
35        $token = Row::nullableString($data, 'token') ?? '';
36        $password = Row::nullableString($data, 'password') ?? '';
37
38        if ($token === '') {
39            throw new BadRequestException('Reset token is required');
40        }
41
42        if ($password === '') {
43            throw new BadRequestException('New password is required');
44        }
45
46        // Validate password strength
47        if (strlen($password) < 8) {
48            throw new InvalidArgumentException('Password must be at least 8 characters');
49        }
50
51        if (!preg_match('/[A-Z]/', $password)) {
52            throw new InvalidArgumentException('Password must contain at least one uppercase letter');
53        }
54
55        if (!preg_match('/[a-z]/', $password)) {
56            throw new InvalidArgumentException('Password must contain at least one lowercase letter');
57        }
58
59        if (!preg_match('/[0-9]/', $password)) {
60            throw new InvalidArgumentException('Password must contain at least one number');
61        }
62
63        $resetToken = $this->authRepository->findValidResetToken($token);
64
65        if ($resetToken === null) {
66            throw new BadRequestException('Invalid or expired reset token');
67        }
68
69        $hash = $this->passwordService->hashPassword($password);
70        $this->authRepository->updatePassword($resetToken['userId'], $hash);
71        $this->authRepository->markResetTokenUsed($resetToken['tokenId']);
72
73        return $this->renderer->json($response, [
74            'success' => true,
75            'message' => 'Password has been reset successfully. You can now log in.',
76        ]);
77    }
78}