How to implement an email verification system with Go&MongoDB?

huangapple go评论86阅读模式
英文:

How to implement an email verification system with Go&MongoDB?

问题

我目前从我的/signup端点返回JWT令牌:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODMwMjcwOTUsInR5cGUiOiJhd2FpdGluZy12ZXJpZmljYXRpb24iLCJ1dWlkIjoiNjQ0ZmEzMTc1MmQwODdhYjg1NmE2NTA3In0.nHZg6RGkYxWM2vy4YyFHxyq_Snb3o9NpoRiMgEKnY2o

它的内容如下:

{
  "exp": 1683027095,
  "type": "awaiting-verification",
  "uuid": "644fa31752d087ab856a6507"
}

我无法理解如何发送验证邮件。我计划创建一个名为"emailcodes"的单独集合,生成一个随机的6位数,比如123123,并将其插入到集合中,如下所示:

{
  "uuid": "644fa31752d087ab856a6507",
  "code": 123123,
  "exp": 5分钟的UNIX时间戳
}

然后从白名单中获取代码(类型为awaiting-verification的令牌)的端点/code-verification。

问题是,我不知道如何发送电子邮件,而不会出现重复发送相同电子邮件等问题。有没有适用于GoLang的库可以实现这个功能?如果没有,我该如何实现一个系统,即使我也不会搞砸?

我已经使用https://pkg.go.dev/github.com/AfterShip/email-verifier?utm_source=godoc 验证了电子邮件的有效性和可达性。

..帮帮我?

英文:

I currently give JWT tokens from my /signup:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODMwMjcwOTUsInR5cGUiOiJhd2FpdGluZy12ZXJpZmljYXRpb24iLCJ1dWlkIjoiNjQ0ZmEzMTc1MmQwODdhYjg1NmE2NTA3In0.nHZg6RGkYxWM2vy4YyFHxyq_Snb3o9NpoRiMgEKnY2o

which is just:

{
  "exp": 1683027095,
  "type": "awaiting-verification",
  "uuid": "644fa31752d087ab856a6507"
}

and can't wrap my head around how can I even send an email for verification. I planned to create a separate collection called "emailcodes", generate a random 6-digit number, let's say 123123, and insert into the collection like:

{
  "uuid": "644fa31752d087ab856a6507"
  "code": 123123
  "exp": 5-minutes-in-unix
}

and take the code from whitelisted (tokens that is type is awaiting-verification) endpoint /code-verification

The problem is, I do not know how can I send emails without screwing everything up like sending the same email a dozen times, etc. Is there a GoLang library for this? If there isn't how can I implement a system for this that even I cant screw it up?

I already verify if the email is valid and reachable with https://pkg.go.dev/github.com/AfterShip/email-verifier?utm_source=godoc

..Help?

答案1

得分: 1

我认为你应该优先选择具有很好使用案例的良好参考资料,而不是解释。

在Go中发送邮件包

  • Go的标准库肯定可以处理这个问题:https://pkg.go.dev/net/mail(但是,请也看看第二个选项)
  • 我更喜欢使用这个第三方包,因为我觉得它更灵活和用户友好 - jordan-wright/email。此外,我使用这个包已经有一段时间了,没有遇到任何问题。所以,根据我作为一个Gopher的经验,我可以自信地推荐它。顺便说一句,如果你想尝试其他开源选项,还有很多其他选择。感谢Go庞大的社区!

实现电子邮件验证

package mail

import (
	"fmt"
	"net/smtp"

	"github.com/jordan-wright/email"
)

const (
	smtpAuthAddress   = "smtp.gmail.com"
	smtpServerAddress = "smtp.gmail.com:587"
)

type EmailSender interface {
	SendEmail(
		subject string,
		content string,
		to []string,
		cc []string,
		bcc []string,
		attachFiles []string,
	) error
}

type GmailSender struct {
	name              string
	fromEmailAddress  string
	fromEmailPassword string
}

func NewGmailSender(name string, fromEmailAddress string, fromEmailPassword string) EmailSender {
	return &GmailSender{
		name:              name,
		fromEmailAddress:  fromEmailAddress,
		fromEmailPassword: fromEmailPassword,
	}
}

