SymfonyCasts / reset-password-bundle

Need a killer reset password feature for your Symfony? Us too!
https://symfonycasts.com
MIT License
469 stars 65 forks source link

Generated tests fail to preg_match the token #333

Open jeremyVignelles opened 5 days ago

jeremyVignelles commented 5 days ago

Version: 1.22.0

I used the maker bundle to generate tests for the password reset feature. I adapted the code so that the mail is in french, but the generated code fails to properly get the token

Test method excerpt:

        // Ensure the reset password email was sent
        // Use either assertQueuedEmailCount() || assertEmailCount() depending on your mailer setup
        self::assertQueuedEmailCount(1);
        // self::assertEmailCount(1);

...

        // Test the link sent in the email is valid
        $email = $messages[0]->toString();
        preg_match('#(/reset-password/reset/[a-zA-Z0-9]+)#', $email, $resetLink);// Matches the first characters of the link, see below

        $this->client->request(\Symfony\Component\HttpFoundation\Request::METHOD_GET, $resetLink[1]);

        self::assertResponseRedirects('/reset-password/reset');

        $this->client->followRedirect();

        // Test we can set a new password
        $this->client->submitForm('Changer votre mot de passe', [// Fails here : can't find the button... $this->client is still on a redirect page
            'change_password_form[plainPassword][first]' => 'newStrongPassword',
            'change_password_form[plainPassword][second]' => 'newStrongPassword',
        ]);

$email value excerpt:

...
Bonjour,=20
    Pour r=C3=A9initialiser votre mot de passe, veuillez clique=
r sur le lien suivant

http://localhost/reset-password/reset/qsfTOThd0P=
F4p0nwzCMVl53pkFjj8yq4r0RczL5P

Ce lien va expirer dans 1 heure.

A=
 bient=C3=B4t !
...
<p>Bonjour, <br />
    Pour r=C3=A9initialiser votre mot de passe, veuill=
ez cliquer sur le lien suivant</p>

<a href=3D"http://localhost/reset-p=
assword/reset/qsfTOThd0PF4p0nwzCMVl53pkFjj8yq4r0RczL5P">http://localhost/re=
set-password/reset/qsfTOThd0PF4p0nwzCMVl5"

Notice how the link is split in two lines. In that case, only qsfTOThd0P matches as the token, which throws an InvalidResetPasswordTokenException because the length is not 40 in ResetPasswordHelper::validateTokenAndFetchUser

Workaround:

instead of getting toString() of a templated message, we can get the raw text body, that doesn't have the formatting issue.

        /** @var Email $email */
        $email = $messages[0];
        preg_match('#(/reset-password/reset/[a-zA-Z0-9]+)#', $email->getTextBody(), $resetLink);
bocharsky-bw commented 3 days ago

Hey Jeremy,

If toString() method does not work, how does that test pass in this repo? And actually, I would suppose that the raw text body contained the line breaks as well, no? Why toString() would add those line breaks? Or am I missing something?

jeremyVignelles commented 2 hours ago

Hi, My assumption is that toString() formats the message as it would be sent over the wire, with the target chunking and encoding (multipart/mixed), while the textBody doesn't have that limitation, it's just the raw text as it is intended.

I assume the =\n part to be because of the multipart encoding that truncates the lines to a maximum length.

I don't know why, and maybe that's a misconfiguration in my case ? Or maybe it's because I'm using the messenger component to send the message asynchronously ?

jeremyVignelles commented 2 hours ago

Seems like toString() calls Email::GenerateBody, which in turn creates a TextPart, which uses quoted-printable as the encoding. The encoder calls the native quoted_printable_encode PHP function which truncates lines to 75 characters as specified by the RFC. The corresponding source code of PHP can be found here. That being said, that doesn't explain how tests can pass in this repo (spoiler: I didn't look at them yet).