Initial commit: DSH Web Manager

This commit is contained in:
2026-10-13 09:34:47 +08:00
commit 85332bbc56
135 changed files with 19712 additions and 0 deletions
+166
View File
@@ -0,0 +1,166 @@
package api
import (
"errors"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
type loginSource struct {
started time.Time
count int
}
func configuredOrigin(raw string) (string, error) {
if raw == "" {
return "", nil
}
u, err := url.Parse(raw)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" || u.Hostname() == "" || u.User != nil || (u.Path != "" && u.Path != "/") || u.RawQuery != "" || u.ForceQuery || u.Fragment != "" {
return "", errors.New("invalid PANEL_PUBLIC_URL")
}
if strings.Contains(raw, "#") || strings.HasSuffix(u.Host, ":") {
return "", errors.New("invalid PANEL_PUBLIC_URL")
}
port := u.Port()
if port != "" {
n, err := strconv.Atoi(port)
if err != nil || n < 1 || n > 65535 {
return "", errors.New("invalid PANEL_PUBLIC_URL")
}
port = strconv.Itoa(n)
}
host := strings.ToLower(u.Hostname())
if net.ParseIP(host) == nil {
if len(host) > 253 {
return "", errors.New("invalid PANEL_PUBLIC_URL")
}
for _, label := range strings.Split(strings.TrimSuffix(host, "."), ".") {
if len(label) == 0 || len(label) > 63 || label[0] == '-' || label[len(label)-1] == '-' {
return "", errors.New("invalid PANEL_PUBLIC_URL")
}
for _, c := range label {
if !(c >= 'a' && c <= 'z' || c >= '0' && c <= '9' || c == '-') {
return "", errors.New("invalid PANEL_PUBLIC_URL")
}
}
}
} else if strings.Contains(host, ":") {
host = "[" + host + "]"
}
if port != "" && !(u.Scheme == "https" && port == "443" || u.Scheme == "http" && port == "80") {
host += ":" + port
}
return u.Scheme + "://" + host, nil
}
func configuredProxyCIDRs(raw string, prior error) ([]*net.IPNet, error) {
if prior != nil || strings.TrimSpace(raw) == "" {
return nil, prior
}
var out []*net.IPNet
for _, value := range strings.Split(raw, ",") {
value = strings.TrimSpace(value)
if value == "" {
return nil, errors.New("invalid TRUSTED_PROXY_CIDRS")
}
_, network, err := net.ParseCIDR(value)
if err != nil {
return nil, errors.New("invalid TRUSTED_PROXY_CIDRS")
}
out = append(out, network)
}
return out, nil
}
func ValidateAuthConfig() error {
_, err := configuredOrigin(os.Getenv("PANEL_PUBLIC_URL"))
_, err = configuredProxyCIDRs(os.Getenv("TRUSTED_PROXY_CIDRS"), err)
return err
}
func (a *API) ConfigError() error { return a.loginConfigErr }
func (a *API) acquireLoginSlot() bool {
a.loginMu.Lock()
defer a.loginMu.Unlock()
if a.loginSlots == nil {
a.loginSlots = make(chan struct{}, 8)
}
select {
case a.loginSlots <- struct{}{}:
return true
default:
return false
}
}
func (a *API) loginSource(r *http.Request) string {
peer, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
peer = r.RemoteAddr
}
ip := net.ParseIP(peer)
if ip == nil {
return peer
}
trusted := false
for _, network := range a.trustedProxies {
if network.Contains(ip) {
trusted = true
break
}
}
if trusted {
parts := strings.Split(r.Header.Get("X-Forwarded-For"), ",")
for i := len(parts) - 1; i >= 0; i-- {
candidate := net.ParseIP(strings.TrimSpace(parts[i]))
if candidate == nil {
return ip.String()
}
isProxy := false
for _, network := range a.trustedProxies {
if network.Contains(candidate) {
isProxy = true
break
}
}
if !isProxy {
return candidate.String()
}
}
}
return ip.String()
}
func (a *API) allowLoginSource(source string) bool {
now := time.Now()
a.loginMu.Lock()
defer a.loginMu.Unlock()
if a.loginSources == nil {
a.loginSources = make(map[string]loginSource)
}
for key, entry := range a.loginSources {
if now.Sub(entry.started) >= time.Minute {
delete(a.loginSources, key)
}
}
entry, ok := a.loginSources[source]
if !ok {
if len(a.loginSources) >= 1024 {
return false
}
entry = loginSource{started: now}
}
if entry.count >= 30 {
return false
}
entry.count++
a.loginSources[source] = entry
return true
}