Dies ist eine detaillierte Analyse, wie ein Reset für einen Benutzer durchgeführt wird, wenn er sein Passwort vergessen hat, und wie E-Mails von Node JS gesendet und das Senden von Nachrichten validiert werden. Die meisten von uns haben den Vorgang der Kontowiederherstellung mindestens einmal erlebt – wenn wir ein Passwort vergessen, müssen wir Verfahren entwickeln, um ein neues Passwort zu erstellen und wieder Zugriff auf das System zu erhalten. Dieser Artikel konzentriert sich auf die Implementierung eines solchen Vorgangs mit Node.js, Knex und einigen nicht genannten Tools sowie Express, um Routen zu verwalten und die erforderlichen Vorgänge auszuführen. Wir behandeln die Routerimplementierung, den Umgang mit URL-Parametern, die Festlegung, was dem Benutzer gesendet werden soll, wenn nur eine E-Mail-Adresse oder Telefonnummer als Nachweis verfügbar ist, die Verwaltung von E-Mail-Einreichungen und die Behandlung von Sicherheitsbedenken. Ablauf zum Vergessen des Passworts Bevor ich mich in die Programmierung stürze, möchte ich sicherstellen, dass wir mit derselben Codebasis arbeiten, auf die Sie über meine öffentliche . Wir führen die Aktualisierung Schritt für Schritt durch, um den Ablauf bei vergessenen Passwörtern zu implementieren. Für den E-Mail-Transport nutzen wir den E-Mail-Dienst von Google. Repository auf GitHub Sehen Sie sich nun das Schema des Ablaufs bei vergessenem Passwort an. Der Server ist dafür verantwortlich, E-Mails mit einem gültigen Link zum Zurücksetzen des Kennworts an das Postfach des Benutzers zu senden. Darüber hinaus überprüft er das Token und die Existenz des Benutzers. Pakete und Migration Um den E-Mail-Dienst zu nutzen und E-Mails mit Node.js zu versenden, müssen wir zusätzlich zu unseren bestehenden Abhängigkeiten die folgenden Pakete installieren: npm i --save nodemailer handlebars : Leistungsstarkes Modul, das das einfache Senden von E-Mails über SMTP oder andere Transportmechanismen ermöglicht. Nodemailer : Handlebars ist eine beliebte Template-Engine für JavaScript. Sie ermöglicht es uns, Templates mit Platzhaltern zu definieren, die beim Rendern mit Daten gefüllt werden können. Handlebars Jetzt müssen wir die Migration erstellen. In meinem Fall muss ich der eine neue Spalte hinzufügen: users forgot_password_token knex migrate:make add_field_forgot_password_token -x ts und in der generierten Datei habe ich den Code eingefügt: 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 für „Kennwort vergessen“-Token in der Benutzertabelle und migrieren Sie dann die neueste Datei: knex migrate:knex So, jetzt können wir in der unser festlegen users forgot_password_token Router Um Controller zu verwalten, die für die Handhabung der Logik bei vergessenen Passwörtern und deren Zurücksetzung verantwortlich sind, müssen wir zwei Routen einrichten. Die erste Route leitet den Vorgang bei vergessenen Passwörtern ein, während die zweite den Vorgang zum Zurücksetzen behandelt und dabei einen Token-Parameter in der URL zur Überprüfung erwartet. Um dies zu implementieren, erstellen Sie eine Datei mit dem Namen im Verzeichnis und fügen den folgenden Code ein: 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); Router-Passwort vergessen Zwei Controller verwalten die Logik zum Versenden von E-Mails und zum Zurücksetzen des Passworts. Passwort vergessen Controller Wenn der Client sein Passwort vergisst, hat er keine Sitzung, was bedeutet, dass wir keine Benutzerdaten außer E-Mail oder anderen Sicherheitskennungen erhalten können. In unserem Fall senden wir eine E-Mail, um eine Passwortzurücksetzung durchzuführen. Diese Logik werden wir in den Controller einbauen. forgotPasswordRouter.post('/', forgotPasswordController); Erinnern Sie sich an den Link „Passwort vergessen?“ unter dem Anmeldeformular, der sich normalerweise in der Benutzeroberfläche aller Clients im Anmeldeformular befindet? Wenn wir darauf klicken, gelangen wir zu einer Ansicht, in der wir eine Kennwortzurücksetzung anfordern können. Wir geben einfach unsere E-Mail-Adresse ein und der Controller übernimmt alle erforderlichen Vorgänge. Sehen wir uns den folgenden Code an: 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); } }; Passwort vergessen Controller Aus dem Textkörper erhalten wir eine E-Mail und finden den Benutzer dann mit . Wenn der Benutzer existiert, erstellen wir mit ein JWT-Token und speichern das Token mit einer Gültigkeit von 1 Tag im Benutzer . Dann senden wir die Nachricht mit einem entsprechenden Link zusammen mit einem Token an die E-Mail, wo der Benutzer sein Passwort ändern kann. UserModel.findByEmail TokenService.sign forgot_password_token Google-Einrichtung Um die E-Mail senden zu können, müssen wir unsere neue E-Mail-Adresse erstellen, die als Absender dient. Gehen wir zu Google, um ein neues E-Mail-Konto zu erstellen. Wenn das Konto erstellt ist, fahren Sie mit dem Link fort. Sie finden ihn oben rechts, indem Sie auf Avatar klicken. Klicken Sie dann im linken Menü auf das Element “ und drücken Sie dann auf . Unten finden Sie den Abschnitt . Klicken Sie auf den Pfeil: „Google-Konto verwalten“ „Sicherheit „Bestätigung in zwei Schritten“ „App-Passwörter“ Geben Sie den Namen ein, der verwendet werden soll. In meinem Fall lege ich fest und drücke . Nodemailer Erstellen Kopieren Sie das generierte Passwort und legen Sie es in Ihrer Datei fest. Wir müssen in der Datei zwei Variablen festlegen: .env MAIL_USER="mygoogleemail@gmail.com" MAIL_PASSWORD="vyew hzek avty iwst" Um eine richtige E-Mail-Adresse wie zu haben, müssen Sie natürlich Google Workspace oder AWS Amazon WorkMail zusammen mit AWS SES oder anderen Diensten einrichten. In unserem Fall verwenden wir jedoch ein einfaches, kostenloses Gmail-Konto. info@company_name.com E-Mail-Dienst Nachdem wir die Datei vorbereitet haben, können wir unseren Dienst zum Senden von E-Mails einrichten. Der Controller verwendet den Dienst mit dem generierten Token und der E-Mail-Adresse des Empfängers für unsere Nachricht. .env await EmailService.sendPasswordResetEmail(email, token); Erstellen wir und definieren die Klasse für den Dienst: src/services/EmailService.ts export class EmailService {} Und jetzt muss ich als Ausgangsdaten die Umgebung für die Verwendung mit erstellen: 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, }; } E-Mail-Dienst Wir müssen uns um die Initialisierung des Dienstes kümmern. Ich habe bereits in meinem vorherigen Artikel darüber geschrieben. . Hier ist ein Beispiel: Artikel 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(); }; Dienste initialisieren Fahren wir nun mit der Erstellung der Initialisierung innerhalb unserer Klasse fort: 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; } } } Initialisierung des E-Mail-Dienstes Es gibt die Initialisierungsmethode , die von der Bibliothek bereitgestellt wird. Sie erstellt ein Transporterobjekt, das zum Senden unserer E-Mails verwendet wird. Die Methode akzeptiert ein Optionsobjekt als Argument, in dem Sie die Konfigurationsdetails für den Transporter angeben. nodemailer.createTransport() nodemailer Wir verwenden Google: gibt den E-Mail-Dienstanbieter an. Nodemailer bietet integrierte Unterstützung für verschiedene E-Mail-Dienstanbieter, und gibt an, dass der Transporter für die Arbeit mit dem SMTP-Server von Gmail konfiguriert wird. service: 'gmail' gmail Für die Authentifizierung müssen die Anmeldeinformationen festgelegt werden, die für den Zugriff auf den SMTP-Server des E-Mail-Dienstanbieters erforderlich sind. auth Für sollte die E-Mail-Adresse eingestellt werden, von der wir E-Mails senden werden, und das Passwort sollte im Google-Konto von App Passwords generiert worden sein. user Lassen Sie uns nun den letzten Teil unseres Dienstes festlegen: 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); } } } E-Mail zum Zurücksetzen des Passworts senden Bevor Sie fortfahren, müssen Sie unbedingt den entsprechenden Host bestimmen, wenn der Client eine E-Mail erhält. Es ist wichtig, einen Link mit einem Token im E-Mail-Text einzurichten. 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}`; }; Host abrufen Als Vorlagen verwende ich und dafür müssen wir in unsere erste HTML-Vorlage erstellen: 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> Vorlage zum Zurücksetzen des Passworts und jetzt können wir diese Vorlage mit dem Helfer wiederverwenden: 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); }; Vorlagen-Helfer generieren Um unsere E-Mail zu verbessern, können wir sogar Anhänge hinzufügen. Fügen Sie dazu die Datei zum Ordner hinzu. Wir können dieses Bild dann mithilfe der folgenden Hilfsfunktion in der E-Mail rendern: 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, }; }); Helfer zum Generieren von Anhängen Nachdem wir alle diese Helfer gesammelt haben, müssen wir in der Lage sein, E-Mails mit Folgendem zu versenden: const info = await EmailService.transporter.sendMail({ from: this.env.USER, to: email, subject: 'Password Reset', html: template, attachments, }); Dieser Ansatz bietet eine angemessene Skalierbarkeit und ermöglicht es dem Dienst, verschiedene Methoden zum Senden von E-Mails mit unterschiedlichem Inhalt zu verwenden. Versuchen wir nun, den Controller mit unserem Router auszulösen und die E-Mail zu senden. Dafür verwende ich : Briefträger Die Konsole teilt Ihnen mit, dass die Nachricht gesendet wurde: Message sent: <1k96ah55-c09t-p9k2–8bv2-j25r9h77f763@gmail.com> Prüfen Sie, ob im Posteingang neue Nachrichten vorhanden sind: Der Link zum muss das Token und den Host enthalten: Zurücksetzen des Passworts http://localhost:3000/reset-password/<token> Der Port wird hier angegeben, da diese Nachricht den Entwicklungsprozess betrifft. Dies zeigt an, dass der Client, der für die Handhabung der Formulare zum Zurücksetzen des Passworts zuständig ist, auch innerhalb der Entwicklungsumgebung ausgeführt wird. 3000 Passwort zurücksetzen Das Token muss auf der Controllerseite mit TokenService validiert werden, wo wir den Benutzer ermitteln können, der diese E-Mail gesendet hat. Lassen Sie uns den Router wiederherstellen, der das Token verwendet: forgotPasswordRouter.post('/reset/:token', resetPasswordController); Der Controller aktualisiert das Passwort nur, wenn das Token gültig ist und nicht abgelaufen ist (die Ablaufzeit beträgt eine Stunde). Um diese Funktion zu implementieren, navigieren Sie zum Ordner und erstellen Sie eine Datei mit dem Namen , die den folgenden Code enthält: 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); } }; Kennwort-Controller zurücksetzen Dieser Controller empfängt das Token, überprüft es, extrahiert die Benutzer-ID aus den entschlüsselten Daten, ruft den entsprechenden Benutzer ab, holt das vom Client im Anforderungstext gesendete neue Kennwort ab und aktualisiert das Kennwort in der Datenbank. Dadurch kann sich der Client schließlich mit dem neuen Kennwort anmelden. Abschluss Die Skalierbarkeit des E-Mail-Dienstes wird durch verschiedene Ansätze demonstriert, wie das Senden von Bestätigungen oder Erfolgsmeldungen, etwa solche, die auf eine Kennwortaktualisierung hinweisen und eine nachfolgende Anmeldung ermöglichen. Die Verwaltung von Kennwörtern ist jedoch eine große Herausforderung, insbesondere wenn die Verbesserung der Anwendungssicherheit unabdingbar ist. Es stehen zahlreiche Optionen zur Verbesserung der Sicherheit zur Verfügung, darunter zusätzliche Prüfungen vor der Zulassung von Kennwortänderungen, etwa Token-Vergleich, E-Mail und Kennwortvalidierung. Eine weitere Möglichkeit ist die Implementierung eines PIN-Code-Systems, bei dem ein Code zur Validierung auf der Serverseite an die E-Mail des Benutzers gesendet wird. Jede dieser Maßnahmen erfordert die Nutzung von E-Mail-Versandfunktionen. Den gesamten implementierten Code finden Sie im . GitHub-Repository hier Bitte führen Sie mit diesem Build beliebige Experimente durch und teilen Sie uns Ihr Feedback dazu mit, welche Aspekte Sie an diesem Thema schätzen. Vielen Dank. Verweise Hier finden Sie einige Referenzen, die ich in diesem Artikel verwendet habe: Repository Initialisierungsdienste mit Node JS Nodemailer Lenker Knex Äußern Briefträger Auch erschienen hier