Saya sedang melaksanakan ciri untuk menetapkan semula kata laluan untuk pengguna dalam apl saya Task-inator 3000 semasa saya menulis siaran ini. Hanya mencatat proses pemikiran saya dan langkah yang diambil
Saya sedang memikirkan aliran seperti ini:
Hadapan
Belakang
Saya akan bermula dengan bahagian belakang
Seperti yang dinyatakan di atas, kami memerlukan dua API
API hanya perlu menerima e-mel daripada pengguna dan tidak mengembalikan kandungan apabila berjaya. Oleh itu, mencipta pengawal seperti berikut:
// controllers/passwordReset.go func SendPasswordResetEmail(c *fiber.Ctx) error { type Input struct { Email string `json:"email"` } var input Input err := c.BodyParser(&input) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "invalid data", }) } // TODO: send email with otp to user return c.SendStatus(fiber.StatusNoContent) }
Sekarang menambah laluan untuknya:
// routes/routes.go // password reset api.Post("/send-otp", controllers.SendPasswordResetEmail)
Saya akan menggunakan net/smtp daripada perpustakaan standard Golang.
Setelah membaca dokumentasi, saya fikir adalah lebih baik untuk mencipta SMTPClient apabila projek itu dimulakan. Oleh itu, saya akan mencipta fail smtpConnection.go dalam direktori /config.
Sebelum itu, saya akan menambah pembolehubah persekitaran berikut sama ada pada .env saya atau pada pelayan pengeluaran.
SMTP_HOST="smtp.zoho.in" SMTP_PORT="587" SMTP_EMAIL="<myemail>" SMTP_PASSWORD="<mypassword>"
Saya menggunakan zohomail, oleh itu hos dan port smtp mereka (untuk TLS) seperti yang dinyatakan di sini.
// config/smtpConnection.go package config import ( "crypto/tls" "fmt" "net/smtp" "os" ) var SMTPClient *smtp.Client func SMTPConnect() { host := os.Getenv("SMTP_HOST") port := os.Getenv("SMTP_PORT") email := os.Getenv("SMTP_EMAIL") password := os.Getenv("SMTP_PASSWORD") smtpAuth := smtp.PlainAuth("", email, password, host) // connect to smtp server client, err := smtp.Dial(host + ":" + port) if err != nil { panic(err) } SMTPClient = client client = nil // initiate TLS handshake if ok, _ := SMTPClient.Extension("STARTTLS"); ok { config := &tls.Config{ServerName: host} if err = SMTPClient.StartTLS(config); err != nil { panic(err) } } // authenticate err = SMTPClient.Auth(smtpAuth) if err != nil { panic(err) } fmt.Println("SMTP Connected") }
Untuk abstraksi, saya akan mencipta fail passwordReset.go dalam /utils. Fail ini akan mempunyai fungsi berikut buat masa ini:
key -> password-reset:<email> value -> hashed otp expiry -> 10 mins
Saya menyimpan cincangan OTP dan bukannya OTP itu sendiri atas sebab keselamatan
Semasa menulis kod saya nampak bahawa kita memerlukan 5 pemalar di sini:
Saya akan segera menambahkannya pada /utils/constants.go
// utils/constants.go package utils import "time" const ( authTokenExp = time.Minute * 10 refreshTokenExp = time.Hour * 24 * 30 // 1 month blacklistKeyPrefix = "blacklisted:" otpKeyPrefix = "password-reset:" otpExp = time.Minute * 10 otpCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" emailTemplate = "To: %s\r\n" + "Subject: Task-inator 3000 Password Reset\r\n" + "\r\n" + "Your OTP for password reset is %s\r\n" // public because needed for testing OTPLength = 10 )
(Perhatikan bahawa kami akan mengimport daripada kripto/rand, dan bukan matematik/rand, kerana ia akan memberikan rawak sebenar)
// utils/passwordReset.go package utils import ( "context" "crypto/rand" "fmt" "math/big" "os" "task-inator3000/config" "golang.org/x/crypto/bcrypt" ) func GenerateOTP() string { result := make([]byte, OTPLength) charsetLength := big.NewInt(int64(len(otpCharSet))) for i := range result { // generate a secure random number in the range of the charset length num, _ := rand.Int(rand.Reader, charsetLength) result[i] = otpCharSet[num.Int64()] } return string(result) } func AddOTPtoRedis(otp string, email string, c context.Context) error { key := otpKeyPrefix + email // hashing the OTP data, _ := bcrypt.GenerateFromPassword([]byte(otp), 10) // storing otp with expiry err := config.RedisClient.Set(c, key, data, otpExp).Err() if err != nil { return err } return nil } func SendOTP(otp string, recipient string) error { sender := os.Getenv("SMTP_EMAIL") client := config.SMTPClient // setting the sender err := client.Mail(sender) if err != nil { return err } // set recipient err = client.Rcpt(recipient) if err != nil { return err } // start writing email writeCloser, err := client.Data() if err != nil { return err } // contents of the email msg := fmt.Sprintf(emailTemplate, recipient, otp) // write the email _, err = writeCloser.Write([]byte(msg)) if err != nil { return err } // close writecloser and send email err = writeCloser.Close() if err != nil { return err } return nil }
Fungsi GenerateOTP() boleh diuji tanpa olok-olok (ujian unit), oleh itu menulis ujian mudah untuknya
package utils_test import ( "task-inator3000/utils" "testing" ) func TestGenerateOTP(t *testing.T) { result := utils.GenerateOTP() if len(result) != utils.OTPLength { t.Errorf("Length of OTP was not %v. OTP: %v", utils.OTPLength, result) } }
Sekarang kita perlu meletakkan semuanya bersama-sama di dalam pengawal. Sebelum semua itu kita perlu pastikan alamat e-mel yang diberikan wujud dalam pangkalan data.
Kod lengkap untuk pengawal adalah seperti berikut:
func SendPasswordResetEmail(c *fiber.Ctx) error { type Input struct { Email string `json:"email"` } var input Input err := c.BodyParser(&input) if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "invalid data", }) } // check if user with email exists users := config.DB.Collection("users") filter := bson.M{"_id": input.Email} err = users.FindOne(c.Context(), filter).Err() if err != nil { if err == mongo.ErrNoDocuments { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ "error": "user with given email not found", }) } return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "error while finding in the database:\n" + err.Error(), }) } // generate otp and add it to redis otp := utils.GenerateOTP() err = utils.AddOTPtoRedis(otp, input.Email, c.Context()) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": err.Error(), }) } // send the otp to user through email err = utils.SendOTP(otp, input.Email) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": err.Error(), }) } return c.SendStatus(fiber.StatusNoContent) }
Kami boleh menguji API dengan menghantar permintaan POST ke URL yang betul. Contoh cURL ialah:
curl --location 'localhost:3000/api/send-otp' \ --header 'Content-Type: application/json' \ --data-raw '{ "email": "yashjaiswal.cse@gmail.com" }'
Kami akan mencipta API seterusnya - untuk Menetapkan Semula Kata Laluan - di bahagian seterusnya siri
Atas ialah kandungan terperinci Ciri Tetapan Semula Kata Laluan: Menghantar E-mel di Golang. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!