spark-server/internal/module/sms/logic/verify.go
2026-09-22 17:07:26 +08:00

66 lines
1.7 KiB
Go

package logic
import (
"context"
"crypto/subtle"
smsentity "spark-server/internal/module/sms/entity"
userentity "spark-server/internal/module/user/entity"
"spark-server/internal/pkg/errs"
)
// CheckAndConsume validates an OTP and deletes it on success.
func (s *Service) CheckAndConsume(ctx context.Context, ch smsentity.Channel, purpose smsentity.Purpose, target, code string) error {
if s == nil || s.store == nil {
return errs.ErrAvailable
}
if !ch.Valid() || !purpose.Valid() {
return errs.ErrInvalidArgument
}
target = normalizeTarget(ch, target)
if target == "" || code == "" {
return errs.ErrVerifyCodeInvalid
}
if ch == smsentity.ChannelEmail && userentity.IsTestEmail(target) {
return nil
}
if ch == smsentity.ChannelPhone && skipSendPhone(target) {
return nil
}
key := storeKey(ch, purpose, target)
want, err := s.store.Get(ctx, key)
if err != nil {
return err
}
if want == "" || subtle.ConstantTimeCompare([]byte(want), []byte(code)) != 1 {
return errs.ErrVerifyCodeInvalid
}
_ = s.store.Del(ctx, key)
return nil
}
// ClearTarget removes stored OTP and cooldown keys for one target (all purposes on that channel).
func (s *Service) ClearTarget(ctx context.Context, ch smsentity.Channel, target string) {
if s == nil || s.store == nil {
return
}
target = normalizeTarget(ch, target)
if target == "" {
return
}
for _, p := range []smsentity.Purpose{smsentity.PurposeLogin, smsentity.PurposeAccountDelete} {
_ = s.store.Del(ctx, storeKey(ch, p, target))
}
}
func normalizeTarget(ch smsentity.Channel, target string) string {
switch ch {
case smsentity.ChannelEmail:
return userentity.NormalizeEmail(target)
case smsentity.ChannelPhone:
return smsentity.NormalizePhone(target)
default:
return ""
}
}