From a77c365fe4196cbafe1b222281c80729f66686c9 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Tue, 14 Jul 2026 23:47:43 +0200 Subject: [PATCH] fix(inbound): accept WireGuard clients when creating an inbound AddInbound's per-client validation switch had cases for every protocol except WireGuard, so a WireGuard client fell through to the default branch that requires a non-empty id. WireGuard clients are keyed by their public key and carry no id, so importing a WireGuard inbound or re-adding one to a reconciling node was rejected with "empty client ID". Add a wireguard case that validates the client key, mirroring addInboundClient. --- internal/web/service/inbound.go | 4 +++ .../service/inbound_finalmask_reality_test.go | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/internal/web/service/inbound.go b/internal/web/service/inbound.go index 4fdcb4726..fc1149126 100644 --- a/internal/web/service/inbound.go +++ b/internal/web/service/inbound.go @@ -804,6 +804,10 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo if client.Auth == "" { return inbound, false, common.NewError("empty client ID") } + case "wireguard": + if client.PublicKey == "" { + return inbound, false, common.NewError("wireguard client requires a key") + } case "mtproto": if client.Secret == "" { return inbound, false, common.NewError("mtproto client requires a secret") diff --git a/internal/web/service/inbound_finalmask_reality_test.go b/internal/web/service/inbound_finalmask_reality_test.go index 6e9c7ab3d..62f3b6aa5 100644 --- a/internal/web/service/inbound_finalmask_reality_test.go +++ b/internal/web/service/inbound_finalmask_reality_test.go @@ -127,6 +127,36 @@ func TestAddInbound_IgnoresBoundIdAndCreatesNewRow(t *testing.T) { } } +// A WireGuard inbound carrying clients (an imported config, or a node-reconcile +// re-add) must be accepted: WG clients are keyed by their public key and have no +// `id`, so the generic default validation branch wrongly rejected them with +// "empty client ID". +func TestAddInbound_AcceptsWireguardClientWithKey(t *testing.T) { + setupConflictDB(t) + svc := &InboundService{} + + settings := `{"secretKey":"` + wgTestSecretKey() + `","mtu":1420,"clients":[{"email":"wgimp@x","enable":true,"privateKey":"keep-priv","publicKey":"keep-pub","allowedIPs":["10.0.0.50/32"]}]}` + in := &model.Inbound{ + Tag: "in-45200-wg", + Enable: true, + Listen: "0.0.0.0", + Port: 45200, + Protocol: model.WireGuard, + Settings: settings, + } + if _, _, err := svc.AddInbound(in); err != nil { + t.Fatalf("AddInbound rejected a keyed WireGuard client: %v", err) + } + + var count int64 + if err := database.GetDB().Model(&model.Inbound{}).Where("tag = ?", "in-45200-wg").Count(&count).Error; err != nil { + t.Fatalf("count: %v", err) + } + if count != 1 { + t.Fatalf("WireGuard inbound with a keyed client was not created, row count = %d", count) + } +} + // end-to-end: same guard on the update path, on a row that was valid before // the edit — the rejected StreamSettings must not overwrite the stored row. func TestUpdateInbound_RejectsFinalMaskRealityCombo(t *testing.T) {