Skip to content

Commit a2fc56e

Browse files
committed
feat: add apikey middleware
1 parent 711c622 commit a2fc56e

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

pkg/middleware/apikey/apikey.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package apikey
2+
3+
import (
4+
"net/http"
5+
"slices"
6+
)
7+
8+
func Middleware(keys ...string) func(http.Handler) http.Handler {
9+
return func(next http.Handler) http.Handler {
10+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
11+
if len(keys) > 0 {
12+
xApiKey := r.Header.Get("x-api-key")
13+
if xApiKey == "" || !slices.Contains(keys, xApiKey) {
14+
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
15+
return
16+
}
17+
}
18+
next.ServeHTTP(w, r)
19+
})
20+
}
21+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package apikey
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httptest"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
// ------------------------------------------------------------------------------------------------
13+
// RECOVERER TESTS
14+
// ------------------------------------------------------------------------------------------------
15+
16+
const (
17+
testAddress string = "localhost:8123"
18+
)
19+
20+
// Test_Recoverer checks that a panic thrown within a request can be recovered from, and then
21+
// return an appropriate error.
22+
func Test_ApiKey(t *testing.T) {
23+
testPattern := "/test"
24+
keys := []string{
25+
"apikey",
26+
"apikey2",
27+
}
28+
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
29+
w.WriteHeader(http.StatusOK)
30+
})
31+
32+
tests := []struct {
33+
name string
34+
expectedCode int
35+
setApiKey bool
36+
apiKey string
37+
handler http.Handler
38+
}{
39+
{
40+
"a 200 response is returned when a valid x-api-key header is sent",
41+
http.StatusOK,
42+
true,
43+
"apikey",
44+
handler,
45+
},
46+
{
47+
"a 200 response is returned when a different valid x-api-key header is sent",
48+
http.StatusOK,
49+
true,
50+
"apikey2",
51+
handler,
52+
},
53+
{
54+
"a 401 response is returned when an invalid x-api-key header is sent",
55+
http.StatusUnauthorized,
56+
true,
57+
"invalid",
58+
handler,
59+
},
60+
{
61+
"a 401 response is returned when an empty x-api-key header is sent",
62+
http.StatusUnauthorized,
63+
true,
64+
"",
65+
handler,
66+
},
67+
{
68+
"a 401 response is returned when no x-api-key header is sent",
69+
http.StatusUnauthorized,
70+
false,
71+
"",
72+
handler,
73+
},
74+
}
75+
76+
for _, test := range tests {
77+
t.Run(test.name, func(t *testing.T) {
78+
h := Middleware(keys...)(test.handler)
79+
80+
url := fmt.Sprintf("http://%s%s", testAddress, testPattern)
81+
req, err := http.NewRequest(http.MethodGet, url, nil)
82+
if err != nil {
83+
t.Errorf("failed to create request: %v", err)
84+
}
85+
86+
if test.setApiKey {
87+
req.Header.Set("x-api-key", test.apiKey)
88+
}
89+
90+
w := httptest.NewRecorder()
91+
h.ServeHTTP(w, req)
92+
res := w.Result()
93+
defer func() {
94+
err := res.Body.Close()
95+
if err != nil {
96+
t.Fatalf("failed to close response body: %v", err)
97+
}
98+
}()
99+
100+
assert.Equal(t, test.expectedCode, res.StatusCode, "they should be equal")
101+
})
102+
}
103+
}

0 commit comments

Comments
 (0)