Files
GoMFT/internal/auth/totp.go
T
StarFleetCPTN a7d912aa44 feat: Implement Two-Factor Authentication (2FA) functionality
- Added support for Two-Factor Authentication, including setup, verification, and disabling features.
- Introduced new components for 2FA setup and verification, enhancing user security.
- Updated user model to include fields for 2FA secret, status, and backup codes.
- Created migration to add necessary database fields for 2FA.
- Enhanced authentication handlers to manage 2FA processes during login and profile updates.
- Updated routes to include 2FA-related endpoints for setup and verification.
2025-03-17 05:17:03 -07:00

159 lines
4.0 KiB
Go

package auth
import (
"bytes"
"crypto/rand"
"encoding/base32"
"encoding/base64"
"fmt"
"image/png"
"strings"
// "github.com/pquerna/otp/base32"
"github.com/pquerna/otp/totp"
)
const (
// IssuerName is the name of the issuer that appears in authenticator apps
IssuerName = "GoMFT"
// SecretSize is the size of the TOTP secret in bytes
SecretSize = 20
// BackupCodeCount is the number of backup codes to generate
BackupCodeCount = 8
// BackupCodeLength is the length of each backup code
BackupCodeLength = 8
)
// GenerateTOTPSecret generates a new TOTP secret for a user
func GenerateTOTPSecret(email string) (string, string, error) {
// Generate TOTP key using the library
key, err := totp.Generate(totp.GenerateOpts{
Issuer: IssuerName,
AccountName: email,
})
if err != nil {
return "", "", fmt.Errorf("failed to generate TOTP key: %v", err)
}
// Generate QR code image
var buf bytes.Buffer
img, err := key.Image(256, 256)
if err != nil {
return "", "", fmt.Errorf("failed to generate QR code image: %v", err)
}
// Encode image as PNG and convert to base64
err = png.Encode(&buf, img)
if err != nil {
return "", "", fmt.Errorf("failed to encode QR code image: %v", err)
}
// Create data URL
dataURL := fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(buf.Bytes()))
return key.Secret(), dataURL, nil
}
// ValidateTOTPCode validates a TOTP code against a secret
func ValidateTOTPCode(secret string, code string) bool {
// Remove any spaces from the code
code = strings.ReplaceAll(code, " ", "")
// Use the library's Validate function
return totp.Validate(code, secret)
}
// GenerateBackupCodes generates a set of backup codes
func GenerateBackupCodes() ([]string, error) {
codes := make([]string, BackupCodeCount)
for i := 0; i < BackupCodeCount; i++ {
// Generate random bytes
bytes := make([]byte, BackupCodeLength/2)
_, err := rand.Read(bytes)
if err != nil {
return nil, fmt.Errorf("failed to generate backup code: %v", err)
}
// Convert to hex string
codes[i] = fmt.Sprintf("%x", bytes)
}
return codes, nil
}
// ValidateBackupCode validates a backup code against a list of codes
func ValidateBackupCode(providedCode string, storedCodes string) bool {
if storedCodes == "" {
return false
}
// Remove any spaces and convert to lowercase
providedCode = strings.ToLower(strings.ReplaceAll(providedCode, " ", ""))
// Split stored codes
codes := strings.Split(storedCodes, ",")
// Check if the provided code matches any stored code
for _, code := range codes {
if code == providedCode {
return true
}
}
return false
}
// RemoveBackupCode removes a used backup code from the list
func RemoveBackupCode(usedCode string, storedCodes string) string {
if storedCodes == "" {
return ""
}
usedCode = strings.ToLower(strings.ReplaceAll(usedCode, " ", ""))
codes := strings.Split(storedCodes, ",")
var newCodes []string
for _, code := range codes {
if code != usedCode {
newCodes = append(newCodes, code)
}
}
return strings.Join(newCodes, ",")
}
// GenerateQRCodeURL generates a QR code URL for an existing secret
func GenerateQRCodeURL(secret string, email string) (string, error) {
// Decode the base32 secret
secretBytes, err := base32.StdEncoding.DecodeString(secret)
if err != nil {
return "", fmt.Errorf("failed to decode secret: %v", err)
}
key, err := totp.Generate(totp.GenerateOpts{
Issuer: IssuerName,
AccountName: email,
Secret: secretBytes,
})
if err != nil {
return "", fmt.Errorf("failed to generate TOTP key: %v", err)
}
// Generate QR code image
var buf bytes.Buffer
img, err := key.Image(256, 256)
if err != nil {
return "", fmt.Errorf("failed to generate QR code image: %v", err)
}
// Encode image as PNG and convert to base64
err = png.Encode(&buf, img)
if err != nil {
return "", fmt.Errorf("failed to encode QR code image: %v", err)
}
// Create data URL
dataURL := fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(buf.Bytes()))
return dataURL, nil
}