paint-brush
Knex ve Express ile NodeJS Kullanan Hesap Kurtarma Süreci Eğitimiile@antonkalik
659 okumalar
659 okumalar

Knex ve Express ile NodeJS Kullanan Hesap Kurtarma Süreci Eğitimi

ile Anton Kalik12m2024/03/28
Read on Terminal Reader

Çok uzun; Okumak

Bu kılavuzda Nodemailer, Knex ve Express kullanılarak kullanıcı parolalarının sıfırlanması anlatılmaktadır. Şifre kurtarma için e-posta göndermeyi ve mesaj teslimini doğrulamayı kapsar.
featured image - Knex ve Express ile NodeJS Kullanan Hesap Kurtarma Süreci Eğitimi
Anton Kalik HackerNoon profile picture
0-item

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. GitHub'daki depo . Ş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.


Şimdi şifremi unuttum akışının şemasına bir göz atın.

Şifremi unuttum akış şeması

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


Nodemailer : SMTP veya diğer aktarım mekanizmalarını kullanarak kolayca e-posta göndermeye olanak tanıyan güçlü modül.


Gidon : 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.


Şimdi geçişi oluşturmamız gerekiyor, dolayısıyla benim durumumda users tablosuna yeni bir forgot_password_token sütunu eklemem gerekiyor:

 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 users tablosuna forgot_password_token ayarlayabiliriz.

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 src/routes/ dizini içinde forgotPasswordRouter.ts adında bir dosya oluşturun ve aşağıdaki kodu ekleyin:

 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ı UserModel.findByEmail kullanarak bulacağız. Kullanıcı mevcutsa TokenService.sign kullanarak bir JWT token oluşturuyoruz ve tokenı 1 günlük süre sonuyla forgot_password_token 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.

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 Google Hesabınızı Yönetin bağlantısına ilerleyin. Sağ üstteki avatara tıklayarak bulabilirsiniz. Ardından soldaki menüde Güvenlik öğesine tıklayın ve ardından 2 Adımlı Doğrulama'ya basın. Aşağıda Uygulama şifreleri bölümünü bulacaksınız, oka tıklayın:


Uygulama Şifreleri

Kullanılması gereken adı girin. Benim durumumda Nodemailer ayarladım ve Create'e bastım.

Uygulama Şifresi Oluştur

Oluşturulan şifreyi kopyalayın ve .env dosyanıza ayarlayın. İki değişkeni dosyalayacak şekilde ayarlamamız gerekiyor:

 MAIL_USER="[email protected]" MAIL_PASSWORD="vyew hzek avty iwst"


Elbette info@company_name.com 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.

E-posta Hizmeti

Hazırlanan .env 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.

 await EmailService.sendPasswordResetEmail(email, token);


Şimdi src/services/EmailService.ts oluşturalım ve servisin sınıfını tanımlayalım:

 export class EmailService {}


Ve şimdi ilk veri olarak ortamı nodemailer ile kullanmam gerekiyor:

 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 madde . İşte bir örnek:

 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 EmailService sınıfımızda başlatmayı oluşturmaya devam edelim:

 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


nodemailer kitaplığı tarafından sağlanan bir yöntem olan nodemailer.createTransport() 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.


Google kullanıyoruz: service: 'gmail' 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 gmail , taşıyıcının Gmail'in SMTP sunucusuyla çalışacak şekilde yapılandırılacağını belirtir.


Kimlik doğrulama auth doğrulaması için, e-posta servis sağlayıcısının SMTP sunucusuna erişmek için gereken kimlik bilgilerinin ayarlanması gerekir.


user için, e-posta göndereceğimiz e-posta adresine ayarlanmalıdır ve bu şifre, Google hesabında Uygulama Şifrelerinden oluşturulmuştur.


Ş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 handlebars kullanıyorum ve bunun için src/temlates/passwordResetTemplate.hbs içinde ilk HTML şablonumuzu oluşturmamız gerekiyor:


 <!-- 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 email_logo.png dosyasını src/assets 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:


 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ı :

Postacıdan E-posta Gönder

Konsol size mesajın gönderildiğini söyleyecektir:

 Message sent: <1k96ah55-c09t-p9k2–[email protected]>


Gelen kutusunda yeni mesajları kontrol edin:

Sunucudan yeni bir e-posta içeren gelen kutusu

Parolayı Sıfırla bağlantısının belirteci ve ana bilgisayarı içermesi gerekir:

 http://localhost:3000/reset-password/<token>


Bu mesaj geliştirme süreci ile ilgili olduğundan port 3000 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.

Ş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 src/controllers/ klasörüne gidin ve aşağıdaki kodu içeren resetPasswordController.ts adında bir dosya oluşturun:

 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:


Burada da yayınlandı