diff --git a/xray/api.go b/xray/api.go index 588663b2..f3d7ce06 100644 --- a/xray/api.go +++ b/xray/api.go @@ -188,6 +188,28 @@ func (x *XrayAPI) HasOutbound(tag string) (bool, error) { } func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]interface{}) error { + account, err := userAccount(Protocol, user) + if err != nil || account == nil { + return err + } + + client := *x.HandlerServiceClient + + _, err = client.AlterInbound(context.Background(), &command.AlterInboundRequest{ + Tag: inboundTag, + Operation: serial.ToTypedMessage(&command.AddUserOperation{ + User: &protocol.User{ + Email: user["email"].(string), + Account: account, + }, + }), + }) + return err +} + +// userAccount builds the account Xray-core attaches to a user added at runtime; +// nil means the protocol cannot take users through the API. +func userAccount(Protocol string, user map[string]interface{}) (*serial.TypedMessage, error) { var account *serial.TypedMessage switch Protocol { case "vmess": @@ -252,9 +274,8 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in CipherType: ssCipherType, }) } else { - account = serial.ToTypedMessage(&shadowsocks_2022.ServerConfig{ - Key: user["password"].(string), - Email: user["email"].(string), + account = serial.ToTypedMessage(&shadowsocks_2022.Account{ + Key: user["password"].(string), }) } case "hysteria": @@ -264,25 +285,13 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]in case "wireguard": peer, err := wireguardPeerConfig(user) if err != nil { - return err + return nil, err } account = serial.ToTypedMessage(peer) default: - return nil + return nil, nil } - - client := *x.HandlerServiceClient - - _, err := client.AlterInbound(context.Background(), &command.AlterInboundRequest{ - Tag: inboundTag, - Operation: serial.ToTypedMessage(&command.AddUserOperation{ - User: &protocol.User{ - Email: user["email"].(string), - Account: account, - }, - }), - }) - return err + return account, nil } // wireguardPeerConfig turns a panel client entry into the peer config Xray diff --git a/xray/api_test.go b/xray/api_test.go index ef43f5b4..10b03a50 100644 --- a/xray/api_test.go +++ b/xray/api_test.go @@ -4,6 +4,8 @@ import ( "encoding/base64" "encoding/json" "testing" + + "github.com/xtls/xray-core/common/protocol" ) // A payload built from a model.Client keeps its Go types; one read back out of @@ -153,3 +155,26 @@ func TestToUint32(t *testing.T) { }) } } + +// Every account handed to AlterInbound has to turn into a memory user, or the +// core refuses it and the panel falls back to restarting Xray. +func TestUserAccountsBecomeMemoryUsers(t *testing.T) { + cases := map[string]map[string]interface{}{ + "vmess": {"id": "5783a3e7-e373-51cd-8642-c83782b807c5"}, + "vless": {"id": "5783a3e7-e373-51cd-8642-c83782b807c5", "flow": ""}, + "trojan": {"password": "pw"}, + "shadowsocks": {"cipher": "", "password": "MDEyMzQ1Njc4OWFiY2RlZg=="}, + "hysteria": {"auth": "pw"}, + } + for proto, user := range cases { + user["email"] = proto + "@test" + account, err := userAccount(proto, user) + if err != nil || account == nil { + t.Fatalf("%s: account %v, err %v", proto, account, err) + } + u := &protocol.User{Email: proto + "@test", Account: account} + if _, err := u.ToMemoryUser(); err != nil { + t.Errorf("%s: %v", proto, err) + } + } +}