mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Server: - Add Let's Encrypt (ACME) TLS mode with autocert, HTTP-01 challenges on :80, and automatic certificate renewal (tls.mode: "acme" in config.yaml) - Add ASCII art startup banner with server info and endpoint URLs - Fix CSP blocking admin panel inline styles/scripts (per-route override) - Suppress TLS handshake error noise in console output - Fix TOCTOU race in invite consumption (atomic UPDATE with row-count check) - Fix sendMsg mutex race condition (hold lock for entire send) - Fix permission override formula (deny-first, allow-wins) - Fix voice join parsing channelID before permission check - Add session expiry check at WebSocket auth and periodic revalidation - Add message length limit (4000 chars) and emoji length validation (32 bytes) - Add file size enforcement in storage after io.Copy - Add checksum URL validation in updater - Add backup path traversal protection (BackupToSafe) - Add self-modification guard in admin handlePatchUser - Fix admin ownerOnlyMiddleware to use context user instead of re-auth - Remove redundant startup log lines (banner shows same info) - Add periodic expired session cleanup (15-min ticker) - Add permissions package with bitfield constants and EffectivePerms - Add rate limiter cleanup goroutine to prevent unbounded growth - Add auth helpers (IsEffectivelyBanned, IsSessionExpired) - Add WebSocket origin validation Client: - Add TOFU certificate trust service - Add receive loop error handling - Fix redundant else-if in OnChatMessage
166 lines
5.1 KiB
Go
166 lines
5.1 KiB
Go
package api
|
|
|
|
// White-box tests for clientIP and isTrustedProxy.
|
|
// These live in package api (not api_test) so they can reach unexported symbols.
|
|
|
|
import (
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
// ─── isTrustedProxy ───────────────────────────────────────────────────────────
|
|
|
|
func TestIsTrustedProxy_EmptyList_ReturnsFalse(t *testing.T) {
|
|
trusted, err := isTrustedProxy("10.0.0.1", nil)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if trusted {
|
|
t.Error("isTrustedProxy(empty list) = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestIsTrustedProxy_ExactIPMatch(t *testing.T) {
|
|
trusted, err := isTrustedProxy("10.0.0.1", []string{"10.0.0.1/32"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !trusted {
|
|
t.Error("isTrustedProxy exact match = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestIsTrustedProxy_CIDRMatch(t *testing.T) {
|
|
trusted, err := isTrustedProxy("192.168.1.50", []string{"192.168.1.0/24"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !trusted {
|
|
t.Error("isTrustedProxy CIDR match = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestIsTrustedProxy_CIDRNoMatch(t *testing.T) {
|
|
trusted, err := isTrustedProxy("10.9.9.9", []string{"192.168.1.0/24"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if trusted {
|
|
t.Error("isTrustedProxy CIDR non-match = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestIsTrustedProxy_MultipleCIDRs_FirstMatches(t *testing.T) {
|
|
trusted, err := isTrustedProxy("10.0.0.5", []string{"172.16.0.0/12", "10.0.0.0/8"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !trusted {
|
|
t.Error("isTrustedProxy multi-CIDR first match = false, want true")
|
|
}
|
|
}
|
|
|
|
func TestIsTrustedProxy_MultipleCIDRs_NoneMatch(t *testing.T) {
|
|
trusted, err := isTrustedProxy("8.8.8.8", []string{"10.0.0.0/8", "192.168.0.0/16"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if trusted {
|
|
t.Error("isTrustedProxy multi-CIDR no match = true, want false")
|
|
}
|
|
}
|
|
|
|
func TestIsTrustedProxy_InvalidCIDR_ReturnsError(t *testing.T) {
|
|
_, err := isTrustedProxy("10.0.0.1", []string{"not-a-cidr"})
|
|
if err == nil {
|
|
t.Error("isTrustedProxy invalid CIDR should return error, got nil")
|
|
}
|
|
}
|
|
|
|
func TestIsTrustedProxy_BarePlainIP_TreatedAsCIDR32(t *testing.T) {
|
|
// Bare IP without mask — should not panic; behaviour is to return error or
|
|
// treat as /32 depending on implementation. We just verify it doesn't panic.
|
|
_, _ = isTrustedProxy("10.0.0.1", []string{"10.0.0.1"})
|
|
}
|
|
|
|
func TestIsTrustedProxy_IPv6Match(t *testing.T) {
|
|
trusted, err := isTrustedProxy("::1", []string{"::1/128"})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !trusted {
|
|
t.Error("isTrustedProxy IPv6 exact match = false, want true")
|
|
}
|
|
}
|
|
|
|
// ─── clientIP with trusted proxies ───────────────────────────────────────────
|
|
|
|
func TestClientIP_NoTrustedProxies_UsesRemoteAddr(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.RemoteAddr = "203.0.113.5:4321"
|
|
req.Header.Set("X-Real-IP", "1.2.3.4")
|
|
req.Header.Set("X-Forwarded-For", "1.2.3.4")
|
|
|
|
ip := clientIPWithProxies(req, nil)
|
|
if ip != "203.0.113.5" {
|
|
t.Errorf("clientIP no trusted proxies = %q, want %q", ip, "203.0.113.5")
|
|
}
|
|
}
|
|
|
|
func TestClientIP_TrustedProxy_UsesXRealIP(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.RemoteAddr = "10.0.0.1:9999"
|
|
req.Header.Set("X-Real-IP", "203.0.113.42")
|
|
|
|
ip := clientIPWithProxies(req, []string{"10.0.0.0/8"})
|
|
if ip != "203.0.113.42" {
|
|
t.Errorf("clientIP trusted proxy = %q, want %q", ip, "203.0.113.42")
|
|
}
|
|
}
|
|
|
|
func TestClientIP_TrustedProxy_NoXRealIP_FallsBackToRemoteAddr(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.RemoteAddr = "10.0.0.1:9999"
|
|
// No X-Real-IP header set.
|
|
|
|
ip := clientIPWithProxies(req, []string{"10.0.0.0/8"})
|
|
if ip != "10.0.0.1" {
|
|
t.Errorf("clientIP trusted proxy no header = %q, want %q", ip, "10.0.0.1")
|
|
}
|
|
}
|
|
|
|
func TestClientIP_UntrustedSource_IgnoresXRealIP(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.RemoteAddr = "8.8.8.8:12345"
|
|
req.Header.Set("X-Real-IP", "192.168.1.1") // attacker-supplied
|
|
|
|
ip := clientIPWithProxies(req, []string{"10.0.0.0/8"})
|
|
// Must use RemoteAddr, not the forged X-Real-IP.
|
|
if ip != "8.8.8.8" {
|
|
t.Errorf("clientIP untrusted source = %q, want %q", ip, "8.8.8.8")
|
|
}
|
|
}
|
|
|
|
func TestClientIP_XForwardedFor_UsedWhenNoXRealIP(t *testing.T) {
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.RemoteAddr = "10.0.0.1:9999"
|
|
req.Header.Set("X-Forwarded-For", "203.0.113.10, 10.0.0.1")
|
|
// No X-Real-IP; X-Forwarded-For first entry should be used.
|
|
|
|
ip := clientIPWithProxies(req, []string{"10.0.0.0/8"})
|
|
if ip != "203.0.113.10" {
|
|
t.Errorf("clientIP X-Forwarded-For = %q, want %q", ip, "203.0.113.10")
|
|
}
|
|
}
|
|
|
|
func TestClientIP_RemoteAddrWithoutPort(t *testing.T) {
|
|
// RemoteAddr sometimes has no port (e.g. Unix sockets in tests).
|
|
req := httptest.NewRequest("GET", "/", nil)
|
|
req.RemoteAddr = "10.0.0.1"
|
|
|
|
ip := clientIPWithProxies(req, nil)
|
|
if ip != "10.0.0.1" {
|
|
t.Errorf("clientIP no port = %q, want %q", ip, "10.0.0.1")
|
|
}
|
|
}
|