thread-master/old/backend/internal/logic/auth/email_verification.go

78 lines
2.4 KiB
Go
Raw Normal View History

2026-07-09 03:45:28 +00:00
package auth
import (
"context"
"crypto/rand"
"fmt"
"math/big"
"time"
"haixun-backend/internal/library/clock"
"haixun-backend/internal/model/member/domain/entity"
"haixun-backend/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
const emailVerificationTTL = 15 * time.Minute
const emailVerificationResendCooldown = 60 * time.Second
func newVerifyCode() (string, error) {
n, err := rand.Int(rand.Reader, big.NewInt(1000000))
if err != nil {
return "", err
}
return fmt.Sprintf("%06d", n.Int64()), nil
}
func requiresNativeEmailVerification(member *entity.Member) bool {
if member == nil {
return false
}
return member.Origin == "" || member.Origin == entity.OriginNative
}
func issueAndSendEmailVerification(ctx context.Context, svcCtx *svc.ServiceContext, member *entity.Member) error {
verifyCode, err := newVerifyCode()
if err != nil {
return err
}
expiresAt := clock.NowUnixNano() + int64(emailVerificationTTL)
if err := svcCtx.Member.SetEmailVerificationCode(ctx, member.TenantID, member.UID, verifyCode, expiresAt); err != nil {
return err
}
if err := sendEmailVerification(ctx, svcCtx, member.Email, verifyCode); err != nil {
logx.WithContext(ctx).Errorf("send verification email failed: uid=%s err=%v", member.UID, err)
}
return nil
}
func sendEmailVerification(ctx context.Context, svcCtx *svc.ServiceContext, email, verifyCode string) error {
if svcCtx.Mailer == nil {
return nil
}
body := fmt.Sprintf("你的巡樓 Console 驗證碼是:%s\n\n此驗證碼 15 分鐘內有效。", verifyCode)
if err := svcCtx.Mailer.Send(ctx, email, "巡樓 Console 帳號驗證碼", body); err != nil {
logx.WithContext(ctx).Errorf("send verification email failed: email=%s err=%v", email, err)
return err
}
return nil
}
func maybeSendLoginVerificationEmail(ctx context.Context, svcCtx *svc.ServiceContext, member *entity.Member) {
if member == nil || !requiresNativeEmailVerification(member) || member.EmailVerified {
return
}
if member.EmailVerifyCode != "" && member.EmailVerifyExpiresAt > clock.NowUnixNano() {
return
}
_ = issueAndSendEmailVerification(ctx, svcCtx, member)
}
func emailVerificationSentRecently(member *entity.Member) bool {
if member == nil || member.EmailVerifyExpiresAt <= 0 {
return false
}
sentAt := member.EmailVerifyExpiresAt - int64(emailVerificationTTL)
return clock.NowUnixNano()-sentAt < int64(emailVerificationResendCooldown)
}