| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- package common
- import (
- "crypto/rand"
- "fmt"
- "os"
- "strconv"
- "strings"
- "github.com/pquerna/otp"
- "github.com/pquerna/otp/totp"
- )
- const (
- // 备用码配置
- BackupCodeLength = 8 // 备用码长度
- BackupCodeCount = 4 // 生成备用码数量
- // 限制配置
- MaxFailAttempts = 5 // 最大失败尝试次数
- LockoutDuration = 300 // 锁定时间(秒)
- )
- // GenerateTOTPSecret 生成TOTP密钥和配置
- func GenerateTOTPSecret(accountName string) (*otp.Key, error) {
- issuer := Get2FAIssuer()
- return totp.Generate(totp.GenerateOpts{
- Issuer: issuer,
- AccountName: accountName,
- Period: 30,
- Digits: otp.DigitsSix,
- Algorithm: otp.AlgorithmSHA1,
- })
- }
- // ValidateTOTPCode 验证TOTP验证码
- func ValidateTOTPCode(secret, code string) bool {
- // 清理验证码格式
- cleanCode := strings.ReplaceAll(code, " ", "")
- if len(cleanCode) != 6 {
- return false
- }
- // 验证验证码
- return totp.Validate(cleanCode, secret)
- }
- // GenerateBackupCodes 生成备用恢复码
- func GenerateBackupCodes() ([]string, error) {
- codes := make([]string, BackupCodeCount)
- for i := 0; i < BackupCodeCount; i++ {
- code, err := generateRandomBackupCode()
- if err != nil {
- return nil, err
- }
- codes[i] = code
- }
- return codes, nil
- }
- // generateRandomBackupCode 生成单个备用码
- func generateRandomBackupCode() (string, error) {
- const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
- code := make([]byte, BackupCodeLength)
- for i := range code {
- randomBytes := make([]byte, 1)
- _, err := rand.Read(randomBytes)
- if err != nil {
- return "", err
- }
- code[i] = charset[int(randomBytes[0])%len(charset)]
- }
- // 格式化为 XXXX-XXXX 格式
- return fmt.Sprintf("%s-%s", string(code[:4]), string(code[4:])), nil
- }
- // ValidateBackupCode 验证备用码格式
- func ValidateBackupCode(code string) bool {
- // 移除所有分隔符并转为大写
- cleanCode := strings.ToUpper(strings.ReplaceAll(code, "-", ""))
- if len(cleanCode) != BackupCodeLength {
- return false
- }
- // 检查字符是否合法
- for _, char := range cleanCode {
- if !((char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9')) {
- return false
- }
- }
- return true
- }
- // NormalizeBackupCode 标准化备用码格式
- func NormalizeBackupCode(code string) string {
- cleanCode := strings.ToUpper(strings.ReplaceAll(code, "-", ""))
- if len(cleanCode) == BackupCodeLength {
- return fmt.Sprintf("%s-%s", cleanCode[:4], cleanCode[4:])
- }
- return code
- }
- // HashBackupCode 对备用码进行哈希
- func HashBackupCode(code string) (string, error) {
- normalizedCode := NormalizeBackupCode(code)
- return Password2Hash(normalizedCode)
- }
- // Get2FAIssuer 获取2FA发行者名称
- func Get2FAIssuer() string {
- return SystemName
- }
- // getEnvOrDefault 获取环境变量或默认值
- func getEnvOrDefault(key, defaultValue string) string {
- if value, exists := os.LookupEnv(key); exists {
- return value
- }
- return defaultValue
- }
- // ValidateNumericCode 验证数字验证码格式
- func ValidateNumericCode(code string) (string, error) {
- // 移除空格
- code = strings.ReplaceAll(code, " ", "")
- if len(code) != 6 {
- return "", fmt.Errorf("验证码必须是6位数字")
- }
- // 检查是否为纯数字
- if _, err := strconv.Atoi(code); err != nil {
- return "", fmt.Errorf("验证码只能包含数字")
- }
- return code, nil
- }
- // GenerateQRCodeData 生成二维码数据
- func GenerateQRCodeData(secret, username string) string {
- issuer := Get2FAIssuer()
- accountName := fmt.Sprintf("%s (%s)", username, issuer)
- return fmt.Sprintf("otpauth://totp/%s:%s?secret=%s&issuer=%s&digits=6&period=30",
- issuer, accountName, secret, issuer)
- }
|