mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2026-02-01 15:34:25 +00:00
* Implement TwoFactorAuthenticator for 2FA login. * Create DatabaseTwoFactorProvider for PostgreSQL integration. * Add MemoryTwoFactorProvider for in-memory testing. * Develop TOTPGenerator for generating and validating codes. * Include tests for all new functionalities. * Ensure backup codes are securely hashed and validated.
219 lines
5.2 KiB
Go
219 lines
5.2 KiB
Go
package security_test
|
|
|
|
import (
|
|
"database/sql"
|
|
"testing"
|
|
|
|
"github.com/bitechdev/ResolveSpec/pkg/security"
|
|
)
|
|
|
|
// Note: These tests require a PostgreSQL database with the schema from totp_database_schema.sql
|
|
// Set TEST_DATABASE_URL environment variable or skip tests
|
|
|
|
func setupTestDB(t *testing.T) *sql.DB {
|
|
// Skip if no test database configured
|
|
t.Skip("Database tests require TEST_DATABASE_URL environment variable")
|
|
return nil
|
|
}
|
|
|
|
func TestDatabaseTwoFactorProvider_Enable2FA(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
if db == nil {
|
|
return
|
|
}
|
|
defer db.Close()
|
|
|
|
provider := security.NewDatabaseTwoFactorProvider(db, nil)
|
|
|
|
// Generate secret and backup codes
|
|
secret, err := provider.Generate2FASecret(1, "TestApp", "test@example.com")
|
|
if err != nil {
|
|
t.Fatalf("Generate2FASecret() error = %v", err)
|
|
}
|
|
|
|
// Enable 2FA
|
|
err = provider.Enable2FA(1, secret.Secret, secret.BackupCodes)
|
|
if err != nil {
|
|
t.Errorf("Enable2FA() error = %v", err)
|
|
}
|
|
|
|
// Verify enabled
|
|
enabled, err := provider.Get2FAStatus(1)
|
|
if err != nil {
|
|
t.Fatalf("Get2FAStatus() error = %v", err)
|
|
}
|
|
|
|
if !enabled {
|
|
t.Error("Get2FAStatus() = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestDatabaseTwoFactorProvider_Disable2FA(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
if db == nil {
|
|
return
|
|
}
|
|
defer db.Close()
|
|
|
|
provider := security.NewDatabaseTwoFactorProvider(db, nil)
|
|
|
|
// Enable first
|
|
secret, _ := provider.Generate2FASecret(1, "TestApp", "test@example.com")
|
|
provider.Enable2FA(1, secret.Secret, secret.BackupCodes)
|
|
|
|
// Disable
|
|
err := provider.Disable2FA(1)
|
|
if err != nil {
|
|
t.Errorf("Disable2FA() error = %v", err)
|
|
}
|
|
|
|
// Verify disabled
|
|
enabled, err := provider.Get2FAStatus(1)
|
|
if err != nil {
|
|
t.Fatalf("Get2FAStatus() error = %v", err)
|
|
}
|
|
|
|
if enabled {
|
|
t.Error("Get2FAStatus() = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestDatabaseTwoFactorProvider_GetSecret(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
if db == nil {
|
|
return
|
|
}
|
|
defer db.Close()
|
|
|
|
provider := security.NewDatabaseTwoFactorProvider(db, nil)
|
|
|
|
// Enable 2FA
|
|
secret, _ := provider.Generate2FASecret(1, "TestApp", "test@example.com")
|
|
provider.Enable2FA(1, secret.Secret, secret.BackupCodes)
|
|
|
|
// Retrieve secret
|
|
retrieved, err := provider.Get2FASecret(1)
|
|
if err != nil {
|
|
t.Errorf("Get2FASecret() error = %v", err)
|
|
}
|
|
|
|
if retrieved != secret.Secret {
|
|
t.Errorf("Get2FASecret() = %v, want %v", retrieved, secret.Secret)
|
|
}
|
|
}
|
|
|
|
func TestDatabaseTwoFactorProvider_ValidateBackupCode(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
if db == nil {
|
|
return
|
|
}
|
|
defer db.Close()
|
|
|
|
provider := security.NewDatabaseTwoFactorProvider(db, nil)
|
|
|
|
// Enable 2FA
|
|
secret, _ := provider.Generate2FASecret(1, "TestApp", "test@example.com")
|
|
provider.Enable2FA(1, secret.Secret, secret.BackupCodes)
|
|
|
|
// Validate backup code
|
|
valid, err := provider.ValidateBackupCode(1, secret.BackupCodes[0])
|
|
if err != nil {
|
|
t.Errorf("ValidateBackupCode() error = %v", err)
|
|
}
|
|
|
|
if !valid {
|
|
t.Error("ValidateBackupCode() = false, want true")
|
|
}
|
|
|
|
// Try to use same code again
|
|
valid, err = provider.ValidateBackupCode(1, secret.BackupCodes[0])
|
|
if err == nil {
|
|
t.Error("ValidateBackupCode() should error on reuse")
|
|
}
|
|
|
|
// Try invalid code
|
|
valid, err = provider.ValidateBackupCode(1, "INVALID")
|
|
if err != nil {
|
|
t.Errorf("ValidateBackupCode() error = %v", err)
|
|
}
|
|
|
|
if valid {
|
|
t.Error("ValidateBackupCode() = true for invalid code")
|
|
}
|
|
}
|
|
|
|
func TestDatabaseTwoFactorProvider_RegenerateBackupCodes(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
if db == nil {
|
|
return
|
|
}
|
|
defer db.Close()
|
|
|
|
provider := security.NewDatabaseTwoFactorProvider(db, nil)
|
|
|
|
// Enable 2FA
|
|
secret, _ := provider.Generate2FASecret(1, "TestApp", "test@example.com")
|
|
provider.Enable2FA(1, secret.Secret, secret.BackupCodes)
|
|
|
|
// Regenerate codes
|
|
newCodes, err := provider.GenerateBackupCodes(1, 10)
|
|
if err != nil {
|
|
t.Errorf("GenerateBackupCodes() error = %v", err)
|
|
}
|
|
|
|
if len(newCodes) != 10 {
|
|
t.Errorf("GenerateBackupCodes() returned %d codes, want 10", len(newCodes))
|
|
}
|
|
|
|
// Old codes should not work
|
|
valid, _ := provider.ValidateBackupCode(1, secret.BackupCodes[0])
|
|
if valid {
|
|
t.Error("Old backup code should not work after regeneration")
|
|
}
|
|
|
|
// New codes should work
|
|
valid, err = provider.ValidateBackupCode(1, newCodes[0])
|
|
if err != nil {
|
|
t.Errorf("ValidateBackupCode() error = %v", err)
|
|
}
|
|
|
|
if !valid {
|
|
t.Error("ValidateBackupCode() = false for new code")
|
|
}
|
|
}
|
|
|
|
func TestDatabaseTwoFactorProvider_Generate2FASecret(t *testing.T) {
|
|
db := setupTestDB(t)
|
|
if db == nil {
|
|
return
|
|
}
|
|
defer db.Close()
|
|
|
|
provider := security.NewDatabaseTwoFactorProvider(db, nil)
|
|
|
|
secret, err := provider.Generate2FASecret(1, "TestApp", "test@example.com")
|
|
if err != nil {
|
|
t.Fatalf("Generate2FASecret() error = %v", err)
|
|
}
|
|
|
|
if secret.Secret == "" {
|
|
t.Error("Generate2FASecret() returned empty secret")
|
|
}
|
|
|
|
if secret.QRCodeURL == "" {
|
|
t.Error("Generate2FASecret() returned empty QR code URL")
|
|
}
|
|
|
|
if len(secret.BackupCodes) != 10 {
|
|
t.Errorf("Generate2FASecret() returned %d backup codes, want 10", len(secret.BackupCodes))
|
|
}
|
|
|
|
if secret.Issuer != "TestApp" {
|
|
t.Errorf("Generate2FASecret() Issuer = %v, want TestApp", secret.Issuer)
|
|
}
|
|
|
|
if secret.AccountName != "test@example.com" {
|
|
t.Errorf("Generate2FASecret() AccountName = %v, want test@example.com", secret.AccountName)
|
|
}
|
|
}
|