feat: enhance auth, add input validation and pagination
- Add IP validation in auth handler with detailed logging - Implement apiKeyMiddleware helper for API key verification - Add IP and CIDR validation for whitelist operations - Support pagination for listing temp and permanent entries - Add parsePagination helper to extract limit and offset - Include comprehensive unit tests for IsIP, IsCIDR, and IPMatch
This commit is contained in:
163
auth_test.go
Normal file
163
auth_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantOK bool
|
||||
}{
|
||||
{"ipv4", "192.168.1.1", true},
|
||||
{"ipv4 loopback", "127.0.0.1", true},
|
||||
{"ipv6", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", true},
|
||||
{"ipv6 short", "::1", true},
|
||||
{"empty", "", false},
|
||||
{"hostname", "example.com", false},
|
||||
{"garbage", "not-an-ip!!", false},
|
||||
{"partial", "192.168", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := IsIP(tt.input); got != tt.wantOK {
|
||||
t.Errorf("IsIP(%q) = %v, want %v", tt.input, got, tt.wantOK)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCIDR(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantOK bool
|
||||
}{
|
||||
{"ipv4 cidr", "192.168.1.0/24", true},
|
||||
{"ipv6 cidr", "2001:db8::/32", true},
|
||||
{"single host", "10.0.0.0/32", true},
|
||||
{"plain ip", "10.0.0.1", false},
|
||||
{"missing prefix", "192.168.1.0", false},
|
||||
{"garbage", "foo/bar", false},
|
||||
{"empty", "", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := IsCIDR(tt.input); got != tt.wantOK {
|
||||
t.Errorf("IsCIDR(%q) = %v, want %v", tt.input, got, tt.wantOK)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPMatchesEntry(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
entry string
|
||||
wantOK bool
|
||||
wantErr bool
|
||||
}{
|
||||
{"exact match", "192.168.1.5", "192.168.1.5", true, false},
|
||||
{"ipv4 cidr match", "10.0.0.5", "10.0.0.0/24", true, false},
|
||||
{"ipv4 cidr no match", "10.0.1.5", "10.0.0.0/24", false, false},
|
||||
{"ipv6 cidr match", "2001:db8::1", "2001:db8::/32", true, false},
|
||||
{"invalid entry", "10.0.0.1", "not-a-cidr", false, false},
|
||||
{"empty entry", "10.0.0.1", "", false, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ipMatchesEntry(tt.ip, tt.entry)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("ipMatchesEntry(%q, %q) = %v, want error", tt.ip, tt.entry, got)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("ipMatchesEntry(%q, %q) error: %v", tt.ip, tt.entry, err)
|
||||
}
|
||||
if got != tt.wantOK {
|
||||
t.Errorf("ipMatchesEntry(%q, %q) = %v, want %v", tt.ip, tt.entry, got, tt.wantOK)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyAPIKey(t *testing.T) {
|
||||
token := "super-secret"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
auth string
|
||||
wantOK bool
|
||||
}{
|
||||
{"correct token", "Bearer super-secret", true},
|
||||
{"missing Bearer", "super-secret", false},
|
||||
{"wrong token", "Bearer wrong", false},
|
||||
{"empty", "", false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := http.Request{Header: http.Header{}}
|
||||
r.Header.Set("Authorization", tt.auth)
|
||||
if got := verifyAPIKey(&r, token); got != tt.wantOK {
|
||||
t.Errorf("verifyAPIKey(%q) = %v, want %v", tt.auth, got, tt.wantOK)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiKeyMiddleware(t *testing.T) {
|
||||
token := "test-token"
|
||||
called := false
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
called = true
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
wrapped := apiKeyMiddleware(next, token)
|
||||
|
||||
t.Run("valid key", func(t *testing.T) {
|
||||
called = false
|
||||
r := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
r.Header.Set("Authorization", "Bearer test-token")
|
||||
w := httptest.NewRecorder()
|
||||
wrapped(w, r)
|
||||
if !called {
|
||||
t.Error("middleware did not call next handler")
|
||||
}
|
||||
if w.Code != http.StatusNoContent {
|
||||
t.Errorf("expected 204, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid key", func(t *testing.T) {
|
||||
called = false
|
||||
r := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
r.Header.Set("Authorization", "Bearer wrong")
|
||||
w := httptest.NewRecorder()
|
||||
wrapped(w, r)
|
||||
if called {
|
||||
t.Error("middleware called next handler despite invalid key")
|
||||
}
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Errorf("expected 401, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing key", func(t *testing.T) {
|
||||
called = false
|
||||
r := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
w := httptest.NewRecorder()
|
||||
wrapped(w, r)
|
||||
if called {
|
||||
t.Error("middleware called next handler despite missing key")
|
||||
}
|
||||
if w.Code != http.StatusUnauthorized {
|
||||
t.Errorf("expected 401, got %d", w.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user