diff --git a/xray/api.go b/xray/api.go index 52dbc14d..a887d666 100644 --- a/xray/api.go +++ b/xray/api.go @@ -37,6 +37,34 @@ type XrayAPI struct { isConnected bool } +func getRequiredUserString(user map[string]any, key string) (string, error) { + value, ok := user[key] + if !ok || value == nil { + return "", fmt.Errorf("missing required user field %q", key) + } + + strValue, ok := value.(string) + if !ok { + return "", fmt.Errorf("invalid type for user field %q: %T", key, value) + } + + return strValue, nil +} + +func getOptionalUserString(user map[string]any, key string) (string, error) { + value, ok := user[key] + if !ok || value == nil { + return "", nil + } + + strValue, ok := value.(string) + if !ok { + return "", fmt.Errorf("invalid type for user field %q: %T", key, value) + } + + return strValue, nil +} + // Init connects to the Xray API server and initializes handler and stats service clients. func (x *XrayAPI) Init(apiPort int) error { if apiPort <= 0 || apiPort > math.MaxUint16 { @@ -104,16 +132,36 @@ func (x *XrayAPI) DelInbound(tag string) error { // AddUser adds a user to an inbound in the Xray core using the specified protocol and user data. func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]any) error { + userEmail, err := getRequiredUserString(user, "email") + if err != nil { + return err + } + var account *serial.TypedMessage switch Protocol { case "vmess": + userID, err := getRequiredUserString(user, "id") + if err != nil { + return err + } + account = serial.ToTypedMessage(&vmess.Account{ - Id: user["id"].(string), + Id: userID, }) case "vless": + userID, err := getRequiredUserString(user, "id") + if err != nil { + return err + } + + userFlow, err := getOptionalUserString(user, "flow") + if err != nil { + return err + } + vlessAccount := &vless.Account{ - Id: user["id"].(string), - Flow: user["flow"].(string), + Id: userID, + Flow: userFlow, } // Add testseed if provided if testseedVal, ok := user["testseed"]; ok { @@ -139,12 +187,27 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]an } account = serial.ToTypedMessage(vlessAccount) case "trojan": + password, err := getRequiredUserString(user, "password") + if err != nil { + return err + } + account = serial.ToTypedMessage(&trojan.Account{ - Password: user["password"].(string), + Password: password, }) case "shadowsocks": + cipher, err := getOptionalUserString(user, "cipher") + if err != nil { + return err + } + + password, err := getRequiredUserString(user, "password") + if err != nil { + return err + } + var ssCipherType shadowsocks.CipherType - switch user["cipher"].(string) { + switch cipher { case "aes-128-gcm": ssCipherType = shadowsocks.CipherType_AES_128_GCM case "aes-256-gcm": @@ -159,18 +222,23 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]an if ssCipherType != shadowsocks.CipherType_NONE { account = serial.ToTypedMessage(&shadowsocks.Account{ - Password: user["password"].(string), + Password: password, CipherType: ssCipherType, }) } else { account = serial.ToTypedMessage(&shadowsocks_2022.ServerConfig{ - Key: user["password"].(string), - Email: user["email"].(string), + Key: password, + Email: userEmail, }) } case "hysteria": + auth, err := getRequiredUserString(user, "auth") + if err != nil { + return err + } + account = serial.ToTypedMessage(&hysteriaAccount.Account{ - Auth: user["auth"].(string), + Auth: auth, }) default: return nil @@ -178,11 +246,11 @@ func (x *XrayAPI) AddUser(Protocol string, inboundTag string, user map[string]an client := *x.HandlerServiceClient - _, err := client.AlterInbound(context.Background(), &command.AlterInboundRequest{ + _, err = client.AlterInbound(context.Background(), &command.AlterInboundRequest{ Tag: inboundTag, Operation: serial.ToTypedMessage(&command.AddUserOperation{ User: &protocol.User{ - Email: user["email"].(string), + Email: userEmail, Account: account, }, }),