summaryrefslogtreecommitdiff
path: root/internal/api/middleware.go
diff options
context:
space:
mode:
authorsergei <sergei@em-sysadmin.xyz>2026-04-14 06:23:55 +0400
committersergei <sergei@em-sysadmin.xyz>2026-04-14 06:23:55 +0400
commit3d51aa455006903345f554a2dd90034993796114 (patch)
tree62a7be2faf047f5eb7886feebc3b815556f03d7f /internal/api/middleware.go
downloadvpnem-3d51aa455006903345f554a2dd90034993796114.tar.gz
vpnem-3d51aa455006903345f554a2dd90034993796114.tar.bz2
vpnem-3d51aa455006903345f554a2dd90034993796114.zip
vpnem: VPN infrastructure with load-balanced multi-protocol nodesHEADmain
- Multi-protocol VPS nodes (VLESS-REALITY + Hysteria2 + SOCKS5) - Smart load balancing via recommendation API - Windows/Linux client (Go + Wails + sing-box) - Server API with RealIP detection and connection tracking - Auto-deployment via vpnui control plane - Silent Windows installer with UAC elevation - Load-based server recommendation (no sticky sessions) - Best Server one-click connection workflow
Diffstat (limited to 'internal/api/middleware.go')
-rw-r--r--internal/api/middleware.go70
1 files changed, 70 insertions, 0 deletions
diff --git a/internal/api/middleware.go b/internal/api/middleware.go
new file mode 100644
index 0000000..76885ac
--- /dev/null
+++ b/internal/api/middleware.go
@@ -0,0 +1,70 @@
+package api
+
+import (
+ "context"
+ "net"
+ "net/http"
+ "strings"
+)
+
+// contextKey for real IP.
+type contextKey string
+
+const ctxRealIP contextKey = "real_ip"
+
+// RealIP middleware extracts the client's real public IP.
+// Priority: X-Forwarded-For (from Traefik) > X-Real-IP > RemoteAddr.
+func RealIP(next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ ip := extractRealIP(r)
+ if ip != "" {
+ r = r.WithContext(context.WithValue(r.Context(), ctxRealIP, ip))
+ }
+ next(w, r)
+ }
+}
+
+// GetRealIP returns the client IP from context.
+func GetRealIP(r *http.Request) string {
+ if ip, ok := r.Context().Value(ctxRealIP).(string); ok {
+ return ip
+ }
+ return ""
+}
+
+func extractRealIP(r *http.Request) string {
+ // 1. X-Forwarded-For (Traefik, nginx, etc.)
+ if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
+ // Can contain multiple IPs: client, proxy1, proxy2
+ // First one is the original client
+ parts := strings.Split(xff, ",")
+ if len(parts) > 0 {
+ ip := strings.TrimSpace(parts[0])
+ if isValidIP(ip) {
+ return ip
+ }
+ }
+ }
+
+ // 2. X-Real-IP (some proxies use this)
+ if xri := r.Header.Get("X-Real-IP"); xri != "" {
+ ip := strings.TrimSpace(xri)
+ if isValidIP(ip) {
+ return ip
+ }
+ }
+
+ // 3. RemoteAddr fallback (direct connection)
+ host, _, err := net.SplitHostPort(r.RemoteAddr)
+ if err == nil && isValidIP(host) {
+ return host
+ }
+
+ return ""
+}
+
+func isValidIP(ip string) bool {
+ // Accept both IPv4 and IPv6
+ parsed := net.ParseIP(ip)
+ return parsed != nil
+}