feat(admin): add security and activity management features

This commit is contained in:
2026-05-01 13:10:23 +03:00
parent dd8dd7cdc2
commit 88ab6e808b
26 changed files with 2208 additions and 262 deletions

89
lib/server/ip.go Normal file
View File

@@ -0,0 +1,89 @@
package server
import (
"net"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func clientIP(ctx *gin.Context) string {
if ctx == nil || ctx.Request == nil {
return ""
}
remoteIP := remoteAddrIP(ctx.Request)
// Only trust forwarding headers when remote hop looks like local/internal proxy.
if isPrivateOrLoopback(remoteIP) {
for _, candidate := range headerIPs(ctx.Request.Header) {
if isPublicIP(candidate) {
return candidate
}
}
candidates := headerIPs(ctx.Request.Header)
if len(candidates) > 0 {
return candidates[0]
}
}
return remoteIP
}
func headerIPs(header http.Header) []string {
keys := []string{
"X-Forwarded-For",
"X-Real-Ip",
"CF-Connecting-IP",
"X-Envoy-External-Address",
"Fly-Client-IP",
}
out := make([]string, 0, 4)
seen := map[string]bool{}
for _, key := range keys {
raw := strings.TrimSpace(header.Get(key))
if raw == "" {
continue
}
for _, part := range strings.Split(raw, ",") {
ip := normalizeIP(strings.TrimSpace(part))
if ip == "" || seen[ip] {
continue
}
seen[ip] = true
out = append(out, ip)
}
}
return out
}
func remoteAddrIP(request *http.Request) string {
host, _, err := net.SplitHostPort(strings.TrimSpace(request.RemoteAddr))
if err != nil {
return normalizeIP(strings.TrimSpace(request.RemoteAddr))
}
return normalizeIP(host)
}
func normalizeIP(raw string) string {
ip := net.ParseIP(strings.TrimSpace(raw))
if ip == nil {
return ""
}
return ip.String()
}
func isPublicIP(value string) bool {
ip := net.ParseIP(value)
if ip == nil || !ip.IsGlobalUnicast() {
return false
}
return !isPrivateOrLoopback(value)
}
func isPrivateOrLoopback(value string) bool {
ip := net.ParseIP(value)
if ip == nil {
return true
}
return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast()
}