70 lines
1.7 KiB
Go
70 lines
1.7 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
smsentity "spark-server/internal/module/sms/entity"
|
|
userentity "spark-server/internal/module/user/entity"
|
|
)
|
|
|
|
const (
|
|
defaultCodeTTLSeconds = 300
|
|
defaultCodeCooldownSeconds = 60
|
|
)
|
|
|
|
// CodeTTLSeconds is the OTP lifetime returned to clients.
|
|
const CodeTTLSeconds = defaultCodeTTLSeconds
|
|
|
|
// AccountEmail resolves the bound mailbox for an authenticated user (account delete OTP).
|
|
type AccountEmail interface {
|
|
EmailByUserID(ctx context.Context, userID int64) (string, error)
|
|
}
|
|
|
|
// Service issues and checks OTPs for email and phone.
|
|
type Service struct {
|
|
store CodeStore
|
|
email EmailSender
|
|
phone []PhoneCarrierSender
|
|
accounts AccountEmail
|
|
}
|
|
|
|
func NewService(store CodeStore, email EmailSender, phone []PhoneCarrierSender, accounts AccountEmail) *Service {
|
|
return &Service{store: store, email: email, phone: phone, accounts: accounts}
|
|
}
|
|
|
|
func storeKey(ch smsentity.Channel, purpose smsentity.Purpose, target string) string {
|
|
return fmt.Sprintf("%d:%d:%s", ch, purpose, target)
|
|
}
|
|
|
|
func randomDigits(n int) (string, error) {
|
|
if n <= 0 {
|
|
return "", fmt.Errorf("digits")
|
|
}
|
|
var buf [8]byte
|
|
out := make([]byte, n)
|
|
for i := 0; i < n; i++ {
|
|
if _, err := rand.Read(buf[:4]); err != nil {
|
|
return "", err
|
|
}
|
|
out[i] = '0' + byte(binary.BigEndian.Uint32(buf[:4])%10)
|
|
}
|
|
return string(out), nil
|
|
}
|
|
|
|
func skipSendEmail(email string) bool {
|
|
return userentity.IsTestEmail(email)
|
|
}
|
|
|
|
func skipSendPhone(phone string) bool {
|
|
// Dev-friendly test MSISDNs matching spark test convention.
|
|
switch phone {
|
|
case "+8613800138000", "+8613800138001", "+8613800138002":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|