Il s'agit d'une analyse détaillée de comment effectuer une réinitialisation pour un utilisateur lorsqu'il a oublié son mot de passe et comment envoyer des emails depuis Node JS et valider l'envoi de messages. La plupart d'entre nous ont expérimenté le processus de récupération de compte au moins une fois : lorsque nous oublions un mot de passe, des procédures sont nécessaires pour en créer un nouveau et retrouver l'accès au système. Cet article se concentre sur la mise en œuvre d'un tel processus à l'aide de Node.js, Knex et de certains outils non divulgués, aux côtés d'Express, pour gérer les routes et effectuer les opérations nécessaires. Nous couvrirons la mise en œuvre du routeur, la gestion des paramètres d'URL, la détermination de ce qu'il faut envoyer à l'utilisateur lorsque seul un e-mail ou un numéro de téléphone est disponible comme preuve, la gestion des envois d'e-mails et la résolution des problèmes de sécurité. Flux de mot de passe oublié Avant de me lancer dans le codage, j'aimerais m'assurer que nous travaillons avec la même base de code, à laquelle vous pouvez accéder depuis mon compte public. . Nous mettrons à niveau étape par étape pour mettre en œuvre le flux de mot de passe oublié. Pour le transport des e-mails, nous utiliserons le service de messagerie de Google. dépôt sur GitHub Maintenant, jetez un œil au schéma du flux de mot de passe oublié. Le serveur se chargera d'envoyer des e-mails à la boîte aux lettres de l'utilisateur contenant un lien valide pour la réinitialisation du mot de passe, et validera également le jeton et l'existence de l'utilisateur. Forfaits et migration Pour commencer à utiliser le service de messagerie et à envoyer des e-mails avec Node.js, nous devons installer les packages suivants en plus de nos dépendances existantes : npm i --save nodemailer handlebars : Module puissant qui permet d'envoyer facilement des emails en utilisant SMTP ou d'autres mécanismes de transport. Nodemailer : Guidon est un moteur de création de modèles populaire pour JavaScript. Cela nous permettra de définir des modèles avec des espaces réservés qui pourront être remplis de données lors du rendu. Guidon Maintenant, nous devons créer la migration, donc dans mon cas, je dois ajouter une nouvelle colonne à la table : forgot_password_token users knex migrate:make add_field_forgot_password_token -x ts et dans le fichier généré, je mets le code : import type { Knex } from 'knex'; export async function up(knex: Knex): Promise<void> { return knex.schema.alterTable('users', table => { table.string('forgot_password_token').unique(); }); } export async function down(knex: Knex): Promise<void> { return knex.schema.alterTable('users', table => { table.dropColumn('forgot_password_token'); }); } Migration du jeton de mot de passe oublié dans le tableau Utilisateurs puis migrez le dernier fichier : knex migrate:knex Alors maintenant, nous pouvons définir dans la table notre users forgot_password_token Routeurs Pour gérer les contrôleurs chargés de gérer la logique d’oubli et de réinitialisation des mots de passe, nous devons établir deux routes. La première route lance le processus de mot de passe oublié, tandis que la seconde gère le processus de réinitialisation, en attendant un paramètre de jeton dans l'URL pour vérification. Pour implémenter cela, créez un fichier nommé dans le répertoire et insérez le code suivant : forgotPasswordRouter.ts src/routes/ import { Router } from 'express'; import { forgotPasswordController } from 'src/controllers/forgotPasswordController'; import { resetPasswordController } from 'src/controllers/resetPasswordController'; export const forgotPasswordRouter = Router(); forgotPasswordRouter.post('/', forgotPasswordController); forgotPasswordRouter.post('/reset/:token', resetPasswordController); Mot de passe oublié du routeur Deux contrôleurs géreront la logique d'envoi des emails et de réinitialisation du mot de passe. Contrôleur de mot de passe oublié Lorsque le client oublie son mot de passe, il n'a pas de session, ce qui signifie que nous ne pouvons pas obtenir de données utilisateur, à l'exception du courrier électronique ou de tout autre identifiant de sécurité. Dans notre cas, nous envoyons un e-mail pour gérer une réinitialisation de mot de passe. Cette logique, nous allons l'intégrer au contrôleur. forgotPasswordRouter.post('/', forgotPasswordController); Vous vous souvenez du « mot de passe oublié ? » lien sous le formulaire de connexion, généralement dans l'interface utilisateur de tous les clients dans le formulaire de connexion ? En cliquant dessus, nous sommes dirigés vers une vue où nous pouvons demander une réinitialisation du mot de passe. Nous saisissons simplement notre e-mail et le contrôleur gère toutes les procédures nécessaires. Examinons le code suivant : import { Request, Response } from 'express'; import { UserModel } from 'src/models/UserModel'; import type { User } from 'src/@types'; import { TokenService } from 'src/services/TokenService'; import { EmailService } from 'src/services/EmailService'; export const forgotPasswordController = async (req: Request, res: Response) => { try { const { email, }: { email: string; } = req.body; const user = await UserModel.findByEmail(email); if (user) { const token = await TokenService.sign( { id: user.id, }, { expiresIn: '1 day', } ); await user.context.update({ forgot_password_token: token }); await EmailService.sendPasswordResetEmail(email, token); } return res.sendStatus(200); } catch (error) { return res.sendStatus(500); } }; Contrôleur de mot de passe oublié À partir du corps, nous allons recevoir un e-mail, puis nous trouverons l'utilisateur en utilisant . Si l'utilisateur existe, nous créons un jeton JWT à l'aide et enregistrons le jeton dans l'utilisateur avec une expiration d'un jour. Ensuite, nous enverrons le message à l'e-mail avec un lien approprié ainsi qu'un jeton où l'utilisateur pourra changer son mot de passe. UserModel.findByEmail TokenService.sign forgot_password_token Configuration Google Pour pouvoir envoyer l'e-mail, nous devons créer notre nouvelle adresse e-mail qui sera expéditeur. Allons sur Google, pour créer un nouveau compte de messagerie, puis, une fois le compte créé, passez au lien . Vous pouvez le trouver en haut à droite en cliquant sur avatar. Ensuite, dans le menu de gauche, cliquez sur l'élément , puis appuyez sur . Ci-dessous vous trouverez la section , cliquez sur la flèche : Gérer votre compte Google Sécurité Vérification en 2 étapes Mots de passe des applications Saisissez le nom qui doit être utilisé. Dans mon cas, je configure et j'appuie sur . Nodemailer Create Copiez le mot de passe généré et définissez-le dans votre fichier . Nous devons définir deux variables dans un fichier : .env MAIL_USER="mygoogleemail@gmail.com" MAIL_PASSWORD="vyew hzek avty iwst" Bien sûr, pour avoir une adresse e-mail appropriée comme , vous devez configurer Google Workspace ou AWS Amazon WorkMail avec AWS SES, ou tout autre service. Mais dans notre cas, nous utilisons gratuitement un simple compte Gmail. info@company_name.com Service de courrier électronique Une fois le fichier préparé, nous sommes prêts à configurer notre service d'envoi d'e-mails. Le contrôleur utilisera le service avec le jeton généré et l'adresse e-mail du destinataire de notre message. .env await EmailService.sendPasswordResetEmail(email, token); Créons et définissons la classe du service : src/services/EmailService.ts export class EmailService {} Et maintenant, comme données initiales, je dois utiliser l'environnement avec : nodemailer import process from 'process'; import * as nodemailer from 'nodemailer'; import * as dotenv from 'dotenv'; dotenv.config(); export class EmailService { private static transporter: nodemailer.Transporter; private static env = { USER: process.env.MAIL_USER, PASS: process.env.MAIL_PASSWORD, }; } Service de courrier électronique Nous devons nous occuper de l’initialisation du service. J'en ai déjà parlé dans mon précédent . Voici un exemple: article import { TokenService } from 'src/services/TokenService'; import { RedisService } from 'src/services/RedisService'; import { EmailService } from 'src/services/EmailService'; export const initialize = async () => { await RedisService.initialize(); TokenService.initialize(); EmailService.initialize(); }; Initialisation des services Passons maintenant à la création de l'initialisation au sein de notre classe : EmailService import process from 'process'; import * as nodemailer from 'nodemailer'; import * as dotenv from 'dotenv'; dotenv.config(); export class EmailService { private static transporter: nodemailer.Transporter; private static env = { USER: process.env.MAIL_USER, PASS: process.env.MAIL_PASSWORD, }; public static initialize() { try { EmailService.transporter = nodemailer.createTransport({ service: 'gmail', auth: { user: this.env.USER, pass: this.env.PASS, }, }); } catch (error) { console.error('Error initializing email service'); throw error; } } } Initialisation du service de messagerie Il existe une initialisation , une méthode fournie par la bibliothèque . Il crée un objet transporteur qui sera utilisé pour envoyer nos emails. La méthode accepte un objet options comme argument dans lequel vous spécifiez les détails de configuration du transporteur. nodemailer.createTransport() nodemailer Nous utilisons Google : précise le fournisseur de service de messagerie. Nodemailer fournit une prise en charge intégrée pour divers fournisseurs de services de messagerie, et indique que le transporteur sera configuré pour fonctionner avec le serveur SMTP de Gmail. service: 'gmail' gmail Pour l' , il est nécessaire de définir les informations d'identification requises pour accéder au serveur SMTP du fournisseur de services de messagerie. auth Pour l'adresse e-mail à partir de laquelle nous allons envoyer des e-mails doit être définie, et ce mot de passe a été généré dans le compte Google à partir des mots de passe d'application. user Maintenant, définissons la dernière partie de notre service : import process from 'process'; import * as nodemailer from 'nodemailer'; import * as dotenv from 'dotenv'; import { generateAttachments } from 'src/helpers/generateAttachments'; import { generateTemplate } from 'src/helpers/generateTemplate'; import { getHost } from 'src/helpers/getHost'; dotenv.config(); export class EmailService { // ...rest code public static async sendPasswordResetEmail(email: string, token: string) { try { const host = getHost(); const template = generateTemplate<{ token: string; host: string; }>('passwordResetTemplate', { token, host }); const attachments = generateAttachments([{ name: 'email_logo' }]); const info = await EmailService.transporter.sendMail({ from: this.env.USER, to: email, subject: 'Password Reset', html: template, attachments, }); console.log('Message sent: %s', info.messageId); } catch (error) { console.error('Error sending email: ', error); } } } Envoyer un e-mail de réinitialisation du mot de passe Avant de continuer, il est crucial de déterminer l'hébergeur approprié lorsque le client reçoit un e-mail. Établir un lien avec un token dans le corps de l’email est essentiel. import * as dotenv from 'dotenv'; import process from 'process'; dotenv.config(); export const getHost = (): string => { const isProduction = process.env.NODE_ENV === 'production'; const protocol = isProduction ? 'https' : 'http'; const port = isProduction ? '' : `:${process.env.CLIENT_PORT}`; return `${protocol}://${process.env.WEB_HOST}${port}`; }; Obtenir un hôte Pour les modèles, j'utilise et pour cela, nous devons créer dans notre premier modèle HTML : handlebars src/temlates/passwordResetTemplate.hbs <!-- passwordResetTemplate.hbs --> <html lang='en'> <head> <style> a { color: #372aff; } .token { font-weight: bold; } </style> <title>Forgot Password</title> </head> <body> <p>You requested a password reset. Please use the following link to reset your password:</p> <a class='token' href="{{ host }}/reset-password/{{ token }}">Reset Password</a> <p>If you did not request a password reset, please ignore this email.</p> <img src="cid:email_logo" alt="Email Logo"/> </body> </html> Modèle de réinitialisation de mot de passe et maintenant nous pouvons réutiliser ce modèle avec l'assistant : import path from 'path'; import fs from 'fs'; import handlebars from 'handlebars'; export const generateTemplate = <T>(name: string, props: T): string => { const templatePath = path.join(__dirname, '..', 'src/templates', `${name}.hbs`); const templateSource = fs.readFileSync(templatePath, 'utf8'); const template = handlebars.compile(templateSource); return template(props); }; Générer un assistant de modèle Pour améliorer notre courrier électronique, nous pouvons même inclure des pièces jointes. Pour ce faire, ajoutez le fichier au dossier . Nous pouvons ensuite restituer cette image dans l'e-mail en utilisant la fonction d'assistance suivante : email_logo.png src/assets import path from 'path'; import { Extension } from 'src/@types/enums'; type AttachmentFile = { name: string; ext?: Extension; cid?: string; }; export const generateAttachments = (files: AttachmentFile[] = []) => files.map(file => { const ext = file.ext || Extension.png; const filename = `${file.name}.${ext}`; const imagePath = path.join(__dirname, '..', 'src/assets', filename); return { filename, path: imagePath, cid: file.cid || file.name, }; }); Aide à la génération de pièces jointes Après avoir collecté toutes ces aides, nous devons pouvoir envoyer des e-mails en utilisant : const info = await EmailService.transporter.sendMail({ from: this.env.USER, to: email, subject: 'Password Reset', html: template, attachments, }); Cette approche offre une évolutivité décente, permettant au service d'utiliser diverses méthodes pour envoyer des e-mails avec un contenu diversifié. Essayons maintenant de déclencher le contrôleur avec notre routeur et d'envoyer l'e-mail. Pour cela, j'utilise : Facteur La console vous indiquera que le message a été envoyé : Message sent: <1k96ah55-c09t-p9k2–8bv2-j25r9h77f763@gmail.com> Vérifiez les nouveaux messages dans la boîte de réception : Le lien vers doit contenir le jeton et l'hôte : Réinitialiser le mot de passe http://localhost:3000/reset-password/<token> Le port est spécifié ici car ce message concerne le processus de développement. Cela indique que le client responsable du traitement des formulaires de réinitialisation de mot de passe fonctionnera également dans l'environnement de développement. 3000 réinitialiser le mot de passe Le jeton doit être validé côté contrôleur avec TokenService d'où nous pouvons obtenir l'utilisateur qui a envoyé cet e-mail. Récupérons le routeur qui utilise le token : forgotPasswordRouter.post('/reset/:token', resetPasswordController); Le contrôleur ne mettra à jour le mot de passe que si le jeton est valide et n'a pas expiré, conformément au délai d'expiration fixé à une heure. Pour implémenter cette fonctionnalité, accédez au dossier et créez un fichier nommé contenant le code suivant : src/controllers/ resetPasswordController.ts import bcrypt from 'bcrypt'; import { Request, Response } from 'express'; import { TokenService } from 'src/services/TokenService'; import { UserModel } from 'src/models/UserModel'; import type { User } from 'src/@types'; export const resetPasswordController = async (req: Request, res: Response) => { try { const token = req.params.token; if (!token) { return res.sendStatus(400); } const userData = await TokenService.verify<{ id: number }>(token); const user = await UserModel.findOneById<User>(userData.id); if (!user) { return res.sendStatus(400); } const newPassword = req.body.password; if (!newPassword) { return res.sendStatus(400); } const hashedPassword = await bcrypt.hash(newPassword, 10); await UserModel.updateById(user.id, { password: hashedPassword, passwordResetToken: null }); return res.sendStatus(200); } catch (error) { const errors = ['jwt malformed', 'TokenExpiredError', 'invalid token']; if (errors.includes(error.message)) { return res.sendStatus(400); } return res.sendStatus(500); } }; Réinitialiser le contrôleur de mot de passe Ce contrôleur recevra le jeton, le vérifiera, extraira l'identifiant de l'utilisateur des données déchiffrées, récupérera l'utilisateur correspondant, acquerra le nouveau mot de passe envoyé par le client dans le corps de la demande et procédera à la mise à jour du mot de passe dans la base de données. En fin de compte, cela permet au client de se connecter en utilisant le nouveau mot de passe. Conclusion L'évolutivité du service de messagerie est démontrée à travers diverses approches, telles que l'envoi de confirmations ou de messages de réussite, comme ceux indiquant une mise à jour du mot de passe et permettant une connexion ultérieure. Cependant, la gestion des mots de passe constitue un défi de taille, en particulier lorsqu'il est impératif d'améliorer la sécurité des applications. Il existe de nombreuses options disponibles pour renforcer la sécurité, notamment des vérifications supplémentaires avant d'autoriser la modification du mot de passe, telles que la comparaison des jetons, l'e-mail et la validation du mot de passe. Une autre option consiste à mettre en œuvre un système de code PIN, dans lequel un code est envoyé à la messagerie électronique de l'utilisateur pour validation côté serveur. Chacune de ces mesures nécessite l'utilisation de capacités d'envoi d'e-mails. Tout le code implémenté que vous pouvez trouver dans le . Dépôt GitHub ici N'hésitez pas à mener des expériences avec cette version et à partager vos commentaires sur les aspects que vous appréciez sur ce sujet. Merci beaucoup. Les références Ici, vous pouvez trouver plusieurs références que j'ai utilisées dans cet article : Dépôt Services d'initialisation avec Node JS Nodemailer Guidon Knex Exprimer Facteur Également publié ici