func (sender *GmailSender) SendEmail(
	subject string,
	content string,
	to []string,
	cc []string,
	bcc []string,
	attachFiles []string,
) error {
	e := email.NewEmail()
	e.From = fmt.Sprintf("%s <%s>", sender.name, sender.fromEmailAddress)
	e.Subject = subject
	e.HTML = []byte(content)
	e.To = to
	e.Cc = cc
	e.Bcc = bcc

	for _, f := range attachFiles {
		_, err := e.AttachFile(f)
		if err != nil {
			return fmt.Errorf("failed to attach file %s: %w", f, err)
		}
	}

	smtpAuth := smtp.PlainAuth("", sender.fromEmailAddress, sender.fromEmailPassword, smtpAuthAddress)
	return e.Send(smtpServerAddress, smtpAuth)
}
  • 电子邮件验证:这是一篇关于使用Go语言的电子邮件验证和密码重置流程的精彩博文,其中包含代码片段,我认为它可以比我现在能做到的更清楚地解释。所以我会引用它:使用golang进行电子邮件验证和密码重置流程

  • 如果你只需要检查电子邮件是否存在,而不是检查提供商用户的所有权,你可以简单地使用这个强大的Go包 - AfterShip/email-verifier

英文:

Instead of explaining I think you should prefer good references with great use cases:

Mailing packages in Go

  • Go's STD can handle this for sure: https://pkg.go.dev/net/mail (But, please have a look at the second option, too)
  • I prefer to use this third-party package, coz I found it more flexible and user-friendly - jordan-wright/email. Additionally, I use this package for a while and got no issues with it. So, confidently recommended by my humble experience as a Gopher. BTW, there are too many other open-source options if you want to play with them. Thanks to the Go's great community!

Implementing email verification

package mail

import (
	&quot;fmt&quot;
	&quot;net/smtp&quot;

	&quot;github.com/jordan-wright/email&quot;
)

const (
	smtpAuthAddress   = &quot;smtp.gmail.com&quot;
	smtpServerAddress = &quot;smtp.gmail.com:587&quot;
)

type EmailSender interface {
	SendEmail(
		subject string,
		content string,
		to []string,
		cc []string,
		bcc []string,
		attachFiles []string,
	) error
}

type GmailSender struct {
	name              string
	fromEmailAddress  string
	fromEmailPassword string
}

func NewGmailSender(name string, fromEmailAddress string, fromEmailPassword string) EmailSender {
	return &amp;GmailSender{
		name:              name,
		fromEmailAddress:  fromEmailAddress,
		fromEmailPassword: fromEmailPassword,
	}
}

func (sender *GmailSender) SendEmail(
	subject string,
	content string,
	to []string,
	cc []string,
	bcc []string,
	attachFiles []string,
) error {
	e := email.NewEmail()
	e.From = fmt.Sprintf(&quot;%s &lt;%s&gt;&quot;, sender.name, sender.fromEmailAddress)
	e.Subject = subject
	e.HTML = []byte(content)
	e.To = to
	e.Cc = cc
	e.Bcc = bcc

	for _, f := range attachFiles {
		_, err := e.AttachFile(f)
		if err != nil {
			return fmt.Errorf(&quot;failed to attach file %s: %w&quot;, f, err)
		}
	}

	smtpAuth := smtp.PlainAuth(&quot;&quot;, sender.fromEmailAddress, sender.fromEmailPassword, smtpAuthAddress)
	return e.Send(smtpServerAddress, smtpAuth)
}
  • Email verification: Here is a brilliant blog post about this flow with the snippets which I believe can explain more clearly then I can do so at this time, so I'd refernce that: Email Verification and Password Reset Flow using golang.

  • If u just need to check of an email exists or not instead of checking ownership on that for the provider user, you can simply use this powerful Go package - AfterShip/email-verifier.

答案2

得分: 0

有几个可用于发送电子邮件的库。

  • Mailgun:https://github.com/mailgun/mailgun-go
  • GoMail:https://github.com/go-gomail/gomail
  • net/smtp:https://pkg.go.dev/net/smtp

以下是如何使用gomail.v2的示例代码:

package main

import (
	"errors"
	"fmt"

	"gopkg.in/gomail.v2"
)

// EmailSender
type EmailSender struct {
	Message *gomail.Message
	dailer  *gomail.Dialer
}

