170 lines
4.1 KiB
Go
170 lines
4.1 KiB
Go
|
|
package hash
|
||
|
|
|
||
|
|
import (
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"github.com/stretchr/testify/assert"
|
||
|
|
"github.com/stretchr/testify/require"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestAPIKey(t *testing.T) {
|
||
|
|
key := "test_api_key_12345" // gitleaks:allow
|
||
|
|
|
||
|
|
hash1, err := APIKey(key)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.NotEmpty(t, hash1)
|
||
|
|
assert.NotEqual(t, key, hash1, "Hash should not equal plaintext")
|
||
|
|
|
||
|
|
// Bcrypt hashes should start with $2
|
||
|
|
assert.True(t, strings.HasPrefix(hash1, "$2"), "Should be a bcrypt hash")
|
||
|
|
|
||
|
|
// Same key should produce different hashes (due to salt)
|
||
|
|
hash2, err := APIKey(key)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.NotEqual(t, hash1, hash2, "Bcrypt should produce different hashes with different salts")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCompareAPIKey_Bcrypt(t *testing.T) {
|
||
|
|
key := "test_api_key_12345" // gitleaks:allow
|
||
|
|
|
||
|
|
hash, err := APIKey(key)
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
// Correct key should match
|
||
|
|
assert.True(t, CompareAPIKey(hash, key))
|
||
|
|
|
||
|
|
// Wrong key should not match
|
||
|
|
assert.False(t, CompareAPIKey(hash, "wrong_key"))
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCompareAPIKey_Legacy(t *testing.T) {
|
||
|
|
key := "test_api_key_12345" // gitleaks:allow
|
||
|
|
|
||
|
|
// Create a legacy SHA256 hash
|
||
|
|
legacyHash := String(key)
|
||
|
|
|
||
|
|
// Should still work with legacy hashes
|
||
|
|
assert.True(t, CompareAPIKey(legacyHash, key))
|
||
|
|
|
||
|
|
// Wrong key should not match
|
||
|
|
assert.False(t, CompareAPIKey(legacyHash, "wrong_key"))
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCompareAPIKey_BackwardCompatibility(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
hashFunc func(string) string
|
||
|
|
expectOK bool
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "bcrypt hash",
|
||
|
|
hashFunc: func(k string) string {
|
||
|
|
h, _ := APIKey(k)
|
||
|
|
return h
|
||
|
|
},
|
||
|
|
expectOK: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "legacy SHA256 hash",
|
||
|
|
hashFunc: func(k string) string {
|
||
|
|
return String(k)
|
||
|
|
},
|
||
|
|
expectOK: true,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
key := "test_key_123"
|
||
|
|
hash := tt.hashFunc(key)
|
||
|
|
|
||
|
|
result := CompareAPIKey(hash, key)
|
||
|
|
assert.Equal(t, tt.expectOK, result)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestString(t *testing.T) {
|
||
|
|
// Test that String function still works (for non-sensitive data)
|
||
|
|
input := "test_string"
|
||
|
|
hash1 := String(input)
|
||
|
|
hash2 := String(input)
|
||
|
|
|
||
|
|
// SHA256 should be deterministic
|
||
|
|
assert.Equal(t, hash1, hash2)
|
||
|
|
assert.NotEmpty(t, hash1)
|
||
|
|
assert.NotEqual(t, input, hash1)
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestIsLegacyHash(t *testing.T) {
|
||
|
|
tests := []struct {
|
||
|
|
name string
|
||
|
|
hash string
|
||
|
|
isLegacy bool
|
||
|
|
}{
|
||
|
|
{
|
||
|
|
name: "bcrypt hash",
|
||
|
|
hash: "$2a$12$abcdefghijklmnopqrstuv",
|
||
|
|
isLegacy: false,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "SHA256 hash",
|
||
|
|
hash: "dXNfYWtfMTIzNDU2Nzg5MDEyMzQ1NuOwxEKY",
|
||
|
|
isLegacy: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: "empty string",
|
||
|
|
hash: "",
|
||
|
|
isLegacy: true,
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, tt := range tests {
|
||
|
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
|
assert.Equal(t, tt.isLegacy, IsLegacyHash(tt.hash))
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestMigrateAPIKeyHash(t *testing.T) {
|
||
|
|
plainKey := "test_api_key_123"
|
||
|
|
|
||
|
|
t.Run("migrate legacy hash", func(t *testing.T) {
|
||
|
|
// Create a legacy SHA256 hash
|
||
|
|
legacyHash := String(plainKey)
|
||
|
|
|
||
|
|
// Migrate it
|
||
|
|
newHash, migrated, err := MigrateAPIKeyHash(legacyHash, plainKey)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.True(t, migrated, "Should indicate migration occurred")
|
||
|
|
assert.NotEqual(t, legacyHash, newHash, "New hash should differ from legacy")
|
||
|
|
assert.True(t, strings.HasPrefix(newHash, "$2"), "New hash should be bcrypt")
|
||
|
|
|
||
|
|
// Verify new hash works
|
||
|
|
assert.True(t, CompareAPIKey(newHash, plainKey))
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("no migration needed for bcrypt", func(t *testing.T) {
|
||
|
|
// Create a bcrypt hash
|
||
|
|
bcryptHash, err := APIKey(plainKey)
|
||
|
|
require.NoError(t, err)
|
||
|
|
|
||
|
|
// Try to migrate it
|
||
|
|
newHash, migrated, err := MigrateAPIKeyHash(bcryptHash, plainKey)
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.False(t, migrated, "Should not migrate bcrypt hash")
|
||
|
|
assert.Equal(t, bcryptHash, newHash, "Hash should remain unchanged")
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("invalid key does not migrate", func(t *testing.T) {
|
||
|
|
legacyHash := String("correct_key")
|
||
|
|
|
||
|
|
// Try to migrate with wrong plaintext
|
||
|
|
newHash, migrated, err := MigrateAPIKeyHash(legacyHash, "wrong_key")
|
||
|
|
require.NoError(t, err)
|
||
|
|
assert.False(t, migrated, "Should not migrate invalid key")
|
||
|
|
assert.Empty(t, newHash, "Should return empty for invalid key")
|
||
|
|
})
|
||
|
|
}
|