78 lines
2.4 KiB
Go
78 lines
2.4 KiB
Go
|
|
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)
|
||
|
|
}
|