package common import ( "crypto/rand" "encoding/base64" "encoding/json" "testing" "github.com/sonr-io/common/webauthn" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // Test WebAuthn Challenge generation func TestNewChallenge(t *testing.T) { challenge, err := NewChallenge() require.NoError(t, err, "NewChallenge should not return an error") assert.NotEmpty(t, challenge, "Challenge should not be empty") // Decode to verify it's valid base64 decoded, err := base64.RawURLEncoding.DecodeString(challenge) require.NoError(t, err, "Challenge should be valid base64 URL encoding") assert.Len(t, decoded, 32, "Challenge should be 32 bytes when decoded") } func TestNewChallenge_Uniqueness(t *testing.T) { challenge1, err1 := NewChallenge() challenge2, err2 := NewChallenge() require.NoError(t, err1) require.NoError(t, err2) assert.NotEqual(t, challenge1, challenge2, "Two challenges should be unique") } // Test Challenge Length func TestChallengeLength(t *testing.T) { length := ChallengeLength() assert.Equal(t, 32, length, "Challenge length should be 32 bytes") } // Test VerifyOrigin func TestVerifyOrigin(t *testing.T) { tests := []struct { name string origin string allowedOrigins []string expectError bool }{ { name: "Valid origin", origin: "https://example.com", allowedOrigins: []string{"https://example.com", "https://test.com"}, expectError: false, }, { name: "Valid origin with port", origin: "https://example.com:8080", allowedOrigins: []string{"https://example.com:8080"}, expectError: false, }, { name: "Origin not in allowed list", origin: "https://malicious.com", allowedOrigins: []string{"https://example.com"}, expectError: true, }, { name: "Invalid origin format", origin: "not-a-url", allowedOrigins: []string{"https://example.com"}, expectError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := VerifyOrigin(tt.origin, tt.allowedOrigins) if tt.expectError { assert.Error(t, err) } else { assert.NoError(t, err) } }) } } // Test Base64 URL Encoding/Decoding func TestEncodeDecodeBase64URL(t *testing.T) { testData := []byte("Hello, WebAuthn!") // Encode encoded := EncodeBase64URL(testData) assert.NotEmpty(t, encoded, "Encoded data should not be empty") assert.NotContains(t, encoded, "=", "URL-safe base64 should not contain padding") // Decode decoded, err := DecodeBase64URL(encoded) require.NoError(t, err, "Decoding should not return an error") assert.Equal(t, testData, decoded, "Decoded data should match original") } func TestDecodeBase64URL_InvalidInput(t *testing.T) { invalidBase64 := "not valid base64!!!" _, err := DecodeBase64URL(invalidBase64) assert.Error(t, err, "Should return error for invalid base64") } // Test Credential Creation Marshal/Unmarshal func TestMarshalUnmarshalCredentialCreation(t *testing.T) { // Create a sample credential creation response original := &webauthn.CredentialCreationResponse{ PublicKeyCredential: webauthn.PublicKeyCredential{ Credential: webauthn.Credential{ ID: "test-credential-id", Type: "public-key", }, RawID: webauthn.URLEncodedBase64("raw-id-bytes"), }, } // Marshal data, err := MarshalCredentialCreation(original) require.NoError(t, err, "Marshaling should not return an error") assert.NotEmpty(t, data, "Marshaled data should not be empty") // Verify it's valid JSON var jsonCheck map[string]interface{} err = json.Unmarshal(data, &jsonCheck) require.NoError(t, err, "Marshaled data should be valid JSON") // Unmarshal unmarshaled, err := UnmarshalCredentialCreation(data) require.NoError(t, err, "Unmarshaling should not return an error") assert.Equal(t, original.ID, unmarshaled.ID, "Credential ID should match") assert.Equal(t, original.Type, unmarshaled.Type, "Credential type should match") } func TestUnmarshalCredentialCreation_InvalidJSON(t *testing.T) { invalidJSON := []byte("{invalid json") _, err := UnmarshalCredentialCreation(invalidJSON) assert.Error(t, err, "Should return error for invalid JSON") } // Test Credential Assertion Marshal/Unmarshal func TestMarshalUnmarshalCredentialAssertion(t *testing.T) { // Create a sample credential assertion response original := &webauthn.CredentialAssertionResponse{ PublicKeyCredential: webauthn.PublicKeyCredential{ Credential: webauthn.Credential{ ID: "test-assertion-id", Type: "public-key", }, RawID: webauthn.URLEncodedBase64("assertion-raw-id"), }, } // Marshal data, err := MarshalCredentialAssertion(original) require.NoError(t, err, "Marshaling should not return an error") assert.NotEmpty(t, data, "Marshaled data should not be empty") // Verify it's valid JSON var jsonCheck map[string]interface{} err = json.Unmarshal(data, &jsonCheck) require.NoError(t, err, "Marshaled data should be valid JSON") // Unmarshal unmarshaled, err := UnmarshalCredentialAssertion(data) require.NoError(t, err, "Unmarshaling should not return an error") assert.Equal(t, original.ID, unmarshaled.ID, "Assertion ID should match") assert.Equal(t, original.Type, unmarshaled.Type, "Assertion type should match") } func TestUnmarshalCredentialAssertion_InvalidJSON(t *testing.T) { invalidJSON := []byte("{invalid json") _, err := UnmarshalCredentialAssertion(invalidJSON) assert.Error(t, err, "Should return error for invalid JSON") } // Test ParseCredentialCreation with valid minimal data func TestParseCredentialCreation(t *testing.T) { // Create a minimal valid credential creation response challenge := make([]byte, 32) rand.Read(challenge) clientDataJSON := map[string]interface{}{ "type": "webauthn.create", "challenge": base64.RawURLEncoding.EncodeToString(challenge), "origin": "https://example.com", } clientDataBytes, _ := json.Marshal(clientDataJSON) // Create a minimal attestation object (this would normally come from an authenticator) // For testing, we'll create a structure that can be parsed credResponse := &webauthn.CredentialCreationResponse{ PublicKeyCredential: webauthn.PublicKeyCredential{ Credential: webauthn.Credential{ ID: base64.RawURLEncoding.EncodeToString([]byte("test-credential-id")), Type: "public-key", }, RawID: webauthn.URLEncodedBase64("test-credential-id"), }, AttestationResponse: webauthn.AuthenticatorAttestationResponse{ AuthenticatorResponse: webauthn.AuthenticatorResponse{ ClientDataJSON: clientDataBytes, }, // Note: In a real scenario, AttestationObject would need to be properly formatted }, } data, err := json.Marshal(credResponse) require.NoError(t, err) // This will fail because we don't have a valid attestation object, // but it tests that the function is wired up correctly _, err = ParseCredentialCreation(data) // We expect an error because the attestation object is not valid assert.Error(t, err, "Should return error for incomplete attestation data") } // Test ParseCredentialAssertion with invalid data func TestParseCredentialAssertion_InvalidData(t *testing.T) { invalidData := []byte(`{"id": "test", "type": "invalid"}`) _, err := ParseCredentialAssertion(invalidData) assert.Error(t, err, "Should return error for invalid assertion data") } // Benchmark tests func BenchmarkNewChallenge(b *testing.B) { for i := 0; i < b.N; i++ { _, _ = NewChallenge() } } func BenchmarkEncodeBase64URL(b *testing.B) { data := make([]byte, 32) rand.Read(data) b.ResetTimer() for i := 0; i < b.N; i++ { _ = EncodeBase64URL(data) } } func BenchmarkDecodeBase64URL(b *testing.B) { data := make([]byte, 32) rand.Read(data) encoded := EncodeBase64URL(data) b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = DecodeBase64URL(encoded) } } func BenchmarkMarshalCredentialCreation(b *testing.B) { cred := &webauthn.CredentialCreationResponse{ PublicKeyCredential: webauthn.PublicKeyCredential{ Credential: webauthn.Credential{ ID: "test-id", Type: "public-key", }, }, } b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = MarshalCredentialCreation(cred) } }