Skip to content

Commit 6d947b8

Browse files
Merge pull request #845 from Thushani-Jayasekera/api-key
Add support for registering/ revoking API Keys generated externally in gateways
2 parents a876e83 + 31630ff commit 6d947b8

File tree

47 files changed

+4716
-633
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+4716
-633
lines changed

common/apikey/api_key_hash_test.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/*
2+
* Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
package apikey
20+
21+
import (
22+
"crypto/rand"
23+
"encoding/base64"
24+
"fmt"
25+
"testing"
26+
"time"
27+
28+
"golang.org/x/crypto/argon2"
29+
)
30+
31+
func TestAPIKeyHashedValidation(t *testing.T) {
32+
store := NewAPIkeyStore()
33+
34+
// Create a plain text API key (69 bytes like real generated keys)
35+
plainAPIKey := "apip_88f8399ef29761f92f4f6d2dbd6dcd78399b3bcb8c53417cb23726e5780ac215"
36+
37+
// Hash the API key using Argon2id (simulating what the gateway controller does)
38+
salt := make([]byte, 16)
39+
_, err := rand.Read(salt)
40+
if err != nil {
41+
t.Fatalf("Failed to generate salt: %v", err)
42+
}
43+
44+
hash := argon2.IDKey([]byte(plainAPIKey), salt, 1, 64*1024, 4, 32)
45+
saltEncoded := base64.RawStdEncoding.EncodeToString(salt)
46+
hashEncoded := base64.RawStdEncoding.EncodeToString(hash)
47+
hashedAPIKey := fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s",
48+
64*1024, 1, 4, saltEncoded, hashEncoded)
49+
50+
// Create API key with hashed value
51+
apiKey := &APIKey{
52+
ID: "test-id-1",
53+
Name: "test-key",
54+
Source: "local",
55+
APIKey: hashedAPIKey, // Store hashed key
56+
APIId: "api-123",
57+
Operations: "[\"*\"]",
58+
Status: Active,
59+
CreatedAt: time.Now(),
60+
CreatedBy: "test-user",
61+
UpdatedAt: time.Now(),
62+
ExpiresAt: nil,
63+
}
64+
65+
// Store the API key
66+
err = store.StoreAPIKey("api-123", apiKey)
67+
if err != nil {
68+
t.Fatalf("Failed to store API key: %v", err)
69+
}
70+
71+
// Test validation with correct plain text key
72+
valid, err := store.ValidateAPIKey("api-123", "/test", "GET", plainAPIKey)
73+
if err != nil {
74+
t.Fatalf("Validation failed with error: %v", err)
75+
}
76+
if !valid {
77+
t.Error("Validation should succeed with correct plain text API key")
78+
}
79+
}
80+
81+
func TestAPIKeyHashedValidationFailures(t *testing.T) {
82+
store := NewAPIkeyStore()
83+
84+
plainAPIKey := "apip_88f8399ef29761f92f4f6d2dbd6dcd78399b3bcb8c53417cb23726e5780ac215"
85+
86+
// Hash the API key using Argon2id
87+
salt := make([]byte, 16)
88+
_, err := rand.Read(salt)
89+
if err != nil {
90+
t.Fatalf("Failed to generate salt: %v", err)
91+
}
92+
93+
hash := argon2.IDKey([]byte(plainAPIKey), salt, 1, 64*1024, 4, 32)
94+
saltEncoded := base64.RawStdEncoding.EncodeToString(salt)
95+
hashEncoded := base64.RawStdEncoding.EncodeToString(hash)
96+
hashedAPIKey := fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s",
97+
64*1024, 1, 4, saltEncoded, hashEncoded)
98+
99+
apiKey := &APIKey{
100+
ID: "test-id-2",
101+
Name: "test-key-2",
102+
APIKey: hashedAPIKey,
103+
Source: "local",
104+
APIId: "api-456",
105+
Operations: "[\"*\"]",
106+
Status: Active,
107+
CreatedAt: time.Now(),
108+
CreatedBy: "test-user",
109+
UpdatedAt: time.Now(),
110+
ExpiresAt: nil,
111+
}
112+
113+
err = store.StoreAPIKey("api-456", apiKey)
114+
if err != nil {
115+
t.Fatalf("Failed to store API key: %v", err)
116+
}
117+
118+
// Test validation with wrong plain text key
119+
wrongKey := "apip_wrong399ef29761f92f4f6d2dbd6dcd78399b3bcb8c53417cb23726e5780ac999"
120+
valid, err := store.ValidateAPIKey("api-456", "/test", "GET", wrongKey)
121+
if err != nil {
122+
if err != ErrNotFound {
123+
t.Fatalf("Expected ErrNotFound, got: %v", err)
124+
}
125+
}
126+
if valid {
127+
t.Error("Validation should fail with incorrect plain text API key")
128+
}
129+
130+
// Test validation with non-existent API
131+
valid, err = store.ValidateAPIKey("non-existent-api", "/test", "GET", plainAPIKey)
132+
if err == nil {
133+
t.Error("Expected error for non-existent API")
134+
}
135+
if valid {
136+
t.Error("Validation should fail for non-existent API")
137+
}
138+
}
139+
140+
func TestAPIKeyHashedRevocation(t *testing.T) {
141+
store := NewAPIkeyStore()
142+
143+
plainAPIKey := "apip_revoke399ef29761f92f4f6d2dbd6dcd78399b3bcb8c53417cb23726e5780ac215"
144+
145+
// Hash the API key using Argon2id
146+
salt := make([]byte, 16)
147+
_, err := rand.Read(salt)
148+
if err != nil {
149+
t.Fatalf("Failed to generate salt: %v", err)
150+
}
151+
152+
hash := argon2.IDKey([]byte(plainAPIKey), salt, 1, 64*1024, 4, 32)
153+
saltEncoded := base64.RawStdEncoding.EncodeToString(salt)
154+
hashEncoded := base64.RawStdEncoding.EncodeToString(hash)
155+
hashedAPIKey := fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s",
156+
64*1024, 1, 4, saltEncoded, hashEncoded)
157+
158+
apiKey := &APIKey{
159+
ID: "test-id-3",
160+
Name: "revoke-test-key",
161+
APIKey: hashedAPIKey,
162+
Source: "local",
163+
APIId: "api-789",
164+
Operations: "[\"*\"]",
165+
Status: Active,
166+
CreatedAt: time.Now(),
167+
CreatedBy: "test-user",
168+
UpdatedAt: time.Now(),
169+
ExpiresAt: nil,
170+
}
171+
172+
err = store.StoreAPIKey("api-789", apiKey)
173+
if err != nil {
174+
t.Fatalf("Failed to store API key: %v", err)
175+
}
176+
177+
// Verify key works before revocation
178+
valid, err := store.ValidateAPIKey("api-789", "/test", "GET", plainAPIKey)
179+
if err != nil {
180+
t.Fatalf("Validation failed before revocation: %v", err)
181+
}
182+
if !valid {
183+
t.Error("API key should be valid before revocation")
184+
}
185+
186+
// Revoke the API key using plain text key
187+
err = store.RevokeAPIKey("api-789", plainAPIKey)
188+
if err != nil {
189+
t.Fatalf("Failed to revoke API key: %v", err)
190+
}
191+
192+
// Verify key no longer works after revocation
193+
valid, err = store.ValidateAPIKey("api-789", "/test", "GET", plainAPIKey)
194+
if err != nil && err != ErrNotFound {
195+
t.Fatalf("Unexpected error during validation after revocation: %v", err)
196+
}
197+
if valid {
198+
t.Error("API key should be invalid after revocation")
199+
}
200+
}

0 commit comments

Comments
 (0)