package auth import ( "crypto/hmac" "crypto/sha1" "encoding/base32" "encoding/binary" "fmt" "math/rand" "strings" "time" ) // GenerateTOTPSecret creates a random base32-encoded secret. func GenerateTOTPSecret() string { b := make([]byte, 20) for i := range b { b[i] = byte(rand.Intn(256)) } return base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(b) } // ValidateTOTP checks if the provided code matches the secret for the current time window. func ValidateTOTP(secret, code string) bool { secret = strings.ToUpper(strings.TrimSpace(secret)) key, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(secret) if err != nil { return false } now := time.Now().Unix() / 30 // Check current window and ±1 for clock drift for _, offset := range []int64{-1, 0, 1} { if generateCode(key, now+offset) == code { return true } } return false } // TOTPUri generates an otpauth:// URI for QR code generation. func TOTPUri(secret, email, issuer string) string { return fmt.Sprintf("otpauth://totp/%s:%s?secret=%s&issuer=%s&algorithm=SHA1&digits=6&period=30", issuer, email, secret, issuer) } func generateCode(key []byte, counter int64) string { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, uint64(counter)) mac := hmac.New(sha1.New, key) mac.Write(buf) hash := mac.Sum(nil) offset := hash[len(hash)-1] & 0x0f code := binary.BigEndian.Uint32(hash[offset:offset+4]) & 0x7fffffff return fmt.Sprintf("%06d", code%1000000) }