Bu, bir kullanıcının şifresini unutması durumunda nasıl sıfırlama yapılacağı ve Node JS'den e-postaların nasıl gönderileceği ve gönderilen mesajların nasıl doğrulanacağının ayrıntılı bir analizidir. Çoğumuz hesap kurtarma sürecini en az bir kez deneyimlemişizdir; bir şifreyi unuttuğumuzda, yeni bir şifre oluşturmak ve sisteme yeniden erişim sağlamak için prosedürlere ihtiyaç duyulur. Bu makale, rotaları yönetmek ve gerekli işlemleri gerçekleştirmek için Express'in yanı sıra Node.js, Knex ve bazı açıklanmayan araçları kullanarak böyle bir sürecin uygulanmasına odaklanmaktadır. Yönlendirici uygulamasını, URL parametrelerini yönetmeyi, kanıt olarak yalnızca bir e-posta veya telefon numarası mevcut olduğunda kullanıcıya ne gönderileceğini belirlemeyi, e-posta gönderimlerini yönetmeyi ve güvenlik endişelerini ele almayı ele alacağız. Şifremi Unuttum Akışı Kodlamaya dalmadan önce, aynı kod tabanıyla çalıştığımızdan emin olmak istiyorum; buna genel bağlantımdan erişebilirsiniz. . Şifremi unuttum akışını uygulamak için adım adım yükseltme yapacağız. E-posta aktarımı için Google'ın e-posta hizmetini kullanacağız. GitHub'daki depo Şimdi şifremi unuttum akışının şemasına bir göz atın. Sunucu, parola sıfırlama için geçerli bir bağlantı içeren e-postaları kullanıcı posta kutusuna göndermekten sorumlu olacak ve ayrıca belirteci ve kullanıcı varlığını doğrulayacaktır. Paketler ve Taşıma Node.js ile e-posta hizmetini kullanmaya ve e-posta göndermeye başlamak için mevcut bağımlılıklarımıza ek olarak aşağıdaki paketleri de yüklememiz gerekiyor: npm i --save nodemailer handlebars : SMTP veya diğer aktarım mekanizmalarını kullanarak kolayca e-posta göndermeye olanak tanıyan güçlü modül. Nodemailer : Gidon, JavaScript için popüler bir şablon oluşturma motorudur. Oluşturma sırasında verilerle doldurulabilecek yer tutuculara sahip şablonlar tanımlamamıza olanak tanıyacaktır. Gidon Şimdi geçişi oluşturmamız gerekiyor, dolayısıyla benim durumumda tablosuna yeni bir sütunu eklemem gerekiyor: users forgot_password_token knex migrate:make add_field_forgot_password_token -x ts ve oluşturulan dosyada kodu ayarladım: 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'); }); } Kullanıcılar tablosundaki Parolamı Unuttum Belirtecinin Taşınması ve ardından en son dosyayı taşıyın: knex migrate:knex Artık tablosuna ayarlayabiliriz. users forgot_password_token Yönlendiriciler Parola unutma ve sıfırlama mantığını ele alan denetleyicileri yönetmek için iki yol oluşturmamız gerekir. İlk rota, şifremi unuttum sürecini başlatırken, ikincisi, doğrulama için URL'de bir belirteç parametresi bekleyerek sıfırlama işlemini gerçekleştirir. Bunu uygulamak için dizini içinde adında bir dosya oluşturun ve aşağıdaki kodu ekleyin: src/routes/ forgotPasswordRouter.ts 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); Şifremi Unuttum Yönlendirici E-posta gönderme ve parolayı sıfırlama mantığını iki denetleyici yönetecektir. Şifremi Unuttum Denetleyicisi Müşteri şifresini unuttuğunda oturumu olmaz, bu da e-posta veya diğer güvenlik tanımlayıcıları dışında kullanıcı verilerini alamadığımız anlamına gelir. Bizim durumumuzda, şifre sıfırlama işlemini gerçekleştirmek için bir e-posta gönderiyoruz. Bu mantığı denetleyiciye yerleştireceğiz. forgotPasswordRouter.post('/', forgotPasswordController); 'Şifrenizi mi unuttunuz?'u hatırlıyor musunuz? Giriş formunun altındaki bağlantı genellikle giriş formundaki herhangi bir müşterinin kullanıcı arayüzünde bulunur mu? Üzerine tıklamak bizi şifre sıfırlama talebinde bulunabileceğimiz bir görünüme yönlendirir. Sadece e-postamızı giriyoruz ve kontrolör gerekli tüm prosedürleri gerçekleştiriyor. Aşağıdaki kodu inceleyelim: 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); } }; Şifremi Unuttum Denetleyicisi Gövdeden bir e-posta alacağız ve ardından kullanıcıyı kullanarak bulacağız. Kullanıcı mevcutsa kullanarak bir JWT token oluşturuyoruz ve tokenı 1 günlük süre sonuyla kullanıcısına kaydediyoruz. Daha sonra mesajı, kullanıcının şifresini değiştirebileceği bir jetonla birlikte uygun bir bağlantıyla birlikte e-postaya göndereceğiz. UserModel.findByEmail TokenService.sign forgot_password_token Google Kurulumu E-postayı gönderebilmek için gönderici olacak yeni e-posta adresimizi oluşturmamız gerekiyor. Yeni bir e-posta hesabı oluşturmak için Google'a gidelim ve ardından hesap oluşturulduğunda bağlantısına ilerleyin. Sağ üstteki avatara tıklayarak bulabilirsiniz. Ardından soldaki menüde öğesine tıklayın ve ardından basın. Aşağıda bölümünü bulacaksınız, oka tıklayın: Google Hesabınızı Yönetin Güvenlik 2 Adımlı Doğrulama'ya Uygulama şifreleri Kullanılması gereken adı girin. Benim durumumda ayarladım ve bastım. Nodemailer Create'e Oluşturulan şifreyi kopyalayın ve dosyanıza ayarlayın. İki değişkeni dosyalayacak şekilde ayarlamamız gerekiyor: .env MAIL_USER="mygoogleemail@gmail.com" MAIL_PASSWORD="vyew hzek avty iwst" Elbette gibi düzgün bir e-postaya sahip olmak için Google Workspace veya AWS Amazon WorkMail'i AWS SES veya başka herhangi bir hizmetle birlikte kurmanız gerekir. Ancak bizim durumumuzda basit bir Gmail hesabını ücretsiz kullanıyoruz. info@company_name.com E-posta Hizmeti Hazırlanan dosyasıyla e-posta gönderme hizmetimizi kurmaya hazırız. Denetleyici, mesajımız için oluşturulan jeton ve alıcı e-posta adresiyle hizmeti kullanacaktır. .env await EmailService.sendPasswordResetEmail(email, token); Şimdi oluşturalım ve servisin sınıfını tanımlayalım: src/services/EmailService.ts export class EmailService {} Ve şimdi ilk veri olarak ortamı ile kullanmam gerekiyor: 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-posta Hizmeti Hizmetin başlatılmasına dikkat etmeliyiz. Daha önceki yazımda da yazmıştım . İşte bir örnek: madde 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(); }; Hizmetleri Başlatma Şimdi sınıfımızda başlatmayı oluşturmaya devam edelim: 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; } } } E-posta Hizmeti Başlatma kitaplığı tarafından sağlanan bir yöntem olan başlatma yöntemi vardır. E-postalarımızı göndermek için kullanılacak bir taşıyıcı nesnesi oluşturur. Yöntem, taşıyıcı için yapılandırma ayrıntılarını belirttiğiniz bir seçenek nesnesini bağımsız değişken olarak kabul eder. nodemailer nodemailer.createTransport() Google kullanıyoruz: e-posta servis sağlayıcısını belirtir. Nodemailer, çeşitli e-posta servis sağlayıcıları için yerleşik destek sağlar ve , taşıyıcının Gmail'in SMTP sunucusuyla çalışacak şekilde yapılandırılacağını belirtir. service: 'gmail' gmail Kimlik doğrulama doğrulaması için, e-posta servis sağlayıcısının SMTP sunucusuna erişmek için gereken kimlik bilgilerinin ayarlanması gerekir. auth için, e-posta göndereceğimiz e-posta adresine ayarlanmalıdır ve bu şifre, Google hesabında Uygulama Şifrelerinden oluşturulmuştur. user Şimdi servisimizin son kısmını ayarlayalım: 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); } } } Şifre Sıfırlama E-postası Gönder Devam etmeden önce, müşterinin e-posta alması için uygun ana bilgisayarı belirlemek çok önemlidir. E-posta gövdesinde bir belirteçle bağlantı kurmak çok önemlidir. 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}`; }; Ev sahibini al Şablonlar için kullanıyorum ve bunun için içinde ilk HTML şablonumuzu oluşturmamız gerekiyor: 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> Şifre Sıfırlama Şablonu ve artık bu şablonu yardımcıyla yeniden kullanabiliriz: 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); }; Şablon Yardımcısı Oluştur E-postamızı geliştirmek için ekler bile ekleyebiliriz. Bunu yapmak için dosyasını klasörüne ekleyin. Daha sonra aşağıdaki yardımcı işlevi kullanarak bu görüntüyü e-postanın içinde oluşturabiliriz: 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, }; }); Ek Oluşturma Yardımcısı Tüm bu yardımcıları topladıktan sonra aşağıdakileri kullanarak e-posta gönderebilmemiz gerekir: const info = await EmailService.transporter.sendMail({ from: this.env.USER, to: email, subject: 'Password Reset', html: template, attachments, }); Bu yaklaşım, hizmetin farklı içeriğe sahip e-postalar göndermek için çeşitli yöntemler kullanmasını sağlayan iyi bir ölçeklenebilirlik sunar. Şimdi yönlendiricimiz ile denetleyiciyi tetikleyip e-postayı göndermeyi deneyelim. Bunun için kullanıyorum : Postacı Konsol size mesajın gönderildiğini söyleyecektir: Message sent: <1k96ah55-c09t-p9k2–8bv2-j25r9h77f763@gmail.com> Gelen kutusunda yeni mesajları kontrol edin: bağlantısının belirteci ve ana bilgisayarı içermesi gerekir: Parolayı Sıfırla http://localhost:3000/reset-password/<token> Bu mesaj geliştirme süreci ile ilgili olduğundan port burada belirtilmiştir. Bu, parola sıfırlama formlarını işlemekten sorumlu olan istemcinin aynı zamanda geliştirme ortamında çalışacağını gösterir. 3000 Şifreyi yenile Belirtecin denetleyici tarafında, o e-postayı gönderen kullanıcıyı alabileceğimiz TokenService ile doğrulanması gerekir. Belirteci kullanan yönlendiriciyi kurtaralım: forgotPasswordRouter.post('/reset/:token', resetPasswordController); Denetleyici, şifreyi yalnızca belirtecin geçerli olması ve süresinin dolmamış olması durumunda, bir saat olarak ayarlanan sona erme süresine göre güncelleyecektir. Bu işlevselliği uygulamak için klasörüne gidin ve aşağıdaki kodu içeren adında bir dosya oluşturun: 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); } }; Şifre Denetleyicisini Sıfırla Bu denetleyici, jetonu alacak, doğrulayacak, şifresi çözülmüş verilerden kullanıcı kimliğini çıkaracak, ilgili kullanıcıyı alacak, istemci tarafından istek gövdesinde gönderilen yeni şifreyi alacak ve veritabanındaki şifreyi güncellemeye devam edecektir. Sonuçta bu, müşterinin yeni şifreyi kullanarak oturum açmasını sağlar. Çözüm E-posta hizmetinin ölçeklenebilirliği, şifre güncellemesini belirten ve daha sonra oturum açmayı sağlayanlar gibi onay veya başarı mesajlarının gönderilmesi gibi çeşitli yaklaşımlarla gösterilmektedir. Ancak parolaları yönetmek, özellikle uygulama güvenliğinin artırılmasının zorunlu olduğu durumlarda büyük bir zorluktur. Şifre değişikliklerine izin vermeden önce belirteç karşılaştırması, e-posta ve şifre doğrulama gibi ek kontroller de dahil olmak üzere güvenliği artırmak için çok sayıda seçenek mevcuttur. Diğer bir seçenek de, sunucu tarafında doğrulama için kullanıcının e-postasına bir kodun gönderildiği bir PIN kodu sistemi uygulamaktır. Bu önlemlerin her biri, e-posta gönderme özelliklerinin kullanılmasını gerektirir. İçinde bulabileceğiniz tüm uygulanan kodlar . GitHub deposu burada Lütfen bu yapıyla ilgili deneyler yapmaktan çekinmeyin ve bu konuyla ilgili beğendiğiniz yönlere ilişkin geri bildirimlerinizi paylaşın. Çok teşekkür ederim. Referanslar Burada, bu makalede kullandığım birkaç referansı bulabilirsiniz: Depo Node JS ile Başlatma Hizmetleri Nodemailer Gidon Diz İfade etmek Postacı da yayınlandı Burada