// NewEmailSender
func NewEmailSender() *EmailSender {
	m := gomail.NewMessage()
	dailer := gomail.NewDialer("smtp.gmail.com", 587, "smtpEmail@example.com", "smtp-password")
	m.SetHeader("From", "from-email@gmail.com")

	return &EmailSender{
		Message: m,
		dailer:  dailer,
	}
}

// Send
func (es *EmailSender) Send(subject, to, message string) error {

	es.Message.SetHeader("To", to)
	es.Message.SetHeader("Subject", subject)
	es.Message.SetBody("text/plain", message)

	if err := es.dailer.DialAndSend(es.Message); err != nil {
		return err
	}

	return nil
}

// 发送电子邮件之前的检查
//   - 验证码是否有效
//   - 电子邮件是否已发送
//   - 验证码是否过期
//
// TODO:实现你的检查逻辑

func ChecksBeforeEmailSend(code string) error {
	return errors.New("电子邮件已发送")
}

func main() {
	code := "123456"

	emailer := NewEmailSender()

	if err := ChecksBeforeEmailSend(code); err != nil {
		panic(fmt.Sprintf("检查失败:%v", err.Error()))
	}

	err := emailer.Send(
		"验证您的电子邮件", // 主题
		"to@gmail.com", // 收件人邮箱
		fmt.Sprintf("您的验证码是 %s", code), // 正文
	)

	if err != nil {
		panic(err)
	}
}

为了防止为验证码发送重复的电子邮件可以在MongoDB集合中使用一个字段来指示该电子邮件是否已经发送给特定的用户和验证码在发送电子邮件之前检查该字段的值

例如

```json
{
  "uuid": "644fa31752d087ab856a6507",
  "code": 123123,
  "exp": 5-minutes-in-unix,
  "sent": true
}

其中的"sent": true表示已经为该用户和验证码发送了电子邮件。

我们可以在ChecksBeforeEmailSend函数中添加其他检查逻辑,以在发送电子邮件之前进行检查。

英文:

There several libraries available to send emails.

Here is an example how we can use gomail.v2

package main
import (
&quot;errors&quot;
&quot;fmt&quot;
&quot;gopkg.in/gomail.v2&quot;
)
// EmailSender
type EmailSender struct {
Message *gomail.Message
dailer  *gomail.Dialer
}
// NewEmailSender
func NewEmailSender() *EmailSender {
m := gomail.NewMessage()
dailer := gomail.NewDialer(&quot;smtp.gmail.com&quot;, 587, &quot;smtpEmail@example.com&quot;, &quot;smtp-password&quot;)
m.SetHeader(&quot;From&quot;, &quot;from-email@gmail.com&quot;)
return &amp;EmailSender{
Message: m,
dailer:  dailer,
}
}
// Send
func (es *EmailSender) Send(subject, to, message string) error {
es.Message.SetHeader(&quot;To&quot;, to)
es.Message.SetHeader(&quot;Subject&quot;, subject)
es.Message.SetBody(&quot;text/plain&quot;, message)
if err := es.dailer.DialAndSend(es.Message); err != nil {
return err
}
return nil
}
// checks before sending the email
//   - code is valid
//   - email already sent
//   - code is expired or not
//
// TODO : Implement your checks
func ChecksBeforeEmailSend(code string) error {
return errors.New(&quot;email already sent&quot;)
}
func main() {
code := &quot;123456&quot;
emailer := NewEmailSender()
if err := ChecksBeforeEmailSend(code); err != nil {
panic(fmt.Sprintf(&quot;Check %v failed&quot;, err.Error()))
}
err := emailer.Send(
&quot;Verify your email&quot;, // subject
&quot;to@gmail.com&quot;, // to email
fmt.Sprintf(&quot;Your verfication code is %d&quot;, code), // message
)
if err != nil {
panic(err)
}
}

To prevent sending duplicate emails for a verification-code, you can use a field within the mongo db collection which will indicate that the email has already send for a particular user and code, check this field value before sending the email.

for example

{
&quot;uuid&quot;: &quot;644fa31752d087ab856a6507&quot;
&quot;code&quot;: 123123
&quot;exp&quot;: 5-minutes-in-unix
&quot;sent&quot; : true
}

"sent" : true, indicates an email has already send for this user and code

We can add additional checks before sending the emails within ChecksBeforeEmailSend.

huangapple
  • 本文由 发表于 2023年5月1日 19:39:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76146627.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定