Files
argoyle 4468903535 feat(cache): implement hashed API key storage and retrieval
Adds a new hashed key storage mechanism for API keys in the cache. 
Replaces direct mapping to API keys with composite keys based on 
organizationId and name. Implements searching of API keys using 
hash comparisons for improved security. Updates related tests to 
ensure correct functionality and validate the hashing. Also, 
adds support for a new dependency `golang.org/x/crypto`.
2025-11-20 22:11:24 +01:00

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")
})
}