Rumah > pembangunan bahagian belakang > Golang > Ciri Tetapan Semula Kata Laluan: Menghantar E-mel di Golang

Ciri Tetapan Semula Kata Laluan: Menghantar E-mel di Golang

Mary-Kate Olsen
Lepaskan: 2024-10-01 06:12:30
asal
1217 orang telah melayarinya

Password Reset Feature: Sending Email in Golang

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


Perancangan

Saya sedang memikirkan aliran seperti ini:

  1. Pengguna mengklik pada 'Lupa Kata Laluan?' butang
  2. Paparkan modal kepada pengguna yang meminta e-mel
  3. Semak sama ada e-mel wujud dan hantar OTP sepanjang 10 aksara ke e-mel
  4. Modal kini meminta OTP dan kata laluan baharu
  5. Kata laluan dicincang dan dikemas kini untuk pengguna

Pemisahan Kebimbangan

Hadapan

  • Buat modal untuk memasukkan e-mel
  • Mod yang sama kemudiannya mengambil OTP dan kata laluan baharu

Belakang

  • Buat API untuk menghantar e-mel
  • Buat API untuk menetapkan semula kata laluan

Saya akan bermula dengan bahagian belakang

Bahagian belakang

Seperti yang dinyatakan di atas, kami memerlukan dua API

1. Menghantar E-mel

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)
}
Salin selepas log masuk

Sekarang menambah laluan untuknya:

// routes/routes.go

// password reset
api.Post("/send-otp", controllers.SendPasswordResetEmail)
Salin selepas log masuk

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>"
Salin selepas log masuk

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")
}
Salin selepas log masuk

Untuk abstraksi, saya akan mencipta fail passwordReset.go dalam /utils. Fail ini akan mempunyai fungsi berikut buat masa ini:

  • GenerateOTP: Untuk menjana OTP alfanumerik 10 digit unik untuk dihantar dalam e-mel
  • AddOTPtoRedis: Untuk menambah OTP ke Redis dalam format nilai utama di mana
key -> password-reset:<email>
value -> hashed otp
expiry -> 10 mins
Salin selepas log masuk

Saya menyimpan cincangan OTP dan bukannya OTP itu sendiri atas sebab keselamatan

  • SendOTP: Untuk menghantar OTP yang dijana ke e-mel pengguna

Semasa menulis kod saya nampak bahawa kita memerlukan 5 pemalar di sini:

  • Awalan untuk kunci redis untuk OTP
  • Masa tamat tempoh untuk OTP
  • Set watak untuk penjanaan OTP
  • Templat untuk e-mel
  • Panjang OTP

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
)
Salin selepas log masuk

(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
}
Salin selepas log masuk

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)
    }
}
Salin selepas log masuk

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)
}
Salin selepas log masuk

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"
}'
Salin selepas log masuk

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!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel terbaru oleh pengarang
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan