Add unit tests for auth helper functions (internal/api)
Tests for detectProxy, isConnectionSecure, getCookieSettings, and generateSessionToken functions. Covers proxy detection for various headers including X-Forwarded-For, CF-Ray, Forwarded (RFC 7239), and secure connection detection via TLS and proxy headers.
This commit is contained in:
parent
3378814a05
commit
554d44aba0
1 changed files with 316 additions and 0 deletions
316
internal/api/auth_helpers_test.go
Normal file
316
internal/api/auth_helpers_test.go
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDetectProxy(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
headers map[string]string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "no proxy headers",
|
||||
headers: map[string]string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-For present",
|
||||
headers: map[string]string{"X-Forwarded-For": "192.168.1.1"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "X-Real-IP present",
|
||||
headers: map[string]string{"X-Real-IP": "10.0.0.1"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-Proto present",
|
||||
headers: map[string]string{"X-Forwarded-Proto": "https"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-Host present",
|
||||
headers: map[string]string{"X-Forwarded-Host": "example.com"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Forwarded header present (RFC 7239)",
|
||||
headers: map[string]string{"Forwarded": "for=192.168.1.1;proto=https"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Cloudflare CF-Ray present",
|
||||
headers: map[string]string{"CF-Ray": "abc123"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Cloudflare CF-Connecting-IP present",
|
||||
headers: map[string]string{"CF-Connecting-IP": "1.2.3.4"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-Server present",
|
||||
headers: map[string]string{"X-Forwarded-Server": "proxy.example.com"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-Port present",
|
||||
headers: map[string]string{"X-Forwarded-Port": "443"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "multiple proxy headers",
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-For": "192.168.1.1",
|
||||
"X-Forwarded-Proto": "https",
|
||||
"CF-Ray": "abc123",
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "unrelated headers only",
|
||||
headers: map[string]string{"Content-Type": "application/json", "User-Agent": "test"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty X-Forwarded-For",
|
||||
headers: map[string]string{"X-Forwarded-For": ""},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
for key, value := range tt.headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
got := detectProxy(req)
|
||||
if got != tt.want {
|
||||
t.Errorf("detectProxy() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsConnectionSecure(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
useTLS bool
|
||||
headers map[string]string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "plain HTTP no headers",
|
||||
useTLS: false,
|
||||
headers: map[string]string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "TLS connection",
|
||||
useTLS: true,
|
||||
headers: map[string]string{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-Proto https",
|
||||
useTLS: false,
|
||||
headers: map[string]string{"X-Forwarded-Proto": "https"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-Proto http",
|
||||
useTLS: false,
|
||||
headers: map[string]string{"X-Forwarded-Proto": "http"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "X-Forwarded-Proto HTTPS uppercase",
|
||||
useTLS: false,
|
||||
headers: map[string]string{"X-Forwarded-Proto": "HTTPS"},
|
||||
want: false, // strict comparison
|
||||
},
|
||||
{
|
||||
name: "Forwarded header with proto=https",
|
||||
useTLS: false,
|
||||
headers: map[string]string{"Forwarded": "for=192.168.1.1;proto=https"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Forwarded header with proto=http",
|
||||
useTLS: false,
|
||||
headers: map[string]string{"Forwarded": "for=192.168.1.1;proto=http"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "TLS with X-Forwarded-Proto http (TLS takes precedence)",
|
||||
useTLS: true,
|
||||
headers: map[string]string{"X-Forwarded-Proto": "http"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "both X-Forwarded-Proto and Forwarded present",
|
||||
useTLS: false,
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-Proto": "https",
|
||||
"Forwarded": "proto=http",
|
||||
},
|
||||
want: true, // X-Forwarded-Proto is checked first
|
||||
},
|
||||
{
|
||||
name: "empty X-Forwarded-Proto",
|
||||
useTLS: false,
|
||||
headers: map[string]string{"X-Forwarded-Proto": ""},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
for key, value := range tt.headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
if tt.useTLS {
|
||||
req.TLS = &tls.ConnectionState{}
|
||||
}
|
||||
|
||||
got := isConnectionSecure(req)
|
||||
if got != tt.want {
|
||||
t.Errorf("isConnectionSecure() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCookieSettings(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
useTLS bool
|
||||
headers map[string]string
|
||||
wantSecure bool
|
||||
wantSameSite http.SameSite
|
||||
}{
|
||||
{
|
||||
name: "plain HTTP no proxy",
|
||||
useTLS: false,
|
||||
headers: map[string]string{},
|
||||
wantSecure: false,
|
||||
wantSameSite: http.SameSiteLaxMode,
|
||||
},
|
||||
{
|
||||
name: "HTTPS direct connection",
|
||||
useTLS: true,
|
||||
headers: map[string]string{},
|
||||
wantSecure: true,
|
||||
wantSameSite: http.SameSiteLaxMode,
|
||||
},
|
||||
{
|
||||
name: "HTTPS through proxy",
|
||||
useTLS: false,
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-For": "192.168.1.1",
|
||||
"X-Forwarded-Proto": "https",
|
||||
},
|
||||
wantSecure: true,
|
||||
wantSameSite: http.SameSiteNoneMode,
|
||||
},
|
||||
{
|
||||
name: "HTTP through proxy",
|
||||
useTLS: false,
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-For": "192.168.1.1",
|
||||
"X-Forwarded-Proto": "http",
|
||||
},
|
||||
wantSecure: false,
|
||||
wantSameSite: http.SameSiteLaxMode,
|
||||
},
|
||||
{
|
||||
name: "Cloudflare tunnel HTTPS",
|
||||
useTLS: false,
|
||||
headers: map[string]string{
|
||||
"CF-Ray": "abc123",
|
||||
"CF-Connecting-IP": "1.2.3.4",
|
||||
"X-Forwarded-Proto": "https",
|
||||
},
|
||||
wantSecure: true,
|
||||
wantSameSite: http.SameSiteNoneMode,
|
||||
},
|
||||
{
|
||||
name: "proxy detected but no proto header",
|
||||
useTLS: false,
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-For": "192.168.1.1",
|
||||
},
|
||||
wantSecure: false,
|
||||
wantSameSite: http.SameSiteLaxMode,
|
||||
},
|
||||
{
|
||||
name: "direct TLS with Forwarded header",
|
||||
useTLS: true,
|
||||
headers: map[string]string{
|
||||
"Forwarded": "for=192.168.1.1;proto=https",
|
||||
},
|
||||
wantSecure: true,
|
||||
wantSameSite: http.SameSiteNoneMode,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
for key, value := range tt.headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
if tt.useTLS {
|
||||
req.TLS = &tls.ConnectionState{}
|
||||
}
|
||||
|
||||
gotSecure, gotSameSite := getCookieSettings(req)
|
||||
if gotSecure != tt.wantSecure {
|
||||
t.Errorf("getCookieSettings() secure = %v, want %v", gotSecure, tt.wantSecure)
|
||||
}
|
||||
if gotSameSite != tt.wantSameSite {
|
||||
t.Errorf("getCookieSettings() sameSite = %v, want %v", gotSameSite, tt.wantSameSite)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateSessionToken(t *testing.T) {
|
||||
// Test that tokens are generated
|
||||
token := generateSessionToken()
|
||||
if token == "" {
|
||||
t.Error("generateSessionToken() returned empty string")
|
||||
}
|
||||
|
||||
// Test token length (32 bytes = 64 hex characters)
|
||||
if len(token) != 64 {
|
||||
t.Errorf("generateSessionToken() length = %d, want 64", len(token))
|
||||
}
|
||||
|
||||
// Test that tokens are unique
|
||||
tokens := make(map[string]bool)
|
||||
for i := 0; i < 100; i++ {
|
||||
tok := generateSessionToken()
|
||||
if tokens[tok] {
|
||||
t.Errorf("generateSessionToken() generated duplicate token: %s", tok)
|
||||
}
|
||||
tokens[tok] = true
|
||||
}
|
||||
|
||||
// Test that token is valid hex
|
||||
for _, c := range token {
|
||||
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
|
||||
t.Errorf("generateSessionToken() contains non-hex character: %c", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue