Refine WireGuard client routing and authentication UI

This commit is contained in:
opsx committed 2026-10-21 21:10:53 +08:00
1 parent eabb3e4472
commit 41fb9d5631
33 files changed
+1709 -469

No files matched your search

+18 -7
View File
@@ -269,12 +269,13 @@ func authorizedLines(username, password, otp string) ([]model.VPNUser, bool, err
}
type clientLine struct {
TunnelID uint `json:"tunnel_id"`
Name string `json:"name"`
Remark string `json:"remark"`
State string `json:"state"`
Endpoint string `json:"endpoint"`
ExpireAt *time.Time `json:"expire_at"`
TunnelID uint `json:"tunnel_id"`
Name string `json:"name"`
Remark string `json:"remark"`
State string `json:"state"`
Endpoint string `json:"endpoint"`
RoutingMode string `json:"routing_mode"`
ExpireAt *time.Time `json:"expire_at"`
}
// linesForUsers 把授权拨号记录映射为 WireGuard 线路列表。
@@ -290,15 +291,25 @@ func linesForUsers(users []model.VPNUser) []clientLine {
if err != nil || t.Protocol != "wireguard" || (t.ExpireAt != nil && time.Now().After(*t.ExpireAt)) {
continue
}
line := clientLine{TunnelID: t.ID, Name: t.Name, Remark: t.Remark, State: t.State, ExpireAt: t.ExpireAt}
line := clientLine{TunnelID: t.ID, Name: t.Name, Remark: t.Remark, State: t.State, ExpireAt: t.ExpireAt, RoutingMode: "split"}
if cfg, err := store.TunnelConfig(t); err == nil {
line.Endpoint = wireguardEndpoint(cfg)
line.RoutingMode = routingModeForAllowedIPs(fmt.Sprint(cfg["allowed_ips"]))
}
lines = append(lines, line)
}
return lines
}
func routingModeForAllowedIPs(value string) string {
for _, entry := range strings.Split(value, ",") {
if strings.TrimSpace(entry) == "0.0.0.0/0" {
return "global_ipv4"
}
}
return "split"
}
func wireguardEndpoint(cfg map[string]interface{}) string {
if ep, ok := cfg["public_endpoint"].(string); ok && ep != "" {
return ep
@@ -0,0 +1,16 @@
package api
import "testing"
func TestRoutingModeReflectsOnlyAuthorizedDefaultRoute(t *testing.T) {
for _, tc := range []struct{ allowed, want string }{
{"10.88.0.0/24", "split"},
{"10.88.0.0/24, 0.0.0.0/0", "global_ipv4"},
{"0.0.0.0/00", "split"},
{"", "split"},
} {
if got := routingModeForAllowedIPs(tc.allowed); got != tc.want {
t.Errorf("%q: got %q, want %q", tc.allowed, got, tc.want)
}
}
}
@@ -265,6 +265,7 @@ func (s *Service) DeviceConnectionParams(t *model.Tunnel, peerID string) (map[st
"endpoint": service.StrOr(cfg, "public_endpoint", fmt.Sprintf("your-server-ip:%d", int(service.Num(cfg, "listen_port", defaultPort)))),
"listen_port": int(service.Num(cfg, "listen_port", defaultPort)),
"allowed_ips": allowed,
"latency_probe": service.Str(cfg, "latency_probe"),
"dns": service.StrOr(cfg, "dns", service.DefaultDNS()),
"mtu": int(service.Num(cfg, "mtu", 0)),
"keepalive": int(service.Num(cfg, "keepalive", 25)),
@@ -106,6 +106,11 @@ func isWireGuardKey(key string) bool {
}
func (s *Service) Validate(cfg map[string]interface{}) (map[string]interface{}, error) {
// Static peers still authenticate by their key; this setting only describes
// how the managed desktop client obtains its device grant.
if mode := service.Str(cfg, "auth_mode"); mode != "" && mode != "password" {
return nil, fmt.Errorf("WireGuard 尚不支持第三方 OAuth2/OIDC 登录,请使用客户端账号与设备密钥")
}
if err := service.MustValidate(cfg, "address"); err != nil {
return nil, err
}
@@ -127,6 +132,13 @@ func (s *Service) Validate(cfg map[string]interface{}) (map[string]interface{},
if err := validateCIDRList(service.Str(out, "allowed_ips")); err != nil {
return nil, fmt.Errorf("allowed_ips: %w", err)
}
if target := service.Str(out, "latency_probe"); target != "" {
host, port, err := net.SplitHostPort(target)
probePort, parseErr := strconv.Atoi(port)
if err != nil || parseErr != nil || net.ParseIP(host) == nil || probePort < 1 || probePort > 65535 {
return nil, fmt.Errorf("latency_probe 必须是可经 VPN 访问的 IP:端口")
}
}
if service.Str(out, "dns") == "" {
out["dns"] = service.DefaultDNS()
}
@@ -83,6 +83,32 @@ func TestValidateRejectsUnsafeNATAndInvalidPeerAddress(t *testing.T) {
}
}
func TestValidateWireGuardAuthMode(t *testing.T) {
for _, mode := range []string{"", "password"} {
if _, err := New().Validate(map[string]interface{}{"address": "10.8.0.1/24", "auth_mode": mode}); err != nil {
t.Fatalf("legacy/password mode %q rejected: %v", mode, err)
}
}
for _, mode := range []string{"oidc", "unknown"} {
if _, err := New().Validate(map[string]interface{}{"address": "10.8.0.1/24", "auth_mode": mode}); err == nil {
t.Fatalf("unsupported authentication mode %q accepted", mode)
}
}
}
func TestValidateLatencyProbeTarget(t *testing.T) {
for _, target := range []string{"10.88.0.1:22", "[2001:db8::1]:443", ""} {
if _, err := New().Validate(map[string]interface{}{"address": "10.8.0.1/24", "latency_probe": target}); err != nil {
t.Fatalf("valid target %q rejected: %v", target, err)
}
}
for _, target := range []string{"example.com:443", "10.88.0.1:0", "10.88.0.1:65536", "invalid"} {
if _, err := New().Validate(map[string]interface{}{"address": "10.8.0.1/24", "latency_probe": target}); err == nil {
t.Fatalf("invalid target %q accepted", target)
}
}
}
func TestNATOnlyAppliesToWireGuardSubnet(t *testing.T) {
conf := renderServerConfig(map[string]interface{}{
"address": "10.88.0.1/24", "listen_port": 51820,