package security import ( "crypto/rand" "encoding/base32" "fmt" "time" "github.com/pquerna/otp/totp" ) // MFAMethod représente une méthode MFA type MFAMethod struct { ID string `json:"id"` UserID string `json:"user_id"` Type string `json:"type"` // totp, sms, email, backup Secret string `json:"secret,omitempty"` Phone string `json:"phone,omitempty"` Email string `json:"email,omitempty"` IsActive bool `json:"is_active"` IsVerified bool `json:"is_verified"` CreatedAt time.Time `json:"created_at"` VerifiedAt time.Time `json:"verified_at,omitempty"` LastUsedAt time.Time `json:"last_used_at,omitempty"` } // MFASession représente une session MFA type MFASession struct { ID string `json:"id"` UserID string `json:"user_id"` MethodID string `json:"method_id"` Token string `json:"token"` ExpiresAt time.Time `json:"expires_at"` Used bool `json:"used"` } // MFAManager gère l'authentification multi-facteurs type MFAManager struct { methods map[string]*MFAMethod sessions map[string]*MFASession } // NewMFAManager crée un nouveau gestionnaire MFA func NewMFAManager() *MFAManager { return &MFAManager{ methods: make(map[string]*MFAMethod), sessions: make(map[string]*MFASession), } } // GenerateTOTPSecret génère un secret TOTP func (mfa *MFAManager) GenerateTOTPSecret(userID, email string) (*MFAMethod, error) { // Générer un secret aléatoire secret := make([]byte, 20) if _, err := rand.Read(secret); err != nil { return nil, fmt.Errorf("failed to generate secret: %w", err) } // Encoder en base32 secretBase32 := base32.StdEncoding.EncodeToString(secret) // Créer la méthode TOTP method := &MFAMethod{ ID: fmt.Sprintf("totp_%s", userID), UserID: userID, Type: "totp", Secret: secretBase32, IsActive: false, IsVerified: false, CreatedAt: time.Now(), } mfa.methods[method.ID] = method return method, nil } // GenerateTOTPQRCode génère le QR code pour TOTP func (mfa *MFAManager) GenerateTOTPQRCode(method *MFAMethod, issuer, accountName string) string { // Format: otpauth://totp/issuer:account?secret=secret&issuer=issuer url := fmt.Sprintf("otpauth://totp/%s:%s?secret=%s&issuer=%s", issuer, accountName, method.Secret, issuer) return url } // VerifyTOTP vérifie un code TOTP func (mfa *MFAManager) VerifyTOTP(methodID, code string) (bool, error) { method, exists := mfa.methods[methodID] if !exists { return false, fmt.Errorf("method not found") } if method.Type != "totp" { return false, fmt.Errorf("method is not TOTP") } // Vérifier le code TOTP valid := totp.Validate(code, method.Secret) if valid { method.LastUsedAt = time.Now() if !method.IsVerified { method.IsVerified = true method.VerifiedAt = time.Now() } } return valid, nil } // GenerateBackupCodes génère des codes de sauvegarde func (mfa *MFAManager) GenerateBackupCodes(userID string, count int) ([]string, error) { codes := make([]string, count) for i := 0; i < count; i++ { // Générer un code de 8 caractères codeBytes := make([]byte, 4) if _, err := rand.Read(codeBytes); err != nil { return nil, fmt.Errorf("failed to generate backup code: %w", err) } // Encoder en base32 et prendre les 8 premiers caractères code := base32.StdEncoding.EncodeToString(codeBytes)[:8] codes[i] = code } // Créer la méthode de sauvegarde method := &MFAMethod{ ID: fmt.Sprintf("backup_%s", userID), UserID: userID, Type: "backup", Secret: "", // Les codes sont stockés séparément IsActive: true, IsVerified: true, CreatedAt: time.Now(), VerifiedAt: time.Now(), } mfa.methods[method.ID] = method return codes, nil } // VerifyBackupCode vérifie un code de sauvegarde func (mfa *MFAManager) VerifyBackupCode(userID, code string) (bool, error) { methodID := fmt.Sprintf("backup_%s", userID) method, exists := mfa.methods[methodID] if !exists { return false, fmt.Errorf("backup method not found") } // Dans un vrai système, les codes seraient stockés de manière sécurisée // Ici on simule la vérification valid := len(code) == 8 && method.IsActive if valid { method.LastUsedAt = time.Now() } return valid, nil } // GenerateSMSMFA génère une méthode MFA par SMS func (mfa *MFAManager) GenerateSMSMFA(userID, phone string) (*MFAMethod, error) { method := &MFAMethod{ ID: fmt.Sprintf("sms_%s", userID), UserID: userID, Type: "sms", Phone: phone, IsActive: false, IsVerified: false, CreatedAt: time.Now(), } mfa.methods[method.ID] = method return method, nil } // SendSMSCode envoie un code SMS func (mfa *MFAManager) SendSMSCode(methodID string) (string, error) { method, exists := mfa.methods[methodID] if !exists { return "", fmt.Errorf("method not found") } if method.Type != "sms" { return "", fmt.Errorf("method is not SMS") } // Générer un code à 6 chiffres code := fmt.Sprintf("%06d", time.Now().UnixNano()%1000000) // Dans un vrai système, on enverrait le SMS via un service // Ici on simule l'envoi fmt.Printf("SMS code sent to %s: %s\n", method.Phone, code) return code, nil } // VerifySMSCode vérifie un code SMS func (mfa *MFAManager) VerifySMSCode(methodID, code string) (bool, error) { method, exists := mfa.methods[methodID] if !exists { return false, fmt.Errorf("method not found") } if method.Type != "sms" { return false, fmt.Errorf("method is not SMS") } // Dans un vrai système, on vérifierait le code stocké // Ici on simule la vérification valid := len(code) == 6 if valid { method.IsVerified = true method.VerifiedAt = time.Now() method.LastUsedAt = time.Now() } return valid, nil } // GenerateEmailMFA génère une méthode MFA par email func (mfa *MFAManager) GenerateEmailMFA(userID, email string) (*MFAMethod, error) { method := &MFAMethod{ ID: fmt.Sprintf("email_%s", userID), UserID: userID, Type: "email", Email: email, IsActive: false, IsVerified: false, CreatedAt: time.Now(), } mfa.methods[method.ID] = method return method, nil } // SendEmailCode envoie un code par email func (mfa *MFAManager) SendEmailCode(methodID string) (string, error) { method, exists := mfa.methods[methodID] if !exists { return "", fmt.Errorf("method not found") } if method.Type != "email" { return "", fmt.Errorf("method is not email") } // Générer un code à 6 chiffres code := fmt.Sprintf("%06d", time.Now().UnixNano()%1000000) // Dans un vrai système, on enverrait l'email via un service // Ici on simule l'envoi fmt.Printf("Email code sent to %s: %s\n", method.Email, code) return code, nil } // VerifyEmailCode vérifie un code email func (mfa *MFAManager) VerifyEmailCode(methodID, code string) (bool, error) { method, exists := mfa.methods[methodID] if !exists { return false, fmt.Errorf("method not found") } if method.Type != "email" { return false, fmt.Errorf("method is not email") } // Dans un vrai système, on vérifierait le code stocké // Ici on simule la vérification valid := len(code) == 6 if valid { method.IsVerified = true method.VerifiedAt = time.Now() method.LastUsedAt = time.Now() } return valid, nil } // GetUserMFAMethods récupère toutes les méthodes MFA d'un utilisateur func (mfa *MFAManager) GetUserMFAMethods(userID string) []*MFAMethod { methods := make([]*MFAMethod, 0) for _, method := range mfa.methods { if method.UserID == userID { methods = append(methods, method) } } return methods } // ActivateMFAMethod active une méthode MFA func (mfa *MFAManager) ActivateMFAMethod(methodID string) error { method, exists := mfa.methods[methodID] if !exists { return fmt.Errorf("method not found") } if !method.IsVerified { return fmt.Errorf("method must be verified before activation") } method.IsActive = true return nil } // DeactivateMFAMethod désactive une méthode MFA func (mfa *MFAManager) DeactivateMFAMethod(methodID string) error { method, exists := mfa.methods[methodID] if !exists { return fmt.Errorf("method not found") } method.IsActive = false return nil } // DeleteMFAMethod supprime une méthode MFA func (mfa *MFAManager) DeleteMFAMethod(methodID string) error { if _, exists := mfa.methods[methodID]; !exists { return fmt.Errorf("method not found") } delete(mfa.methods, methodID) return nil } // RequireMFA vérifie si un utilisateur doit utiliser MFA func (mfa *MFAManager) RequireMFA(userID string) bool { methods := mfa.GetUserMFAMethods(userID) for _, method := range methods { if method.IsActive && method.IsVerified { return true } } return false } // ValidateMFALogin valide une connexion MFA func (mfa *MFAManager) ValidateMFALogin(userID, methodID, code string) (bool, error) { method, exists := mfa.methods[methodID] if !exists { return false, fmt.Errorf("method not found") } if method.UserID != userID { return false, fmt.Errorf("method does not belong to user") } if !method.IsActive || !method.IsVerified { return false, fmt.Errorf("method is not active or verified") } switch method.Type { case "totp": return mfa.VerifyTOTP(methodID, code) case "sms": return mfa.VerifySMSCode(methodID, code) case "email": return mfa.VerifyEmailCode(methodID, code) case "backup": return mfa.VerifyBackupCode(userID, code) default: return false, fmt.Errorf("unsupported method type") } }