package auth
import (
"crypto/rand"
"encoding/base32"
"fmt"
"time"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
)
type TOTPConfig struct {
Secret string
Period uint
}
func GenerateSecret(s string) (*TOTPConfig, error) {
var secret string
if s != "" {
secret = s
} else {
bytes := make([]byte, 20)
if _, err := rand.Read(bytes); err != nil {
return nil, fmt.Errorf("生成随机密钥失败: %v", err)
}
secret = base32.StdEncoding.EncodeToString(bytes)
}
return &TOTPConfig{
Secret: secret,
Period: 30,
}, nil
}
func (c *TOTPConfig) GenerateCode() (string, error) {
code, err := totp.GenerateCode(c.Secret, time.Now())
if err != nil {
return "", fmt.Errorf("生成验证码失败: %v", err)
}
return code, nil
}
func (c *TOTPConfig) ValidateCode(code string) bool {
return totp.Validate(code, c.Secret)
}
func (c *TOTPConfig) GetQRCodeURL(accountName, issuer string) string {
key, err := otp.NewKeyFromURL(fmt.Sprintf("otpauth://totp/%s:%s?secret=%s&issuer=%s&period=%d",
issuer,
accountName,
c.Secret,
issuer,
c.Period))
if err != nil {
return ""
}
return key.URL()
}
package main
import (
"fmt"
"time"
"my_otp/auth"
)
func main() {
config, err := auth.GenerateSecret("4MEE7Q2BZBD4G6NC")
if err != nil {
panic(err)
}
fmt.Printf("密钥: %s\n", config.Secret)
qrURL := config.GetQRCodeURL("GitHub:liuxiaobopro", "GitHub")
fmt.Printf("二维码 URL: %s\n", qrURL)
code, err := config.GenerateCode()
if err != nil {
panic(err)
}
fmt.Printf("当前验证码: %s\n", code)
isValid := config.ValidateCode(code)
fmt.Printf("验证结果: %v\n", isValid)
time.Sleep(31 * time.Second)
for range time.Tick(time.Second * 30) {
newCode, _ := config.GenerateCode()
fmt.Printf("新的验证码: %s\n", newCode)
}
}