feat(auth): #127 forgot-password + reset-password API (BDD + mail)
Made-with: Cursor
This commit is contained in:
@@ -68,4 +68,20 @@ describe('MailService (ticket #28)', () => {
|
||||
expect(html).toContain('2025-000001');
|
||||
expect(html).toContain(`https://app.test/create-password?token=${encodeURIComponent(token)}`);
|
||||
});
|
||||
|
||||
it('sendPasswordResetEmail utilise Handlebars + lien /reset-password', async () => {
|
||||
const token = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';
|
||||
await service.sendPasswordResetEmail({
|
||||
email: 'u@test.fr',
|
||||
prenom: 'Marie',
|
||||
nom: 'Curie',
|
||||
token,
|
||||
});
|
||||
expect(sendEmailSpy).toHaveBeenCalledTimes(1);
|
||||
const [, subject, html] = sendEmailSpy.mock.calls[0];
|
||||
expect(subject).toContain('Réinitialisation');
|
||||
expect(html).toContain('Marie');
|
||||
expect(html).toContain(`https://app.test/reset-password?token=${encodeURIComponent(token)}`);
|
||||
expect(html).not.toContain('create-password');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -238,4 +238,31 @@ export class MailService {
|
||||
|
||||
await this.sendEmail(to, subject, html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticket #127 — lien application `/reset-password?token=` (pas create-password).
|
||||
*/
|
||||
async sendPasswordResetEmail(recipient: {
|
||||
email: string;
|
||||
prenom: string;
|
||||
nom: string;
|
||||
token: string;
|
||||
}): Promise<void> {
|
||||
const appName = this.configService.get<string>('app_name', "P'titsPas");
|
||||
const appUrl = (this.configService.get<string>('app_url', 'https://app.ptits-pas.fr') || '').replace(/\/+$/, '');
|
||||
const resetPasswordUrl = `${appUrl}/reset-password?token=${encodeURIComponent(recipient.token)}`;
|
||||
|
||||
const filePath = join(__dirname, 'templates', 'password-reset.hbs');
|
||||
const source = readFileSync(filePath, 'utf8');
|
||||
const compiled = Handlebars.compile(source);
|
||||
const html = compiled({
|
||||
prenom: recipient.prenom || '',
|
||||
nom: recipient.nom || '',
|
||||
appName,
|
||||
resetPasswordUrl,
|
||||
}) as string;
|
||||
|
||||
const subject = `${appName} — Réinitialisation de votre mot de passe`;
|
||||
await this.sendEmail(recipient.email, subject, html);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
||||
<h2 style="color: #2D6A4F;">Bonjour {{prenom}} {{nom}},</h2>
|
||||
<p>Vous avez demandé à <strong>réinitialiser votre mot de passe</strong> sur <strong>{{appName}}</strong>.</p>
|
||||
<p>Cliquez sur le bouton ci-dessous pour en choisir un nouveau. Ce lien est valable une durée limitée.</p>
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="{{{resetPasswordUrl}}}" style="background-color: #2D6A4F; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-weight: bold;">Réinitialiser mon mot de passe</a>
|
||||
</div>
|
||||
<p style="color: #666; font-size: 13px;">Si le bouton ne fonctionne pas, copiez ce lien dans votre navigateur :<br /><span style="word-break: break-all;">{{{resetPasswordUrl}}}</span></p>
|
||||
<p style="color: #666; font-size: 12px;">Si vous n'êtes pas à l'origine de cette demande, vous pouvez ignorer cet e-mail.</p>
|
||||
<hr style="border: 1px solid #eee; margin: 20px 0;" />
|
||||
<p style="color: #666; font-size: 12px;">Cet email a été envoyé automatiquement par {{appName}}. Merci de ne pas y répondre.</p>
|
||||
</div>
|
||||
Reference in New Issue
Block a user