Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
ForgotPasswordAction
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
2 / 2
5
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
 __invoke
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5namespace App\Action\Auth;
6
7use App\Domain\Auth\Repository\AuthRepository;
8use App\Domain\Auth\Service\PasswordResetService;
9use App\Renderer\JsonRenderer;
10use App\Support\AppUrlResolver;
11use App\Support\Row;
12use Psr\Http\Message\ResponseInterface;
13use Psr\Http\Message\ServerRequestInterface;
14
15/**
16 * POST /api/auth/forgot-password
17 *
18 * Accepts { email } and sends a password reset link if the account exists.
19 * Always returns 200 to prevent email enumeration.
20 */
21final readonly class ForgotPasswordAction
22{
23    /**
24     * @param array<string, mixed> $emailConfig
25     * @param AuthRepository $authRepository
26     * @param PasswordResetService $passwordResetService
27     * @param JsonRenderer $renderer
28     */
29    public function __construct(
30        private AuthRepository $authRepository,
31        private PasswordResetService $passwordResetService,
32        private JsonRenderer $renderer,
33        private array $emailConfig,
34    ) {}
35
36    public function __invoke(
37        ServerRequestInterface $request,
38        ResponseInterface $response,
39    ): ResponseInterface {
40        $data = (array)$request->getParsedBody();
41        $email = Row::nullableString($data, 'email') ?? '';
42
43        if ($email !== '') {
44            $user = $this->authRepository->findUserByEmail($email);
45
46            if ($user !== null && $user->isActive) {
47                $appUrl = AppUrlResolver::resolve($request, Row::nullableString($this->emailConfig, 'app_url'));
48                $this->passwordResetService->sendResetLink($user, $appUrl);
49            }
50        }
51
52        return $this->renderer->json($response, [
53            'success' => true,
54            'message' => 'If an account with that email exists, a reset link has been sent.',
55        ]);
56    }
57}