template-monorepo/internal/logic/member/verify_helper.go

132 lines
3.9 KiB
Go
Raw Permalink Normal View History

2026-05-20 23:51:22 +00:00
package member
import (
"context"
2026-05-21 23:52:39 +00:00
"fmt"
"os"
2026-05-20 23:51:22 +00:00
"time"
memberdom "gateway/internal/model/member/domain"
"gateway/internal/model/member/domain/enum"
domusecase "gateway/internal/model/member/domain/usecase"
notifenum "gateway/internal/model/notification/domain/enum"
notifuc "gateway/internal/model/notification/domain/usecase"
"gateway/internal/svc"
"gateway/internal/types"
"github.com/zeromicro/go-zero/core/logx"
2026-05-20 23:51:22 +00:00
)
func startVerification(
ctx context.Context,
sc *svc.ServiceContext,
actor Actor,
purpose enum.OTPPurpose,
channel notifenum.Channel,
kind notifenum.NotifyKind,
target string,
) (*types.VerificationStartData, error) {
if sc.MemberOTP == nil {
return nil, errb.SysNotImplemented("member OTP not configured")
2026-05-20 23:51:22 +00:00
}
if sc.Notifier == nil {
return nil, errb.SysNotImplemented("notifier not configured")
}
if sc.MemberVerifyRate == nil {
return nil, errb.SysNotImplemented("member verify rate not configured")
2026-05-20 23:51:22 +00:00
}
if target == "" {
return nil, errb.InputMissingRequired("target is required")
}
cfg := sc.Config.Member.Defaults()
rateKey := memberdom.GetVerifyRateRedisKey(actor.TenantID, actor.UID, string(purpose))
if err := sc.MemberVerifyRate.AssertResendAllowed(ctx, rateKey, time.Duration(cfg.OTP.ResendCooldownSeconds)*time.Second); err != nil {
return nil, err
2026-05-20 23:51:22 +00:00
}
dailyKey := memberdom.GetVerifyDailyRedisKey(actor.TenantID, actor.UID, string(purpose))
if err := sc.MemberVerifyRate.AssertDailyAllowed(ctx, dailyKey, 24*time.Hour, cfg.OTP.DailyVerifyLimit); err != nil {
return nil, err
2026-05-20 23:51:22 +00:00
}
dto, plainCode, err := sc.MemberOTP.Generate(ctx, &domusecase.GenerateOTPRequest{
TenantID: actor.TenantID,
UID: actor.UID,
Purpose: purpose,
Target: target,
})
if err != nil {
return nil, err
}
locale := sc.Config.Notification.DefaultLocale
if _, sendErr := sc.Notifier.Send(ctx, &notifuc.SendRequest{
TenantID: actor.TenantID,
UID: actor.UID,
Channel: channel,
Kind: kind,
Target: target,
Locale: locale,
Data: map[string]any{"code": plainCode, "expires_in": dto.ExpiresIn},
IdempotencyKey: dto.ChallengeID,
DoNotPersistBody: true,
Severity: notifenum.SeverityInfo,
}); sendErr != nil {
if invErr := sc.MemberOTP.Invalidate(ctx, dto.ChallengeID); invErr != nil {
return nil, invErr
2026-05-20 23:51:22 +00:00
}
return nil, sendErr
}
2026-05-21 23:52:39 +00:00
if os.Getenv("GATEWAY_E2E") == "1" && sc.Redis != nil && sc.Redis.Zero() != nil {
key := fmt.Sprintf("e2e:otp:%s", dto.ChallengeID)
if setErr := sc.Redis.Zero().SetexCtx(ctx, key, plainCode, dto.ExpiresIn); setErr != nil {
logx.WithContext(ctx).Infof("e2e otp mirror skipped: %v", setErr)
}
2026-05-21 23:52:39 +00:00
}
2026-05-20 23:51:22 +00:00
return &types.VerificationStartData{
ChallengeID: dto.ChallengeID,
ExpiresIn: dto.ExpiresIn,
}, nil
}
func confirmVerification(
ctx context.Context,
sc *svc.ServiceContext,
actor Actor,
req *types.VerificationConfirmReq,
purpose enum.OTPPurpose,
setVerified func(context.Context, string, string, string) error,
) error {
if sc.MemberOTP == nil || sc.MemberProfile == nil {
return errb.SysNotImplemented("member module not configured")
2026-05-20 23:51:22 +00:00
}
if req == nil || req.ChallengeID == "" || req.Code == "" {
return errb.InputMissingRequired("challenge_id and code are required")
}
target, err := sc.MemberOTP.Verify(ctx, &domusecase.VerifyOTPRequest{
TenantID: actor.TenantID,
UID: actor.UID,
ChallengeID: req.ChallengeID,
Code: req.Code,
Purpose: purpose,
})
if err != nil {
return err
}
return setVerified(ctx, actor.TenantID, actor.UID, target)
}
func requireTOTP(sc *svc.ServiceContext) error {
if sc.MemberTOTP == nil {
return errb.SysNotImplemented("member TOTP not configured")
2026-05-20 23:51:22 +00:00
}
return nil
}
func actorOrErr(ctx context.Context) (Actor, error) {
actor, err := ActorFromContext(ctx)
if err != nil {
return Actor{}, errb.AuthUnauthorized("missing bearer token or X-Tenant-ID/X-UID headers")
2026-05-20 23:51:22 +00:00
}
return actor, nil
}